Install
$ agentstack add skill-dolphinllc-claude-security-skills-express-attack-probe ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
About
Express Attack Probe
Authorized probe of an Express.js app the user owns. Follow [shared probing conventions](../../../PROBING.md) — discover base URL via env / package.json scripts.dev / Dockerfile. Never hardcode the port.
Express-specific attack surface
- Middleware order is silently security-critical: routes registered before auth bypass it entirely.
req.bodyisObject-prototype-mergeable in apps usinglodash.merge/Object.assign(target, req.body)— prototype pollution → privilege escalation when the merged object is later checked forisAdmin.qs(default Express query parser) parses?a[b][c]=1into nested objects up to depth 5; can cause CPU bombs and bypass naive type checks.trust proxyset totruelets attackers spoofreq.ip/X-Forwarded-For, defeating per-IP rate-limit / geo-block.res.sendFile/res.downloadwithout containment leaks files outside the intended root.
Procedure
- Authorization preflight + base URL discovery.
- Identify routes (parse
app.use/app.get/router.*in source if available, else crawl). - Probe per the rule table; stop at request budget.
Rules
| ID | Severity (if confirmed) | Probe | Confirmed when | |----|------|-------|----------------| | EXP-PP-001 | high | POST JSON body with {"__proto__": {"isAdmin": true}} to any update/profile endpoint, then make an authenticated read | A subsequent normal-user response shows admin-only data / privileged flag | | EXP-PP-002 | medium | Same body with {"constructor": {"prototype": {"polluted": 1}}}, then probe an endpoint that reflects an object key | Response contains polluted | | EXP-HPP-001 | medium | POST form a=1&a=2&a=3 to a route that uses req.body.a as a string | Server logs / response treats a as array, indicating type-confusion bug | | EXP-QS-001 | medium | GET /?a[b][c][d][e][f]=1 (depth bomb) | 500 / OOM signal — Express's default qs allows depth 5 but apps mutate it | | EXP-QS-002 | low | GET /?a[]=1&a[]=2&... (1000 entries) — keep below DoS threshold per PROBING.md (≤200 reqs total) | App accepts and processes; combined with mass-assign = high | | EXP-PROXY-001 | high | Send X-Forwarded-For: 1.2.3.4 to /login or rate-limited endpoint, repeat with rotating values | Each request counted as different IP = trust proxy = true | | EXP-ORDER-001 | critical | Identify a sensitive route (/admin, /api/users/{id}) and call without auth headers | 2xx response identical to authenticated = auth registered after route | | EXP-SF-001 | high | If a file-serving route exists (GET /files/:name), request GET /files/..%2F..%2Fpackage.json and /files/..%2F..%2F.env | Response body matches the file = path traversal in sendFile | | EXP-CORS-001 | high | OPTIONS /api/* with Origin: https://evil.test + credentials | ACAO: https://evil.test AND ACAC: true reflected = cors({origin: true, credentials: true}) | | EXP-JWT-001 | high | If JWT-based: send Authorization: Bearer and ` | 2xx accepted = jwt.verify without algorithms option | | EXP-EH-001 | medium | Trigger an error (POST /api/x with malformed JSON, very long string, type confusion) | 500 response leaks stack trace mentioning file paths / node_modules | | EXP-SESS-001 | medium | Login, capture connect.sid`. Re-fix the same SID before second login attempt; check if accepted | Session ID not regenerated on login = fixation |
Wrong vs. right (defenses confirmed by these probes)
EXP-PP-001 (prototype pollution)
// ❌
app.patch('/me', (req, res) => {
Object.assign(req.user, req.body); // pollutes Object.prototype
res.json(req.user);
});
// ✅
const ALLOWED = ['name', 'email', 'avatar'];
app.patch('/me', (req, res) => {
const updates = Object.fromEntries(
ALLOWED.filter(k => k in req.body).map(k => [k, req.body[k]])
);
Object.assign(req.user, updates);
res.json(req.user);
});
EXP-PROXY-001 (loose trust proxy)
// ❌
app.set('trust proxy', true); // any client can spoof X-Forwarded-For
app.use(rateLimit({ keyGenerator: req => req.ip }));
// ✅
app.set('trust proxy', 1); // exactly one hop = your LB
// or: app.set('trust proxy', 'loopback, 10.0.0.0/8');
References
- OWASP Cheat Sheet — Node.js: https://cheatsheetseries.owasp.org/cheatsheets/NodejsSecurityCheat_Sheet.html
- Prototype pollution: https://learn.snyk.io/lesson/prototype-pollution/
- Express trust proxy: https://expressjs.com/en/guide/behind-proxies.html
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Dolphinllc
- Source: Dolphinllc/claude-security-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.