III. Phân tích khai thác một số lỗ hổng Business logic (Tiếp)
6. Lỗ hổng logic trong tính năng sử dụng voucher
Để thu hút thêm khách hàng mua sắm sản phẩm, các cửa hàng thường tổ chức các hình thức khuyến mại như chương trình giảm giá, mua một tặng một. Đối với các trang web mua sắm online, có một từ khóa không còn lạ lẫm với chúng ta - voucher. Voucher được biết đến là một loại phiếu mua hàng, phiếu quà tặng có thể quy đổi sang sản phẩm hoặc giảm giá trực tiếp trên hóa đơn thanh toán. Nó có giá trị bằng một khoản nhất định và được chi trả cho một mặt hàng, dịch vụ cụ thể nào đó. Chẳng hạn một món hàng có giá trị $100, và chúng ta có một voucher giảm giá 15%, khi đó chúng ta chỉ mất đô la để mua món hàng đó.
Thường các cửa hàng sẽ quy định cần có một "điều kiện" nào đó để có thể sử dụng voucher giảm giá, có thể thông qua hình thức tích điểm, hoặc giá trị đơn hàng tối thiểu cần đạt ngưỡng nào đó. Xét ví dụ sau: đoạn code thể hiện chức năng xử lý giảm giá khi áp dụng voucher cho đơn hàng với điều kiện tổng giá trị đơn hàng đạt tối thiểu $100.
<?php $discount = false;
$discount_value = 100;
if (isset($_POST['voucher']) { if ($total >= $discount_value) { $discount = true; }
}
if (isset($_POST['pay']) { if ($discount) { // process with discount } else { // process }
} ?>
Đoạn code quy định biến $discount
thể hiện quyết định giảm giá của đơn hàng. Khi tổng giá trị đơn hàng $total
đạt từ điều kiện giảm giá $discount_value
trở lên thì $discount
sẽ nhận giá trị true
. Tuy nhiên, nếu chức năng không được cài đặt chặt chẽ, kẻ tấn công có thể thêm sản phẩm vào giỏ hàng tới khi đạt giá trị tối thiểu, áp dụng voucher để lấy giá trị biến $discount
bằng true
, sau đó bỏ đi một số sản phẩm. Lúc này nếu thiếu bước kiểm tra lại giá trị đơn hàng trước khi thanh toán, hệ thống sẽ "cho phép" giảm giá đơn hàng dù không đạt điều kiện (Do $discount=true
không được sửa lại thành false
).
Mở rộng hơn nữa, với cơ chế gồm nhiều voucher, xét đoạn code sau:
<?php $voucher1 = 0.15; // for sale: 15%, code: VOUCHER1
$voucher2 = 10; // 10$ off, code: VOUCHER2
$checkVoucher1 = false;
$checkVoucher2 = false; voucherApply() { $voucher = $_POST['voucher']; if ($voucher === "VOUCHER1") { if ($checkVoucher1 == false) { $checkVoucher1 = true; $checkVoucher2 = false; } else { echo "you can not apply this voucher again!"; } } if ($voucher === "VOUCHER2") { if ($checkVoucher2 == false) { $checkVoucher2 = true; $checkVoucher1 = false; } else { echo "you can not apply this voucher again!"; } } if ($checkVoucher1 == true) { $total = $total * (1 - $voucher1); } if ($checkVoucher2 == true) { $total = $total - $voucher2; }
} ?>
Hàm voucherApply()
có nhiệm vụ giảm giá đơn hàng dựa trên mã voucher người dùng nhập. Ở đây chúng ta có 2 voucher tương ứng với giảm và giảm đô. Hàm sử dụng hai biến $checkVoucher1
và $checkVoucher2
để phân biến hình thức giảm giá, và có cơ chế kiểm tra để người dùng không được phép nhập lại voucher đã sử dụng. Ngoài ra khi người dùng áp dụng voucher này, biến check của voucher kia sẽ được đặt lại giá trị false
để tránh trường hợp hệ thống giảm giá 2 lần liên tiếp.
Tuy nhiên, một ngày đẹp trời, hệ thống cho phép người dùng sử dụng nhiều loại voucher cho cùng một đơn hàng, cứ mỗi khi người dùng áp dụng voucher sẽ gọi hàm voucherApply()
để tính toán lại tổng giá tiền. Chúng ta có thể thấy, vấn đề đã xảy ra: Tuy người dùng không thể áp dụng lại voucher giống nhau liên tiếp, nhưng họ có thể "lách luật" bằng cách áp dụng xen kẽ các voucher khác nhau. Do mỗi khi sử dụng voucher này thì biến check của voucher kia được đặt lại bằng false
nên người dùng lại có thể sử dụng tiếp voucher còn lại.
Phân tích lab Flawed enforcement of business rules
Miêu tả: Trang web mua sắm chứa lỗ hổng logic trong chức năng thanh toán với voucher. Chúng ta cần khai thác lỗ hổng để mua sắm sản phẩm "Lightweight l33t leather jacket". Tài khoản hợp lệ được sử dụng: wiener:peter
.
Trước khi đăng nhập, chú ý chúng ta có thể nhận được hai voucher khác nhau sau:
- Voucher 1: NEWCUTST5 dành cho khách hàng mới.
- Voucher 2: Tại cuối trang chủ, cho người dùng mới khi đăng ký tài khoản: SIGNUP30
Đăng nhập với tài khoản wiener:peter
. Sử dụng ý tưởng từ phân tích trong đoạn code mẫu phía trên, chúng ta liên tục thêm xen kẽ cả hai voucher vào đơn hàng "Lightweight l33t leather jacket" cho đến đủ tiền mua được đơn hàng này:
Đặt hàng và hoàn thành bài lab:
Phân tích lab Infinite money logic flaw
Miêu tả: Trang web mua sắm chứa lỗ hổng logic trong chức năng thanh toán với voucher. Chúng ta cần khai thác lỗ hổng để mua sắm sản phẩm "Lightweight l33t leather jacket". Tài khoản hợp lệ được sử dụng:wiener:peter
.
Giống với lab trên, tình huống này chúng ta cũng có voucher SIGNUP30 giảm giá đơn hàng, lấy voucher tại cuối trang chủ:
Ngoài ra, trang web có một món hàng đáng chú ý là Gift card:
Có thể mua sản phẩm Gift card với giá $:
Chúng ta thu được một mã code, khi nhập code này tại trang đặt hàng sẽ được hoàn trả $ vào tài khoản.
Để ý rằng chúng ta có voucher SIGNUP30 sử dụng sẽ giảm giá giá trị sản phẩm. Như vậy chúng ta có thể sử dụng nó trong việc mua sản phẩm Gift card (với giá $), sau đó nhập code để được hoàn trả $, dẫn đến mỗi lần thực hiện chúng ta được tặng miễn phí $. Nếu voucher SIGNUP30 có thể được sử dụng lại nhiều, thì tài khoản của chúng ta sẽ tăng vĩnh viễn.
Do khối lượng request lặp lại lớn nên chúng ta sẽ thao tác bằng tính năng macro trong Burp Suite. Ở đây, chúng ta cần lấy được mã code Gift card trong mỗi lượt thực hiện.
- Bước 1. Chọn request GET
/cart/order-confirmation?order-confirmed=true
, chọn configure item, đặt tên code cần note lại là gift-card và đánh dấu code cần lấy.
- Bước 2. Chọn request POST
/gift-card
, chọn Configure item, Sửa thành tính năng derived from the prior response (response 4) trong mục Parameter handling của từ khóa gift-card.
- Bước 3. Test lại macro kiểm tra kết quả chính xác.
- Bước 4. Thực hiện tấn công qua tính năng Burp Intruder với Null payload.
Và chúng ta chỉ cần đợi tới khi số dư trong ví lớn hơn $ là có thể đặt hàng (hoặc chỉ cần $ do có thể sử dụng voucher SIGNUP30)
7. Lỗ hổng logic trong cơ chế mã hóa dữ liệu
Chúng ta thường sử dụng các tham số (chẳng hạn như cookie, token, ...) nhằm thể hiện cho một yếu tố nào đó, và tất nhiên sẽ cần tới sự hỗ trợ của việc mã hóa dữ liệu. Phép mã hóa được xây dựng cần phù hợp với mục đích mã hóa dữ liệu, đồng thời có đủ độ phức tạp nhằm phòng tránh các cuộc tấn công từ kẻ xấu. Xét ví dụ nếu một trang web sử dụng một thuật toán mã hóa đủ mạnh có thể chống được tấn công, tuy nhiên, tồn tại chức năng đóng vai trò "mã hóa", đồng thời có một chức năng khác đóng vai trò "giải mã". Hai chức năng này hoạt động song song dẫn tới kẻ tấn công có thể lợi dụng hai cơ chế "mã hóa và giải mã miễn phí" này nhằm tìm kiếm dạng plaintext của một số tham số mã hóa. Từ đó họ có thể dựa vào một số đặc trưng của plaintext xây dựng các payload tấn công, mã hóa các payload này bằng cơ chế mã hóa sẵn có trong trang web. Cùng xem xét trường hợp sau để hiểu rõ hơn.
Phân tích lab Authentication bypass via encryption oracle
Miêu tả: Trang web chứa lỗ hổng logic dẫn tới tiết lộ cơ chế mã hóa encryption oracle. Để hoàn thành bài lab, chúng ta cần khai thác lỗ hổng này và truy cập tới trang quản trị, xóa tài khoản người dùng Carlos. Tài khoản hợp lệ được sử dụng: wiener:peter
.
Tại trang đăng nhập có tính năng Stay logged in, đăng nhập cùng với ghi nhớ mật khẩu:
Hệ thống xác nhận tính năng ghi nhớ đăng nhập của người dùng thông qua cookie sta-logged-in
:
Sau khi thử và đoán nhiều lần thì ... chúng ta chưa thể biết đây là dạng encode nào.
Tại chức năng comment, quan sát source code nhận thấy trường email không yêu cầu thuộc tính type:
Bình luận với một email sai định dạng wrongemail
, nhận được thông báo cùng với cookie notification
:
So sánh hai request chứa cookie notification
và không chứa nó.
- Request chứa cookie
notification
, response chứa dòng thông báo lỗi.
- Request không chứa cookie
notification
, response không còn dòng thông báo lỗi.
Từ đây suy ra dòng thông báo lỗi trong response được quy định xuất hiện dựa vào cookie notification
, hay giá trị cookie notification
chính là mã hóa của dòng thông báo Invalid email address: wrongemail
. Và dòng thông báo trả về cho người dùng chính là giá trị sau khi giải mã cho cookie notification
.
Dự đoán trang web sử dụng cùng một kiểu mã hóa trong các cookie stay-logged-in
và notification
. Để kiểm tra điều này, chúng ta có thể thay giá trị cookie notification
bằng giá trị stay-logged-in
. Khi đó thông báo trong phản hồi sẽ chính là kết quả sau khi giải mã của giá trị stay-logged-in
. Response tại trang comment blog đóng vai trò như một "máy giải mã".
Giá trị của cookie thể hiện cho tính năng ghi nhớ đăng nhập người dùng có định dạng: username:timestamp
Như vậy cookie stay-logged-in
có liên hệ với tên đăng nhập của người dùng. Chúng ta có ý tưởng sau: nếu có thể encode giá trị chuỗi administrator:timestamp
theo cơ chế encode của hệ thống sẽ có thể giả mạo tài khoản administrator.
Tuy nhiên, ở đây chúng ta gặp một vấn đề là không biết cơ chế mã hóa của hệ thống, nên ta sẽ cần mã hóa gián tiếp chuỗi administrator:timestamp
, lựa chọn timestamp
giống với tài khoản wiener
vừa đăng nhập, chúng ta cần mã hóa chuỗi administrator:1661759417904
. Chú ý rằng trường email đóng vai trò như một bộ máy mã hóa. Nhập giá trị administrator:1661759417904
cho trường email, thu được cookie notification
mới cùng thông báo mới Invalid email address: administrator:1661759417904
Không khó để nhận ra rằng nhiệm vụ của chúng ta bây giờ là đưa thông báo Invalid email address: administrator:1661759417904
về thông báo có định dạng mong muốn administrator:1661759417904
.
Xóa thử ký tự đầu tiên của cookie notification
nhận được thông báo:
Như vậy chúng ta có thể thao tác xóa các bits. Sử dụng tính năng Decoder trong Burp Suite, đầu tiên decode URL, tiếp theo decode Base64, để dạng hex thu được:
Xóa thử bit đầu tiên, mã hóa trở lại theo quy trình trên, thay trở lại giá trị notification
, nhận được thông báo:
Ta có độ dài bit input (hay độ dài bit notification
) phải là bội của . Lúc này chúng ta xóa cả một dòng đầu tiên (16 bits), mã hóa trở lại và thu được response:
Chúng ta được phép tăng hoặc giảm một bội số của bits, với bits vừa xóa thì chuỗi thông báo từ Invalid email address: administrator:1661759417904
trở thành dress: administrator:1661759417904
, nếu tiếp tục xóa thêm bits nữa sẽ thu được:
Bởi vậy, chúng ta cần thêm một lượng ký tự trước xx...xadministrator:1661759417904
để sau khi xóa bits thì chỉ còn lại administrator:1661759417904
. Sau khi thử nhiều lần, thì ký tự là vừa đủ 123456789administrator:1661759417904
. Sau khi xóa đi bits và mã hóa ngược lại, chúng ta thu được kết quả như mong muốn:
Như vậy chúng ta thu được payload là cookie xác nhận tính năng tự động ghi nhớ đăng nhập của tài khoản administrator
. Thay giá trị này vào cookie stay-logged-in
(lưu ý cần xóa cookie session
đi vì nó đang tương ứng với người dùng wiener
), chúng ta truy cập thành công vào administrator
:
Xóa tài khoản người dùng Carlos và hoàn thành bài lab: