Try   HackMD

1. WarmupPHP 01

Đề bài

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Có được source code của chall bằng /?source:
Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

<?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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Đ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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Với tài liệu trên, mình exploit bằng: /?name={$smarty.const.FLAG1} -> solved
Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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
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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Để 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 debugsymlink 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
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Mình thử đọc /etc/passwd trước đã:
Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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 Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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:

$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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

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
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

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
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 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
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

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
image

image

image

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
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

<?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 khá là thú vị

image
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

Flag: KCSC{multi_ins3rt_4nd_byp4ss_tr1cky_filter}