// HackTricks · Network Services

NodeJS Express

NodeJS Express

Quick Fingerprinting

Useful Express indicators during recon:

  • X-Powered-By: Express or stack traces mentioning express, body-parser, qs, cookie-parser, express-session, or finalhandler
  • Cookies prefixed with s: (signed cookie) or j: (JSON cookie)
  • Session cookies such as connect.sid
  • Hidden form fields or query parameters such as _method=PUT / _method=DELETE
  • Error pages leaking Cannot GET /path, Cannot POST /path, Unexpected token in body-parser, or URIError during query parsing

When you confirm Express, focus on the middleware chain, because most interesting bugs come from parsers, proxy trust, session handling, and method-tunneling rather than from the framework core itself.

The cookie-monster tool automates testing candidate Express.js cookie secrets and re-signing cookie values once a secret is known.[3]

Express commonly exposes two useful cookie formats:

  • s:<value>.<sig> signed cookies handled by cookie-parser or express-session
  • j:<json> JSON cookies that are automatically parsed by cookie-parser

If cookie-parser receives a signed cookie and its signature is invalid, the unsigned value becomes false rather than the attacker-supplied string. When the application supplies an array of secrets, verification tries each secret in order, so retained rotation keys continue to validate old signatures.[4]

cookie-monster -c eyJmb28iOiJiYXIifQ== -s LVMVxSNPdU_G8S3mkjlShUD78s4 -n session

Custom wordlist

cookie-monster -c eyJmb28iOiJiYXIifQ== -s LVMVxSNPdU_G8S3mkjlShUD78s4 -w custom.lst

Test multiple cookies using batch mode

cookie-monster -b -f cookies.json

Test multiple cookies using batch mode with a custom wordlist

cookie-monster -b -f cookies.json -w custom.lst

If you know the secret you can sign the cookie.

cookie-monster -e -f new_cookie.json -k secret

Query String and URL-Encoded Parser Abuse

Express targets often become interesting when they parse attacker-controlled keys into nested objects.

  • req.query can be configured with different parsers, including qs
  • express.urlencoded({ extended: true }) uses qs-style parsing for application/x-www-form-urlencoded
  • Nested parsing unlocks object injection, mass assignment, NoSQL injection, and prototype pollution chains if the parsed object is merged into application state[2]

Practical payloads to try:

# Mass assignment style probe
curl 'https://target.example/profile?role=admin&isAdmin=true'

# Nested object / qs syntax
curl 'https://target.example/search?user[role]=admin&filters[name][$ne]=x'

# URL-encoded body against express.urlencoded({ extended: true })
curl -X POST 'https://target.example/api/update'   -H 'Content-Type: application/x-www-form-urlencoded'   --data 'profile[role]=admin&filters[$ne]=x'

If the app reflects or persists the resulting object, pivot into the dedicated pages for exploitation details:

Mass Assignment Cwe 915

Express Prototype Pollution Gadgets

Extra tests that are worth sending against Express specifically:

  • Deep nesting to look for parser limits, timeouts, or 400/413 differences
  • Duplicate keys to see whether the app keeps the first value, the last one, or an array
  • Bracket syntax such as a[b][c]=1, dotted syntax such as a.b=1, and __proto__ / constructor[prototype] payloads

trust proxy Abuse

If the app uses app.set("trust proxy", true) or trusts too many hops, Express will derive security-relevant values from forwarding headers. If the reverse proxy does not overwrite them, a client can spoof them directly.[1]

That affects:

  • req.hostname via X-Forwarded-Host
  • req.protocol via X-Forwarded-Proto
  • req.ip / req.ips via X-Forwarded-For

This is useful for:

  • Password reset poisoning and absolute URL poisoning
  • Bypassing IP-based allowlists, rate limits, or audit trails
  • Influencing secure cookie handling and HTTPS-only logic in apps that key off req.protocol
  • Poisoning redirects or cacheable responses when the app templates absolute links with forwarded host/proto headers
POST /reset-password HTTP/1.1
Host: target.example
X-Forwarded-Host: attacker.example
X-Forwarded-Proto: https
X-Forwarded-For: 127.0.0.1
Content-Type: application/json

{"email":"victim@target.example"}

Check whether generated links, redirect locations, logs, or access-control decisions now use attacker-supplied values.

Related pages:

Reset Password

Readme

express-session Testing Notes

Common Express deployments use express-session, which signs the session identifier cookie but stores the real state server-side.

Useful checks:

  • Session fixation: authenticate with a pre-login cookie and verify whether the SID stays the same after login
  • Weak secret rotation: some deployments verify cookies with an array of old secrets, so previously valid signatures may continue to work
  • saveUninitialized: true: the application stores new but unmodified sessions, increasing anonymous session volume. It does not create fixation by itself, but it supplies pre-authentication SIDs that make a failure to rotate the SID easier to test.
  • MemoryStore is intentionally unsuitable for production: it leaks memory under many workloads, does not scale beyond one process, and loses sessions on restart.[5]

A practical fixation workflow:

  1. Obtain an anonymous session cookie from the target.
  2. Send that cookie to a victim or authenticate with it yourself.
  3. Check whether login binds the authenticated state to the existing SID.
  4. If it does, replay the same cookie in a separate browser session.

If the application does not rotate or regenerate the session after authentication, test whether authenticated state remains bound to a SID chosen or learned before login. req.session.regenerate() is the middleware’s built-in rotation primitive.[5]

Method Override Tunneling

Some Express apps use method-override to tunnel verbs that HTML forms cannot send natively. When enabled, always test whether you can smuggle dangerous methods through a route that the front-end, WAF, or CSRF logic assumed was only POST.

Typical probes:

POST /users/42 HTTP/1.1
Host: target.example
X-HTTP-Method-Override: DELETE
Content-Type: application/x-www-form-urlencoded

confirm=yes
POST /users/42?_method=PUT HTTP/1.1
Host: target.example
Content-Type: application/x-www-form-urlencoded

role=admin

Interesting impacts:

  • Reaching hidden PUT / PATCH / DELETE routes through a POST-only edge control
  • Bypassing route-specific middleware that only checks req.method
  • Triggering state-changing handlers via CSRF when the application validates only the outer request method

The official method-override middleware checks only original POST requests by default (options.methods: ['POST']), so prioritize POST requests with header, body, and query-string override values.[6]

References