← Resources

From Applying ASVS, Part 1: Safe Concurrency

OTP service threat model

The full threat model behind the concurrency series: deciding what to defend against in a one-time password service, and what to let go.

Accepted risks

Accepted risks are residual risks the team documents and moves past, usually because the mitigation lives outside this service or because the business has decided the tradeoff is worth it.

SIM swapping, SS7 interception, and compromised email inboxes are all attacks on how the OTP gets to the user in the first place (i.e. delivery-channel attacks). The team heard the security guidance that SMS is no longer considered a strong authentication factor, and they agreed with it, but they decided to ship SMS anyway because they wanted to start with lower-friction options to keep the user experience smooth. This kind of tradeoff is a routine part of AppSec work, so to make it defensible the team documented this decision, made sure org leaders were aware of the risk, and committed to revisit it from time to time.

A verifier written in any language can’t defend against an attacker who already controls the channel, and the team decided that was a tradeoff they were willing to make. A related problem is SMS bombing: an attacker hits the issuance endpoint over and over to run up the SMS bill and spam the targeted user with codes they didn’t ask for. Fixing that is a product and provider job, not something this code can solve.

The other risk the team accepted is this service restarting. The rate limiter keeps its counts in memory, so when the service goes down and comes back up, the counts go back to zero. An attacker mid-brute-force would get a fresh five attempts to keep guessing, but the timing has to line up just right and the codes only stay valid for a few minutes anyway. The team could have used Redis to keep the counts across restarts, but the platform team asks application teams to keep their Redis footprint small. A rare, complex attack that buys the attacker a handful of extra guesses against a single short-lived code was not worth that budget.

If the team wanted to scale this service horizontally, meaning they ran more than one instance of it at the same time, they’d hit a similar problem. Each instance would count guesses on its own, so an attacker could spread their guesses across instances and stay under the limit on any one of them. For now they only run one instance, which sidesteps the problem entirely. If they scale up to more than one, they’ll need to revisit the limiter design.

Accepted risks
ID Risk ROAM STRIDE Notes
OTP-6 Email account compromise Accepted
Spoofing
Attacker controls the delivery channel and receives codes intended for the legitimate user
OTP-2 Issuance flooding / SMS bombing Accepted
Denial of Service
Exhausts SMS budget and disrupts the user's device with unsolicited codes
OTP-11 Multi-replica state divergence Accepted
Tampering
Routing requests across replicas circumvents the rate limit counter on any single replica
OTP-10 Service restart wipes rate limit state Accepted
Tampering
The rate limit counter is effectively reset by an out-of-band action, weakening the control
OTP-4 SIM swapping Accepted
Spoofing
Attacker takes control of the delivery channel to receive codes intended for the legitimate user
OTP-5 SS7 interception Accepted
Information Disclosure
Code is exposed to a network-level adversary in transit

Owned risks to act on

Owned risks are the ones the team has to address as they implement this service.

Spoofing is the most common category of risks, since it covers most of what an attacker is actually trying to do: authenticate as the real user without controlling the delivery channel (i.e. email, SMS). Online guessing against the six-digit code is the obvious risk, so the team will implement a per-user rate limit with a hard cap on attempts within the validity window. Weak code generation is another spoofing path: if the service derives codes from timestamps, counters, or a non-cryptographic PRNG, an attacker may be able to predict the next code without exhausting the rate limit at all. Issuance has to use a CSPRNG.

The team also has to protect against attacks when an OTP is used successfully. If they don’t mark it used and reject it on the next try, an attacker who intercepts a valid OTP can submit it again and bypass MFA. They also need to make sure an OTP issued for one user can’t be accepted for a different one. Both are cases of Elevation of Privilege, since the attacker would end up in an account that was never theirs.

Finally, a timing attack constitutes Information Disclosure: when verifying an OTP, a comparison that short-circuits on the first wrong byte leaks how much of the guess was correct. So, the internal verification API call needs to be constant-time.

Owned risks
ID Risk ROAM STRIDE Notes
OTP-3 Code prediction / weak RNG Owned
Spoofing
Predictable codes let an attacker derive a valid second factor without observing it
OTP-9 Cross-user code confusion Owned
Spoofing Elevation of Privilege
A code valid for one user is accepted for another, granting access to a different account
OTP-1 Online brute force of the code Owned
Spoofing
Attacker impersonates the legitimate user by guessing their second factor
OTP-8 Replay of consumed codes Owned
Spoofing Elevation of Privilege
An already-used code is presented again to authenticate a second time
OTP-7 Timing attacks on code comparison Owned
Information Disclosure
Response latency leaks information about correct prefixes of the code

Full register

Every enumerated risk with ROAM and STRIDE metadata.

All risks
ID Risk ROAM STRIDE Notes
OTP-3 Code prediction / weak RNG Owned
Spoofing
Predictable codes let an attacker derive a valid second factor without observing it
OTP-9 Cross-user code confusion Owned
Spoofing Elevation of Privilege
A code valid for one user is accepted for another, granting access to a different account
OTP-1 Online brute force of the code Owned
Spoofing
Attacker impersonates the legitimate user by guessing their second factor
OTP-8 Replay of consumed codes Owned
Spoofing Elevation of Privilege
An already-used code is presented again to authenticate a second time
OTP-7 Timing attacks on code comparison Owned
Information Disclosure
Response latency leaks information about correct prefixes of the code
OTP-6 Email account compromise Accepted
Spoofing
Attacker controls the delivery channel and receives codes intended for the legitimate user
OTP-2 Issuance flooding / SMS bombing Accepted
Denial of Service
Exhausts SMS budget and disrupts the user's device with unsolicited codes
OTP-11 Multi-replica state divergence Accepted
Tampering
Routing requests across replicas circumvents the rate limit counter on any single replica
OTP-10 Service restart wipes rate limit state Accepted
Tampering
The rate limit counter is effectively reset by an out-of-band action, weakening the control
OTP-4 SIM swapping Accepted
Spoofing
Attacker takes control of the delivery channel to receive codes intended for the legitimate user
OTP-5 SS7 interception Accepted
Information Disclosure
Code is exposed to a network-level adversary in transit