From 84c27d2189378d062dbf5888eec1185f875f1a82 Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Sat, 4 Jul 2026 05:14:12 +0530 Subject: [PATCH 1/2] Add Express.js and WebSocket security skills Fill framework and protocol gaps explicitly listed in the skills README. --- strix/skills/frameworks/express.md | 206 ++++++++++++++++++++++++++++ strix/skills/protocols/websocket.md | 187 +++++++++++++++++++++++++ 2 files changed, 393 insertions(+) create mode 100644 strix/skills/frameworks/express.md create mode 100644 strix/skills/protocols/websocket.md diff --git a/strix/skills/frameworks/express.md b/strix/skills/frameworks/express.md new file mode 100644 index 000000000..40051bac6 --- /dev/null +++ b/strix/skills/frameworks/express.md @@ -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. diff --git a/strix/skills/protocols/websocket.md b/strix/skills/protocols/websocket.md new file mode 100644 index 000000000..0b6aededb --- /dev/null +++ b/strix/skills/protocols/websocket.md @@ -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 + +``` + +- 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. From ffd15a5abbcc24dc2e331ce9990261b39f30cba9 Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Sat, 4 Jul 2026 05:36:06 +0530 Subject: [PATCH 2/2] Address Greptile review: CSRF and CSWSH guidance - Replace deprecated csurf reference with maintained CSRF patterns - Add SameSite caveat for cross-site WebSocket hijacking tests --- strix/skills/frameworks/express.md | 2 +- strix/skills/protocols/websocket.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/strix/skills/frameworks/express.md b/strix/skills/frameworks/express.md index 40051bac6..8a4118260 100644 --- a/strix/skills/frameworks/express.md +++ b/strix/skills/frameworks/express.md @@ -109,7 +109,7 @@ 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 +- `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 diff --git a/strix/skills/protocols/websocket.md b/strix/skills/protocols/websocket.md index 0b6aededb..4262a248c 100644 --- a/strix/skills/protocols/websocket.md +++ b/strix/skills/protocols/websocket.md @@ -66,6 +66,8 @@ Capture legitimate client traffic; map event names (`join`, `subscribe`, `messag Cross-Site WebSocket Hijacking: victim browser opens WS to target with victim's cookies because server doesn't validate `Origin`. +**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