Last updated: 2026-09-28

U
Undergraduate level

Trusting an Unsigned Token: A Case Study in Authentication and Data Integrity

In September 2026 a security researcher published an account of a flaw in an internal Microsoft analytics service. The service accepted access tokens that carried no signature at all, so anyone who could reach its API could write themselves a token, name themselves an administrator, and run database queries. The flaw is small and the potential reach is very large, and both facts make it useful for teaching. This page uses the reported case to cover how signed tokens are meant to work, which design principles the flaw breaks, why integrity matters as much as confidentiality, and how to read a headline number.cf. security-design-principles

Everything below about the incident comes from the researcher's own account [1] and the trade press that repeats it. Nothing here is an invitation to test systems you don't own or lack permission to test; the ethical considerations on the SQL injection page apply in full.

What Was Reported Ephemeral / ToolingKnowledge that evolves in months to a year — check for updates

The researcher says the service, an internal analytics system called Titan, sat behind a page telling visitors a VPN was required, while its API endpoint was reachable over the public internet. The API's specification was public, and archived copies of its configuration were available through the Wayback Machine. Their tool found the endpoint, and a token with the header {"alg":"none","typ":"JWT"} and an empty signature section was accepted. According to the account, the server checked the tenant, audience, application ID and user lookup, but never verified the signature. Changing a claim named upn to "admin" resolved to a local administrator account, and the query endpoint then executed raw SQL against a set of ClickHouse analytics databases [1].alg:none tells the parser to skip signature verification

The reported timeline is short. The report went to Microsoft's security response centre on 5 September 2026, the endpoint was locked down on 9 September, a $5,000 bounty followed on 17 September, and the post appeared on 25 September after Microsoft edited it [1].

Two limits on the evidence matter. The trade-press coverage restates the researcher's account, and none of it independently reproduces the findings. Microsoft is quoted as saying the report helped it harden its services, but it has not confirmed the headline row count or the technical details [2], and the researcher says Microsoft had editorial control over the post and cut sections and figures [1]. The page therefore treats the mechanism as reported and plausible, since it matches a well-known class of flaw, and the scale as unconfirmed.

How a Signed Token Is Supposed to Work FoundationalKnowledge that endures for decades — core principles

A JSON Web Token (JWT) is three text segments joined by dots: a header naming the signing algorithm, a payload of claims such as who the user is and which service the token is for, and a signature [3][4]. The signature is what makes the claims believable. It is computed over the header and payload with a key only the issuer holds, so a server that checks it can be sure the claims came from the issuer and haven't been altered. A token without a valid signature is just a note that anyone could have written.

The standard defines a way to have no signature at all. An "unsecured" JWT uses the algorithm value none and an empty third segment, and conforming libraries must support it [3][4]. The duty to reject it belongs to the application. The 2020 best-practice RFC says libraries must let the caller state which algorithms are acceptable and must not use any others, and it names the attack of changing the algorithm to none [5]. Tim McLean showed in 2015 that widely used libraries accepted such tokens as valid, and recommended that the verify call always take the expected algorithm explicitly [6]. The weakness has a formal name, CWE-347, improper verification of cryptographic signature [7].none is an algorithm, not a missing signature

graph TD T["Incoming token"] --> V{"Signature valid
under a pinned algorithm
and the issuer's key?"} V -->|no| X["Reject: 401"] V -->|yes| C{"Audience, issuer,
expiry all valid?"} C -->|no| X C -->|yes| M["Map the verified identity
to a server-side account"] M --> A{"Is that account
allowed this action?"} A -->|no| Y["Reject: 403"] A -->|yes| R["Run the request"] style X fill:#FFC857 style Y fill:#FFC857 style R fill:#8FBF6A

The order matters. Verification comes first, because until the signature has been checked nothing in the payload has any standing. In the reported flaw the checks that ran, on tenant, audience and application, were applied to claims nobody had proved.

What Went Wrong, Step by Step Applied / MethodologicalKnowledge with a 5–10 year half-life — stable practice

Read against the design principles in Security Design Principles, each step of the reported chain fails a principle Saltzer and Schroeder set out in 1975 [8].cf. least privilege and fail-safe defaults

Step, as reportedControl that should have appliedPrinciple
A public specification and archived configuration described an internal APIAssume the design is known, and keep security in keys and checksOpen design
A token with algorithm none was acceptedPin the accepted algorithms and reject unsigned tokens [5]Fail-safe defaults
Claims were checked, the signature was notVerify the signature on every request before reading any claimComplete mediation
One forged claim resolved to a local administratorMap identity to accounts server-side, with roles that need more than a self-declared nameSeparation of privilege, least privilege
The query endpoint took raw SQL from the callerGive the service a narrowly scoped database role, and restrict what queries it will runLeast privilege, economy of mechanism

The VPN page deserves a comment of its own. A notice on a web front end is a usability measure, and it controls nothing when the API sits elsewhere on the public internet. OWASP's API security list makes the same point about function-level authorisation: an endpoint can't be treated as admin-only because of its place in the URL space [9], and its authentication entry names accepting unsigned JWTs as a vulnerable practice.

graph LR A["Public spec and
archived config"] --> B["Forged token:
alg none, empty signature"] B --> C["Server checks claims,
skips signature"] C --> D["upn claim maps
to local admin"] D --> E["Raw SQL against
analytics databases"] style B fill:#F2B8B5 style C fill:#F2B8B5

Breaking any single link would have stopped the chain. That is the argument for defence in depth: the flaw needed all five links, and a design with independent controls at each one would have survived the failure of any of them.

Integrity, Not Just Confidentiality FoundationalKnowledge that endures for decades — core principles

The headline reads as a confidentiality story: a lot of data could be read. The integrity question is quieter and can be worse. NIST defines integrity as guarding against improper information modification or destruction, including ensuring authenticity [10], and a signature is the integrity control for a token. An unsigned token doesn't leak a secret. It lets an attacker author their own credentials, which is an integrity failure at the identity layer that then breaks confidentiality further along the chain.the token is the credential, not the data

The researcher reports that the API's specification listed an insert route alongside the query route, and says that only read queries were tested [1]. Whether writes were possible is therefore unknown. The general consequence is worth working through, with the caveat that this is reasoning about analytics systems and not a claim about this one. Data that feeds reports, dashboards and models is trusted because of where it came from. If an outsider can write to it, or an insider can't prove they didn't, every downstream conclusion inherits the doubt, and the damage can persist long after the hole is closed. The defences are the integrity tools listed on the CIA triad table: signed or checksummed records, append-only audit logs, and backups that can be compared against the live data.

Reading the Number Applied / MethodologicalKnowledge with a 5–10 year half-life — stable practice

The figure in the headline is 17,333,335,124,315, about 17.3 trillion. The researcher derived it by summing row counts held in ClickHouse's own metadata across 17 databases and 9,863 tables, and says the total likely includes historical, duplicated and derived data [1]. The ClickHouse documentation explains the source. The total_rows column of system.tables is filled in when the row count can be determined quickly and exactly, and is otherwise NULL [11], while system.parts lists the rows in each data part, including inactive parts awaiting deletion after merges [12]. The documentation doesn't say how replicated data is counted, so totals built from these views can't be read as counts of distinct rows, which fits the researcher's own caveat about duplicated and derived data.

Rows are storage units, and a single person can generate thousands of them. The number measures the size of what the credential could have reached and says nothing about how many people or records were affected. Four terms help keep the claims apart:rows are storage units, not people

TermMeansShown in this case, as reported
CapabilityWhat an attacker could do with the flawRun queries as an administrator
AccessWhat the researcher touchedMetadata queries and two one-row samples
ExposureData that left the organisationNone reported
ExploitationMisuse by anyoneNo evidence reported, and unknown

The researcher's own title is a counterfactual ("could've accessed"). Some trade-press headlines on the same story say the records were exposed, which claims more than the account supports. Checking which of the four terms a headline is actually using is a quick habit that transfers to any breach report.

Disclosure and Response Applied / MethodologicalKnowledge with a 5–10 year half-life — stable practice

The reported timeline is a reasonable example of coordinated disclosure working: four days from report to lockdown, a bounty, and publication after the fix. Microsoft's bounty guidelines include a legal safe harbour for good-faith research, on conditions: researchers are asked to avoid accessing, modifying or exfiltrating customer data, and to stop if unsure [13]. Its coordinated vulnerability disclosure policy asks researchers to let the vendor fix an issue before publishing details [14], and cites the ISO/IEC 29147 standard on vulnerability disclosure [15].

The researcher says testing stopped at two one-row samples and that no individuals were identified or records correlated [1]. That restraint is what the safe harbour is designed to reward, and it is also what keeps a test within the law. The UK's Computer Misuse Act treats unauthorised access as an offence regardless of intent, as covered in Legal Framework in Computing. Two further points affect how the published account should be read. Microsoft edited the post before it appeared [1], so the text is the product of a negotiation between researcher and vendor. And the researcher credits a personal AI tool with the systematic enumeration and a human hunch, that the upn claim was a local username, with the decisive step [1]. That is a self-report and can't be assessed here. It does fit a familiar division of labour, in which automation supplies breadth and a person supplies the idea worth trying.intent is irrelevant under the Act

What Builders Should Do Applied / MethodologicalKnowledge with a 5–10 year half-life — stable practice

The fix is a few lines in most libraries. The dangerous pattern is decoding a token for its claims without verifying it, and the safe one names the expected algorithm, audience and issuer, and requires the claims that matter:

import jwt   # PyJWT

# VULNERABLE: reads the claims, never checks who wrote them
claims = jwt.decode(token, options={"verify_signature": False})

# SAFE: pinned algorithm, issuer's public key, required claims
claims = jwt.decode(
    token, public_key,
    algorithms=["RS256"],
    audience="analytics-api",
    issuer="https://login.example.test",
    options={"require": ["exp", "aud", "iss"]},
)

With the safe form, a token using none raises an invalid-algorithm error, a token signed with the wrong key raises an invalid-signature error, and expired or wrong-audience tokens are rejected too. OWASP's JWT guidance says the same in words: make sure none is not accepted, and hard-code the accepted algorithms where possible [16].

This is the approved approach. Verify the signature first, with the algorithm pinned by your code and never read from the token, and only then look at any claim.
Don't do this! Don't treat a token claim as proof of who someone is or what they may do until the signature has been verified, and don't let a self-declared name select an administrator account.

Beyond the verifier, the case suggests a short checklist:

  • Test the failure paths. Include an unsigned token, a wrong-key token, an expired token and a wrong-audience token in the automated tests for any service that accepts tokens.
  • Separate authentication from authorisation. Map a verified identity to a server-side account and its roles, and don't derive a role from a claim's text.
  • Don't rely on a front-end notice. If a service is internal, its API must be unreachable or authenticated in its own right.
  • Scope the database role. The service account should carry only the permissions its function needs, and read-only where reading is all it does.
  • Log rejections. A run of failed or unsigned tokens is an early signal that someone is probing.
Practical Exercise: A Verifier That Fails Safe. In a local sandbox with a library such as PyJWT, write two functions: one that decodes a token without verifying it, and one that verifies it with a pinned algorithm, audience and issuer. Generate a key pair and craft five tokens: a correctly signed one, one with alg set to none and an empty signature, one signed with a different key, one expired, and one for the wrong audience. Run all five through both functions and record which each function accepts. For each rejection, name the design principle from the table above that the test protects. Finish by writing one sentence on what the naive function would allow if it fed an authorisation decision.

Summary

  • A signed token's claims are believable only after the signature has been verified with an algorithm the server chose.
  • The reported flaw failed five design principles in sequence, and any one working control would have broken the chain.
  • An unsigned token is an integrity failure at the identity layer, and it can lead to confidentiality and further integrity failures downstream.
  • A headline row count measures how much a credential could reach and says nothing about exposure. Capability, access, exposure and exploitation are four different claims.
  • Only the researcher's account of the incident is available, and Microsoft has not confirmed the figures.

References

  1. Faav (2026, 25 September). How I Could've Accessed 17 Trillion Microsoft Records. https://blog.faav.net/how-i-couldve-accessed-17-trillion-microsoft-records
  2. Markovic, H. (2026, 28 September). Microsoft Titan JWT signature flaw. Help Net Security. https://www.helpnetsecurity.com/2026/09/28/microsoft-titan-jwt-signature-flaw/
  3. Jones, M., Bradley, J., & Sakimura, N. (2015). JSON Web Token (JWT) (RFC 7519). IETF. https://www.rfc-editor.org/rfc/rfc7519
  4. Jones, M., Bradley, J., & Sakimura, N. (2015). JSON Web Signature (JWS) (RFC 7515). IETF. https://www.rfc-editor.org/rfc/rfc7515
  5. Sheffer, Y., Hardt, D., & Jones, M. B. (2020). JSON Web Token Best Current Practices (RFC 8725, BCP 225). IETF. https://www.rfc-editor.org/rfc/rfc8725
  6. McLean, T. (2015, 31 March). Critical vulnerabilities in JSON Web Token libraries. https://www.chosenplaintext.ca/2015/03/31/jwt-algorithm-confusion.html (also published on the Auth0 blog).
  7. MITRE. CWE-347: Improper Verification of Cryptographic Signature. https://cwe.mitre.org/data/definitions/347.html
  8. Saltzer, J. H., & Schroeder, M. D. (1975). The protection of information in computer systems. Proceedings of the IEEE, 63(9), 1278–1308. https://doi.org/10.1109/PROC.1975.9939
  9. OWASP. OWASP API Security Top 10 – 2023 (API2:2023 Broken Authentication; API5:2023 Broken Function Level Authorization). https://owasp.org/API-Security/editions/2023/en/0x11-t10/
  10. NIST. Computer Security Resource Center glossary: integrity (definition from 44 U.S.C. §3542 and FIPS 200). https://csrc.nist.gov/glossary/term/integrity
  11. ClickHouse. system.tables. https://clickhouse.com/docs/reference/system-tables/tables
  12. ClickHouse. system.parts. https://clickhouse.com/docs/reference/engines/table-engines/mergetree-family/system-tables/parts
  13. Microsoft Security Response Center. Microsoft Bounty Program guidelines: legal safe harbor. https://www.microsoft.com/en-us/msrc/bounty-guidelines#safeharbor
  14. Microsoft Security Response Center. Coordinated Vulnerability Disclosure. https://www.microsoft.com/en-us/msrc/cvd
  15. ISO/IEC 29147:2018. Information technology — Security techniques — Vulnerability disclosure. https://www.iso.org/standard/72311.html
  16. OWASP. JSON Web Token Cheat Sheet. https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_Cheat_Sheet.html