alpacahack round 11 web - Jackpot & Tiny Note
Author : 堇姬 Naup
前言
那天在忙,只打了第一題,這篇順便補另外一題 (窩不會 XSS)
Jackpot
1 | from flask import Flask, request, render_template, jsonify |
一個 slot 的網站,如果可以骰到 15 個 7 就可以拿到 flag
通過 candidates args 可以去決定 random 的集合,這個集合長度為 10
不過這個集合有進行驗證,他驗證個方法是將這個傳入的長度與轉成集合後的長度去對比
因為轉成集合後不一樣的會被刪除,因此傳入的 candidates 不能有相同的字元
最後把傳入的字串轉為 int
這邊就會有問題了,python 會去把 unicode 字元通過 int 轉換成數字
因此只要找到 10 種不同的 unicode 通過 int 會轉換成 7 就可以成功了
1 | http://34.170.146.252:33352/slot?candidates=%EF%BC%97%D9%A7%F0%9D%9F%A9%E0%BB%97%F0%9D%9F%9F%E1%81%87%E0%A7%AD%EA%98%A7%DB%B7%DF%87 |
Flag : Alpaca{what_i5_your_f4vorite_s3ven?}
Tiny Note
source code
兩個服務,一個是後端,一個是 nginx
他把後端架在內網,並用 nginx proxy 到內網
後端出不了網
1 | services: |
所有的服務,path 若以 /
開頭都會用 nginx proxy 到內網
1 | server { |
初始化
1 | app = Flask(__name__) |
這邊設定 CACHE,並指定 /tmp/cache
為快取目錄
並且去初始化快取並清空,以及清空舊的 note
1 |
|
通過 /create post 來去傳入 title 跟 content
- title 限制 64 個字元
- content 限制 24 個字元
他會去做 validate
1 | def validate(label: str, text: str | None, limit: tuple[int, int]) -> str: |
他會去驗證長度以及看是否有 ..
去過濾 path traversal
之後
- 使用 uuid.uuid4() 產生一個隨機的唯一 ID(當作資料夾名)。
- 將標題 title 用 URL 編碼(urllib.parse.quote)作為檔案名稱,組合成路徑
./notes/<UUID>/<encoded_title>
- 確保父目錄存在 (mkdir(parents=True, exist_ok=True))。
- 將 content 寫入該檔案。
- 最後重導向到 /UUID/標題,即剛建立的筆記頁面
1 |
|
這部分會把你的 note 顯示出來
bug
首先是雖然過濾掉了 ..
但是仍舊存在 path traversal
1 | import pathlib, uuid, shutil, urllib.parse |
如果 title 的開頭是 /
那他會無視前面的 path 串接
直接把後面的當根目錄,這樣就可以 path traversal 來任意寫檔
我們可以去寫 .html,來做 SSTI
驗證一下,我們先寫掉 /app/templates/note.html
成 {{7*7}}
之後去創建一個新的 note,當他 redirect 後就會看到 49

至此可以驗證 SSTI 可以成功
這邊開始思考如何串 SSTI,因為有個限制只能輸入 24 bytes 的 SSTI payload
首先想先思考如何 RCE
如果可以通過 RCE 把 flag 寫到 templates 下
在通過 include 把他引入
有一個簡短的方式可以拿到 python 任意執行,就是用 confid 下的 from_pyfile
他會用來跑 python
所以我們先創建這些檔案
- /app/a.py :
import os;r=os.system
- /app/b :
import a;a.r("sh /sc")
- /sc :
cat /flag* > templates/f
目標是執行到 /app/b
用 SSTI 執行他可以使用 {{config.from_pyfile("b")}}
但太長了,所以稍微利用一下它可以傳入 title
就可以使用 {{config[content]("b")}}
(這個寫入 /app/templates/note.html)
之後先創建一個 note
title 是 tomorin
content 是 from_pyfile
之後把 index.html 改寫成 {%include "f"%}
最後去打開 tomorin 來 trigger SSTI
exploit
1 | import requests |
Flag: Alpaca{I_kn0w_that_cache_is_d4ngerou5_in_CTF}
after all
XSS 真的好難,躺
