Presentation I gave with incremental improvements:
1. DEFCON 2022
2. OWASP Global AppSec USA SF 2022
3. OWASP Global Appsec Dubling 2023
You likely receive OTPs (one-time-passwords) all the time, usually in the form of an SMS with a 4 to 8 digit code in it. Pretty common when you sign-in (or register) to Uber, your bank (usually as a second factor), Whatsapp, etc. The most adopted OTP size is 6 digits, and we just accept that it's hard to guess, after all its 1 in a million chance, and it's valid just for a few minutes, and leave it there. A few paranoid folks might wonder, what if get a new OTP after the first one expire, they may assume it's another 1 in a million chance, and continue with their life. The truth is that when you calculate the actual chance of guessing an OTP one after the other, the odds are NOT 1 in a million. You will be surprised how the probabilities of guessing spiral once you start thinking of brute forcing OTPs one after the other, and what about parallelising the brute force among different users, the surprise is even bigger.
2. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Who am I
● Working in CyberSecurity since 2008
● Working at Twilio since 2015
○ 6 years as Authy (Account Security) Security Officer
○ Now Staff Product Security Engineer
3. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
What’s an OTP/TOTP
4. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
5. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
6. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
7. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
8. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
What’s an OTP
● OTP: One Time Password
● Usually:
○ 4 to 8 digits
○ Random
○ Expires after a few minutes of creation
9. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
● TOTP: Time-based One Time Password
○ Created algorithmically (time + random secret)
○ 6-8 digits long
○ Also expires (usually) after a few minutes even if shown for some
seconds.
○ Many TOTP can be valid at any given time.
What’s an OTP
10. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Short Story
11. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Design & Implementation bugs
Many things can go wrong
12. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Design bugs
● Long expiration time
● Unlimited (too many) attempts
● OTP too short
● Too many OTPs valid at any given time
● OTP Reuse
● TOTP back use
● Answer machine take-over
13. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Implementation bugs
● Bypassing OTP flow
○ Forging OTP “pass” parameter/cookie
○ Using OTP validation from one user for another one.
○ OTP in HTML/cookie hidden in plain-text or hashed
● Legacy login without 2FA
● Insecure random
● etc…
14. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Implementation bugs
● Bypassing OTP flow
○ Forging OTP “pass” parameter/cookie
○ Using OTP validation from one user for another one.
○ OTP in HTML/cookie hidden in plain-text or hashed
● Legacy login without 2FA
● Insecure random
● etc…
15. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
● Implementation bugs
● SIM Swaps
● Network related attacks (e.g. SS7)
● Social Engineering
● Malware
What we won’t cover
16. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Attacking a Single OTP
17. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Windows
18. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Windows
● Arbitrary time:
○ a token is valid
○ attempts are counted
● Fixed and Sliding windows
0 N
OTP_0 OTP_1 OTP_2 Expiration
19. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Attacking a Single OTP
● 4 digit OTP: 0000-9999 → 10,000 possibilities.
● 1 attempt → 1 out of 10,000 chances of guessing.
● 5 attempts → 5 out of 10,000 chances of guessing.
So 0.05%. Not bad, not great.
20. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Let’s assume this:
● 4 digit OTP sent through SMS/email
● Valid for 3 minutes (considering SMS/email delays)
● 5 valid attempts in 3 minutes.
○ “Windows” are 3 minute long
21. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
You don’t receive the OTP
● Request multiple OTPs in 3 minutes
22. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
You don’t receive the OTP
● Request multiple OTPs in 3 minutes
○ For example up to 10
● 5*10/10,000 = 0.5%, so 1 in 200 chances of guessing.
23. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Repeating the attack
24. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Repeating the attack
● What happens after 3 minutes?
● Attacker can request another 10 OTPs
○ The first 10 are expired though
● So it’s still 1 in 200, right?
25. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Probability 101
26. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Going back to the basics
● Throwing a dice twice
● First throw: Odd of having a 6? 1 out of 6.
● Odd of at least one of the 2 throws being a 6?
● 1: 6 2: 1-6 or [6,1][6,2][6,3][6,4][6,5][6,6]
● 1: 1-6 2: 6 or [1,6][2,6][3,6][4,6][5,6][6,6]
● 11/36 ~ 1 out of 3 (0.305)
27. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Another way of calculating this
28. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Another way of calculating this
● Probability(of at least one throw being 6):
● 1 - Probability(Both Throws Not being 6) = 1 - P(BTN6)
● P(OTN6) = 1 - P(T6) = 1 - (1 / 6) = 0.83
● P(BTN6) = P(OTN6)2
= 0.832
= 0.694
● 1 - P(BTN6) = 0.305
29. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Wake up!
30. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
So: let’s keep the attack going!
● In 1 Hour:
○ 20 windows of 3 minutes (60 / 3 = 20)
● In a Day
○ 480 windows (24 * 20).
≈ 91% chance of success in just ONE day.
(4 digits OTP)
31. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
OTP Size increase to the Rescue
● Let’s try with 6 digits
32. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
OTP Size increase to the Rescue
● Let’s try with 6 digits
106
digits
Windows x day
Attempts x
Window
Valid OTPs
x Window
33. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
OTP Size increase to the Rescue
● Let’s try with 6 digits
● Probability of success in a day is: 2.3%
● Probability of success in 100 days, it’s 91%
● Probability of success in 6 months it’s around 99%
34. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
35. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
The math must be wrong!
● https://github.com/kantos/otp-brute-force-simulator/
36. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
The math must be wrong!
● https://github.com/kantos/otp-brute-force-simulator/
valid_otp_window = 10
attempts_per_otp = 5
window_length_minutes = 3
attack_days = 1
otp_creations = int(60/window_length_minutes )*24*attack_days
simulations = 10000
otp_length = 6
otp_max_size = 10**otp_length
otp_type = "numeric"
users = 1
37. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
The math must be wrong!
● https://github.com/kantos/otp-brute-force-simulator/
38. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
The math must be wrong!
● https://github.com/kantos/otp-brute-force-simulator/
$python otp_brute_force.py
Simulations: 1000
OTP windows: 480
OTP length:4
Probability of success: 0.91
(4 digits OTP)
≈ 91% chance of success in just ONE day.
39. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
OTP Size increase to the Rescue
● Let’s try with 6 digits
● Probability of success in a day is: 2.3%
● Probability of success in 100 days, it’s 91%
● Probability of success in 6 months it’s around 99%
$python otp_brute_force.py
Simulations: 100000
OTP windows: 480
OTP length:6
Probability of success: 0.0238
40. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
OTP Size increase to the Rescue
● Let’s try with 6 digits
● Probability of success in a day is: 2.3%
● Probability of success in 100 days, it’s 91%
● Probability of success in 6 months it’s around 99%
$python otp_brute_force.py
Simulations: 100000
OTP windows: 48000
OTP length:6
Probability of success: 0.90762
41. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
OTP Size increase to the Rescue
● Let’s try with 6 digits
● Probability of success in a day is: 2.3%
● Probability of success in 100 days, it’s 91%
● Probability of success in 6 months it’s around 99%
$python otp_brute_force.py
Simulations: 10000
OTP windows: 86400
OTP length:6
Probability of success: 0.9884
42. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Brute forcing windows
● Random OTPs are independent within windows.
○ OK to attempt same OTPs.
○ E.g. 000001, 000002, 000003, 000004, 000005, and repeat.
● TOTPs are not independent. It’s due to usage of sliding windows.
○ RFC6238: “We RECOMMEND that at most one time step is
allowed as the network delay.”
○ Recommendation: Incremental OTPs (and start over)
43. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
But TOTP is safer! Right? Right?
Time-step: 30 seconds. Window 1 minute. 2 time-steps valid per window. Since
it’s sliding there are 3 OTP valid at any given window.
TimeStep 0
OTP_0 OTP_0 / OTP_1 OTP_1 / OTP_2
TimeStep 1 TimeStep 2
Window 1
44. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
But TOTP is safer! Right? Right?
● Probability of guessing in 6 months is
around 93%
Time-step: 30 seconds. Window 1 minute. 2 time-steps valid per window.
Since it’s sliding there are 3 OTP valid at any given window.
6 months
45. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Let's take it up a notch
46. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Parallelizing the attack!
47. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Parallelizing the attack!
● Why attack one user at a time?
● Whatsapp, Uber, etc: Login = 1 valid OTP!
48. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Parallelizing the attack!
● Credential stuffing!!
○ Auth0(Okta): “nearly half of all login requests we receive each day are attempts at
credential stuffing.”
○ Google: “65% of all people reuse the same password on multiple (and sometimes
all) accounts”
https://auth0.com/blog/what-is-credential-stuffing/
https://services.google.com/fh/files/blogs/google_security_infographic.pdf
49. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Parallelizing the attack!
● 6 digit OTP, 10 valid at a time for 3 minutes, 5 attempts.
● Let’s attack 100 users in parallel.
● In 1 day: 91% chance of success
50. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Parallelizing the attack!
● OK, you convinced me, let’s go with 8 digit OTPs, that
should be enough.
51. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Parallelizing the attack! (8 digit OTP)
● Let’s attack 100 users in parallel.
○ In 100 days: 91% chance of success
● Let’s attack 1000 user in parallel
○ In 15 days: 97% chance of success
52. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
I know the drill, you will now introduce Alphanumeric
characters
53. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Let’s try something instead!
● Let’s introduce “attempt” rate limits
○ 60 per month (login/transact twice a day)
● In 1 month, attacking 1000 users (6 digit OTP)
○ 95% chance of success.
Rate limit
54. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
55. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Alphanumeric… sucks!
● Copy/Paste → OK!
● Typo errors
○ Shortsighted, minor/major disabilities, being human
Tubaam
wEze4A
MTB8QK
xjSehq
5t3CCv
6i8tKD
FmK7Xg
DUiz9n
xnPCdH
Am3Dwu
● Mobile phones automatic
○ Case change
○ Autocorrect
● Avoid l, 1, O, 0 (maybe B, 8)
56. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Alphanumeric… sucks! But works
● 58 character universe
● 6 OTP length
● 60 attempts x month (rate limit)
● For 6 months
● 10,000 attacks in parallel
● 0.47% chance of success.
57. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Mitigations
58. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Mitigations
1. Deliver always the same OTP during the same window.
2. 3-5 attempts per window.
3. Limit amount of attempts, per user, per day, per week, per month.
a. 5 per day, 10 per week, 20 per month.
b. Incremental failure delays
4. Detect credential stuffing attacks.
a. E.g. IP rate limits
b. Leaked password checks
c. Same invalid password attempts in different users.
d. etc.
59. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Mitigations
5. Don’t disclose if the OTP or the Password is wrong.
6. Use better solutions
○ U2F keys, push authentication, Direct carrier billing.
○ Enforce it on critical users or use-cases
60. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Mitigations
7. If you detect something “odd” implement some or all:
a. Service wide or per user CAPTCHA
b. Javascript (POW) validations,
c. Challenge the user with a knowledge question
d. Send another OTP through another channel (challenge them
separately)
e. Increase the length of the OTP (or character set).
8. Send email alerts to users
61. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Mitigations
● Combine them
● Do the math
● Accept what’s reasonable to your use case
62. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Final thoughts
63. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Final thoughts
● [T]OTP implementations are not foolproof.
● Design your security around your acceptable risk
● These findings apply to any low entropy challenge
64. [T]OTPs are not as secure as you might believe
Santiago Kantorowicz
Thank you!
https://xkcd.com/2543/