Может ли HTML влиять на JavaScript - Введение в DOM clobbering
Знали ли вы, что HTML также может влиять на JavaScript не только с помощью Prototype Pollution?
Все мы знаем, что JavaScript может манипулировать HTML с помощью DOM API. Но как HTML может влиять на выполнение JavaScript? Именно здесь и начинается самое интересное.
Прежде чем мы начнем, давайте начнем с небольшой забавной задачи.
Предположим, у вас есть следующий фрагмент кода с кнопкой и скриптом:
Теперь попробуйте реализовать функцию вызова "alert(1)" при нажатии на кнопку с помощью "самого короткого кода" внутри тега <script>.
Например, с помощью такого кода:
Каким был бы ваш ответ, чтобы сделать код как можно короче?
Прежде чем продолжить, подумайте над вопросом. Как только у вас будет ответ, продолжим!
Quantum Entanglement of DOM and window
Знали ли вы, что элементы внутри DOM могут влиять на объект window?
Оказалось, что когда вы определяете элемент с id в HTML, вы можете напрямую получить к нему доступ в JavaScript:
Из-за области видимости, вы даже можете получить к нему доступ напрямую, используя просто btn, поскольку текущая область будет искать вверх, пока не найдет window.
Так что ответ на предыдущий вопрос:
Нет необходимости в getElementById или querySelector. Просто используйте переменную с тем же именем, что и id, чтобы получить доступ к объекту.
Здесь есть два ключевых момента:
1. Значение атрибута name для всех элементов embed, form, img и object, которые имеют непустой атрибут name. 2. Значение атрибута id для всех HTML-элементов, которые имеют непустой id атрибут.
Это означает, что наряду с использованием id для прямого доступа к элементам через window, вы также можете использовать атрибут name для доступа к элементам <embed>, <form>, <img>, <object>:
Понимание этой спецификации приводит нас к выводу:
Мы можем влиять на JavaScript через элементы HTML.
Эту технику можно использовать для атак, известных как DOM clobbering. Впервые я столкнулся с термином "clobbering" в контексте этой атаки, что означает "перезапись". Он относится к использованию DOM для перезаписи определенных элементов для достижения атаки.
Введение в DOM Clobbering
При каких обстоятельствах мы можем использовать DOM clobbering для атак?
Во-первых, должна быть возможность отображать наш пользовательский HTML на странице; в противном случае это невозможно.
Так что потенциальный сценарий атаки может выглядеть так:
Допустим, есть доска комментариев, где вы можете ввести любой контент. Однако ввод проходит обработку на стороне сервера, удаляя все, что может выполнить JavaScript. Так что <script></script> удаляется, и атрибут onerror из <img src=x onerror=alert(1)> отбрасывается. Многие полезные нагрузки XSS не сработают.
Вкратце, вы не можете выполнить JavaScript для достижения XSS, потому что все такие попытки попадают под фильтрацию.
Однако, по различным причинам, HTML-теги не фильтруются, поэтому вы можете внедрить пользовательский HTML. Пока JavaScript не выполняется, вы можете вставить любой HTML-тег и установить любой атрибут.
Так что вы можете сделать следующее
Опираясь на приобретенные выше знания, вы можете вставить тег с id "TEST_MODE" - <div id="TEST_MODE"></div>. Таким образом, JavaScript if (window.TEST_MODE) пройдет, потому что window.TEST_MODE будет этим элементом div.
Затем мы можем использовать <a id="TEST_SCRIPT_SRC" href="my_evil_script"></a> чтобы сделать window.TEST_SCRIPT_SRC строкой, которую мы хотим получить после конвертации.
В большинстве случаев простого переопределения переменной с помощью HTML-элемента недостаточно. Например, если вы преобразуете window.TEST_MODE в код на вышеуказанного сниппета в строку и напечатаете его:
Результат будет: [object HTMLDivElement].
Преобразование HTML-элемента в строку приведет к этому формату, который в данном случае не пригоден для использования. Однако, к счастью, есть два элемента в HTML, которые обрабатываются по-разному при преобразовании в строку, <base> и <a>:
Эти два элемента возвращают URL при вызове toString, и мы можем установить URL, используя атрибут href, что позволяет нам контролировать содержимое после toString.
Итак, объединяя вышеуказанные методы, мы узнали:
1. Мы можем использовать HTML с атрибутом id для изменения переменных JavaScript. 2. Использование <a> с href и id чтобы сделать результат toString элемента желаемым значением.
Используя эти две техники в соответствующем контексте, мы можем потенциально скомпрометировать DOM.
Однако важно помнить: если переменная, которую вы хотите атаковать, уже существует, её нельзя переопределить с помощью DOM. Например:
Вложенное DOM Clobbering
В предыдущем примере мы использовали DOM для переопределения window.TEST_MODE и создания неожиданного поведения. Но что, если целью переопределения является объект? Возможно ли это?
Например, window.config.isTest, можем ли мы его переопределить с помощью DOM clobbering?
Есть несколько способов переопределить это. Первый - использование иерархической структуры HTML-тегов, в частности элемента form:
Мы можем использовать form[name] или form[id] для доступа к его дочерним элементам, например:
Таким образом, мы можем создать два уровня компрометации DOM. Однако стоит отметить: здесь нет <a>, поэтому результат toString будет в непригодном для использования виде.
Более вероятная возможность для эксплуатации - когда вам нужно переопределить что-то, к чему обращаются с помощью свойства value, например: config.environment.value. В этом случае вы можете использовать атрибут value <input> для его переопределения:
Простыми словами, можно переопределить только встроенные атрибуты, другие - нет.
В дополнение к использованию иерархической природы самого HTML, можно использовать также HTMLCollection.
В раннее упомянутом разделе "Именованный доступ к объекту Window" в спецификации указано:
Если есть несколько вещей для возврата, возвращается HTMLCollection.
Вот так:
Вы можете генерировать HTMLCollection, используя один и тот же id, а затем использовать имя для извлечения конкретного элемента из HTMLCollection, достигая двухуровневого вложения.
И если мы сочетаем <form> с HTMLCollection, мы можем достичь трех уровней:
Используя один и тот же id, мы позволяем config иметь доступ к HTMLCollection. Затем, используя config.prod, можно извлечь элемент с именем "prod" из HTMLCollection, который является формой. Далее, мы используем form.apiUrl для доступа к входу под формой, и, наконец, используем value для извлечения его атрибута.
Так что, если желаемый атрибут является HTML-атрибутом, мы можем получить четыре уровня взаимодействия; в противном случае, мы можем использовать только три уровня.
Однако в Firefox всё немного по-другому. В Firefox не возвращается HTMLCollection. Например, с тем же кодом:
В Firefox он будет выводить только первый элемент <a>, а не HTMLCollection. Поэтому, в Firefox, мы можем использовать только <form>, а также <iframe>, который будет упомянут позже.
Более сложное вложение
Предыдущее упоминание о трёх уровнях или условные четыре уровня уже являются пределом. Есть ли способ преодолеть это ограничение?
Когда вы создаете iframe и даёте ему имя, вы можете получить доступ к window внутри iframe, используя это имя. Это может быть сделано так:
Причина использования setTimeout здесь заключается в том, что iframes не загружаются синхронно, поэтому нам нужно некоторое время, чтобы правильно получить доступ к содержимому внутри iframe.
С помощью iframes мы можем создать ещё больше уровней:
Расширение поверхности атаки через document
Как упоминалось ранее, возможность использовать DOM clobbering не велика, поскольку код должен сначала использовать глобальную переменную, которая не объявлена. Обычно такие ситуации находятся ESLint во время разработки, так что как это все угодило в интернет?
Сила DOM clobbering заключается в том, что, помимо window, есть несколько элементов, которые вместе с именем могут повлиять на document.
Давайте рассмотрим пример, чтобы понять:
Здесь мы использовали HTML-элемент для влияния на document. Изначально document.cookie должен отображать куки, но теперь это стал элемент <img name=cookie>. Кроме того, lastElementChild, который должен возвращать последний элемент, переопределён именем под формой, что приводит к извлечению элемента с таким же именем.
Даже document.getElementById может быть переопределён с помощью DOM, что вызывает ошибку при вызове document.getElementById(), что может вызвать сбой всей страницы.
В задачах CTF это часто используется в сочетании с ранее упомянутым загрязнением прототипа для достижения импакта:
Почему так происходит?
Теперь, document.cookie - это HTML-элемент. Используя синтаксис шаблона, если содержимое не является строкой, автоматически вызывается метод toString. однако HTML-элементы сами по себе не реализуют toString. Следовательно, согласно цепочке прототипов, в конечном итоге вызывается наше загрязнённое Object.prototype.toString, возвращая загрязненный результат.
Комбинируя эти уязвимости, мы можем манипулировать значением document.cookie и таким образом влиять на последующий поток.
DOMPurify, о котором упоминалось ранее, на самом деле обрабатывает эту часть специально при санитизации:
Если значения id или name уже существуют в document или formElement, он пропускает предотвращение компрометации DOM против документа и формы.
Он не предоставляет защиту от DOM clobbering по умолчанию.
Анализ случая: Gmail AMP4Email XSS
В Gmail вы можете использовать некоторые функции AMP, и у Google есть строгий валидатор для этого формата, что делает сложной задачу проведения XSS-атак обычными методами.
Однако кто-то обнаружил, что возможно установить id на HTML-элементе. Они обнаружили, что при установке <a id="AMP_MODE"> в консоли происходит ошибка, указывающая на ошибку загрузки скрипта с частью URL, являющейся undefined. Изучив код внимательно, они нашли фрагмент кода, который выглядел так:
Если мы можем сделать AMP_MODE.test и AMP_MODE.localDev истинными, и установить window.testLocation, мы можем загрузить любой скрипт, который захотим!
Таким образом, эксплуатация выглядела бы так:
Наконец, успешная загрузка любого скрипта позволяет достичь XSS!
Однако автору удалось дойти только до этого шага, прежде чем он был заблокирован CSP, что показывает, что CSP все еще очень полезен
Это один из самых известных примеров DOM clobbering, и исследователем, который обнаружил эту уязвимость, является Михаэль Бентковски, который создал множество классических случаев, упомянутых ранее при обсуждении Mutation XSS и загрязнения прототипа.
Заключение
Хотя варианты использования DOM clobbering ограничены, это действительно интересный метод атаки! Более того, если вы не знаете об этой функции, вы никогда не подумали бы, что HTML может использоваться для влияния на содержимое глобальных переменных.
Ссылки:
Last updated