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.
Session cookies
Section titled “Session cookies”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:
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.
# 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/nullcurl -s -b jar.txt https://target.example/account >/dev/null # workscurl -s -b jar.txt https://target.example/logout >/dev/null # log outcurl -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.
JWT (JSON Web Tokens)
Section titled “JWT (JSON Web Tokens)”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:
# Split on '.' and base64url-decode the header and payload:JWT='eyJhbGciOi...' # your own tokenfor part in 1 2; do echo "$JWT" | cut -d. -f$part | tr '_-' '/+' | base64 -d 2>/dev/null | jq .donealg: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:
# Offline only — no traffic to the target. Mode 16500 = JWT (HMAC).echo "$JWT" > jwt.txthashcat -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:
python3 jwt_tool.py "$JWT" # decode + summarizepython3 jwt_tool.py -h # confirm flags for alg:none, cracking, tamperingImpact. Forging a JWT means minting tokens for any user or role — total authentication bypass and privilege escalation.
OAuth / SSO
Section titled “OAuth / SSO”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.
# 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.
Password reset and reset-token flaws
Section titled “Password reset and reset-token flaws”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
Hostheader, 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.
# 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.
MFA bypass classes (conceptual)
Section titled “MFA bypass classes (conceptual)”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 and brute-force protections
Section titled “Rate limiting and brute-force protections”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.
# 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 2doneIf 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.
Key points
Section titled “Key points”- 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.