Try   HackMD

WriteupCTF intern VNPT

Sau đây em xin trình bày về quá trình thực hiện khai thác lỗi của 2 chall web

Web 1:

Review

  • Khi bắt đầu truy cập trang web, thì có 2 chức năng chính: login, và register (Nếu chưa đăng nhập thì sẽ chuyển hướng về trang login)

Login:

  • Kết hợp thử chức năng và xem source code được cung cấp.

  • Sau khi gửi lên 2 tham số email và password, sẽ đi qua hàm login để kiểm tra:

  • Có thể thấy ở đây sử dụng câu query nối chuỗi, sẽ có thể bị SQLi. Tuy nhiên sau khi thử inject abc@gmail.com ' or 1 = 1 -- ' thì lại không thành công. Bởi vì ở trước đó đã có một hàm filter input vào của mình

  • Tất cả những input GET, POST đều đi qua filter này, đặc biệt là filter dấu '\ để khai thác sql. (Vì ở dòng 55 hàm sanity_input đã được gọi nên mọi input đều sẽ bị check)

Register

  • Vì đã bị filter các input nên chỉ có thể đăng ký một tài khoản bình thường để sử dụng các chức năng tiếp theo bên trong.

Exploit

Sau khi đăng nhập thì thêm chức năng view trang cá nhân.

  • Đến đây kết hợp với đọc code sẽ bắt đầu thấy thêm một số lỗi.

  • Trang web nhận vào tham số page. sau đó đưa qua hàm include_page

  • Lỗi đầu tiên là Path traversal dùng để khai thác đọc các file trong hệ thống

  • Khi thao tác ở trang user-detail.php thì nhớ đến trong source code có nhận vào một tham số đó là user_id mà trang web không hiện thị chức năng này

Thực hiện brute force id ở đây sẽ leak ra được thông tin của những người khác(Lỗi idor)

LFI dẫn tới RCE

  • Đây là lỗi impact cao nhất của bài này, điều kiện cần thì đã có hàm include ở index.php có thể control.
  • Tiếp theo cần inject code vào chỗ nào để thực hiện rce, đầu tiên thì em nghĩ đến việc inject vào User-Agent -> Sau đó truy cập vào file log của apache để execute code.
  • Nhưng ý tưởng bị stuck do setup chỗ nào đó mà không thể thực hiện đọc file đó.
  • Nghĩ lại đơn giản hơn thì kết hợp với việc chức năng update file ảnh

    Mặc dù đã kiểm tra qua loại tệp tải lên là chỉ file ảnh, nhưng lỗi ở hàm include đã làm vô dụng đi hàm kiểm tra này.

Việc cần làm tiếp theo là upload shell code vào file ảnh.


F12 lên kiểm tra tên file, và truy cập tới để RCE

Web2 (CVE-2021-36394):

Source code:

<?php class contact { public $email; public $name; public $phone; public $address; } class Example { private $hook; function __construct(){ // some PHP code... } function __wakeup(){ if(isset($this->hook)){ $flag = file_get_contents('Flag.txt'); echo "<h3>".$flag."</h3>"; } } } function unserialize_object($serializedstring) { try{ $variables = array(); $a = preg_split("/(\w+)\|/", $serializedstring, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $counta = count($a); for ($i = 0; $i < $counta; $i = $i + 2) { $variables[$a[$i]] = unserialize($a[$i + 1]); } return $variables; } catch(Exception $e) { echo '<div class="alert alert-success alert-dismissible show">'.$variables.'</div>'; } } function contact($serialize_object) { $serialize_object = urldecode($serialize_object); unserialize_object($serialize_object); } function validate_email($email) { if (filter_var($email, FILTER_VALIDATE_EMAIL)) { return 1; } return -1; } if(isset($_POST['submit'])) { $object = new contact(); $object->email = $_POST['contact_email']; $object->name = $_POST['contact_name']; $object->phone = $_POST['contact_phone']; $object->address = urlencode($_POST['contact_address']); $serialize_object = "OBJECTION|".serialize($object); $condition = preg_match('/%22|%7C|%23|%26|%5E|%27|%29|%28/', $object->address ); if (validate_email($object->email) === -1) { $error = $object->email . _(" không hợp lệ!"); }elseif ($condition){ $suggest = $serialize_object; contact($serialize_object); } else { $success = "Chúng tôi sẽ liên hệ với bạn sau"; } } ?>

Review

Sau khi xem qua source code trên, thì có thể tóm tắt đơn giản là sau khi chúng ta nhập vào một form điền thông tin.
Đi qua một số hàm, thì dữ liệu của chúng ta sẽ được unserialize (trước đó nó đã được serialize trước, ở dòng 57)

  • Mục tiêu cần lấy flag là ở class Example (Tuy nhiên ở đây sẽ gặp một chút vấn đề)
  • Trước tiên thì em sẽ phân tích code, để tìm ra chỗ inject thích hợp, có thể kích hoạt method wakeup của class Example

Follow code

  • Đầu tiên class contact được khởi tạo (Dòng 52)
  • Sau đó gắn giá trị từ các tham số do người dùng điền vào.
  • Đến dòng 57 thì $serialize_object sẽ được gắn thành một chuỗi "OBJECTION" + serialize (Class contract)
    Ví dụ:
OBJECTION|O:7:"contact":4:{s:5:"email";s:14:"dung@gmail.com";s:4:"name";s:4:"Dung";s:5:"phone";s:7:"0399999";s:7:"address";s:7:"Nghe+An";}
  • $condition sẽ là kết quả của hàm preg_match sau khi kiểm tra trong chuỗi của address có ký tự nào trong list đó không. (Nếu có sẽ trả về 1 <=> True). Đây cũng là điều kiện cần để có thể đi tiếp trong code.

  • Cần một email hợp lệ, và address có một ký tự trong list (Dùng ' tương đương %27 trong urlencode)

  • Tiếp tục sẽ đi đến hàm contact -> Và sau khi urldecode lại tiếp tục truyền chuỗi serialize vào hàm unserialize_object

  • Trong hàm unserialize_object sử dụng preg_split để phân tích chuỗi.
    Cụ thể như sau:

  • preg_split sẽ phân tích $serializedstring (đoạn ser từ những input từ form của mình) theo regex.

  • Vậy có nghĩa là từ sau ký tự | thì O:7:"contact":4:{s:5:"email";s:14:"dung@gmail.com";s:4:"name";s:4:"Dung";s:5:"phone";s:2:"03";s:7:"address";s:7:"Nghe An";} sẽ được unserialize.

  • Câu hỏi đặt ra là mình có thể thêm một chuỗi ser để hàm thực hiện unser cho mình không. Trong khi các giá trị input đều được control từ người dùng.

Sau khi thử thêm | vào tham số phone, hàm preg_split đã parse ra 3 đoạn để unser. (Vậy thì việc cần làm là thay giá trị ở input như trên ví dụ để gọi ra class Example lấy flag )

  • Tuy nhiên như đã nói ở trên thì muốn gọi ra flag ở method wakeup xảy ra một vấn đề
class Example { private $hook; function __construct(){ // some PHP code... } function __wakeup(){ if(isset($this->hook)){ $flag = file_get_contents('Flag.txt'); echo "<h3>".$flag."</h3>"; } } }
  • $hook cần được khởi tạo để kiểm tra ở hàm isset, nhưng trong lớp nó là biến private, không thể gọi ngoài như public.
  • Sau một hồi search thử thì e có thấy một bài ctf tương tự
  • Trong này có nhắc đến sử dụng PHP's ReflectionClass để set giá trị cho một biến private.

Đoạn code test:

<?php class Example { private $hook; function __construct() { // some PHP code... } function __wakeup(){ if(isset($this->hook)){ $flag = "flag{this_is_flag}"; echo "<h3>".$flag."</h3>"; } } } $obj = new Example(); // Use ReflectionClass to access the private property $hook $reflectionClass = new ReflectionClass('Example'); $reflectionProperty = $reflectionClass->getProperty('hook'); $reflectionProperty->setAccessible(true); // Set the value of $hook to true $reflectionProperty->setValue($obj, true); // Serialize the object $data = (serialize($obj)); echo base64_encode($data); // Unserialize the object and call __wakeup() unserialize($data); ?>

Ở đây em lấy giá trị base64 để đưa vào burp, nếu chỉ cop ser của $data thì có khả năng cao là bị lỗi, vì một số ký tự không hiện ra.

Đưa vào burp và decode đoạn đó.