Application Security Review: Implicit-Grant Identity Confusion

Overview:
This challenge box covers both sides of web application security: web application black-box testing and white-box auditing.
Objective #1: Penetration Test
Our engineering team is currently rolling out "HackSmarter ID," a centralized Single Sign-On (SSO) solution designed to provide seamless access across all of our internal platforms. To support lightweight, browser-based applications, the developers have implemented an OAuth-style Implicit Grant flow.
You have been contracted to perform a targeted penetration test against this new integration before it goes live to production. Can you log in as the administrator user?
Objective #2: Secure Code Review
I strongly recommend you try to do this without AI to see if you can spot the vulnerability.
As the attending Application Security Engineer, you must immediately triage the vulnerable codebase, implement a hot-patch, and deploy the fix via our automated CI/CD pipeline.
You can access the internal Git server at http://[lab-ip]:3000 and log in with these credentials: student:HackSmarter2026!
All changes you make to app.py will automatically be pushed to production (it can take 2 - 4 minutes for the action to run). Your task is to fix the vuln you discovered (without breaking the app).
Verify the fix by attempting to re-exploit the app.
1. Black Box Testing:
Initial Access:
VPN connection to the environment is established.
The nmap scan identified 4 running services including a hosted web application:
wizard-zero@Wiz-Zero:~$ sudo nmap -sS -sV -Pn -T4 10.0.16.72
[sudo] password for wizard-zero:
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-09-29 00:42
CET Nmap scan report for 10.0.16.72 Host is up (0.17s latency). Not shown: 996 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.14 (Ubuntu Linux; protocol 2.0)
80/tcp open http Werkzeug/3.0.1 Python/3.10.19
222/tcp open ssh OpenSSH 9.3 (protocol 2.0) 3000/tcp open ppp?
The user interface indicates that a new recent authentication system was implemented in this web application. In addition, this appears to be a flask app hosted on a werkzeug server: Werkzeug/3.0.1 Python/3.10.19
following the user interface, we get to the endpoint /sso/login
In short, SSO lets a user authenticate once with a central identity provider, which then vouches for them to multiple applications via signed tokens, so they don't log in repeatedly. Security-wise, this centralizes and strengthens authentication (MFA, consistent policy) while reducing password sprawl, but it also concentrates risk: compromise of the identity provider or its signing keys can grant access across every connected application, making strict token validation and strong key protection essential.
After attempting to register an account as administrator, you get an error message that the user already exists. Furthermore, input analysis for for client side injections, and username collision did not provide positive signs. Since this is a SSO flow, the next logical step is to examine the session management flow and token issues.
After additional testing on the authentication flow with Caido (burp suite alternative) by registering another account and login after, we can see that there is a returned query which has an access token along with the username we used to register.
The first idea that comes to mind is to manipulate the username in the query to administrator, it's possible that the the server does not check that the token's claim matches the supplied username.
Restarting the authentication flow, we intercept the key request again after a new registration and change the username to administrator:
Looking at the response in the browser, we've successfully obtained administrator access and managed to get the flag.
This was what's known as implicit-grant identity confusion vulnerability which is an OAuth 2.0 method that is now considered deprecated and insecure for browser based applications because it exposes tokens in URL fragments, lacks client authentication, and fails in modern browsers that block third party cookies.
In this case we had two separate inputs, and the server treated them independently:
access_token→ checked for validity (exists, not expired, maybe signature).username→ written straight intosession["sso_user"]as we've examined earlier.
And that's the bug. The server verifies the token, then trusted a user supplied username for the actual identity. It's like we've been verified that we have valid ticket to an event, but then being able to write any name on the guest list.
Reference:
https://portswigger.net/web-security/oauth/grant-types#implicit-grant-type
First objective completed.
2. White Box Patching:
For the secure code review we will login to Gitea in port 3000 to patch the vulnerability as we've been instructed using the given credentials and access the repository:
student:HackSmarter2026!
Looking at the code, we can find the root cause at line 103 which is what we deduced earlier, the token is checked for existence, but its identity mapping is never used and the server trusts client_username from the request body. Authentication passes (token exists), but identity comes from the attacker.
if client_token in valid_tokens: session['username'] = client_username
BUG: ignores valid_tokens[token]
To fix this with a correct posture, we should remove client_username. If the token is the identity proof, removing the parameter is stronger than validating it.
@app.route('/api/login', methods=['POST'])
def api_login():
data = request.get_json()
if not data:
return jsonify({"success": False, "message": "Invalid request."}), 400
client_token = data.get('access_token')
# Identity comes ONLY from the token, never from the request body.
token_user = valid_tokens.get(client_token)
if token_user is None:
return jsonify({"success": False, "message": "Invalid or expired access token."}), 401
session['username'] = token_user
return jsonify({"success": True})
Now we test the authentication flow once again to validate the patch, and change the username to administrator:
Verification:
Re-running the authentication flow and changing the query's username to administrator no longer grants access. The implementation is validated: the server now checks only the access token.
Second objective completed.
Summary:
This lab shows what happens when an app checks that you logged in but not who you are. The app uses an OAuth 2.0 style implicit flow. After login, the SSO provider sends the browser a token and a username, and the browser posts both to /api/login. The server validated the token, a real check that passed but then trusted the username straight from the request and wrote it into the session. The token was proven genuine but nothing verified who it belonged to.
A normal user could register, grab their own valid token, and replay it with the username changed to administrator passing authentication while forging identity, and landing an admin session.
A credential must be the only source of identity. The server should read the username from the token, never from a client-supplied field sitting next to it. Accepting both in one request is the design flaw.
The fix: session['username'] = valid_tokens.get(client_token), and remove the username parameter entirely, eliminating it is stronger than validating it. The patch was deployed via CI/CD and verified by re-running the exploit, which no longer works.





