01 февраля 2021

VolgaCTF 2020 / Notes writeup


Немного запоздавший разбор задания Notes из дополнительного конкурса финала VolgaCTF 2020.
Задание представляет собой сервис для создания заметок с поддержкой HTML тегов и смайлов.

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

Заголовок заметки позволяет использовать только текст и смайлы, а сам контент фильтруется с использованием jQuery и DOMPurify.

<h3>Title &lt;XSS&gt; <img class="smile" src="/static/smiles/dance.gif" name="dance"></h3>
<p id="content"></p>
...
<script>
var note = "Content\x20\x3CXSS\x3E\x20\x3Cimg\x20class\x3D\x22smile\x22\x20src\x3D\x22\x2Fstatic\x2Fsmiles\x2Fdance.gif\x22\x20name\x3D\x22dance\x22\x3E\x20";
$(document).ready(function() {
  try {
    $.globalEval("note = DOMPurify.sanitize(note)");
  } finally {
    content.innerHTML = note;
  }
});

Как видно из кода, для эксплуатации XSS необходимо, чтобы $.globalEval вернул исключение. Однако, сделать это через содержимое заметки довольно сложно, поэтому участникам необходимо было изучить реализацию остальных функций приложения, а именно - смайлов. Каждый смайл добавлялся в виде тега <img> с аттрибутом name . Добавление именованных img позволяет частично использовать технику DOM Clobbering.

То есть, создание заметки со смайлом :dance: приведет к созданию HTML тега <img name=dance> , который будет доступен в JavaScript через window.dance . Но именованные свойства поддерживаются не только объектом window , но и document, более того, используя это можно переопределить такие значения как document.cookie , document.documentElement , document.body и другие (https://html.spec.whatwg.org/multipage/dom.html#dom-document-namedItem-which).

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

Рассмотрим, как именно выполняется функция $.globalEval

    function DOMEval( code, node, doc ) {
        doc = doc || document;
        var i, val,
            script = doc.createElement( "script" );
        script.text = code;
        ...
        doc.head.appendChild( script ).parentNode.removeChild( script );

Используя смайл :head: можно изменить логику выполнения данной функции и перезаписать document.head , но скрипт все равно выполнится корректно и DOMPurify удалит XSS.

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

Но использование двух смайлов :head: :head: приведет к созданию HTMLCollection в document.head в результате чего вызов document.head.appendChild вернет исключение в $.globalEval и контент заметки не будет отфильтрован.

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

Данная особенность позволяет изменять логику выполнения JS скриптов и приводит к забавным эффектам.
Пример (не относится к CTF таску):

<div id=x></div>
<iframe name=documentElement srcdoc='<a href="tel:<img/src/onerror=alert(1)>" id=clientWidth>a</a>'></iframe>
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script>
  $("#x").html('innerWidth:'+$(window).innerWidth()) //  == innerWidth:tel:<img/src/onerror=alert(1)>
</script>

Рекомендуемые ссылки:
https://portswigger.net/research/dom-clobbering-strikes-back
https://bugzilla.mozilla.org/show_bug.cgi?id=1420032
https://html.spec.whatwg.org/multipage/dom.html#dom-document-namedItem-which
https://medium.com/@terjanq/clobbering-the-clobbered-vol-2-fb199ad7ec41
https://blog.bi0s.in/2020/08/26/Web/GoogleCTF20-SafeHtmlPaste/