thjcc-2024-winter-wp

Web - notepad+++

solve: 54
100 points

會出這題 是因為看到 php 的 tmp
想要做一個類似的東西出來

先看到 dockerfile
這些只要少了一行 都有可能破壞他微妙的權限控管

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM python

WORKDIR /app
COPY . .

RUN pip3 install flask
RUN mkdir /app/tmp
RUN useradd -m -d /home/ctfuser -s /bin/bash ctfuser

RUN chown -R ctfuser:ctfuser /app && \
chmod 755 /app && \
chmod 755 /app/app.py && \
chmod 444 /app/* && \
chmod 555 /app/templates && \
chmod 770 /app/tmp

USER ctfuser

CMD ["python3", "app.py"]

反正簡言之 他就是一個安全的 dockerfile 用來跑 python 的 app.py

接著看到 app.py
會發現在多個地方 都有很多用 g.session 開檔的狀況

1
2
3
with open(f'./tmp/{g.session}', mode='a+', encoding='utf8') as f:
f.seek(0)
ctx = f.read()

所以我們可以試著去控制 g.session 的值

我們可以發現
在 before_request 的部分

1
2
3
4
5
6
7
8
9
@app.before_request
def auth():
if not request.cookies.get('session'):
res = make_response(redirect(url_for('root')))
g.session = hashlib.md5(str(time.time()).encode()).hexdigest()
res.set_cookie('session', g.session)
return res

g.session = request.cookies.get('session')

如果沒有叫做 session 的 cookie
那他就會用 md5 隨機生成 session

但是

如果有的話

1
g.session = request.cookies.get('session')

他就會把 cookie 中的值直接的賦值給 g.session
所以我們可以將 cookie 的值改成../flag.txt 就能取得 flag
題外話 會看到很多

1
2
with open(f'./tmp/{g.session}', mode='r', encoding='utf8') as f:
ctx = f.read()

也是為了保持他微妙的平衡 我已經改不出更好的code了 或是原本架構就寫得不好
但是沒有關係 更精彩的題目 就在下一次的THJCC !!


Web - cr4ck the w0rd13

solve: 3
480 points

前言:
這題其實有點鬧
而且我忘記講flag有unicode :(
會出這題是因為我那時候在寫 Vue
然而這題的Vue source已經被我搞丟了:(
然後我原本隨便放一個api上去
結果到一半他突然掛掉 靠邀
只能自己寫一個出來
然後一個偷天換日把 container 馬上砍掉再復活
喔還有 那時候剛開始
有人開 ticket 說他把 cookie 的 jwt decode
然後送出 對了 但什麼都沒發生
我當下其實很矇 因為我根本沒有印象我有寫 jwt
但是上去之後 蛤 真的有 :O

正文:
進來可以先看到幾個 route

/api 會告訴你那些有哪些api能call 以及他們的用途

/api/new 刷新挑戰

/api/check 檢查你送出的答案是否正確

可能有人真的認真在打 wordle 然後送出
五個全對了 結果發現沒有flag 這是正常的
我們可以注意到

1
2
3
4
5
6
7
8
9
10
11
12
13
@app.route('/api/check', methods=['POST'])
def check():
answer = session.get('answer')
guess = request.json['answer']

if guess==SPECIALWORD:
return {'success':True, 'msg':FLAG}

if not answer or not guess:
return {'success': False, 'msg': 'missing answer'}
if not len(guess)==5:
return {'success': False, 'msg':'wrong length'}
return check_answer(answer, guess)

flag會在

1
2
if guess==SPECIALWORD:
return {'success':True, 'msg':FLAG}

被回傳
但是我們可以注意到SPECIALWORD並不是指題目的正確答案
我們必須想辦法找到他
那我們可以把焦點放到js上
先看到 app.8b8315e5.js
往下滑一點點 就會看到

1
2
const n = [["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], ["A", "S", "D", "F", "G", "H", "J", "K", "L"], ["ENTER", "Z", "X", "C", "V", "B", "N", "M", "⌫"]]
, t = ["ArrowUp", "ArrowUp", "ArrowDown", "ArrowDown", "ArrowLeft", "ArrowLeft", "ArrowRight", "ArrowRight", "B", "A", "B", "A"]

稍微判斷一下之後
發現 n 就是螢幕上的keyboard
thjcc-keyboard
那接著看到 t
把它念出來之後發現 上上下下左左右右BABA !
看起來就超怪的
把他在鍵盤上打一次之後就會發現
他使用了上上下下左左右右BABA去對 /api/check請求
thjcc-payload
就能成功得到 flag
thjcc-w0rd13-flag