# 1. WarmupPHP 01 ## Đề bài ![image](https://hackmd.io/_uploads/r1Obhr28p.png) - Link to challenge: http://103.162.14.116:10007/ ## Phương hướng & Khai Thác ### Phương hướng Đầu tiên mình thấy một trang web quá trắng, trắng tinh, và hiện ra chữ `KCSC hello fen, name` và tên là giá trị mình truyền lên URL thông qua param **name** Dirsearch thì mình thấy trang web có robots.txt nên vào xem sao: ![image](https://hackmd.io/_uploads/rys0TrhIa.png) Có được source code của chall bằng /?source: ![image](https://hackmd.io/_uploads/ByZZ0rnIT.png) ```php! <?php include('flag1.php'); include('flag2.php'); include_once('/app/vendor/autoload.php'); define('FLAG1', $flag1); if(!isset($_GET['name'])) { header('Location: /?name=guest'); } if (isset($_GET['source'])) { show_source(__FILE__); } $smarty = new Smarty(); $policy = new Smarty_Security($smarty); if(str_starts_with($_GET['name'], 'kcsc')) { $policy->php_functions = $allow_php_func; } $smarty->enableSecurity($policy); $smarty->display('string:KCSC hello fen, '.$_GET['name']); ?> ``` Đoạn render template string này rất ngon, mình có thể khai thác param `name` để SSTI: ![image](https://hackmd.io/_uploads/HyYThy0L6.png) Sau đó mình đã đi tìm cũng kha khá cách để exploit SSTI smarty nhưng không thành công, version của phiên bản này không còn dính những vuln mà mình tìm được nữa, cụ thể là version 4.3.4: ![image](https://hackmd.io/_uploads/rkOE6kA86.png) Đang bế tắc thì mình thấy là flag 1 không cần phải RCE để đọc được, trong dòng code có một đoạn define constant **FLAG1** chứa giá trị flag1 mà mình cần, ngồi đọc doc 1 lúc thì có 1 syntax giúp mình đọc constant của php: ![image](https://hackmd.io/_uploads/B11yCJRIa.png) Với tài liệu trên, mình exploit bằng: `/?name={$smarty.const.FLAG1}` -> solved ![image](https://hackmd.io/_uploads/HkfXCyCLp.png) Challenge lộ ra hint của chall WarmupPHP 02 là file `flag2.php.bak` Flag: **KCSC{warmup01_begin_using_smarty}** # 2. WarmupPHP 02 ## Đề bài ![image](https://hackmd.io/_uploads/HJDlExALT.png) Vẫn là trang web đó, nhưng giờ mình có được file `flag2.php.bak` được hint trước đó, truy cập đến và mình có đoạn xử lý của chall: ```php! <?php function debug($input) { if (str_contains($input, '/') || str_contains($input, '.')) { die('invalid filepath'); } if (strlen(readlink($input)) >= 128) { echo file_get_contents($input); } } $allow_php_func = [ 'debug', 'symlink' ]; ?> ``` Chức năng allow_php_func của index giờ mới có tác dụng, khi được allow nó sẽ cho phép mình sử dụng 2 hàm của php, 1 là symlink và 2 là hàm debug tự định nghĩa. Hàm debug ngay từ lúc vào đã chặn path traversal khá chặt bằng việc cấm không có `/` hay `.` :)) mình no hope inject path traversal vào biến này rùi Tiếp đến nó kiểm tra xem độ dài của từ được link từ input có dài hơn 128 ký tự không, nếu có sẽ tiến hành đọc file đó ## Phương Hướng Trước hết, mình có thể gọi đến chức năng của php bằng gọi nó ngay bên trong `{}` ![image](https://hackmd.io/_uploads/H1tz-gR86.png) Tất nhiên là trong trường hợp nếu như nó không bị chặn hoặc không được enable :laughing: Để trigger được tính năng allow_php_func thì mình cần có biến `kcsc` ở đằng trước, thì mới có thể dùng được `debug` và `symlink` bên trong `{}` được hehe Hàm readlink xuất hiện trước rồi mới đếm số ký tự, mình khá chắc chắn mình sẽ sử dụng chức năng symlink để ém path traversal rồi đấy, rồi đấy symlink đó vào trong hàm debug để bypass đoạn kiểm tra path traversal, chỉ cần pathtraversal đủ 128 ký tự là được :rolling_on_the_floor_laughing: ![image](https://hackmd.io/_uploads/Byb4egAUa.png) Mình thử đọc `/etc/passwd` trước đã: ![image](https://hackmd.io/_uploads/HJQ_eeAI6.png) Thông báo về 1 nghĩa là đã symlink thành công rùi, giờ mình gọi đến hàm debug thui Vậy là mình có thể đọc file tùy ý rùi: ![image](https://hackmd.io/_uploads/SJvFWlA8a.png) Giờ vấn đề còn lại là tìm được đường dẫn đến flag2.php là có thể solve được rùi Fuzz một hồi `/var/www/html/src` các kiểu không được, mình đi tìm cách bypass thì mình thấy có thể dùng được thư mục `/proc/self/cwd` để lấy được file trong cùng thư mục mà không cần đến đường dẫn đến thư mục đó (vì file flag2.php được include không nên mình đoán nó ở cùng thư mục với index) ![image](https://hackmd.io/_uploads/BkDTzeA8p.png) Vậy thì cần gì biết đường dẫn nữa, mình táng ngay đường dẫn này vào symlink là có thể solve được rùi: ![image](https://hackmd.io/_uploads/HyAeQgCUp.png) ![image](https://hackmd.io/_uploads/SkvZQgRLp.png) Flag: **KCSC{warmup02_smarty_is_interesting}** Ngoài cách sử dụng path traversal ra, mình cũng có thể dùng nhiều dấu `./` vì bao nhiêu dấu đó thì mình vẫn đang ở cùng folder thui: ![image](https://hackmd.io/_uploads/HyVPXx0LT.png) ![image](https://hackmd.io/_uploads/SJRD7gA86.png) Chall cũng hé lộ là mình flag3 ở folder / và mình cần phải RCE mới đọc được =)) quá khó P/s: Lúc đầu hàm check của challenge là `count(readlink())`, ở phiên bản php 8.2 nếu đưa string vào hàm count sẽ thông báo ra lỗi -> 500 server, còn ở phiên bản 7.4 thì nó chỉ đưa ra warning, còn count sẽ trả về giá trị 1. Lúc đó count sẽ return 1 với mọi thứ mà nó không đếm được, ngoại trừ null thì là 0. # 3. Kpop Đây là một chall khá hay mà mình đã không làm kịp trong thời gian cuộc thi đang diễn ra, sau khi đi học hỏi cách giải thì mình đã làm được, hehe ## Đề bài: ![image](https://hackmd.io/_uploads/H1rkEg0Lp.png) Trang web có một giao diện login, trong index có 2 tính năng chính là history.php cho phép xem code được gen khi mình login, và nếu như mình là admin thì có thể được được thực thi câu lệnh cat file ## Phương Hướng & Khai Thác Bắt đầu với trang login, các chức năng đã prepare statement hết, chỉ độc còn một chỗ nối chuỗi là khi đăng nhập thành công nó sẽ insert logcode và username tương ứng vào bảng logs: ```php! $row = $result->fetch_assoc(); $query = "INSERT INTO logs (logcode,username) VALUES (" . $logcode . ",?)"; $stmt->prepare($query); $stmt->bind_param("s", $username); $stmt->execute(); $_SESSION['username'] = $username; header("Location: index.php"); exit(); ``` Mình đã rất chật vật với đoạn code này, vì ngoài prepare statement thì chall cũng filter kha khá ký tự ở `filter.php` nữa :cry: ```php! function containsDangerousCharacters($inputString) { $specialCharacters = array("`", "\"", "'", "-", "#","flag","sleep","benchmark"); foreach ($specialCharacters as $char) { if (strpos($inputString, $char) !== false) { return true; } } return false; } ``` Vậy là họ đã cấm 2 dấu nháy, việc thoát khỏi câu lệnh SQL này là no hope, và mình cũng không thể stack query được vì câu lệnh được prepare rùi :cry: Sau khi cuộc thi kết thúc, mình đi tìm hiểu cách giải và được giải ngố rằng không cần thiết phải thoát ra ngoài câu lệnh, giờ mình có biến logcode là int, và username là varchar, mình cần phải inject vào biến username vì username cũng được hiện ra ở `history.php` ```php! CREATE TABLE IF NOT EXISTS `logs` ( `id` int(11) PRIMARY KEY NOT NULL AUTO_INCREMENT, `logcode` int(12) NOT NULL, `username` varchar(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; // history.php -> Hiển thị ra cả logcode và username tương ứng $stmt = $conn->prepare($query); $stmt->bind_param("s", $paramValue); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { echo "<table><tr><th>LogCode</th><th>Username</th></tr>"; while ($row = $result->fetch_assoc()) { echo "<tr><td>" . $row["logcode"] . "</td><td>" . $row["username"] . "</td></tr>"; } echo "</table>"; } else { echo "<p>0 log found</p>"; } ``` Vậy làm thế nào để chèn thêm được giá trị username? Mình sẽ lợi dụng việc câu lệnh insert into có thể **chèn nhiều hàng bằng 1 câu lệnh** để có thể inject được 1 hàng riêng cho mình: Ý tưởng là như vậy, nhưng mình cần phải có một tài khoản đúng đã thì mới có thể insert được :)), mình lấy trong src code lun hehe: ![image](https://hackmd.io/_uploads/ryLk3xA8T.png) Mình select 'a' bằng char(97), sau đó vào history.php và phát hiện ra để lấy được logcode theo ý muốn thì cần phải có điều kiện ```php! if (isset($_REQUEST['view_by_logcode']) && isset($_POST['logcode'])) { $view = 1; } $query = "SELECT * FROM logs WHERE "; if (@$view) { $query .= "logcode = ?"; $paramValue = $_REQUEST['logcode']; } else { $query .= "username = ?"; $paramValue = $_SESSION['username']; } ``` Tồn tại biến view_by_logcode ở reuqest và biến logcode ở POST thì mới hiện ra được ;">, nên mình nhét view_by_logcode lên trên URL: ![image](https://hackmd.io/_uploads/BJi83e0Ip.png) Website đã trả về chữ a mà mình muốn rồi Vậy thì giờ select lấy password của admin thui, tài khoản của admin trong db là `aespa` ```php! <?php if ($_SESSION['username'] == 'aespa') echo "<button onclick=\"goTo('/admin.php')\">Admin</button>\n" ?> ``` Mình sẽ biểu diễn về thành dạng char để không phải dùng dấu nháy đơn: `concat(char(97),char(101),char(115),char(112),char(97))` Nhưng khi mình select chay lấy pass thì không thành công, không hiểu lắm nên mình đi lấy length của password trước: ![image](https://hackmd.io/_uploads/HkFVTeCLT.png) Mật khẩu dài tận 110 ký tự thì username chết là đúng rồi, username được tạo mỗi là varchar(50) =)))) ![image](https://hackmd.io/_uploads/H15I6xAIT.png) Vậy thì mình sẽ dùng subtring lấy cứ 50 ký tự 1 lần vậy: ```! 1,(select substring(password,1,50) from users where username=concat(char(97),char(101),char(115),char(112),char(97)))),(2 ``` ![image](https://hackmd.io/_uploads/Skvy0lCUp.png) ![image](https://hackmd.io/_uploads/HJ1lCxCUT.png) ![image](https://hackmd.io/_uploads/BJpEAgAU6.png) ![image](https://hackmd.io/_uploads/B1lv0x0Up.png) Mình có mật khẩu đầy đủ là: `https://youtu.be/ZeerrnuLi5E&SampleTextThatMakeYouNeedToUsingXXXXXXToGetFullPasswordAndMakeYouUseItThr3eT1m3s5`, đăng nhập với tài khoản này là mình có thể vào trang admin rồi: Đến với chức năng cat flag ở admin, mình có được nhập vào biến cmd và website sẽ thực hiện cat ra cho mình, nhưng nó đã được escapeshellcmd, cộng thêm hàm filter kia nữa thì siêu khoai vì mình không chèn thêm được flags hay switch hay dấu gạch chéo được vì bị filter cấm rùi, dĩ nhiên là cũng ko thể cat thằng /flag.txt vì filter chặn chữ `flag` :laughing: ```php! <?php if ($_SERVER['REQUEST_METHOD'] == "POST") { $command = isset($_POST['cmd']) ? escapeshellcmd($_POST['cmd']) : "/hint_to_get_flag.txt"; system("cat " . $command); } ?> ``` Sau đó mình tìm được link [StackOverFlow](https://stackoverflow.com/questions/73485236/php-escapeshellcmd-is-escaping-turkish-language-characters) khá là thú vị ![image](https://hackmd.io/_uploads/HkeSlWAI6.png) escapeshellcmd sẽ loại bỏ những chữ Thổ Nhĩ Kỳ như `Ğ,ğ,Ü,ü,Ş,ş,İ,i,Ö,ö,Ç,ç`, vậy mình có thể chèn chữ này vào giữa chữ flag để bypass được hàm filter: ![image](https://hackmd.io/_uploads/SyddgZ0Lp.png) Flag: **KCSC{multi_ins3rt_4nd_byp4ss_tr1cky_filter}**