在做题中,遇见了不止一次反序列化相关的内容,在这里做一个总结。
serialize / unserialize
编程语言一般都会提供序列化和反序列化的功能,从而使得对象和字符串进行相互转换,从而完成持久化,PHP提供了serialize
和unserialize
,python提供了pickle.dumps()
和pickle.loads()
,以PHP为例:
|
|
运行后可以看到结果:
|
|
仔细的人会发现,itemdate
部分为什么长度是10,不应该是8吗?,这一点非常坑,我们把它用文件输出然后拖到hex里面看看,可以发现,其类名和变量名之前都添加了%00
字符,而在终端中是没办法赋值%00
字符的,导致问题的出现,所以对于反序列化的操作,最好放到文件中,不要从终端直接操作,如果谁有可以复制的,请mail推荐给我! iterm2
好像不行:
当然,支持多种类型的PHP变量integers / floats / boolean/ strings / array / objects
具体的字段含义如下:
|
|
要从字符串中反序列化得到object
只需要调用unserialize($str)
即可。
反序列化漏洞一般是对输入进行反序列化$obj=unserialize($_GET['object'])
,但仅仅将字符串反序列化为对象并没有什么用,还需要利用魔术方法或者其他敏感函数进行恶意操作
__sleep
& __wakeup
两个魔术方法,当一个对象被序列化时,PHP会调用__sleep
方法(如果有的话),这个功能可以用于清理对象,并返回一个包含对象中变量名称的数组,例如下面例子,我们在序列化的过程中,清楚掉$id
字段内容:
|
|
而同理,__wakeup
用于反序列化的相关操作,比如数据库重新连接,变量初始化等内容。
|
|
可以看到,反序列化时,调用了__wakeup
函数进行了变量赋值。
利用其他程序逻辑
比如,以下代码:
|
|
在原始逻辑中对反序列化的对象进行了某些操作,然后造成可以利用。这种方式不仅限于魔术方法,还包括正常逻辑的函数。对于__toString()
来说,不只只有echo $obj
的时候才会触发,其他的例如字符串拼接、格式化字符串、字符串比较、in_array()
等等,当然在针对对象的操作中,例如class_exists()
、对象创建、对象执行、对象销毁都可能触发。
CVE-2016-7124
__wakeup
一般用于数据反序列化的清理,CVE-2016-7124
发现,在PHP低版本中,当输入的反序列化的对象个数大于真实的个数时,会使得__wakeup
函数失效从而可以绕过,同时对象被销毁,执行__destruct().所以攻击者可以绕过__wakeup
调用精心设计的__destruct()
方法
影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
首先看正常的反序列化内容结果:
|
|
输出结果为:
|
|
我们可以看到__wakeup()
调用的时候,对象已经被创建成功,然后对name
字段进行清理,然后调用var_dump
发现反序列化后的对象数据确实干净,最终程序执行完成退出时,调用__destruct()
销毁对象。
我们将file.txt
文件内容改为O:4:"User":3:{s:3:"age";N;s:4:"name";s:4:"test";}
(属性个数2改为了3),我们看在不同版本php下的结果:
|
|
可以看到 绕过了__wakeup()
的清理,并执行了__destruct()
方法
SugarCRM v6.5.23 PHP反序列化漏洞
由于sugarCRM这个开源项目已经被删除了,所以没办法复现了…尴尬 那么就根据文章来学习一下好了
参考文章为这篇:SugarCRM v6.5.23 PHP反序列化 对象注入漏洞
具体的写的非常清晰,总结一下:
主要利用的是被反序列化字符串过滤正则的不完善,(其本身意图想过滤所有对象类型,但o:4->o:+4
,使用加号可以绕过)和php低版本__wakeup()
的绕过,最终寻找到一个__destruct()
方法调用了写入文件,从而可以将payload恶意内容写入,使用的是SugarCacheFile
对象
观察其补丁:
|
|
更改了正则表达式,无法反序列化对象内容
HITCON 2016 web 审计
一道源码审计,基于注入和反序列化,源码如下:
|
|
好吧,代码有点长,我们一点点分析。
看完代码,发现flag
在login
函数中,得到flag
的方式,需要使得login
方法中$obj
不为空且为admin
,但上面由于:
|
|
看起来是矛盾的,这里发现php字符编码不是UTF-8,可以用Ą、Ã
绕过。为了获得orange
密码,可以发现在show
函数中存在注入点,$username
没有过滤,而在__wakeup()
中对字符进行了转义,可以使用之前CVE的方式使其失效,然后刚好可以利用__destruct()
进行执行。这样就得出了整道题的思路。
session反序列化相关
关于php session的资料网上有很多,可以去搜一下,这里总结一下相关字段的含义:
字段 | 含义 | 常见值 | 额外说明 |
---|---|---|---|
Registered save handlers |
支持的session存储类型 | files user sqlite memcache |
可保存为文件、用户定义、数据库或memcached (在内存中存储seesion) |
Registered serializer handlers |
支持的seesion序列环类型 | php php_binary wddx |
php 类型格式为:键值+|+序列化内容php_serialize 格式为:序列化内容 |
session.auto_start |
是否自动创建session | on/off | on的时候不需要调用session_start() ,自动添加 |
session.cache_expire |
当前缓存的到期时间 | 180分钟 | |
session.cache_limiter |
缓存限制器 | public 、private 、private_no_expire 、nocache |
|
session.cookie_domain |
cookie支持的域名 | ||
session.cookie_httponly |
cookie httponly | ||
session.cookie_path |
cookie存储的相对路径 | / |
和session.save_path 合用 |
session.cookie_secure |
是否只有https的时候才加上cookie | ||
session.entropy_file |
设定session值的文件 | ||
session.entropy_length |
session值长度 | ||
session.gc_divisor |
设置进程比率 | 1000 | |
session.gc_maxlifetime |
(垃圾收集)被处理前的生存期 | 1440秒 | |
session.gc_probability |
垃圾收集的处理几率 | 1 | 默认千分之一的比率回收,概率=session.gc_probability/session.gc_divisor |
session.hash_bits_per_character |
定义当转换2进制hash数据为一些可读的数据时,每个字符存储多少个比特 | 5 | 5 比特: 0-9, a-v |
session.hash_function |
选择一个HASH函数,0为MD5(128比特强度),1为SHA-1(160比特强度) | 0 | 默认MD5 |
session.name |
session名称 | PHPSESSID | 默认PHPSESSID |
session.referer_check |
包含有用来检查每个 HTTP Referer 的子串 | no value | 如果客户端发送了 Referer 信息但是在其中并未找到该子串,则嵌入的会话ID 会被标记为无效。默认为空字符串 |
session.save_handler |
保存session的类型 | files | 用文件存储session |
session.save_path |
session存储绝对路径 | C:\phpStudy\PHPTutorial\tmp\tmp | |
session.serialize_handler |
使用的seesion序列化引擎 | php | |
session.upload_progress.cleanup |
是否在上传完成后及时删除进度数据 | On | 上传完毕后删除 |
session.upload_progress.enabled |
选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态 | On | |
session.upload_progress.freq |
选项控制了上传进度信息应该多久被重新计算一次 | ||
session.upload_progress.min_freq |
类似同上 | ||
session.upload_progress.name |
PHP_SESSION_UPLOAD_PROGRESS |
||
session.upload_progress.prefix |
upload_progress_ |
||
session.use_cookies |
On | ||
session.use_only_cookies |
On | ||
session.use_trans_sid |
0 |
相关安全隐患
#session.serialize_handler
当反序列化的数据与反序列化处理器不同时,通过特殊构造,可以伪造数据:
由于php handler
和php_serialize handler
的结构不同,若将
|
|
注意,php 5.4之后 默认session.serialize_handler
为php_serialize
#upload_progress
当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。
当一个上传在处理中,同时POST一个与INI中设置的
session.upload_progress.name
同名变量时,上传进度可以在$_SESSION
中获得。 当PHP检测到这种POST请求时,它会在$_SESSION
中添加一组数据, 索引是session.upload_progress.prefix
与session.upload_progress.name
连接在一起的值。
根据说明,我们可以得出:
当upload_progress.enabled
开启时,当POST一个session.upload_progress.name
同名变量时,会向$_SESSION
的session.upload_progress.prefix
. session.upload_progress.name
连接在一起的字段中写入内容
可以从一道题中,看出其利用方式,题目在这里http://web.jarvisoj.com:32784/
|
|
根据代码结构可以想到是一个反序列化的题目,应该是想办法创建OowoO对象,等到对象销毁时执行eval命令得到flag。由于没有unserialize
函数,没有明显的写入点。这时,upload_progress
就有了作用。
整体思想就是:利用upload_progress
上传一个名字为PHP_SESSION_UPLOAD_PROGRESS
的字段,然后利用session.serialize_handler
的不一致,由于其会向$_SESSION
中upload_progress_PHP_SESSION_UPLOAD_PROGRESS
字段写入内容,其必然会调用php 反序列化处理器,我们向其中构造一个OowoO对象,mdzz
字段为要执行的代码,当程序运行完,执行析构函数时,便会执行我们构造的恶意代码。
所以可以构造表单:
|
|
然后修改其值: