README.md
Rendering markdown...
# Recon: What to Put In, Where to Submit
Forging the JWE payload is the easy part. The hard part is **what claims the app expects** and **where/how it consumes the token**.
## 1. What to Put In (Claim Discovery)
### pac4j defaults (library behavior)
- **Subject** → becomes the profile id (e.g. `UserProfile.getId()`). Apps often use it as username or user id. Common values to try: `admin`, `admin#something`, `administrator`, numeric ids if the app uses them.
- **`$int_roles`** → internal roles list. pac4j maps these to `UserProfile.getRoles()`. Many Spring/pac4j apps expect `ROLE_*` (e.g. `ROLE_ADMIN`, `ROLE_USER`, `ROLE_SUPERUSER`). The app may only check for specific strings.
- **`exp`** → must be in the future or the token may be rejected.
### How to discover app-specific claims
- **Valid token (if you have one):** Decode a real JWT/JWE (if you can decrypt or it’s leaked). Inspect claim names and values (subject format, role names, any custom claims like `email`, `tenant_id`, `iss`).
- **Docs / OpenID config:** Some apps document required claims or expose `/.well-known/openid-configuration`. Check for claim requirements or userinfo schema.
- **Application behavior:** Try a low-privilege account, capture a legitimate token (e.g. from browser devtools or a proxy), decode and mirror structure with elevated subject/roles.
- **Code / config:** If you have access to app or framework config, search for `JwtAuthenticator`, `profileDefinition`, `PrincipalNameAttribute`, or custom claim mappings. That shows which claim is used as username and how roles are read.
- **Trial and error:** Start with `sub` + `$int_roles` + `exp`. If the app still returns 401 or doesn’t treat you as admin, try: different subject formats (with/without `#`), different role names (e.g. `ADMIN` vs `ROLE_ADMIN`), and adding common claims (`email`, `preferred_username`, app-specific keys).
### Common claim shapes to try
| Scenario | sub | $int_roles | Other |
|----------|-----|------------|--------|
| Default pac4j | `admin#override` or `admin` | `ROLE_ADMIN`, `ROLE_SUPERUSER` | — |
| Spring Security style | `admin` | `ROLE_ADMIN`, `ROLE_USER` | — |
| With email | same | same | `email`: `[email protected]` |
| Multi-tenant | target user id | same | `tenant_id` or app-specific |
Use the token-forge CLI `--subject`, `--roles`, and `--claim key=value` to iterate without changing code.
---
## 2. Where to Submit (Endpoint and Transport)
### How the token is sent
- **Authorization header (most common):** `Authorization: Bearer <token>`. This is the default for many pac4j JWT clients and the usual place to try first.
- **Cookie:** Some apps store the JWT in a cookie (e.g. after a callback). Cookie name is often configurable; look for names like `pac4jSessionToken`, `SESSION`, `auth_token`, or names in the app’s security config. Set the cookie to your forged token and reload or hit an API that reads it.
- **Custom header:** Less common, but possible (e.g. `X-Auth-Token`, `X-JWT`). Check docs or responses (e.g. `WWW-Authenticate` or error messages that mention a header).
### Finding protected endpoints
- **401 / 403 vs 200:** Unauthenticated request returns 401 (or 302 to login); same request with a valid token returns 200 (or different body). Use that to confirm which paths require auth and accept your token.
- **Common paths:** Try `/`, `/api`, `/api/**`, `/admin`, `/user`, `/auth/callback`, `/rest/**`, `/graphql`, and any paths mentioned in docs or UI (links, API base URL).
- **Framework hints:** pac4j is often used with Spring Boot, Dropwizard, or other Java frameworks. Their default prefixes and security config (e.g. “everything under `/api` is protected”) give you a list of URLs to test.
- **Incremental testing:** Once you have a forged token, send it (e.g. `Authorization: Bearer <token>`) to a small set of paths. If you get 200 and user-specific data or different behavior than without the token, you’ve found a submission point and can refine claims (e.g. switch to an admin subject/roles) and retest.
### Quick submission checklist
1. Obtain RSA public key (JWKS, config, or docs).
2. Forge token with `token_forge` (or Java PoC) using a first-guess subject and roles.
3. Send `GET` (or relevant method) to likely paths with `Authorization: Bearer <token>`.
4. If no success, try cookie (guess name from app/framework), then other headers.
5. If still 401/403, adjust claims (subject format, role names, extra claims) and repeat.
---
## 3. Using the Tools
```bash
# Try default admin + roles
python -m token_forge --public-key pub.pem --subject admin --roles "ROLE_ADMIN,ROLE_USER"
# Try with extra claims (app might expect email or preferred_username)
python -m token_forge --public-key pub.pem --subject admin --roles "ROLE_ADMIN" --claim [email protected]
# Pipe into curl
TOKEN=$(python -m token_forge --public-key pub.pem --subject admin)
curl -H "Authorization: Bearer $TOKEN" https://target/api/admin/users
```
Use recon to decide `--subject`, `--roles`, and `--claim`; use the checklist to decide where to send the token and how (header vs cookie).
---
## 4. Automated discovery with Kiterunner (kr)
Requires **kr** in `~/bin/kr`. The script forges a token and runs `kr brute` (or `kr scan`) with `Authorization: Bearer <token>` so every request is authenticated.
```bash
# From repo root. Default: kr brute with Assetnote apiroutes-210228 wordlist
python scripts/kr_scan.py https://target.com --jwks-url https://target.com/.well-known/jwks.json
# With PEM and custom subject/roles
python scripts/kr_scan.py https://target.com --public-key pub.pem --subject admin --roles "ROLE_ADMIN,ROLE_USER"
# Custom wordlist, JSON output, dry-run to print the kr command
python scripts/kr_scan.py https://target.com --public-key pub.pem -w paths.txt -o json --dry-run
# Use 'kr scan' instead of 'kr brute', different Assetnote wordlist
python scripts/kr_scan.py https://target.com --jwks-url https://target.com/.well-known/jwks.json --scan -A apiroutes-210228
```
Options: `--claim K=V`, `--exp-sec`, `--timeout`, `--kr-extra '--delay 500ms'`. Hits that return different status/length with the forged token than without indicate protected endpoints that accept the token.