Skip to content

Authentication & Session Attacks

Authentication decides who you are; session management decides how the server keeps remembering. Bugs in either let you become someone else, which is why this class produces some of the highest-impact reports. This page is terminal-focused and deliberately polite: low request volumes, your own accounts, no real-user data.

After you log in, the server usually hands the browser a session cookie that stands in for your credentials on every later request. If that cookie is weak, guessable, or mishandled, the whole login is moot.

Check the flags first. They’re visible in one request:

Terminal window
curl -s -i https://target.example/login -d 'user=me&pass=...' | grep -i '^set-cookie'
# Set-Cookie: session=...; Path=/; Secure; HttpOnly; SameSite=Lax
Flag What it does Missing =
Secure Cookie only sent over HTTPS Session can leak over plain HTTP
HttpOnly JavaScript can’t read the cookie XSS can steal the session
SameSite Limits cross-site sending Broader CSRF exposure
Path / Domain Scope of the cookie Over-broad domain shares the cookie with subdomains

Predictable tokens. Grab several session tokens from fresh logins and eyeball them. Sequential numbers, timestamps, or short/low-entropy values mean a token could be guessed. Real session IDs should look like long random blobs. Collect a handful (from your own logins) and compare — you’re looking for structure, not doing statistics on live users.

Session fixation. The server should issue a new session ID at login. If the ID you had before logging in still works after, an attacker who planted a known ID in the victim’s browser can ride the authenticated session. Test: note the pre-login session value, log in, and check whether the same value is now authenticated.

Invalidation. Two events must kill sessions server-side:

  • Logout should invalidate the token on the server, not just delete the client cookie. Save a valid session, log out, then replay a request with the saved cookie — it must now fail.
  • Password change / reset should invalidate all other sessions. Log in on two “devices” (two cookie jars), change the password on one, and confirm the other is kicked out.
Terminal window
# Prove logout doesn't invalidate server-side (using your own account):
curl -s -c jar.txt https://target.example/login -d 'user=me&pass=...' >/dev/null
curl -s -b jar.txt https://target.example/account >/dev/null # works
curl -s -b jar.txt https://target.example/logout >/dev/null # log out
curl -s -b jar.txt -o /dev/null -w '%{http_code}\n' https://target.example/account
# Still 200 after logout? The session wasn't invalidated server-side.

Impact. A stolen or guessed session is account takeover with no password needed. Missing invalidation means a leaked token stays valid forever.

A JWT is three base64url parts joined by dots: header.payload.signature. The header names the signing algorithm; the payload holds claims (sub, exp, custom roles); the signature is what stops you editing the payload. Every JWT bug is a way to change the payload without invalidating the signature check.

Decode one with plain tooling — no secrets needed for the header/payload:

Terminal window
# Split on '.' and base64url-decode the header and payload:
JWT='eyJhbGciOi...' # your own token
for part in 1 2; do
echo "$JWT" | cut -d. -f$part | tr '_-' '/+' | base64 -d 2>/dev/null | jq .
done

alg:none. Some libraries honor a header of {"alg":"none"} and then skip signature verification entirely. If accepted, you can forge any payload. Test by crafting a token with alg:none and an empty signature and seeing if the server trusts it.

Weak-secret brute force (HS256). HS256 signs with a shared secret. If that secret is weak (secret, password, a dictionary word), it can be recovered offline — no requests to the target at all, which is why it’s both powerful and polite. Conceptually, feed the token to hashcat’s JWT mode:

Terminal window
# Offline only — no traffic to the target. Mode 16500 = JWT (HMAC).
echo "$JWT" > jwt.txt
hashcat -a 0 -m 16500 jwt.txt wordlist.txt
# If it cracks, you can sign arbitrary tokens with that secret.

Missing signature verification. Distinct from alg:none: the server decodes the payload but never checks the signature at all. Test by tampering with a payload claim and leaving the signature untouched — if the server accepts it, verification is absent.

Expiry and kid abuse (conceptually). exp must be enforced — replay an expired token and it should be rejected. The kid (key ID) header tells the server which key to use; if it’s used to build a file path or SQL lookup, it can become path traversal or injection that points verification at a key you control. These are subtle; understand them before probing.

jwt_tool. The community tool jwt_tool automates decoding and the standard attack modes. Point it at a token you own and read its help for exact, current flags:

Terminal window
python3 jwt_tool.py "$JWT" # decode + summarize
python3 jwt_tool.py -h # confirm flags for alg:none, cracking, tampering

Impact. Forging a JWT means minting tokens for any user or role — total authentication bypass and privilege escalation.

OAuth lets an app act on your behalf without seeing your password; “Sign in with…” (SSO) is OAuth plus identity. It’s central to the Shopify app model — apps get access via OAuth scopes — so these bugs matter for that program. The flow has several moving parts, each with a classic flaw.

redirect_uri validation. After you approve, the provider sends the authorization code/token back to a redirect_uri. If the provider accepts a redirect_uri the attacker controls (via loose matching, an open redirect on an allowed domain, or an appended path/subdomain), the code/token is delivered to the attacker.

Terminal window
# Signal: does the provider reject a foreign redirect_uri, or bounce the code to it?
curl -s -i "https://provider.example/oauth/authorize?client_id=APP&response_type=code&redirect_uri=https://attacker.example/cb" \
| grep -i '^location'
# A Location that sends the code toward attacker.example = redirect_uri weakness.

state / CSRF. The state parameter is a random value the client generates and must verify on return; it ties the callback to the user who started the flow. If state is missing or unchecked, an attacker can complete a flow that links their provider account to the victim’s session — “login CSRF” / forced account linking.

Token leakage via Referer. If the code or token ends up in a URL that then loads third-party content, the browser can send it in the Referer header to another origin. Watch for access_token/code sitting in a URL on a page that also loads external scripts, images, or ads.

Scope escalation. Request broader scopes than the app should have, or tamper with the scope parameter, and see whether the provider grants more than intended.

Account linking. “Connect your Google account” flows are prone to attaching an attacker-controlled identity to a victim’s account (or vice versa) when linking doesn’t re-verify ownership or lacks state.

Impact. Stolen authorization codes/tokens are account takeover; scope escalation broadens what a malicious app can do; broken account linking silently hands over the account. Chain a benign open redirect into redirect_uri for a strong, still non-destructive PoC.

The reset flow is a second, often weaker, authentication path. Common issues:

  • Predictable / short tokens. Reset links using sequential IDs, timestamps, or short numeric codes can be guessed. (Confirm structure with your own reset emails.)
  • Token not expiring or not single-use. A reset link that still works days later, or twice, is a standing takeover primitive.
  • Host header / link poisoning. If the reset email’s link is built from a request Host header, an attacker may be able to make the victim’s link point at their own domain, capturing the token.
  • User enumeration. Different responses for “email exists” vs “doesn’t” leak valid accounts.
  • Response leaks the token. Occasionally the API returns the reset token in its JSON response — test on your own account.
Terminal window
# Enumeration signal — compare responses for a known vs unknown email:
for e in me@my-mailbox.example nobody-$RANDOM@example.com; do
printf '%s -> ' "$e"
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' \
https://target.example/password/reset -d "email=$e"
done
# Different status or body size between the two = user enumeration.

Impact. A guessable, reusable, or leaked reset token is direct account takeover.

Multi-factor auth adds a second check; the bugs are usually in how it’s wired, not the algorithm. Understand these classes before touching a target, and only test your own enrolled account:

  • Skippable step. The post-password endpoint is reachable directly, without the MFA step ever being enforced.
  • No rate limit on the code. A 6-digit code with unlimited attempts is brute-forceable — but see the caution below; do not actually brute a live code.
  • Code reuse / no expiry. The same OTP works more than once or long after issue.
  • Backup-code weakness. Recovery codes that are short, predictable, or not invalidated after use.
  • Response reveals success. The verify response leaks whether the code was right independent of session state.

Rate limiting is itself a testable control, and testing it politely is an art. The goal is to learn the policy with the fewest requests, not to hammer the endpoint.

How to test politely. Send a small, bounded number of requests and watch for the response that signals throttling: HTTP 429 Too Many Requests, a Retry-After header, or X-RateLimit-* headers.

Terminal window
# Low-volume probe: 10 requests, spaced out, watching status + rate headers.
for i in $(seq 1 10); do
curl -s -o /dev/null -w '%{http_code}\n' \
-D - https://target.example/api/login -d 'user=me&pass=wrong' 2>/dev/null \
| grep -iE '^(http|429|x-ratelimit|retry-after)'
sleep 2
done

If you see 429/Retry-After early, the control works — note the threshold and stop. If you don’t see any throttling, the finding is “missing rate limiting”; you prove that with the same small sample plus a description of the impact, not by actually running a giant brute-force.

Impact. Absent rate limits turn every other auth weakness (guessable OTPs, weak passwords, reset codes) into a practical attack. It’s frequently reported as a standalone medium-severity issue and as an amplifier for the bugs above.

  • Check cookie flags, token entropy, and invalidation-on-logout/password-change first — they’re cheap and often broken.
  • JWT attacks (alg:none, weak-secret cracking, missing verification) are mostly offline; only the forged token touches the target, once, on your own account.
  • OAuth bugs cluster around redirect_uri, state, and token leakage; chain an open redirect for a clean PoC.
  • Reset flows and MFA fail on predictability, reuse, expiry, and missing rate limits — prove the class minimally, describe impact in words.
  • Test rate limiting with a small sample; stop the moment you see 429. Politeness keeps you in scope.