» » » Фундаментальная уязвимость HTML при встраивании скриптов

 

Фундаментальная уязвимость HTML при встраивании скриптов

Автор: admin от 12-02-2018, 10:25, посмотрело: 104

Чтобы описать суть проблемы, мне нужно рассказать, как вообще устроен HTML. Вы наверняка в общих чертах представляли себе, но я все равно коротко пробегусь по основным моментам, которые понадобятся для понимания. Если кому-то не терпится, сразу переходите к сути.



HTML — это язык гипертекстовой разметки. Чтобы говорить на этом языке, нужно соблюдать его формат, иначе тот, кто читает написанное, не сможет вас понять. Например, в HTML у тегов есть атрибуты:



<p name="value">


Тут [name] — это имя атрибута, а [value] — это его значение. В статье я буду использовать квадратные скобки вокруг кода, чтобы было понятно, где он начинается и заканчивается. После имени стоит знак равенства, а после него — значение, заключенное в кавычки. Значение атрибута начинается сразу после первого символа кавычки и заканчивается сразу перед следующим символом кавычки, где бы он не находился. Это значит, что если вместо [value] вы запишете [OOO "Рога и копыта".], то значение атрибута name будет [OOO ], а еще у вашего элемента будет три других атрибута с именами: [рога], [и] и [копыта"."], но без значений.



<p name="OOO "Рога и копыта"."></p>


Если это не то, чего вы ожидали, вам нужно как-то изменить значение атрибута, чтобы в нем не встречалась кавычка. Самое простое, что можно придумать — просто вырезать кавычки.



<p name="OOO Рога и копыта."></p>
Стандарт HTML прямо говорит, что в содержимом тега <script> не может быть последовательности символов ни в каком виде. А стандарт javascript не запрещает такой последовательности быть где угодно в строковых литералах.



Получается парадоксальная ситуация: после встраивания валидного javascript в валидный документ HTML абсолютно валидными средствами мы можем получить невалидный результат.



На мой взгляд это и является уязвимостью разметки HTML, приводящей к уязвимостям в реальных приложениях.



Как эксплуатируется уязвимость



Конечно, когда вы просто пишете какой-то код, трудно представить, что вы напишете в строке и не заметите проблем. Как минимум, подсветка синтаксиса даст вам знать, что тег закрылся раньше времени, как максимум, написанный вами код не запустится и вы будете долго искать, что произошло. Но это не является основной проблемой с этой уязвимостью. Проблема возникает там, где вы вставляете какой-то контент в javascript, когда генерируете HTML. Вот частый кусок кода приложений на реакте с серверным рендерингом:



<script>
window.__INITIAL_STATE__ = <%- JSON.stringify(initialState) %>;
</script>


В initialState может появиться в любом месте, где данные поступают от пользователя или из других систем. JSON.stringify не будет менять такие строки при сериализации, потому что они полностью соответствуют формату JSON и javascript, поэтому они просто попадут на страницу и позволят злоумышленнику выполнить произвольный javascript в браузере пользователя.



Другой пример:



<script>
  analytics.identify(
      '<%- user.id.replace(/('|)/g, "$1") %>',
      '<%- request.HTTP_REFERER.replace(/('|)/g, "$1") %>',
      ...
  );
</script>


Тут в строки с соответствующим экранированием записываются id пользователя и referer, который пришел на сервер. И, если в user.id вряд ли будет что-то кроме цифр, то в referer злоумышленник может запихнуть что угодно.



Но на закрывающем теге приколы не заканчиваются. Опасность представляет и открывающий тег <script>, если перед ним в любом месте есть символы [<!--], которые в обычном HTML обозначают начало многострочного комментария. Причем в этом случае вам уже не поможет подсветка синтаксиса большинства редакторов.



<script>
  var a = 'Consider this string: <!--';
  var b = '<script>';
</script>
<p>Any text</p>
<script>
  var s = 'another script';
</script>


Что видит здоровый человек и большинство подсветок синтаксиса в этом коде? Два тега <script>, между которыми находится параграф.



Что видит больной парсер HTML5? Он видит один (!) незакрытый (!) тег <script>, содержащий весь текст со второй строчки до последней.



Я до конца не понимаю, почему это так работает, мне понятно лишь, что встретив где-либо символы [<!--], парсер HTML начинает считать открывающие и закрывающие теги <script> и не считает скрипт законченным, пока не будут закрыты все открытые теги <script>. То есть в большинстве случаев этот скрипт будет идти до конца страницы (если только кто-то не смог внедрить еще один лишний закрывающий тег ниже, хе-хе). Если вы до этого не сталкивались с подобным, то можете подумать, что я сейчас шучу. К сожалению, нет. Вот скриншот DOM-дерева примера выше:



Фундаментальная уязвимость HTML при встраивании скриптов

Самое неприятное, что в отличие от закрывающего тега , который в javascript может встретиться только внутри строковых литералов, последовательности символов <!-- и <script могут встретиться и в самом коде! И будут иметь точно такой же эффект.



<script>
  if (x<!--y) { ... }
  if ( player<script ) { ... }
</script>


А вы точно спецификация?



Спецификация HTML, помимо того, что запрещает использование легальных последовательностей символов внутри тега <script> и не дает никакого способа их экранирования в рамках HTML, также советует следующее:



The easiest and safest way to avoid the rather strange restrictions described in this section is to always escape "<!--" as "<!--", "<script" as "<script", and "

Что можно перевести как «Всегда экранируйте последовательности "<!--" как "<!--", "<script" как "<script", а "" как "", когда они встречаются в строковых литералах в ваших скриптах и избегайте этих выражений в самом коде». Эта рекомендация меня умиляет. Тут делается сразу несколько наивных предположений:




  1. Во встраиваемом скрипте (а это не обязательно javascript) перечисленные выше последовательности символов могут быть либо внутри строковых литералов, либо их можно легко избежать в синтаксисе языка.

  2. Во встраиваемом скрипте в строковых литералах можно экранировать не специальные символы и это не приводит к изменению значений литералов.

  3. Тот, кто встраивает скрипт, знает что это за скрипт, глубоко понимает его синтаксис и способен производить изменения в его структуре.



И, если первые два пункта выполняются хотя бы для javascript, то последний не выполняется даже для него. Не всегда скрипт в HTML вставляет квалифицированный человек, это может быть какой-то генератор HTML. Вот пример того, как с этим не справляется сам браузер:



var script = document.createElement('script')
script.innerText = 'var s = "</script><script>alert('whoops!')</script>"';
console.log(script.outerHTML);

 <script>var s = "</script><script>alert('whoops!')</script>"</script>


Как видите, строка с сериализованным элементом не будет распаршена в элемент, аналогичный исходному. Преобразование DOM-дерево HTML-текст в общем случает не является однозначным и обратимым. Некоторые DOM-деревья просто нельзя представить в виде исходного HTML-текста.



Как избежать проблем?



Как вы уже поняли, способа безопасно вставить javascript в HTML нет. Но есть способы сделать javascript безопасным для вставки в HTML (почувствуйте разницу). Правда для этого нужно быть предельно внимательным всё время, пока вы пишете что-то внутри тега <script>, особенно если вы вставляете любые данные с помощью шаблонизатора.



Во-первых, вероятность того, что у вас в исходном тексте (даже после минификации) не в строковых литералах встретятся символы [<!-- <script>] крайне мала. Сами вы вряд ли напишете что-то такое, а если злоумышленник что-то сможет написать прямо в теге <script>, то внедрение этих символов будет беспокоить вас в последнюю очередь.



Остается проблема внедрения символов в строки. В этом случае, как и написано в спецификации, всего-то нужно заменить все "<!--" на "<!--", "<script" на "<script", а "" на "". Но беда в том, что если вы выводите какую-то структуру с помощью JSON.stringify(), то вряд ли вы захотите потом её распарсить еще раз, чтобы найти все строковые литералы и заэкранировать в них что-то. Так же не хочется советовать пользоваться другими пакетами для сериализации, где эта проблема уже учтена, потому что ситуации бывают разными, а защититься хочется всегда и решение должно быть универсальным. Поэтому я бы советовал экранировать символы / и ! с помощью обратного слеша уже после сериализации. Эти символы не могут встречаться в JSON нигде кроме как внутри строк, поэтому простая замена будет абсолютно безопасной. Это не изменит последовательность символов "<script", но она и не представляет опасности, если встречается сама по себе.



<script>
window.__INITIAL_STATE__ = <%- JSON.stringify(initialState).replace(/(/|!)/g, "$1") %>;
</script>


Точно так же можно экранировать и отдельные строки.



Другой совет — не встраивайте в тег <script /> ничего вообще. Храните данные в местах, где трансформации для вставки данных однозначны и обратимы. Например, в атрибутах других элементов. Правда смотрится это довольно грязно и работает только со строками, JSON придется парсить отдельно.



<var id="s" data="surprise!</script><script>alert("whoops!")</script>"></var>
<script>
  var s = document.getElementById('s').getAttribute('data');
  console.log(s);
</script>


Но, по-хорошему, конечно, если вы хотите нормально разрабатывать приложения, а не аккуратно ходить по минному полю, нужен надежный способ встраивания скриптов в HTML. Поэтому правильным решением считаю вообще отказаться от тега <script />, как от не безопасного.



Тег



Если не использовать встраиваемые скрипты, то что тогда? Конечно, подключать все скрипты извне — не вариант, иногда иметь какой-то javascript с данными внутри HTML-документа очень удобно: нет лишних HTTP-запросов, не нужно делать дополнительных роутов на стороне сервера.



Поэтому я предлагаю ввести новый тег — , содержимое которого будет полностью подчинятся обычным правилам HTML — будут работать HTML entities для экранирования контента — и поэтому встраивание в него любого скрипта будет абсолютно безопасным.



<safescript>
  var s = "surprise!</script><script>alert('whoops!')</script>";
</safescript>

<safescript>
  var a = 'Consider this string: <!--';
  var b = '<script>';
</safescript>


При этом нет необходимости дожидаться реализации этого тега в браузерах. Я написал очень простой полифил safescript, который позволяет использовать его прямо сейчас. Вот всё, что для этого нужно:



<script type="text/javascript" src="/static/safescript.js"></script>
<style type="text/css">safescript {display: none !important}</style>


Код внутри выглядит ужасно и непривычно. Но это код, который попадет в сам HTML. В шаблонизаторе, который вы используете, можно сделать простой фильтр, который будет вставлять тег и экранировать все его содержимое. Вот так может выглядеть код в шаблонизаторе Django:



{% safescript %}
  var s = "surprise!</script><script>alert('whoops!')</script>";
{% endsafescript %}

{% safescript %}
  var a = 'Consider this string: <!--';
  var b = '<script>';
{% endsafescript %}


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



Источник: Хабрахабр

Категория: Операционные системы » Android

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *