// 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

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.