From 294bfb090be2b781724d53fe67c3a46b0ce2dad7 Mon Sep 17 00:00:00 2001 From: Tigah <88289044+TBX3D@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:35:51 -0700 Subject: [PATCH] feat(modules): add host-header injection and xxe probe modules (#277) * feat(modules): add host-header injection and xxe probe modules host-header-injection sets an rfc 2606 .invalid marker in the forwarded-host headers and fires only when it comes back inside an absolute-url construct (href/src/action/content/url), so a benign echo into text or an attribute cannot trigger it. xxe-error-based expands a SYSTEM entity pointing at a local file and matches only that file's contents, so an echo of the payload (a path string) cannot satisfy it. * chore(modules): trim redundant module header comments drop the leading comment restating id/name on host-header-injection and xxe-error-based --- modules/http/host-header-injection.yaml | 47 +++++++++++++++++++ modules/http/xxe-error-based.yaml | 60 +++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 modules/http/host-header-injection.yaml create mode 100644 modules/http/xxe-error-based.yaml diff --git a/modules/http/host-header-injection.yaml b/modules/http/host-header-injection.yaml new file mode 100644 index 0000000..ac9d40b --- /dev/null +++ b/modules/http/host-header-injection.yaml @@ -0,0 +1,47 @@ +id: host-header-injection +info: + name: Host Header Injection + author: sif + severity: medium + description: Detects host header injection by confirming an attacker-controlled forwarded-host value is echoed back inside an absolute URL construct (href/src/action/content/url) in the rendered response, proving the app used the untrusted header to build a link + tags: [host-header-injection, ssrf, cache-poisoning, owasp-top10] + +type: http + +http: + method: GET + paths: + - "{{BaseURL}}/" + - "{{BaseURL}}/login" + - "{{BaseURL}}/reset-password" + - "{{BaseURL}}/password/reset" + - "{{BaseURL}}/forgot-password" + - "{{BaseURL}}/oauth/authorize" + + # RFC 2606 .invalid guarantees the marker can never collide with a real + # domain, so any absolute-URL reflection of it in the body is proof the + # forwarded-host header (not the request line) drove link generation. + headers: + X-Forwarded-Host: "sifprobe-a8f31c92.invalid" + X-Forwarded-Server: "sifprobe-a8f31c92.invalid" + X-Host: "sifprobe-a8f31c92.invalid" + X-Original-Host: "sifprobe-a8f31c92.invalid" + Forwarded: "for=127.0.0.1;host=sifprobe-a8f31c92.invalid;proto=http" + + threads: 10 + + matchers: + - type: regex + part: body + regex: + - '(?is)(?:href|src|action|content)\s*=\s*["'']?(?:https?:)?//sifprobe-a8f31c92\.invalid' + - '(?is)url\(\s*["'']?(?:https?:)?//sifprobe-a8f31c92\.invalid' + condition: or + + extractors: + - type: regex + name: reflected_context + part: body + regex: + - '((?:href|src|action|content|url)\s*[=(]\s*["'']?(?:https?:)?//sifprobe-a8f31c92\.invalid[^"'')\s]*)' + group: 1 diff --git a/modules/http/xxe-error-based.yaml b/modules/http/xxe-error-based.yaml new file mode 100644 index 0000000..1d1754a --- /dev/null +++ b/modules/http/xxe-error-based.yaml @@ -0,0 +1,60 @@ +id: xxe-error-based +info: + name: XML External Entity Injection + author: sif + severity: high + description: Detects XML external entity injection by confirming the parser expanded a SYSTEM entity and returned the contents of a local file the payload never contained + tags: [xxe, injection, xml, owasp-top10] + +type: http + +http: + method: POST + paths: + - "{{BaseURL}}/" + - "{{BaseURL}}/api" + - "{{BaseURL}}/api/xml" + - "{{BaseURL}}/xml" + - "{{BaseURL}}/soap" + - "{{BaseURL}}/upload" + - "{{BaseURL}}/import" + - "{{BaseURL}}/xmlrpc.php" + + headers: + Content-Type: application/xml + Accept: application/xml + + # the payload is only ever a file *path*; a match requires the target + # file's actual contents, so a benign echo of the payload text (the path + # string itself) can never satisfy the matcher. + payloads: + - "/etc/passwd" + - "/etc/hosts" + - "/windows/win.ini" + + body: | + + ]> + &xxe; + + threads: 10 + + matchers: + - type: regex + part: body + regex: + - "root:.*:0:0:" + - "daemon:.*:1:1:" + - "nobody:.*:65534:" + - "127\\.0\\.0\\.1\\s+localhost" + - "\\[fonts\\]" + - "; for 16-bit app support" + condition: or + + extractors: + - type: regex + name: leaked_content + part: body + regex: + - "(root|daemon|nobody):.*:[0-9]+:[0-9]+:" + group: 0