Исправьте инъекцию команд в этом помощнике миниатюр, вызывающем оболочку
Этот помощник строит миниатюру, вызывая внешний графический инструмент. filename приходит из HTTP-запроса и больше нигде не проверяется.
Ограничения:
- внешний бинарь
convertостаётся - вызывающий код по-прежнему получает готовые байты
- не полагайтесь на WAF и на фильтрацию метасимволов
import subprocess
def make_thumbnail(filename: str) -> bytes:
cmd = f"convert /var/uploads/{filename} -resize 200x200 png:-"
return subprocess.check_output(cmd, shell=True)
Найдите и исправьте уязвимость.
При shell=True оболочка разбирает всю собранную строку, поэтому filename может дописать команды. Уберите оболочку и передайте список аргументов, затем ограничьте значение: сведите к чистому имени по строгому шаблону.
- ✗Заковычивать подставленный путь вместо отказа от
shell=True - ✗Делать основной защитой фильтрацию чёрного списка метасимволов
- ✗Передавать сырое имя в список аргументов без всякой проверки
- →Почему список аргументов останавливает инъекцию даже без валидации?
- →Почему путь надо пересобрать из имени файла, а не доверять присланному?
Уязвимость
shell=True заставляет /bin/sh разобрать всю собранную строку. filename попадает в неё как текст, поэтому метасимволы оболочки превращают одну команду в несколько — ввод становится кодом.
Исправление
Два независимых слоя: убрать интерпретатор и ограничить значение.
import re
import subprocess
from pathlib import Path
UPLOADS = Path("/var/uploads")
SAFE_NAME = re.compile(r"\A[A-Za-z0-9_-]{1,64}\.(png|jpg|jpeg)\Z")
def make_thumbnail(filename: str) -> bytes:
name = Path(filename).name # отбрасываем любые сегменты пути
if not SAFE_NAME.fullmatch(name):
raise ValueError("bad filename")
src = UPLOADS / name
return subprocess.check_output(
["convert", "--", str(src), "-resize", "200x200", "png:-"],
)
✅ Списка аргументов достаточно, чтобы оболочки в цепочке не было вовсе. Проверка по шаблону и Path(...).name дополнительно закрывают обход каталога, а -- не даёт трактовать имя как опцию.