Пожалуйста, обратите внимание, что пользователь заблокирован
В этой статье мы увидим, как я обнаружил уязвимость удаленного выполнения кода и обошел правило(а) Akamai WAF. Пока я проводил сканирование на безопасность, я заметил endpoint, который включает управляемые пользователем данные в строку и отражает их обратно в ответе. Заметив отражение текста, я попробовал несколько XSS-payloads, но не смог успешно выполнить JavaScript, так как типом ответа Content-Type было application/json. Однако, при вводе полезной нагрузки, такой как ${191*7}. Я был удивлен, увидев, что арифметическое выражение было успешно выведено в ответе как
…getApprovalGroupByContext.contextType: 1337…
Примечание: "RCE_" не является частью полезной нагрузки, он используется только для поиска отраженного текста.
Требование символа $ в синтаксисе для успешной оценки выражения обычно указывает на то, что при обработке выражения задействован некий шаблонный движок/обработка со стороны сервера.
В этом случае пользователь управляет содержимым параметра context_type в запросе . После обнаружения инъекции шаблона, следующим шагом было определение используемого шаблонного движка. Этот шаг иногда столь же тривиален, как и отправка некорректного синтаксиса, так как шаблонизаторы могут идентифицировать себя в сообщениях об ошибках. Однако, эта техника не работает, когда вывод сообщений об ошибках отключен. После немного большего исследования в Интернете, у меня появилось несколько догадок о том, какой шаблонный движок это приложение могло бы использовать.
Для оценки воздействия уязвимости я провел еще несколько исследований, чтобы найти полезную нагрузку, которую я мог бы использовать для выполнения команд удаленно, и обнаружил, что следующую полезную нагрузку можно было бы использовать для выполнения команды ls на удаленном сервере:
После выполнения я не видел результатов команды в ответе, потому что это была слепая инъекция, но я получил ссылку на Unix-процесс (выделена в теле ответа выше). Это подтвердило, что команда была выполнена успешно.
Теперь все стало интереснее, так как я хотел загрузить reverse shell (тип оболочки, в котором целевая машина взаимодействует обратно с атакующей машиной). Я выполнил еще две команды, чтобы посмотреть, установлены ли уже модули python и wget, чтобы загрузить и выполнить обратный скрипт оболочки. В результате выполнения я получил ссылку на процесс, указывающую, что утилиты предустановлены. Это неудивительно, так как такие утилиты очень часто встречаются в комплекте на многих Unix.
Поэтому я запустил HTTP-сервер на порту 80 с помощью python и разместил обратный скрипт оболочки, как показано ниже:
Сначала я загрузил reverse shell на уязвимый сервер.
Ссылки на процесс Unix в ответе было достаточно, чтобы понять, что reverse shell был загружен на удаленную машину. Следующим шагом был запуск Netcat listener на порту 443 (чтобы поймать шелл) и выполнение скрипта. Как вы видите на скриншоте ниже, я смог получить reverse shell и выполнить команды.
Следующей задачей было обойти Akamai WAF для выполнения RCE на производственном сервере.
Я получил такой же ответ от производственного сервера для основных математических операций, таких как умножение и деление, который подтвердил, что ошибка существует и на производственном сервере. Однако, конечная полезная нагрузка не выполнилась, в результате чего появилась страница 403 ошибки, отображающая сообщение "Access Denied" (Доступ запрещен).
Я решил разбить полезную нагрузку на несколько частей, чтобы проверить, что безопасно и опасно согласно правилам WAF. Этот метод проб и ошибок помог мне вычислить следующие два ключевых слова как небезопасную строку для WAF:
Я начал просматривать документацию по языку Java, чтобы узнать, есть ли альтернативный способ вернуть строку "java.lang.Runtime". Потратив некоторое время, я обнаружил несколько способов сделать это, но для этого случая сработал только метод concat.
“java.lang.Runtime” === “java.lang”.concat(“.Runtime”)
Так я смог обойти первую проверку. Пока что полезная нагрузка:
Следующей задачей было обойти (). проверку по ключевым словам. После проведения некоторого анализа я заметил, что если я поставлю какой-нибудь символ между () и . брандмауэром не блокирует его, но это сломает нашу полезную нагрузку, так как метод цепочки не будет работать.
Есть ли способ сделать это, не сломав цепочку? Пока я вспоминал основы JavaScript, я думал о самоназванных функциях. Я никогда не программировал на Java, так что я не был уверен, что это вообще сработает, но я подумал о том, что можно попробовать.
В случае JavaScript console.log(“hello”), (console.log(“hello”)) и (console.log)(“hello”) имеют одинаковое значение.
Я заменил getClass() на (getClass()) и так как не было никакой проверки по ключевому слову ()), я смог обойти и вторую проверку.
Конечный payload был:
Я выполнил полезную нагрузку и в ответе получил номер процесса, похожий на тот, который я получил для инсценировки, это подтвердило, что я смог удаленно выполнять команды на рабочем сервере.
По понятным причинам я не пытался загрузить шелл/вредный код на рабочий сервер, однако я проверил, возможно ли сделать исходящий запрос с рабочего сервера к Burp Collaborator.
Я выполнил запрос, чтобы узнать, будет ли производственный сервер выполнять DNS поиск или нет.
Через несколько секунд я получил запрос на поиск, раскрывающий реальный IP адрес производственного (исходного) сервера.
Противодействие:
Лучший способ предотвратить инъекцию шаблонов на стороне сервера - не позволять никаким пользователям изменять или отправлять новые шаблоны. Однако иногда это неизбежно в связи с требованиями бизнеса.
Один из простейших способов избежать SSTI - всегда использовать "беслогичный" шаблонный движок, такой как Mustache, если в этом нет абсолютной необходимости. Максимально возможное отделение логики от вывода информации может значительно уменьшить вашу подверженность наиболее опасным атакам на основе шаблонов.
Другой мерой является выполнение пользовательского кода только в песочнице, где потенциально опасные модули и функции были полностью удалены. К сожалению, недоверенный код в песочнице по своей природе сложен и склонен к обходу.
Наконец, еще одним дополнительным подходом является признание того, что произвольное выполнение кода практически неизбежно, и применение собственной "песочницы" путем развертывания среды шаблонов, например, в заблокированном контейнере Docker.
Другие предложения:
Для предотвращения загрузки злоумышленниками вредоносного кода или обхода фильтрации данных, должны быть установлены белые списки URL-адресов.
Ключевые моменты:
…getApprovalGroupByContext.contextType: 1337…
Примечание: "RCE_" не является частью полезной нагрузки, он используется только для поиска отраженного текста.
Требование символа $ в синтаксисе для успешной оценки выражения обычно указывает на то, что при обработке выражения задействован некий шаблонный движок/обработка со стороны сервера.
Шаблонные движки широко используются веб-приложениями для представления динамических данных через веб-страницы и электронную почту. Небезопасное встраивание пользовательского ввода в шаблоны позволяет выполнять Server-Side Template Injection.
В этом случае пользователь управляет содержимым параметра context_type в запросе . После обнаружения инъекции шаблона, следующим шагом было определение используемого шаблонного движка. Этот шаг иногда столь же тривиален, как и отправка некорректного синтаксиса, так как шаблонизаторы могут идентифицировать себя в сообщениях об ошибках. Однако, эта техника не работает, когда вывод сообщений об ошибках отключен. После немного большего исследования в Интернете, у меня появилось несколько догадок о том, какой шаблонный движок это приложение могло бы использовать.
Для оценки воздействия уязвимости я провел еще несколько исследований, чтобы найти полезную нагрузку, которую я мог бы использовать для выполнения команд удаленно, и обнаружил, что следующую полезную нагрузку можно было бы использовать для выполнения команды ls на удаленном сервере:
Код:
${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke("".getClass().forName("java.lang.Runtime")).exec("ls")}
После выполнения я не видел результатов команды в ответе, потому что это была слепая инъекция, но я получил ссылку на Unix-процесс (выделена в теле ответа выше). Это подтвердило, что команда была выполнена успешно.
Теперь все стало интереснее, так как я хотел загрузить reverse shell (тип оболочки, в котором целевая машина взаимодействует обратно с атакующей машиной). Я выполнил еще две команды, чтобы посмотреть, установлены ли уже модули python и wget, чтобы загрузить и выполнить обратный скрипт оболочки. В результате выполнения я получил ссылку на процесс, указывающую, что утилиты предустановлены. Это неудивительно, так как такие утилиты очень часто встречаются в комплекте на многих Unix.
Поэтому я запустил HTTP-сервер на порту 80 с помощью python и разместил обратный скрипт оболочки, как показано ниже:
Сначала я загрузил reverse shell на уязвимый сервер.
Ссылки на процесс Unix в ответе было достаточно, чтобы понять, что reverse shell был загружен на удаленную машину. Следующим шагом был запуск Netcat listener на порту 443 (чтобы поймать шелл) и выполнение скрипта. Как вы видите на скриншоте ниже, я смог получить reverse shell и выполнить команды.
Следующей задачей было обойти Akamai WAF для выполнения RCE на производственном сервере.
Я получил такой же ответ от производственного сервера для основных математических операций, таких как умножение и деление, который подтвердил, что ошибка существует и на производственном сервере. Однако, конечная полезная нагрузка не выполнилась, в результате чего появилась страница 403 ошибки, отображающая сообщение "Access Denied" (Доступ запрещен).
Я решил разбить полезную нагрузку на несколько частей, чтобы проверить, что безопасно и опасно согласно правилам WAF. Этот метод проб и ошибок помог мне вычислить следующие два ключевых слова как небезопасную строку для WAF:
- “java.lang.Runtime”
- ().
Код:
${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke("".getClass().forName("java.lang.Runtime")).exec("wget")}
Я начал просматривать документацию по языку Java, чтобы узнать, есть ли альтернативный способ вернуть строку "java.lang.Runtime". Потратив некоторое время, я обнаружил несколько способов сделать это, но для этого случая сработал только метод concat.
“java.lang.Runtime” === “java.lang”.concat(“.Runtime”)
Так я смог обойти первую проверку. Пока что полезная нагрузка:
Код:
${"".getClass().forName("java.lang".concat("Runtime")).getMethods()[6].invoke("".getClass().forName("java.lang".concat("Runtime"))).exec("wget")}
Следующей задачей было обойти (). проверку по ключевым словам. После проведения некоторого анализа я заметил, что если я поставлю какой-нибудь символ между () и . брандмауэром не блокирует его, но это сломает нашу полезную нагрузку, так как метод цепочки не будет работать.
Есть ли способ сделать это, не сломав цепочку? Пока я вспоминал основы JavaScript, я думал о самоназванных функциях. Я никогда не программировал на Java, так что я не был уверен, что это вообще сработает, но я подумал о том, что можно попробовать.
В случае JavaScript console.log(“hello”), (console.log(“hello”)) и (console.log)(“hello”) имеют одинаковое значение.
Я заменил getClass() на (getClass()) и так как не было никакой проверки по ключевому слову ()), я смог обойти и вторую проверку.
Конечный payload был:
Код:
${("".getClass()).forName("java.lang".concat("Runtime")).getMethods()[6].invoke(("".getClass()).forName("java.lang".concat("Runtime"))).exec("wget")}
Я выполнил полезную нагрузку и в ответе получил номер процесса, похожий на тот, который я получил для инсценировки, это подтвердило, что я смог удаленно выполнять команды на рабочем сервере.
По понятным причинам я не пытался загрузить шелл/вредный код на рабочий сервер, однако я проверил, возможно ли сделать исходящий запрос с рабочего сервера к Burp Collaborator.
Я выполнил запрос, чтобы узнать, будет ли производственный сервер выполнять DNS поиск или нет.
Через несколько секунд я получил запрос на поиск, раскрывающий реальный IP адрес производственного (исходного) сервера.
Противодействие:
Лучший способ предотвратить инъекцию шаблонов на стороне сервера - не позволять никаким пользователям изменять или отправлять новые шаблоны. Однако иногда это неизбежно в связи с требованиями бизнеса.
Один из простейших способов избежать SSTI - всегда использовать "беслогичный" шаблонный движок, такой как Mustache, если в этом нет абсолютной необходимости. Максимально возможное отделение логики от вывода информации может значительно уменьшить вашу подверженность наиболее опасным атакам на основе шаблонов.
Другой мерой является выполнение пользовательского кода только в песочнице, где потенциально опасные модули и функции были полностью удалены. К сожалению, недоверенный код в песочнице по своей природе сложен и склонен к обходу.
Наконец, еще одним дополнительным подходом является признание того, что произвольное выполнение кода практически неизбежно, и применение собственной "песочницы" путем развертывания среды шаблонов, например, в заблокированном контейнере Docker.
Другие предложения:
Для предотвращения загрузки злоумышленниками вредоносного кода или обхода фильтрации данных, должны быть установлены белые списки URL-адресов.
Ключевые моменты:
- Ни один WAF не является 100% точным, и ни один WAF не является непобедимым
- Если вы нашли ошибку, попробуйте исправить ее на уровне кода и не полагайтесь полностью на WAF.
- Всегда старайтесь повысить уязвимость до максимального уровня.
- Знание о внутренних и внешних зависимостях, структурах и т.д., это плюс.