III. Phân tích và khai thác lỗ hổng trong xác thực mật khẩu (password)
1. Tổng quan
Đa số các ứng dụng web thường xác thực người dùng thông qua tài khoản bao gồm tên đăng nhập - username và mật khẩu - password (Một số trường hợp có thể là email - password, phone number - password, ...). Bởi vậy, nếu để lộ các thông tin nhạy cảm này, kẻ tấn công có thể mạo danh xác thực bằng danh tính của bạn một cách dễ dàng.
2. Kỹ thuật tấn công vét cạn (Brute-force attacks)
Khi cơ chế xác thực cho phép người dùng có thể xác thực danh tính (đăng nhập - login) không giới hạn, kẻ tấn công có thể sử dụng kỹ thuật tấn công vét cạn để tìm kiếm tên đăng nhập cũng như mật khẩu đúng của bạn. Bạn nghĩ rằng công việc này rất mất thời gian và làm sao họ có thể đoán được tên đăng nhập hay mật khẩu của bạn để ngồi thử liên tục ư? Vậy thì bạn đã lầm, họ hoàn toàn có thể tự động hóa công việc này với một số công cụ (chẳng hạn như công cụ Burp Suite) cũng như danh sách username, password thông dụng (tham khảo Danh sách 100 mật khẩu thông dụng nhất tại Việt Nam) hoặc đơn giản là liệt kê tất cả các tổ hợp mật khẩu có thể từ các kí tự alphabet, chữ số, ... Cộng thêm tính chủ quan của người sử dụng, đăng ký tài khoản với mật khẩu ngắn, dễ đoán cũng giúp cho kẻ tấn công có thể dễ dàng đạt được mục đích.
Trước khi thực hiện kỹ thuật tấn công vét cạn, kẻ tấn công có thể tạo một tài khoản với hệ thống mục tiêu để xem xét, phân tích dạng cụ thể của username, password. Chẳng hạn, hệ thống yêu cầu đăng nhập bằng gmail, mật khẩu với độ dài tối thiểu, tối đa, phạm vi kí tự được sử dụng để đặt tên đăng nhập và mật khẩu, ... từ đó, bên cạnh danh sách tên đăng nhập, mật khẩu thông dụng, kẻ tấn công có thể tự tạo một danh sách tấn công phù hợp với đặc tính của từng mục tiêu, đối tượng khác nhau.
3. Dựa vào thông tin phản hồi để xác định tên đăng nhập, mật khẩu victim (nạn nhân)
Xem xét đoạn code sau:
$connect = mysqli_connect ('localhost', 'root', '', 'user'); $username = $_POST['username'];
$password = $_POST['password']; $query = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($connect, $query);
$count = mysqli_num_rows($result); if ($count == 1) { $row = mysqli_fetch_array($result); if ($password != $row['password']) { echo "Wrong password!"; }
} else { echo "Wrong username!";
}
Các biến $username
và $password
là tên đăng nhập và mật khẩu do người dùng nhập vào. Đoạn code thực hiện các chức năng lần lượt như sau:
- Kết nối tới database, tìm kiếm trong table
users
các thông tin của tài khoản có tên đăng nhập là giá trị biến$username
do người dùng nhập. - Thực hiện kiểm tra tên đăng nhập, nếu tên đăng nhập không tồn tại, trả về thông báo "Wrong username!".
- Nếu tên đăng nhập tồn tại, tiếp tục kiểm tra mật khẩu tương ứng, nếu mật khẩu sai, trả về thông báo "Wrong password!".
Bởi hệ thống cho phép người dùng có thể đăng nhập vố số lần, và từ các thông báo "nhạy cảm" này, kẻ tấn công có thể dễ dàng thực hiện một cuộc tấn công vét cạn để tìm kiếm tên đăng nhập và mật khẩu của victim.
Phân tích lab Username enumeration via different responses
Đề bài cung cấp 2 danh sách Candidate usernames và Candidate passwords. Nhiệm vụ của chúng ta là sử dụng kĩ thuật tấn công vét cạn tìm kiếm tên đăng nhập và mật khẩu người dùng, truy cập vào tài khoản của victim.
Đăng nhập với tên đăng nhập và mật khẩu bất kì, hệ thống xuất hiện thông báo Invalid username.
Có thể suy đoán hệ thống check username không chính xác nên đưa ra thông báo username không hợp lệ. Thử đăng nhập liên tiếp nhiều lần vẫn nhận lại thông báo Invalid username, chứng tỏ hệ thống không thực hiện hành động block hoặc ngăn cản đăng nhập, như vậy có thể brute force username theo danh sách được cung cấp.
Sử dụng Burp Suite, bắt request proxy rồi gửi tới Intruder.
Trước hết, chúng ta sẽ tìm ra username đúng trong danh sách được cung cấp, xóa các dấu §, chỉ để lại tại tham số username, giá trị password điền bất kì.
Tại mục Payloads, sao chép danh sách username được cung cấp và Paste vào mục Payload Options.
Click Start attack, bắt đầu tấn công (với dạng tấn công Sniper).
Trong cột Lengh, xuất hiện dòng response với độ dài khác so với các độ dài còn lại, trong phần Response trả về thông báo Incorrect password.
Điều này chứng tỏ username ec2-user
là một username đúng (tồn tại trong database). Thay username trong Positions bằng ec2-user
, thêm §§ và password.
Tương tự với username, tiếp tục brute force password với danh sách password được cung cấp.
Như vậy chúng ta đã tìm được username:password
là ec2-user:computer
. Đăng nhập thôi!
Phân tích lab Username enumeration via subtly different responses
Đề bài cung cấp 2 danh sách Candidate usernames và Candidate passwords. Nhiệm vụ của chúng ta là sử dụng kĩ thuật tấn công vét cạn tìm kiếm tên đăng nhập và mật khẩu người dùng, truy cập vào tài khoản của victim.
Ở tình huống này, khi đăng nhập với tên đăng nhập và mật khẩu bất kì, chúng ta nhận được thông báo Invalid username or password.
Oh, với lab này, người lập trình đã khôn ngoan hơn. Dòng thông báo chỉ cho chúng ta biết rằng tên đăng nhập hoặc mật khẩu không đúng, khiến chúng ta không thể trực tiếp xác định cụ thể là tên đăng nhập sai hay mật khẩu sai nữa. Tuy nhiên, các đoạn code là do con người viết ra, chúng ta vẫn thực hiện brute force bình thường (do hệ thống không ngăn chặn kĩ thuật tấn công này), và mong rằng sẽ có một sự khác biệt nhỏ nhoi nào đó nếu tên đăng nhập của chúng ta là đúng.
Thật ra, lab này nên cho chúng ta một tài khoản đúng để kiểm nghiệm. Lúc này chúng ta có thể so sánh thông báo trả về giữa một tài khoản đúng và một tài khoản với tên đăng nhập sai. Trong các tình huống thực tế, thì kẻ tấn công có thể tự lập một tài khoản để thử nghiệm điều này. (Rõ ràng, hacker cũng đồng thời là user mà nhỉ!) Thôi thì tên lab cũng nói rõ sẽ có một sự khác biệt giữa các thông báo trả về, nên ta cứ thử thôi. Tình huống thông báo khác nhau này ở thực tế cũng hoàn toàn có thể xảy ra nhé!
Sử dụng Burp Suite, bắt request đăng nhập và gửi qua Intruder, thực hiện tương tự các bước như ở lab Username enumeration via different responses.
Lúc này, chúng ta cần có thao tác tự động hóa việc nhận biết sự khác nhau của phản hồi do hệ thống đưa ra. Đi tới tính năng Options, tại mục Grep - Extract, chọn Add, bôi đen dòng thông báo trả về trong Response và chọn OK.
Khi đó chúng ta đã thêm cột tính năng quan sát đoạn thông báo phản hồi này cho cuộc tấn công.
Bắt đầu tấn công thôi nào! Sau khi kết thúc, sắp xếp lại cột warining nhận thấy request 91 trả về dòng thông báo Invalid username or password khác với các dòng thông báo khác (Thiếu dấu . đây là sự bất cẩn của người lập trình). Như vậy chúng ta thu được username của victim là att.
Thực hiện tấn công tương tự với danh sách passwords được cung cấp.
Thu được request 40 không trả về thông báo Invalid username or password và Status trả về là 302 Found.
Như vậy chúng ta có tài khoản victim là att:buster
.
Đăng nhập và hoàn thành lab thôi nào!
4. Dựa vào thời gian phản hồi để xác định tên đăng nhập, mật khẩu victim (nạn nhân)
Trong thực tế, password của người dùng khi lưu vào cơ sở dữ liệu (database) thường không còn để ở dạng rõ (plaintext), thậm chí một số cơ sở dữ liệu còn mã hóa cả username! Bởi nếu lưu các thông tin của người dùng ở bản rõ có thể mang đến một số nguy cơ sau:
- Nếu cơ sở dữ liệu bị lộ (bị tấn công chẳng hạn) sẽ lộ thông tin tất cả người dùng ngay lập tức.
- Nhân viên có đủ quyền hạn có thể biết được các thông tin của khách hàng, không thể loại trừ trường hợp họ sử dụng vào mục đích xấu.
Bởi vậy, các thông tin khi lưu vào cơ sở dữ liệu thường được lưu ở dạng bản mã (ciphertext) thông qua các kĩ thuật băm (hash). Các kĩ thuật hash dựa trên lý thuyết và mong muốn của người sử dụng sẽ mã hóa dữ liệu theo một chiều, chúng ta không thể từ bản mã tìm ra bản rõ được. (Có rất nhiều kĩ thuật hash khác nhau, tuy nhiên hiện nay với sự phát triển lớn mạnh của dữ liệu, nhiều kĩ thuật hash đã không còn được an toàn như mong muốn ban đầu của nó). Tham khảo thêm ở Hàm băm là gì.
Xem xét đoạn code sau:
$connect = mysqli_connect ('localhost', 'root', '', 'user'); $username = $_POST['username'];
$password = $_POST['password']; $query = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($connect, $query);
$count = mysqli_num_rows($result); if ($count == 1) { $row = mysqli_fetch_array($result); if (md5($password) != $row['password']) { echo "Invalid username or password!"; }
} else { echo "Invalid username or password!";
}
Phân tích đoạn code:
- So với đoạn code ở mục 3, người lập trình đã cẩn thận hơn với thông báo đưa ra tới người dùng, khiến kẻ tấn công không thể xác định độ chính xác của tên đăng nhập và mật khẩu.
- Cơ chế xác thực khi kiểm tra username đúng, đã thực hiện hash mật khẩu (ở đây sử dụng MD5) và so sánh với mật khẩu được lưu trong cơ sở dữ liệu để xác thực người dùng. Đây là một lỗ hổng có thể bị lợi dụng bởi kẻ tấn công.
Cơ chế hash string thường sẽ sử dụng vòng lặp (loop) để thực hiện tính toán, để thu được độ dài cố định cần thiết (mỗi dạng hash sẽ trả về một đoạn mã sau khi hash với độ dài cô định) thì cần lặp liên tục rồi tính toán thu nhỏ dần. Bởi vậy, thời gian thực hiện sẽ khác nhau khi hash một chuổi với độ dài nhỏ và một chuỗi với độ dài rất lớn.
Dựa vào cơ chế hoạt động này, kẻ tấn công có thể nhập vào một chuỗi mật khẩu rất dài, để so sánh thời gian phản hồi khi hệ thống thực hiện tính toán băm chuỗi mật khẩu, khi đó username sẽ là chính xác, do nếu kiểm tra username sai sẽ lập tức trả về Invalid username or password!
Phân tích lab Username enumeration via response timing
Đề bài cung cấp 2 danh sách Candidate usernames và Candidate passwords. Ngoài ra còn có một tài khoản hợp lệ wiener:peter
. Nhiệm vụ của chúng ta là sử dụng kĩ thuật tấn công vét cạn tìm kiếm tên đăng nhập và mật khẩu người dùng, truy cập vào tài khoản của victim.
Gợi ý từ đề bài: chúng ta cần vượt qua một cơ chế bảo vệ brute force ở tình huống này.
Thử đăng nhập với username wiener, password bất kì. Quan sát reponse trả về, nhận thấy rằng sau 4 lần liên tiếp đăng nhập sai, hệ thống sẽ ngăn cản chúng ta đăng nhập và yêu cầu đăng nhập lại sau 30 phút.
Bởi dấu hiệu có thể đăng nhập lại sau 30 phút, từ đó suy đoán rằng hệ thống thực hiện block địa chỉ IP của chúng ta trong 30 phút. Thêm Request Header X-Forwarded-For: 1.1.1.1 với tác dụng thay đổi địa chỉ IP thành 1.1.1.1 và thực hiện đăng nhập với tài khoản đúng, chúng ta có thể đăng nhập thành công.
Như vậy chúng ta có thể thay đổi IP với từng request trong tấn công brute force để bypass cơ chế block IP của hệ thống.
Thử đăng nhập với một username không tồn tại và một password rất dài, chúng ta nhận được phản hồi ngay lập tức.
Đăng nhập với username wiener và password rất dài, response được trả về chậm hơn (khoảng 3 - 4 giây, thời gian này tùy thuộc vào từng môi trường khác nhau, có thể ảnh hưởng bởi đường truyền mạng, bộ xử lý, ...).
Từ sự khác biệt về thời gian phản hồi này, chúng ta có thể suy đoán rằng hệ thống thực hiện kiểm tra username trước, nếu không tồn tại username trong cơ sở dữ liệu sẽ ngay lập tức trả về đăng nhập thất bại, nếu username tồn tại (ở đây là wiener), hệ thống thực hiện kiểm tra password và vì chúng ta sử dụng một password rất dài nên dẫn tới thời gian xử lý lâu hơn ở cơ chế hash, dẫn đến thời gian phản hồi chậm hơn.
Kết hợp 2 điều trên, chúng ta sẽ thực hiện brute force kết hợp thay đổi địa chỉ IP cho mỗi username, sử dụng password dài mặc định cho mỗi lần thử. (với Attack type: Pitchforce)
Thiết lập cho địa chỉ IP thay đổi lần lượt từ 1.1.1.0 đến 1.1.1.100
Quan sát cột giá trị Response received và Response completed thấy request 70 có giá trị lớn. Đây là bằng chứng cho thấy hệ thống đang xử lý chuỗi mật khẩu rất lớn của chúng ta.
Điều này chứng tỏ username cần tìm là apollo. Tiếp tục thực hiện như trên với danh sách passwords.
Từ request 42 chúng ta thu được tài khoản hợp lệ là apollo:harley
. Đăng nhập và giải quyết lab:
5. Vượt qua cơ chế block IP lỏng lẻo
Hiện nay, các trang web thường sử dụng một số cách sau để ngăn chặn tấn công brute force:
- Nếu phát hiện một user thực hiện đăng nhập sai quá số lần cho phép với thời gian giữa các lần đăng nhập ngắn, thực hiện khóa IP của họ vì đây có thể là một cuộc tấn công vét cạn.
- Tương tự, hệ thống có thể tạm khóa tài khoản khả nghi trong một thời gian nhất định.
Mỗi cách phòng chống có ưu, nhược điểm của nó. Tuy nhiên cũng không phải không thể vượt qua được, nhất là khi cơ chế ngăn chặn tồn tại một số lỗ hổng logic.
Xét ví dụ sau: Một hệ thống thực hiện khóa IP kẻ tấn công sau mỗi 3 lần đăng nhập sai liên tiếp tài khoản trong thời gian nhất định. Nhưng để ít mang đến sự phiền phức nhất tới người dùng, hệ thống sẽ reset cơ chế này mỗi khi người dùng nhập đúng tài khoản của họ. Đây là một lỗ hổng có thể bị lợi dụng bởi kẻ tấn công. Họ có thể thực hiện brute force với 2 request giả và 1 request thật xen kẽ: tức là mỗi khi brute force 2 tài khoản khác nhau, sẽ đăng nhập với tài khoản hợp lệ nhằm reset lại cơ chế bảo vệ của hệ thống.
Phân tích lab Broken brute-force protection, IP block
Ở tình huống này, chúng ta được cung cấp một tài khoản hợp lệ wiener:peter
và username victim là carlos
, danh sách passwords có chứa password của carlos.
Gợi ý của đề bài: chúng ta có thể sử dụng tính năng macro trong Turbo Intruder extension để tối ưu và tự động hóa cuộc tấn công. Tuy nhiên chúng ta cũng có thể tự code lại danh sách username và password phù hợp trong trường hợp này.
Đăng nhập với username hợp lệ wiener và password sai trả về Invalid passwd.
Tuy nhiên nếu đăng nhập sai liên tiếp 3 lần chúng ta sẽ bị ngăn chặn đăng nhập trong 1 phút.
Cách ngăn chặn địa chỉ IP này không thể bypass bằng X-Forwarded-For như lab trước. Tuy nhiên nếu ở lần thứ 3 chúng ta đăng nhập với tài khoản hợp lệ wiener:peter
thì cơ chế này được reset. Tức là chúng ta có thể thực hiện brute force username, password với quy tắc: sau mỗi 2 lượt thực hiện brute force, sẽ thực hiện 1 lần đăng nhập hợp lệ bằng tài khoản wiener:peter
(sử dụng Attack type: Pitchfork).
Công việc tự code lại danh sách username và password xin dành cho bạn đọc, điều này không quá khó để thực hiện. Sau khi code xong chúng ta thu được danh sách username và password lần lượt có dạng như sau:
username list
carlos
carlos
wiener
carlos
carlos
wiener
...
password list
123456
password
peter
12345678
qwerty
peter
...
Như vậy, chúng ta sẽ thực hiện tấn công brute force tương tự các labs trước. Lưu ý rằng khi đăng nhập hợp lệ thì status trả về là 302 (Tìm kiếm status trả về 302 của các request có username là carlos
).
Quan sát thu được tài khoản victim là carlos:cheese
. Đăng nhập và giải quyết lab:
Bên cạnh cơ chế khóa IP kẻ tấn công, đối với cơ chế bảo vệ brute force thông qua khóa tài khoản khả nghi trong thời gian nhất định. Và thường hệ thống chỉ khóa được tài khoản tồn tại trong cơ sở dữ liệu - đây chính là yếu tố có lợi có kẻ tấn công, họ sẽ lợi dụng điều này để tìm ra một tài khoản tồn tại và sẽ bị khóa trong một thời gian bởi hệ thống (đồng nghĩa với việc sau khi hết thời gian khóa họ có thể tìm kiếm mật khẩu của tài khoản đó).
Phân tích lab Username enumeration via account lock
Đề bài cung cấp 2 danh sách Candidate usernames và Candidate passwords. Nhiệm vụ của chúng ta là sử dụng kĩ thuật tấn công vét cạn tìm kiếm tên đăng nhập và mật khẩu người dùng, truy cập vào tài khoản của victim. Đề bài cho biết hệ thống sẽ khóa tài khoản của người dùng trong một thời gian nếu bị phát hiện hành vi brute force.
Thử đăng nhập với username và password bất kì, hệ thống đưa ra thông báo Invalid username or password.. Đăng nhập liên tiếp nhiều lần, vẫn nhận được thông báo trên, hệ thống không thực hiện block hoặc ngăn cản hành vi đăng nhập, và chúng ta suy đoán rằng hệ thống chỉ thực hiện cơ chế bảo vệ khóa tài khoản đối với username tồn tại trong cơ sở dữ liệu. Như vậy chúng ta có thể sử dụng kĩ thuật tấn công brute force để tìm kiếm username victim.
Để tự động hóa việc đăng nhập nhiều lần với mỗi username, chúng ta có thể sử dụng lựa chọn Null payloads (generate 4 payloads) với Attack type: Cluster bomb. Ở lựa chọn payload set 1 sẽ là danh sách username, payload set 2 là Null payloads:
Lúc này, cuộc tấn công của chúng ta sẽ lặp lại đăng nhập 4 lần với mỗi username, mục đích để tìm kiếm tài khoản sẽ bị khóa bởi cơ chế bảo vệ của hệ thống.
Username anaheim
bị khóa, suy ra đây là một tên đăng nhập hợp lệ. Tiếp tục thực hiện brute force password:
Request 47 có Length khác với các request còn lại và không hiển thị dòng thông báo lỗi. Như vậy ta có tài khoản victim là anaheim:iloveyou
. Đăng nhập và giải quyết lab này: