Multi-Factor Authentication

Overview

Multi-Factor Authentication adds a second or additional proof of identity beyond a password, but poor implementation can still leave the login flow vulnerable. These notes cover MFA factor types, common 2FA mechanisms, weak OTP generation, OTP leakage, logic flaws, brute-force conditions, and phishing-based MFA bypass strategies.

MFA vs 2FA

MFA requires two or more different verification factors. Two-Factor Authentication is just one subset of MFA that uses exactly two factors. All 2FA is MFA, but not all MFA is 2FA.

The security value comes from forcing the attacker to compromise more than one trust category instead of relying solely on a password.

Authentication Factor Types

Testing point: biometric factors and contextual factors are still implementation-dependent. In practice, most web pentests focus on passwords plus possession-based controls like OTPs, push prompts, or hardware tokens.

Common 2FA Mechanisms

TOTP

Time-based one-time passwords rotate regularly and are generated locally in authenticator apps such as Google Authenticator or Authy. They are stronger than static codes but still vulnerable if the surrounding workflow is weak.

Push Notifications

Push-based MFA sends an approval request to a trusted device. These systems are user-friendly but can be abused through MFA fatigue if the victim is spammed with approval prompts.

SMS

SMS-based OTPs are still common but weaker than other options due to SIM swapping, interception, and telecom-layer weaknesses.

Hardware Tokens

Physical devices such as YubiKeys provide stronger possession-based authentication and are generally harder to intercept remotely.

Conditional Access

Many modern environments do not enforce MFA uniformly. Instead, they vary enforcement based on context.

Weak OTP Generation

The security of OTP-based MFA depends on the unpredictability of the OTP. Weak generation logic, low entropy, or narrow value ranges make brute force realistic.

If OTP generation uses predictable seeds or simple random functions with small ranges, the attacker’s search space may be far smaller than the user expects.

Application Leaking the 2FA Token

A surprisingly common failure is when the application issues an OTP through an XHR request and includes the generated token directly in the HTTP response. That turns the second factor into client-visible data and removes the value of the control entirely.

During testing, inspect the network tab carefully when the MFA page loads. If the OTP is requested asynchronously, review the response body rather than assuming the code was sent only through email, SMS, or an app.

Remediation: the OTP generation endpoint should return only a generic success response, never the actual code.

Logic Flaw: Access Without Completing MFA

Some applications correctly ask for an OTP but fail to enforce that the OTP step was completed before granting access to authenticated routes. This usually comes down to broken session state management.

A common anti-pattern is setting the authenticated session flag immediately after password verification rather than after the OTP check succeeds.

if (authenticate($email, $password)) {
    $_SESSION['authenticated'] = true; // This should only happen after MFA
    $_SESSION['email'] = $_POST['email'];
    header('Location: ' . ROOT_DIR . '/mfa');
    return;
}

If the dashboard later only checks $_SESSION['authenticated'], an attacker who knows the dashboard route can skip the OTP step entirely.

Fix: split session state into at least two phases. One session should represent “password verified but pending MFA”, and another should represent “fully authenticated after MFA success”.

Lack of Rate Limiting

Even strong OTPs fail if the verification endpoint allows unlimited guesses. OTP brute force becomes viable when there is no rate limit, no lockout, no cooldown, and no additional friction after repeated failure.

Pentesters should test both the obvious OTP form and any underlying API endpoints, because the visible UI may slow users down while the backend remains unrestricted.

Evilginx and MFA Phishing

MFA does not eliminate phishing risk if the attacker can proxy the victim’s live session. Tools like Evilginx act as a man-in-the-middle phishing proxy that captures credentials, OTPs, and session cookies while relaying traffic to the real site.

In these attacks, the attacker often does not need to crack the second factor. They just need the victim to complete it once through the attacker-controlled proxy.

Why OTP Leakage Happens

Brute Force with Re-Login Automation

Some applications log the user out after a failed OTP attempt. That slows down manual testing but does not stop automation. A script can simply re-authenticate, obtain a fresh MFA prompt, and keep testing candidate codes.

In the supplied example, the OTP range is intentionally small and generated from a weak range:

function generateToken()
{
    $token = strval(rand(1250, 1350));

    $_SESSION['token'] = $token;
    return 'success';
}

That makes repeated login-and-guess automation practical.

import requests

login_url = 'http://mfa.thm/labs/third/'
otp_url = 'http://mfa.thm/labs/third/mfa'

credentials = {
    'email': 'thm@mail.thm',
    'password': 'test123'
}

def login(session):
    return session.post(login_url, data=credentials)

def submit_otp(session, otp):
    otp_data = {
        'code-1': otp[0],
        'code-2': otp[1],
        'code-3': otp[2],
        'code-4': otp[3]
    }
    return session.post(otp_url, data=otp_data, allow_redirects=False)

Key point: if the application forces a logout after failed MFA, that is only effective if login itself is sufficiently rate-limited and the OTP space is large enough to make automated retries impractical.

Testing Approach

Defensive Guidance

Key Takeaways