Aryaman Malhotra

I Break Into SaaS Apps
So Hackers Can't.

Manual penetration testing for early-stage SaaS founders. Clear reports. Actionable fixes.

8+ High-Impact Vulnerabilities Found on Live Products  |  Admin Panel Exposures  |  Auth Bypasses  |  OAuth Misconfigurations

What I Test

Services

AUTHENTICATION TESTING

Broken Login & Session Security

I test your login flows, token handling, session management, and password reset logic. One broken auth flow = any user's account taken over.

AUTHORIZATION TESTING

IDOR & Access Control Flaws

I test whether users can access each other's data by manipulating IDs or parameters. The most common and most damaging vulnerability in SaaS apps.

API SECURITY

API Endpoint Vulnerabilities

I test your API endpoints for mass assignment, improper rate limiting, broken object level authorization, and data exposure issues.

DELIVERABLE

Full Written Report

Every engagement ends with a severity-rated vulnerability report — Critical, High, Medium, Low — with exact proof of concept steps and remediation guidance.

The Process

Simple. Fast. Thorough.

01

Scoping Call

15 minutes. You tell me about your app, I tell you exactly what I'll test. No commitment needed.

02

Manual Testing

I manually test your auth, access control, and session security. No automated scan dumps.

03

Report & Fixes

You get a full severity-rated report with exact steps to reproduce and fix every issue found.

SECURITY RESEARCH

Recent Findings & Writeups

JWT BYPASS

● CRITICAL

Weak JWT Implementation — Full Admin Access via Token Forgery

A hardcoded signing secret allowed any authenticated user to forge tokens, escalate to Owner role, and hijack any account on the platform.

SESSION FIXATION

● CRITICAL

Session Fixation & Insecure Cookie Config — Full Account Takeover

Missing session regeneration on login allowed an attacker to force a known session ID, wait for victim authentication, and instantly hijack their account.

REFRESH TOKEN HIJACKING

● CRITICAL

Persistent Account Takeover via Insecure Refresh Token Storage

Refresh tokens stored in localStorage with no rotation or revocation allowed persistent access even after the victim changed their password and logged out.

Transparent Pricing

One Price. Full Assessment.

$199

Web Application Security Assessment


  • ✓ Authentication & Session Testing
  • ✓ Authorization & Access Control Testing
  • ✓ API Security Testing (OWASP API Top 10)
  • ✓ Critical Web Vulnerabilities (OWASP Top 10)
  • ✓ Severity-Rated Vulnerability Report
  • ✓ Proof-of-Concept Steps for Every Finding
  • ✓ Remediation Guidance Included
Book a Free Scoping Call →

Indian founder? Reach out for local pricing.

Your app has vulnerabilities.
Let's find them before someone else does.

Most issues take less than 10 minutes to exploit. Let's find them first.

Broken Object Level Authorization (BOLA) — Unauthorized Access to User Data via Order ID Manipulation

Target: crAPI (Completely Ridiculous API) — an intentionally vulnerable application simulating a real-world
e-commerce backend
Category: Broken Object Level Authorization (BOLA) — OWASP API Security Top 10: API1
Severity: High

Vulnerability Summary
An authenticated user could access order data belonging to other users by simply modifying the order ID in an API request. The API performed no server-side check to verify whether the requesting user was the legitimate owner of the requested object, making unauthorized data access trivial.

Environment
crAPI is an intentionally vulnerable API designed to simulate real-world API security flaws. All testing was performed in an isolated local environment. No real user data was accessed or affected.

Steps to Reproduce
1. Authenticate as a normal user
Log in to the application and obtain a valid session token.
2. Intercept a legitimate request
Using Burp Suite, intercept the API request that fetches a user-specific order. The request looks like this:
GET /workshop/api/shop/orders/1124
Authorization: Bearer <your_token>

3. Modify the object ID
Change the order ID in the request to an ID belonging to another user:
GET /workshop/api/shop/orders/1125
Authorization: Bearer <your_token>

4. Observe the response
The API returns full order details belonging to the other user — no authorization error, no ownership check, no additional privileges required.

Impact
In a real production environment, this vulnerability would allow an attacker to:
• Access other users' order history and personal information
• Enumerate all orders across the platform by iterating IDs
• Tamper with or manipulate objects belonging to other users
• Create serious GDPR/compliance exposure and reputational damage for the business
No elevated privileges or complex exploitation are required — any authenticated user can exploit this.
A systemic pattern of similar authorization gaps was observed across multiple object access endpoints, indicating this is an architectural issue rather than an isolated oversight.

Root Cause
The API trusted client-supplied object IDs without enforcing server-side ownership validation. Authorization was assumed rather than explicitly verified — a pattern that consistently produces BOLA vulnerabilities at scale.

Remediation
• Enforce strict server-side authorization checks on every object access request
• Validate that the authenticated user is the legitimate owner of the requested resource before returning data
• Never rely solely on client-supplied IDs for access control decisions
• Apply consistent ownership checks across all object endpoints, not just high-visibility ones

Mass Assignment via Parameter Injection —
Purchasing a $1,337 Jacket for Free

Target: PortSwigger Web Security Academy — E-commerce checkout lab
Category: Mass Assignment / Broken Object Property Level Authorization — OWASP API Security Top 10: API3:2023
Severity: Medium

Vulnerability Summary
An API endpoint accepted undocumented parameters that were never intended to be user-controlled. By injecting a discount field into the checkout request, it was possible to apply a 100% discount and complete a purchase worth $1,337 at zero cost. No authentication bypass or elevated privileges were required.

Environment
PortSwigger Web Security Academy lab simulating a real-world e-commerce checkout flow. All testing was performed in an isolated lab environment. No real systems or transactions were affected.

Discovery
While intercepting the checkout flow in Burp Suite, the GET request loading the checkout page returned this response structure:
{
"chosen_discount": {
"percentage": 0
},
"chosen_products": [...]
}

A discount field existed in the system — set to 0% by default — but was absent from the POST request the UI sent when placing an order:
{
"chosen_products": [
{
"product_id": "1",
"quantity": 1
}
]
}

The discrepancy raised a direct question: if the discount field exists in the data model but isn't sent in the order request, what happens if it's added manually?

Steps to Reproduce
1. Authenticate and add item to cart
Log in as a normal user and add the target product to the cart.
2. Intercept the checkout POST request
Using Burp Suite, intercept the order placement request:
POST /api/checkout
{
"chosen_products": [
{
"product_id": "1",
"quantity": 1
}
]
}

3. Inject the discount parameter
Modify the request to include the discount field observed in the GET response:
POST /api/checkout
{
"chosen_discount": {
"percentage": 100
},
"chosen_products": [
{
"product_id": "1",
"quantity": 1
}
]
}

4. Forward the request
The API accepted the modified request, applied the 100% discount, and completed the order. No validation error. No authorization check.

Root Cause
The API accepted the entire incoming request object and mapped it directly to the data model without validating which fields were permitted. The development assumption — that users wouldn't send fields absent from the UI form — was never enforced at the API level.
This is a classic Mass Assignment pattern: the discount field was never meant to be user-controlled, but no explicit allowlist prevented it from being submitted and processed.

Impact
In a real production environment, this vulnerability class enables:
• Price manipulation in e-commerce checkouts
• Unauthorized discount or coupon application
• Privilege escalation by injecting fields like is_admin.
• Bypassing payment verification steps
Mass assignment appears regularly in real-world bug bounties — not because it's sophisticated, but because it's easy to overlook when building features quickly and letting frameworks map entire request objects to data models.

Remediation
• Define an explicit allowlist of accepted fields for every API endpoint
• Reject or strip any parameters not on the allowlist before processing
• Never rely on UI constraints as a substitute for server-side input validation
• Audit all endpoints that accept JSON bodies for unintended parameter acceptance

Broken Authentication via JWT Signature Validation Bypass — Full Admin Access by Modifying 3 Characters

Target: crAPI (Completely Ridiculous API) — an intentionally vulnerable API simulating real-world authentication flows
Category: Broken Authentication — OWASP Top 10: A07:2021 | OWASP API Security Top 10: API2:2023
Severity: Critical

Vulnerability Summary
The API accepted JWT tokens without properly validating their cryptographic signature. By decoding the token payload, modifying the role claim from user to admin, and re-encoding it, full administrative access was obtained. The server performed no integrity check — it trusted whatever token was sent.

Environment
crAPI is an intentionally vulnerable API designed to simulate real-world API security flaws. All testing was performed in an isolated local environment. No real user data was accessed or affected.

How JWT Authentication Is Supposed to Work
A JWT token has three parts:
header.payload.signature
The signature is cryptographic proof that the header and payload haven't been modified. A correct implementation:
1. Extracts the token from the request
2. Verifies the signature matches the header + payload using the server's secret key
3. If signature fails — rejects the request immediately
4. If signature passes — checks expiration, validates claims, proceeds
The signature is the entire point. Without validating it, the token is just a base64-encoded JSON object anyone can edit.

Steps to Reproduce
1. Authenticate as a normal user
Log in and capture the JWT token issued by the API.
2. Decode the token payload
The payload is base64-encoded, not encrypted. Decode it to reveal the claims:
{
"role": "user",
"email": "[email protected]",
"sub": "124"
}

3. Modify the role claim
Change "role": "user" to "role": "admin"— that's 3 characters changed.
4. Re-encode and send
Base64-encode the modified payload, reconstruct the token with the original header and signature, and send it in the next request.
5. Observe the response
The API accepted the modified token without any validation error. Full admin access granted — no cryptographic check, no signature mismatch error, no rejection.

Impact
With admin access obtained via token manipulation:
• All user accounts were visible and modifiable
• Privileged admin endpoints were fully accessible
• Any user could be impersonated by changing the email field
• Tokens could be crafted for users that don't exist
In a real production environment — particularly e-commerce or fintech — this vulnerability enables mass data exfiltration, fraudulent transactions, account takeover at scale, and complete platform compromise.

Root Cause
The API was checking that tokens were present and not expired, but was not validating the cryptographic signature. This meant the server had no way to detect that the token had been tampered with after issuance.
This falls into a broader category of JWT misconfigurations seen regularly in production systems:
• Accepting tokens with "alg": "none"— no signature required at all
• Not validating signatures (as demonstrated here)
• Using weak secrets that can be brute-forced offline
• Accepting tokens signed with a public key when a private key should be required

Remediation
• Always validate the JWT signature on every request before trusting any claims
• Use a strong, randomly generated secret key — never a dictionary word or default value
• Explicitly reject tokens with "alg": "none" at the library or middleware level
• Do not trust client-supplied claims (role, is_admin, permissions) without signature verification
• Use a well-maintained JWT library — never implement JWT validation manually

Session Fixation & Insecure Cookie Configuration — Full Account Takeover

Target: [Redacted] — A Team Collaboration & Link Management Platform, An Early-Stage SaaS (Identity Withheld on Request)

Vulnerability Class: Broken Authentication — Session Management

OWASP Reference: API2:2023 — Broken Authentication | A07:2021 — Identification and Authentication Failures
Severity: Critical

OverviewDuring a private security assessment of an early-stage SaaS product, I identified a critical session management vulnerability. By exploiting a combination of session fixation, missing session regeneration on login, and insecure cookie configuration, an attacker can fully hijack any authenticated user's session — including admins — without ever knowing their credentials.

Background
The target is an early-stage SaaS product offering team collaboration and link management features with workspace support and analytics. Authentication is handled via server-side sessions with session IDs passed via cookie on every request. Specific stack details withheld to protect the client.

Root Cause
The development team made a combination of critical mistakes in their session implementation:
1. No session regeneration on login — The login endpoint sets user identity on the existing session without ever issuing a new session ID. This means a session ID established before login remains valid after authentication.2. Session fixation attack surface — Because the backend reuses the pre-login session ID post-authentication, an attacker can force a victim to use a known session ID, wait for them to log in, and instantly inherit their authenticated session.3. Insecure cookie configuration — Multiple misconfigurations compound the risk:
- No HttpOnly flag — session cookie readable via document.cookie in browser console, trivially stolen via XSS
- No Secure flag — cookie transmitted over HTTP, visible in plaintext to network observers
- SameSite=None or missing — no cross-site request protection
- maxAge set to 30 days with no server-side expiration enforcement — sessions persist far beyond reasonable timeout
- Predictable cookie name — signals default/careless configuration to an attacker
4. No session binding — Sessions are not bound to IP address or User-Agent, meaning a hijacked session ID works from any device or location.

Discovery & ExploitationStep 1 — Establish a Pre-Authentication Session
Intercepting login requests in Burp Suite, I visited the application without logging in and captured the initial Set-Cookie header:
HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123-known-id; Path=/; SameSite=None
The session ID was noted before any authentication occurred.
Step 2 — Force the Victim to Use Your Session ID*
An attacker can craft a phishing page or URL that sets the victim's session cookie to the attacker-controlled value:
document.cookie = "sessionId=abc123-known-id; path=/";
The victim's browser is now carrying the attacker's known session ID.Step 3 — Victim Authenticates
The victim navigates to the login page and enters their credentials normally. The backend processes the login and sets user identity on the existing session — without issuing a new session ID. The pre-authentication session ID is now associated with the victim's authenticated account.
Step 4 — Attacker Reuses the Session
The attacker sends any authenticated request using the same session ID:
GET /api/v1/workspaces
Cookie: sessionId=abc123-known-id
Response: 200 OK — Full authenticated access as victim
Complete access to the victim's account achieved — without ever knowing their password.
Step 5 — Confirming Additional Weaknesses
Additional indicators confirmed the full scope during testing:
// In browser console:
document.cookie
// Returns: "sessionId=abc123-known-id"
// HttpOnly flag absent — cookie fully readable via JavaScript
Logging out from one device did not invalidate the session on another — no proper server-side session destruction on logout.

Impact- Full account takeover — attacker inherits complete authenticated session with zero credential knowledge
- Admin escalation — if victim holds an admin or owner role, attacker inherits full privileges
- Multi-tenant data exposure — compromised admin session exposes all team workspaces, analytics, and data
- Persistent access — 30-day session lifetime means access persists long after the victim has logged out
- XSS-amplified risk — missing HttpOnly flag means any XSS vulnerability on the platform can trivially steal session cookies at scale
- Network interception — missing Secure flag exposes session IDs in plaintext on non-HTTPS connections

Remediation1. Regenerate session ID on every login (Critical)
Never reuse a pre-authentication session ID after a privilege change:
req.session.regenerate((err) => {
if (err) return next(err);
req.session.userId = user._id;
req.session.role = user.role;
res.redirect('/dashboard');
});
2. Secure cookie configuration
Replace current session configuration with a hardened setup:
app.use(session({
secret: process.env.SESSION_SECRET,
name: 'app.sid',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'Strict',
maxAge: 1000 * 60 * 60 * 8 // 8 hours
}
}));
3. Destroy session on logout
Explicitly destroy the session server-side on logout:
req.session.destroy((err) => {
res.clearCookie('app.sid');
res.redirect('/login');
});
4. Implement server-side session expiration
Enforce idle timeout and absolute expiration server-side. Track lastActivity on the session object and invalidate stale sessions at the middleware level.
5. Add CSRF protection
With session-based auth, CSRF protection is mandatory. Implement CSRF tokens on all state-changing requests.

Tools Used
- Burp Suite — request interception, cookie inspection, session ID tracking
- Browser DevTools — document.cookie verification, HttpOnly flag confirmation
- Manual crafted HTTP requests — session reuse validation

Key Takeaway
Session fixation requires only that an attacker can influence a victim's session ID before they authenticate, and that the backend fails to issue a new one after login. Combined with a 30-day session lifetime, no HttpOnly flag, and no Secure flag, a single exploitation gives an attacker persistent, invisible access to any account on the platform. Session regeneration on login is a one-line fix that eliminates the entire attack class.

Identified during a private security assessment. Published with permission. Client identity withheld on request.

JWT Refresh Token Hijacking via Insecure Storage & Missing Rotation — Persistent Account Takeover

Target: [Redacted] — A Customer Relationship Management Platform(Identity Withheld on Request)Vulnerability Class: Broken Authentication — Session & Token ManagementOWASP Reference: API2:2023 — Broken Authentication | A07:2021 — Identification and Authentication FailuresSeverity: Critical

Overview
During a private security assessment of an early-stage SaaS product, I identified a critical broken authentication vulnerability. By exploiting a combination of insecure refresh token storage, missing token rotation, and absent revocation mechanisms, an attacker who obtains a single refresh token can maintain persistent, unrevokable access to a victim's account — even after the victim changes their password or explicitly logs out from all devices.
Background
The target is an early-stage SaaS CRM product used by small sales teams, offering contact management, deal tracking, and team collaboration features. Authentication is implemented via a dual-token system — short-lived JWT access tokens paired with long-lived refresh tokens — a pattern commonly used to balance security and user experience, but frequently misconfigured in early-stage products. Specific stack details withheld to protect the client.

Root Cause
The development team attempted to implement a secure token-based authentication system but made a combination of critical mistakes:
1. Refresh tokens stored in localStorage — The refresh token was returned in the login response body and stored client-side via JavaScript. localStorage is accessible to any script running on the page — making it trivially stealable via any XSS vulnerability on the platform.2. No refresh token rotation — When the refresh endpoint issued a new access token, it did not invalidate the old refresh token. The same refresh token could be used repeatedly and indefinitely.3. Excessive token lifetime — Refresh tokens were valid for 90 days with no server-side idle expiration. A stolen token grants 90 days of persistent access with zero intervention possible.4. No revocation mechanism — Logout, password change, and "revoke all sessions" actions did not invalidate existing refresh tokens. The token simply remained valid regardless of the victim's actions.5. No session binding — Refresh tokens had no binding to device fingerprint, IP address, User-Agent, or session ID. A stolen token worked from any device or location with no anomaly detection.6. Weak refresh endpoint validation — The refresh endpoint only verified that the token existed and had not expired. No additional context or binding was validated.

Discovery & Exploitation
Step 1 — Authenticate and Capture Traffic
Logged in as a standard user and intercepted all traffic using Burp Suite. The login response immediately revealed two tokens:
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"expires_in": 900
}
The refresh token — a plain UUID — was returned directly in the response body, not set as an HttpOnly cookie.Step 2 — Confirm Insecure Storage
Inspecting the browser DevTools confirmed the refresh token was fully readable via JavaScript:
localStorage.getItem('refreshToken')
// Returns: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
Any XSS vulnerability on the platform would instantly compromise it.Step 3 — Test the Refresh Endpoint
Sent a POST request to the refresh endpoint using only the refresh token:
POST /api/auth/refresh
Content-Type: application/json
{
"refresh_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Response:
HTTP/1.1 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...[NEW TOKEN]",
"refresh_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
The old refresh token was returned unchanged. No rotation occurred — the same token could be reused indefinitely.Step 4 — Confirm No Revocation on Logout
Logged out from the application, then immediately used the same refresh token again:
POST /api/auth/refresh
{
"refresh_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Response: 200 OK — New access token issued
Logout did not invalidate the token. Repeating the same test after a password change produced an identical result.Step 5 — Full Exploit Chain
An attacker steals the refresh token via any available vector — XSS, MITM on non-HTTPS traffic, or direct localStorage access on a shared device. From a completely different device and browser, the attacker calls the refresh endpoint and receives a valid access token. Full authenticated access to the victim's account is achieved with no binding check on device, IP, or User-Agent.
The victim changes their password and clicks "Logout from all devices." The attacker calls the refresh endpoint again. Still 200 OK. Persistent access maintained for the full 90-day token lifetime.

Impact
- Persistent account takeover — attacker maintains access for up to 90 days regardless of victim's password changes or logout actions
- Full CRM data exposure — all customer contacts, deal values, email threads, and team communications accessible
- Business impact — attacker can impersonate the victim in client communications, export or delete the entire contact database, or sabotage active deals
- No victim-initiated remediation — victim has no way to revoke attacker access without contacting platform support directly
- XSS amplification — localStorage storage means any single XSS vulnerability instantly harvests refresh tokens from all active users at scale
- Cross-device exploitation — no session binding means stolen tokens work globally from any device or location

Remediation
1. Store refresh tokens in HttpOnly cookies (Critical)
Never return refresh tokens in the response body. Issue them exclusively as HttpOnly, Secure, SameSite=Strict cookies:
res.cookie('refreshToken', token, {
httpOnly: true,
secure: true,
sameSite: 'Strict',
maxAge: 1000 * 60 * 60 * 24 * 14 // 14 days max
});
2. Implement refresh token rotation
Every time a refresh token is used, invalidate the old one and issue a new one. If an already-used token is presented, treat it as a compromise indicator and invalidate the entire token family:
const newRefreshToken = generateSecureToken();
await db.refreshTokens.findOneAndDelete({ token: oldRefreshToken });
await db.refreshTokens.create({ token: newRefreshToken, userId: user._id });
3. Implement a revocation mechanism
Password changes, explicit logout, and "revoke all sessions" must invalidate all refresh tokens associated with the user:
// On password change or logout all
await db.refreshTokens.deleteMany({ userId: user._id });
4. Add session binding
Bind each refresh token to a hash of the issuing device's User-Agent and IP address. Validate on every refresh request:
const sessionHash = crypto
.createHash('sha256')
.update(req.headers['user-agent'] + req.ip)
.digest('hex');
5. Reduce token lifetime
90-day refresh token lifetime is excessive. Reduce to 7–14 days maximum. Implement server-side idle expiration — tokens unused for 24–48 hours should be automatically invalidated regardless of expiry date.
6. Rate limit the refresh endpoint
Enforce strict rate limiting on the refresh endpoint — no more than 10 requests per hour per user. Log and alert on anomalous refresh activity from new devices or locations.

Tools Used
- Burp Suite — request interception, token capture, refresh endpoint testing
- Browser DevTools — localStorage inspection, HttpOnly flag verification
- Manual crafted HTTP requests — rotation and revocation validation across devices.
Key Takeaway
A dual-token authentication system only provides security if both tokens are handled correctly. Storing refresh tokens in localStorage eliminates the security benefit of HttpOnly cookies. Missing rotation means a single stolen token grants indefinite access. Missing revocation means the victim is helpless. These three failures compound each other — any one of them alone is a serious issue, but together they create a persistent, unrevokable account takeover that survives password changes, logout, and device revocation attempts. Each fix is individually straightforward; the danger lies in implementing only some of them.

Identified during a private security assessment. Published with permission. Client identity withheld on request.

Broken Authentication via Weak JWT Implementation & Algorithm Confusion

Target: [Redacted] — A Project Management Platform(Identity Withheld on Request)Vulnerability Class: Broken AuthenticationOWASP Reference: API2:2023 — Broken Authentication | A07:2021 — Identification and Authentication FailuresSeverity: Critical

OverviewDuring a private security assessment of an early-stage SaaS product, I identified a critical authentication vulnerability in a JWT-based authentication implementation. A low-privilege user could completely hijack any other user's account — including Admins and Owners — without knowing their password or triggering any 2FA challenge.Background
The target is an early-stage collaborative project management SaaS with a web app and REST API. Users operate under role-based access control with multiple privilege tiers. Authentication is handled via JWT tokens passed in the Authorization: Bearer header on every request. Specific stack details withheld to protect the client.

Root Cause
The development team made a combination of critical mistakes in their JWT implementation:
1. Weak, hardcoded secret — Tokens were HMAC-SHA256 signed with a predictable, hardcoded string instead of a cryptographically secure 256+ bit key stored in environment variables.
2. No claims validation — The backend never validated iss (issuer) or aud (audience) claims, meaning tokens could be crafted without origin verification.
3. No user binding — The sub claim (user ID) was present but never validated against the authenticated session. The backend trusted whatever was in the token payload.
4. Algorithm confusion attack surface — The server accepted both HS256 and RS256 without explicit algorithm enforcement, and the public key was exposed at /.well-known/jwks.json — enabling token forgery via algorithm confusion.
The combined result: any valid JWT could be decoded, its payload modified (role escalated, user ID changed), re-signed with the known weak secret, and accepted by the server as legitimate.

Discovery & ExploitationStep 1 — Capture the JWT
After authenticating as a standard low-privilege user, the issued JWT was captured from the Authorization: Bearer header using Burp Suite:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VySWQiOiI2NzEyMzQ1Njc4OTAiLCJyb2xlIjoiTWVtYmVyIiwiaWF0IjoxNzQxNTIzNDAwLCJleHAiOjE3NDE1MjcwMDB9.
abc123signature
Step 2 — Decode the Token
Decoding the base64 payload at jwt.io immediately revealed:
{
"userId": "671234567890",
"role": "Member",
"iat": 1741523400,
"exp": 1741527000
}
Observations:
- Role claim is plaintext and directly readable
- User ID is sequential and easily enumerable
- No iss, aud, or sub claims present
- Secret appeared suspiciously weak — worth testing
Step 3 — Test for Weak Secret
Using jwt_tool, the token was tested against a wordlist of common weak secrets. The secret was cracked within seconds:
python3 jwt_tool.py <token> -C -d common-secrets.txt
# Result: Secret found
Step 4 — Forge Admin Token
With the secret known, the payload was modified to escalate privileges and target a different user's account:
{
"userId": "671234567891",
"role": "Owner",
"iat": 1741523400,
"exp": 1741527000
}
The modified payload was re-signed with the cracked secret and sent in the Authorization header:
GET /api/v1/admin/workspaces
Authorization: Bearer [FORGED_TOKEN]
Response: 200 OK — Full Owner-level access granted
Step 5 — Algorithm Confusion (Bonus Vector)
The server exposed its public key at /.well-known/jwks.json. By switching the algorithm from RS256 to HS256 and re-signing the token using the exposed public key as the HMAC secret, the same account takeover was achievable through a second independent attack path — no secret cracking required.

Impact- Complete account takeover — any low-privilege user can impersonate any Admin or Owner without their password
- Full workspace access — read, modify, and delete any workspace or project across the platform
- Privilege escalation — instant role escalation from standard user to Owner
- Data exfiltration — all user data, project data, and workspace data accessible
- Billing and settings manipulation — Owner-level access includes billing controls
- Algorithm confusion — second independent exploit path via exposed JWKS endpoint

Remediation1. Use a strong, randomly generated secret (Critical)
Generate a cryptographically secure 256+ bit secret and store it in environment variables — never hardcode secrets in source code:
openssl rand -hex 32
2. Validate all JWT claims on every request
Enforce iss, aud, exp, nbf, and sub validation. The sub claim must match the authenticated user on every protected endpoint — never trust payload claims without verification.
3. Explicitly enforce allowed algorithms
Reject any token not using your expected algorithm. Never accept alg: none. If using RS256, never allow HS256 as a fallback:
jwt.verify(token, secret, { algorithms: ['HS256'] })
4. Protect the JWKS endpoint
Never expose the public key at a publicly accessible endpoint if it can be used to forge tokens. If a JWKS endpoint is required, ensure algorithm confusion is architecturally impossible.
5. Implement token binding and short lifetimes
- Access tokens: 15–60 minute expiry
- Refresh tokens: long-lived, stored server-side, with rotation and revocation support
- Consider binding tokens to session, IP, or device fingerprint for high-sensitivity applications
6. Implement rate limiting on auth endpoints
Prevent secret brute-forcing by rate limiting login, token, and refresh endpoints.

Tools Used
- Burp Suite — request interception and token capture
- jwt.io — token decoding and payload inspection
- jwt_tool — weak secret brute-forcing and token forgery
- Manual crafted HTTP requests
Key Takeaway
JWT authentication is only as strong as its implementation. A predictable secret, missing claims validation, and algorithm confusion surface together created a complete authentication bypass exploitable by any authenticated user. This class of vulnerability is common in early-stage SaaS products where developers implement JWT from scratch without following security best practices.

Identified during a private security assessment. Reported directly to the development team. Published with permission. Client identity withheld on request.