Арбалеты Сибири сделали передовой продукт для сканирования того, чем стал интернет. Если ничего не предпринять они монополизируют и этот рынок. Мы должны их остановить.
`Arbalety Sibiri' just released an advanced product for scanning what the internet has become. If we don't react they will monopolize this market as well. We have to stop them now.
Need whale deployment.
Image: cr.yandex/crp56e8fvolm1rqugnkf/web-scanner:latest
Port: 5000
Compose(local) deploy:
cd deploy
docker compose -p web-scanner up --build -d
Provide zip file: public/web-scanner.zip.
SSRF with CRLF Injection -> Redis write -> Celery RCE.
Перед нами веб-сервис, который позволяет отправить HTTP-запрос на заданный URL и сохранить ответ. Мы полностью контролируем URL, можем указывать заголовки и HTTP-версию для запроса.
Сервис использует celery для того, чтобы асинхронно выполнять задачи отправке HTTP-запросов.
Для решения также важны следующие факты:
- Сервис использует библиотеку aiohttp для отправки HTTP-запросов.
- Redis, Celery и сервер находятся в одном контейнере.
- Celery использует redis как бэкенд для очередей и результатов.
- В приложении описан класс исключения
InvalidHostException
, который содержит shell-injection. Но в самом приложении аргументы для конструктора этого исключения всегда экранируются.
Решение основано на CVE-2021-23727.
При десериализации ошибки Celery создаст объект исключения с параметрами, сохраненными в бэкенде (в нашем случае Redis).
Это означает, что если мы сможем положить в Redis сериализацию InvalidHostException
с shell-инъекцией в параметре как
результат задачи, то при получении результата этой задачи Celery создаст исключение напрямую и мы получим RCE.
Для записи в Redis мы можем использовать SSRF. Протокол HTTP - текстовый, а значит что если мы запросим http://localhost:6379, то Redis будет пытаться исполнять сырой HTTP-запрос как команды.
Однако внутри Redis'а
есть защита,
которая игнорирует команды в запросе после получения Host:
.
Если мы сможем вставить команду до заголовка Host, она будет исполнена Redis.
Здесь нам поможет ошибка в библиотеке aiohttp . Если передать в функцию список строк, то их значения не будут провалидированы и значения будут поставленны как есть .
Если в значениях списка будет "\r\n", то мы получим CRLF инъекцию и сможем записать текст до заголовка Host и выполнить любую команду в Redis.
Решение:
- Подготовить JSON-представление исключения с RCE.
- Отправить запрос на Scan, указать version как массив c
\r\n
в одном из значений, после\r\n
должно идтиSET celery-task-meta-<uid> <json>
. - Подождать пока запрос будет обработан Celery.
- Сходить на результат
uid
, что приведет к десериализации и RCE.
We have a web service that allows us to send an HTTP request to a given URL and save the response. We have full control over the URL, and can specify headers and HTTP version for the request.
Service uses celery in order to asynchronously perform the task of sending the HTTP requests.
The following facts are also important for the solution:
- The service uses the aiohttp library to send HTTP requests.
- Redis, Celery and the server are running in the same container.
- Celery uses redis as a backend for queues and results.
- There is an
InvalidHostException
exception class that contains a shell-injection. But in the application, arguments to the constructor of this exception are always sanitized.
The solution idea is based on CVE-2021-23727.
When deserializing an error, Celery will create an exception object with parameters stored in the backend (in our case Redis).
This means that if we can put a serialized InvalidHostException
to Redis with shell injection in the parameter as
the result of a task, then when we will get the result of that task Celery will create an exception directly, and we
will get an RCE.
To write to Redis, we can use SSRF. The HTTP protocol is text-based, which means that if we will fire request to the http://localhost:6379, Redis will try to execute the raw HTTP request as commands.
However, inside Redis.
there's
a protection
which ignores commands in the request after Host:
is received.
But if we can insert a command before the Host header, it will be executed by Redis.
A bug in aiohttp library will help us. If we pass a list of strings to the function, their values will not be validated and values will be sent as they are .
If the values of the list have "\r\n", we will get CRLF injection, and we will be able to write text before the Host header and execute any command in Redis.
Solution:
- Prepare a JSON representation of the exception with RCE.
- Send a request to Scan, specify version as an array with
\r\n
in one of the values, after the\r\n
there should be aSET celery-task-meta-<uid> <json>
. - Wait for the request to be processed by Celery.
- Go to the
uid
result, which will result in deserialization and RCE.
Should be auto-generated by Whale.
No
Should be auto-generated by Whale.