PHP反序列化命令执行
1、 序列化与反序列化原理
序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。序列化是一种将对象的状态信息转换为可以存储或传输的形式的过程(转化为信息流)。在序列化期间,对象将其当前状态写入到临时或持久性存储区以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化。
序列化与反序列化为数据交换提供了可能,但是因为传递的是字节码,可读性差。在应用层开发过程中不易调试,为了解决这种问题,最 直接的想法就是将对象的内容转换为字符串的形式进行传递。具体的传输格式可自行定义,但自定义格式有一个很大的问题——兼容性,如果引入其他系统的模块,就需要对数据格式进行转换,维护其他的系统时,还要先了解一下它的序列化方式。为了统一数据传输的格式,出现了几种数据交换协议,如:JSON, Protobuf,XML。这些数据交换协议可视为是应用层面的序列化/反序列化。
2、 序列化与反序列化用法
JSON中的元素都是键值对——key:value形式,键值对之间以":“分隔,每个键需用双引号引起来,值的类型为String时也需要双引号。其中value的类型包括:对象,数组,值,每种类型具有不同的语法表示。json数据中地值(value)可以是双引号括起来的字符串(string)、数值(number)、true、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。对象是一个无序的键值对集合。以”{“开始,以”}“结束, 每个成员以”,"分隔。 首先我们需要创建类,随之才能对类进行序列化。创建类写法如下:
Class类名{
变量1 = 参数1;
变量2 = 参数2;
变量3 = 参数3;
方法1(){}
方法2(){}
}
实例如下:
<?Php
Class student{
Public $name = ‘xiaoming’
Public $name = ‘20’
Public $score = ‘60’
}
?>
上图定义了一个类student,该类含有三个变量:姓名、岁数和分数。无需对变量进行数据类型定义,直接进行赋值。不同变量之间用;分号隔开。 而对类进行序列化后输出的结果格式如下图,类含有的变类型要进行缩写,string字符型用s表示,Int数值型用i表示。而关于序列符号,参数与变量之间用;分号隔开,同一变量和同一参数之间用:冒号隔开。对象中只有变量,方法不会体现在序列化后的对象中:
O:对象名长度:“对象名”:对象成员变量个数:{变量1类型:变量名1长度:"变量名";参数1类型:参数1长度:"参数1";变量2类型:变量名2长度:“变量名2”;参数2类型:参数2长度:"参数2";... .. .}
通过serialize()函数对之前创建的student类进行序列化后输出结果如下:
O:7:"student":3:{s:4:"name";s:8:"xiaoming";s:3:"age"; :20;s:5:"score";i:60;}
序列化的原本意图是希望对一个Java对象作一下“变换”,变成字节序列,这样一来方便持久化存储到磁盘,避免程序运行结束后对象就从内存里消失,另外变换成字节序列也更便于网络运输和传播,所以概念上很好理解。而反序列化,则是把字节序列恢复为原先的Java对象。下面一张图能够帮助大家理解: 实际中我们可以通过php自带函数中的serialize()和unserialize()轻易实现序列化和反序列化。
序列化:serialize()
使用函数serialize()序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
反序列化:unserialize()
使用unserialize()函数对已序列化的变量进行操作,重新生成一个对象。
一起来分析下面这个完整案例从而了解下这序列化和反序列化这两个过程的写法和效果: 首先,创建示例类student,并输出该类序列化的结果: 对应上述代码的输出结果如下: 对上例的student序列化后的数据通过unserialize()进行反序列化: 输出反序列化结果如下:
Class student #2(3){
Public $name => string(8) “xiaoming”
Public $age => int(20)
Public $score => int(60)
}
从上述序列化和反序列化两个输出结果对比发现,序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。所以对象A和对象B序列化后并没有什么区别。unserialize()函数能够重新把字符串变回php原来的值。再多给几个例子对比: 序列化目的: 1、以某种存储形式使自定义对象持久化 比如说刚才的类里面保存着学生的个人信息,我想要将这些信息保存起来,以后可以随时取出来用,就可以采用序列化。将一个Word保存为docx,这就是序列化的过程。打开docx文档,显示内容,就是反序列化的过程。 2、将对象从一个地方传递到另一个地方 比如说在进行网络传输的过程中,发送数据和接收数据都是通过byte[]来获取的时候,如果你要发送一个对象,需要进行序列化。 然后接受数据的时候进行反序列化,还原对象。 3、使程序更具维护性 序列化可以将内存中的对象保存到本地文件和数据库中,大大提高了代码的可维护性。
3、 反序列化漏洞
漏洞的根源在于unserialize()函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码有能够被我们控制,漏洞就这样产生了,根据不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。
3.1 魔术方法
PHP的类中可能会包含一些特殊的函数叫魔术函数,魔术函数命名是以符号__开头的。这些函数在某些情况下会自动调用,其中,反序列化漏洞主要由以下面魔术方法造成:
__construct():在对象创建时自动被调用;
__sleep():在对象序列化的时候自动被调用;
__destruct():在脚本运行结束时自动被调用;
__wakeup():在反序列化为对象时自动被调用;
__toString(): 直接输出对象引用时自动被调用。
接下来,详细的展示下上述几种常见的魔术方法和他们的调用场景。
__sleep():在对象序列化的时候自动被调用
某类中存在__sleep()函数,当所处类被进行序列化成为对象时,__sleep()函数就会被自带调用,从而执行__sleep()函数中的语句。
__sleep()函数中的echo语句在serialize($student)时被执行了从而打印在页面上:
__wakeup():在反序列化为对象时被调用
某类中存在__wakeup()函数,当所处类被反序列化时,__wakeup()函数就会被自带调用,从而执行__wakeup()函数中的语句。
__wakeup()函数中的echo语句在unserialize($a)时被执行了从而打印在页面上:
__destruct():在对象销毁前被调用
某类中存在__destruct()函数,当对象销毁前,__destruct()函数就会被自带调用,从而执行__destruct()函数中的语句。每个对象在所有代码执行结束时都会进行销毁。
__destruct()函数中的echo语句在所有代码执行结束后被执行了从而打印在页面上:
__toString():直接输出对象引用时自动被调用
某类中存在__toString()函数,当对象被直接输出时,__toString()函数就会被自带调用,从而执行__toString()函数中的语句。
__toString()函数中的echo语句在echo $student对象直接被输出时被执行了从而打印在页面上:
3.2 反序列化漏洞利用
上面介绍了多种魔术方法,具体反序列化漏洞的利用方式我们通过分析实际案例来掌握。
案例1
漏洞举例:反序列化导致任意文件读取。 上图是源码本意是输出错误日志代码,读取error.log文件内容。FileClass类中有魔术方法__toString(),方法中的语句是利用file_get_content()函数读取变量filename中的内容。由此可见,如果我们可以控制变量filename的值从而传入任意文件名,就可以读取任意文件内容。重点在后面的
o
b
j
=
u
n
s
e
r
i
a
l
i
z
e
(
obj=unserialize(
obj=unserialize(_GET[‘file’]),既然这边存在反序列化函数,并随之echo $obj语句执行时就会自动调用__toString()函数,那么我们便可以手动构造一个序列化的对象FileClass修改该对象中的filename变量值为目标文件名,将构造好的对象放入URL通过GET方式提交表单,从而在其对象进行反序列化后实现指定文件读取。 以读取当前目录下的1.txt文件为例构造对象:
Class FileClass{
Public $filename = ‘1.txt’;
}
构造对象时无需声明方法,只需要声明变量及变量值即可。上图进行序列化后获得对象:
O:9:“FileClass”:1:{s:8:”filename”;s:5:”1.txt”}
获得对象后将其作为file参数值添加到URL中:
http://127.0.0.1/unserialize. php?file=
0:9:"FileClass" :1:{s:8:"filename";s:5:"1.txt";}
成果展示,成功读取当前目录下的1.txt:
案例2
反序列化漏洞的根源在于unserialize()函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码有能够被我们控制,漏洞就这样产生了,根据不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。漏洞代码如下: 上图中漏洞代码使用了php的魔术方法__destruct(),而destruct()是当一个对象被销毁时被自动调用的析构方法。然后unserialize()中参数可控,这样我们就可以构造一个序列化的对象A来控制其中的变量a的值,最终会产生漏洞。 以上边的漏洞代码为例,我们需要构造一个序列化的对象A并给其中变量a赋值为<?php phpinfo();?> 如下: 这个时候,就会向hello.php文件中写入<?php phpinfo();?>: 通过这个思路还可以写入一句话木马然后实现远程连接get webshell。
命令执行漏洞防范
1、尽量少用执行命令的函数或者直接禁用; system()、assert()、shell_exec()、passthru()等命令执行函数。 2、尽量不要执行外部命令; 尽量使用自定义函数或函数库实现外部应用程序或命令的功能。在执行system、eval等命令执行功能的函数前,要确认参数内容。 3、使用自定义函数或者函数库来代替外部命令的功能; 4、参数值尽量使用引号包括,并在拼接前调用addslashes函数进行转义; 5、在使用动态函数之前,确保使用的函数是指定的函数之一; 6、在进入执行命令的函数方法之前,对参数进行过滤,对敏感字符进行转义; 7、使用safe_mode_exec_dir执行可执行的文件路径; 将php.ini文件中的safe_mode设置为On,然后将允许执行的文件放入一个目录,并使用safe_mode_exec_dir指定这个可执行的文件路径。这样,在需要执行相应的外部程序时,程序必须在safe_mode_exec_dir指定的目录中才会允许执行,否则执行将失败。 8、对于可控点是程序参数的情况下,使用escapeshellcmd函数进行过滤,对于可控点是程序参数值的情况下,使用escapeshellarg函数进行过滤。escapeshellarg函数会将用户引起参数或命令结束的字符进行转义,如单引号“’”会被转义为“’”,双引号““”会被转义为“””,分号“;”会被转义为“;”,这样escapeshellarg会将参数内容限制在一对单引号或双引号里面,转义参数中包括的单引号或双引号,使其无法对当前执行进行截断,实现防范命令注入攻击的目的。
|