Basic Headless Navigation Example

This template visits a URL in the headless browser and waits for it to load.

id: basic-headless-request

info:
  name: Basic Headless Request
  author: pdteam
  severity: info

headless:
  - steps: 
    - action: navigate
      args:
        url: "{{BaseURL}}" 
    - action: waitload

Headless prototype pollution detection

The below template detects prototype pollution on pages with Nuclei headless capabilities. The code for detection is taken from https://github.com/msrkp/PPScan. We make use of script injection capabilities of nuclei to provide reliable detection for prototype pollution.

id: prototype-pollution-check

info:
  name: Prototype Pollution Check
  author: pdteam
  severity: medium
  reference: https://github.com/msrkp/PPScan

headless:
  - steps:
      - action: setheader
        args:
          part: response
          key: Content-Security-Policy
          value: "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;"
      - action: setheader
        args:
          part: response
          key: X-Frame-Options
          value: foo
      - action: setheader
        args:
          part: response
          key: If-None-Match
          value: foo
      # Set the hook to override window.data for xss detection
      - action: script
        args:
          hook: true
          code: |
            // Hooking code adapted from https://github.com/msrkp/PPScan/blob/main/scripts/content_script.js
            (function() {window.alerts = [];

            function logger(found) {
            	window.alerts.push(found);
            }

            function check() {
                loc = location.href;

                if (loc.indexOf("e32a5ec9c99") >= 0 && loc.search("a0def12bce") == -1) {
                    setTimeout(function() {
                        if (Object.prototype.e32a5ec9c99 == "ddcb362f1d60") {
                            logger(location.href);
                        }
                        var url = new URL(location.origin + location.pathname);
                        url.hash = "__proto__[a0def12bce]=ddcb362f1d60&__proto__.a0def12bce=ddcb362f1d60&dummy";
                        location = url.href;
                    }, 5 * 1000);
                } else if (loc.search("a0def12bce") != -1) {
                    setTimeout(function() {
                        if (Object.prototype.a0def12bce == "ddcb362f1d60") {
                            logger(location.href);
                        }
                        window.close();
                    }, 5 * 1000);
                } else {
                    var url = new URL(loc);
                    url.searchParams.append("__proto__[e32a5ec9c99]", "ddcb362f1d60");
                    url.searchParams.append("__proto__.e32a5ec9c99", "ddcb362f1d60");
                    location = url.href;
                }
            }

            window.onload = function() {
                if (Object.prototype.e32a5ec9c99 == "ddcb362f1d60" ||  Object.prototype.a0def12bce == "ddcb362f1d60") {
                    logger(location.href);
                } else {
                    check();
                }
            };

            var timerID = setInterval(function() {
                if (Object.prototype.e32a5ec9c99 == "ddcb362f1d60" || Object.prototype.a0def12bce == "ddcb362f1d60") {
                    logger(location.href);
                    clearInterval(timerID);
                }
            }, 5 * 1000)})();
      - args:
          url: "{{BaseURL}}"
        action: navigate
      - action: waitload
      - action: script
        name: alerts
        args:
          code: "window.alerts"
    matchers:
      - type: word
        part: alerts
        words:
          - "__proto__"
    extractors:
      - type: kval
        part: alerts
        kval:
          - alerts

DVWA XSS Reproduction With Headless Mode

This template logs into DVWA (Damn Vulnerable Web App) and tries to automatically reproduce a Reflected XSS, returning a match if it found that the payload was executed successfully.

id: dvwa-xss-verification

info:
  name: DVWA Reflected XSS Verification
  author: pdteam
  severity: info

headless:
  - steps:
      - args:
          url: "{{BaseURL}}"
        action: navigate
      - action: waitload

      # Set the hook to override window.data for xss detection
      - action: script
        args:
          hook: true
          code: "(function() { window.alert = function() { window.data = 'found' } })()"
      - args:
          by: x
          value: admin
          xpath: /html/body/div/div[2]/form/fieldset/input
        action: text
      - args:
          by: x
          value: password
          xpath: /html/body/div/div[2]/form/fieldset/input[2]
        action: text
      - args:
          by: x
          xpath: /html/body/div/div[2]/form/fieldset/p/input
        action: click
      - action: waitload
      - args:
          by: x
          xpath: /html/body/div/div[2]/div/ul[2]/li[11]/a
        action: click
      - action: waitload
      - args:
          by: x
          value: '"><svg/onload=alert(1)>'
          xpath: /html/body/div/div[3]/div/div/form/p/input
        action: text
      - args:
          keys: "\r" # Press the enter key on the keyboard
        action: keyboard
      - action: waitload
      - action: script
        name: alert
        args:
          code: "window.data"
    matchers:
      - part: alert
        type: word
        words:
          - "found"

XSS Detection

This template detects triggered XSS payloads by using the waitdialog action.

headless:
  - steps:
      - action: navigate
        args:
          url: "{{BaseURL}}/search?text=foo{{url_encode('\\\"><script>alert(3+4)</script>')}}"

      - action: waitdialog
        name: reflected_text_query
        args:
          max-duration: 10s

    matchers:
      - type: dsl
        dsl:
          - reflected_text_query == true

    extractors:
      - type: dsl
        dsl:
          - reflected_text_query_type # Output: "alert"
          - reflected_text_query_message # Output: "7"

DOM XSS Detection

This template performs detection of DOM-XSS for window.name source by hooking common sinks such as eval, innerHTML and document.write.

id: window-name-domxss

info:
  name: window.name DOM XSS
  author: pdteam
  severity: medium

headless:
  - steps:
      - action: setheader
        args:
          part: response
          key: Content-Security-Policy
          value: "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;"
      - action: script
        args:
          hook: true
          code: |
            (function() {window.alerts = [];

            function logger(found) {
            	window.alerts.push(found);
            }

            function getStackTrace () {
              var stack;
              try {
                throw new Error('');
              }
              catch (error) {
                stack = error.stack || '';
              }
              stack = stack.split('\n').map(function (line) { return line.trim(); });
              return stack.splice(stack[0] == 'Error' ? 2 : 1);
            }
            window.name = "{{randstr_1}}'\"<>";

            var oldEval = eval;
            var oldDocumentWrite = document.write;
            var setter = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML').set;
            Object.defineProperty(Element.prototype, 'innerHTML', {
              set: function innerHTML_Setter(val) {
                if (val.includes("{{randstr_1}}'\"<>")) {
                  logger({sink: 'innerHTML', source: 'window.name', code: val, stack: getStackTrace()});
                }
                return setter.call(this, val)
              }
            });
            eval = function(data) {
              if (data.includes("{{randstr_1}}'\"<>")) {
                logger({sink: 'eval' ,source: 'window.name', code: data, stack: getStackTrace()});
              }
              return oldEval.apply(this, arguments);
            };
            document.write = function(data) {
              if (data.includes("{{randstr_1}}'\"<>")) {
                logger({sink: 'document.write' ,source: 'window.name', code: data, stack: getStackTrace()});
              }
              return oldEval.apply(this, arguments);
            };
            })();
      - args:
          url: "{{BaseURL}}"
        action: navigate
      - action: waitload
      - action: script
        name: alerts
        args:
          code: "window.alerts"
    matchers:
      - type: word
        part: alerts
        words:
          - "sink:"
    extractors:
      - type: kval
        part: alerts
        kval:
          - alerts