在学习 session 反序列化之前,我们需要了解这几个参数的含义。
Directive | 含义 |
---|---|
session.save_handler | session保存形式。默认为files |
session.save_path | session保存路径。 |
session.serialize_handler | session序列化存储所用处理器。默认为php |
session.upload_progress.cleanup | 一旦读取了所有POST数据,立即清除进度信息。默认开启 |
session.upload_progress.enabled | 将上传文件的进度信息存在session中。默认开启。 |
在上述的配置中,session.serialize_handler
是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。
处理器名称 | 存储格式 |
---|---|
php | 键名 + 竖线 + 经过serialize() 函数序列化处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize() 函数序列化处理的值 |
php_serialize | 经过serialize()函数序列化处理的数组 |
那么具体而言,在默认配置情况下:
1 |
|
SESSION文件的内容是:name|s:5:"ca01h"
,name是键值,s:5:"ca01h";
是serialize("ca01h")
的结果。
在php_serialize引擎下:
1 |
|
SESSION文件的内容是a:1:{s:4:"name";s:5:"ca01h";}
。a:1
是使用php_serialize进行序列话都会加上。同时使用php_serialize会将session中的key和value都会进行序列化。
在php_binary引擎下:
1 |
|
SESSION文件的内容是names:6:"spoock";
。由于name
的长度是4,4在ASCII表中对应的就是EOT
。根据php_binary的存储规则,最后就是names:6:"spoock";
。
PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:
1 | $_SESSION['ca01h'] = '|O:8:"stdClass":0:{}'; |
上面的 $_SESSION 数据,在存储时使用的序列化处理器为 php_serialize,存储的格式如下:
1 | a:1:{s:5:"ca01h";s:20:"|O:8:"stdClass":0:{}";} |
在读取数据时如果用的反序列化处理器不是 php_serialize,而是 php 的话,那么反序列化后的数据将会变成:
1 |
|
这是因为当使用php引擎的时候,php引擎会以|
作为作为key和value的分隔符,那么就会将a:1:{s:5:"ca01h";s:20:"
作为SESSION的key,将O:8:"stdClass":0:{}
作为value,然后进行反序列化,最后就会得到stdClass这个类。
实际利用的话一般分为两种:
session.auto_start=On
当配置选项 session.auto_start=On,会自动注册 Session 会话(相当于执行了session_start()
),因为该过程是发生在脚本代码执行前,所以在脚本中设定的包括序列化处理器在内的 session 相关配选项的设置是不起作用的。因此一些需要在脚本中设置序列化处理器配置的程序会在 session.auto_start=On 时,销毁自动生成的 Session 会话。然后设置需要的序列化处理器,再调用 session_start() 函数注册会话,这时如果脚本中设置的序列化处理器与 php.ini 中设置的不同,就会出现安全问题。
1 | // foo1.php |
访问http://127.0.0.1/test/serialize/foo1.php?test=|O:8:"stdClass":0:{}
如果在这个session设置成功后,有其他的页面使用这个session,由于处理器的不同,就会导致安全问题。然而PHP自动注册Session会话是在脚本执行前,所以通过该方式只能注入PHP的内置类。
1 |
|
php.ini配置中session_use_trans_sid = 1才能跨页面访问SESSION
session.auto_start=Off
当配置选项 session.auto_start=Off,两个脚本注册 Session 会话时使用的序列化处理器不同,就会出现安全问题,如下面的代码:
1 |
|
1 |
|
题目环境:https://github.com/shimmeris/CTF-Web-Challenges/tree/master/File-Inclusion/XCTF-Final-2018-Bestphp
直接给出了index.php
的源码:
call_user_func()
函数中的两个参数都是可控的,那么就可以回调extract()
函数用户变量覆盖,进而读取本地文件:
http://192.168.153.133:8003/?function=extract&file=php://filter/read=convert.base64-encode/resource=function.php
1 |
|
http://192.168.153.133:8003/?function=extract&file=php://filter/read=convert.base64-encode/resource=admin.php
1 | hello admin |
接下来主要讨论session漏洞利用问题session+lfi,由于代码:
1 | ini_set('open_basedir', '/var/www/html:/tmp'); |
限制了我们无法直接去包含默认的路径:
1 | /var/lib/php/sessions/sess_phpsessid |
常见的Session存储位置:
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/var/lib/php5/sess_PHPSESSID
/var/lib/php5/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
但是因为有变量覆盖因此可以通过session_start()
,改变save_path的方式让session存储路径在open_basedir允许的目录下:
1 | ?function=session_start&save_path=/tmp |
然后去包含:
1 | ?function=extract&file=/tmp/sess_arfguipj1mthu7bkma10j0f5o3 |
这里有一个$_SESSION['name']
,并且其可以被我们post的name复制,那这就可以达到控制session内容的目的。
使用Hackbar直接POST数据:
1 | name=<?=phpinfo();?> |
再去包含对应的session:
1 | ?function=extract&file=/tmp/sess_arfguipj1mthu7bkma10j0f5o3 |
后面找到flag直接cat即可。
介绍另外一种思路:PHP7.0本地文件包含漏洞 包含自身从而导致死循环:https://xz.aliyun.com/t/3174#toc-4
题目地址:http://web.jarvisoj.com:32784
在讲这道题之前,我们先来介绍一下session.upload_progress.enabled
当 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连接在一起的值。
1 |
|
这道题涉及到两个知识点,第一个知识点就是PHP Session 序列化及反序列化处理器设置使用不当。我们可以看到,INI中默认的处理器是php_serialize,而程序使用的却是php处理器。
形成的原理就是在用session.serialize_handler = php_serialize
存储的字符可以引入 | , 再用session.serialize_handler = php
格式取出$_SESSION
的值时, |
会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞。
第二个知识点就是Upload progress in sessions,当一个上传在处理中,同时 post 一个与 ini 设置的 session.upload_progress.name 同名变量时,php 检测到这种 post 请求时就会在 $SESSION 中添加一组数据,所以可通过 session.upload_progress 来设置 session。
首先构造一个上传表单:
1 | <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data"> |
再构造exp:
1 |
|
修改文件内容:
从phpinfo中可以看到网站的根目录在/opt/lampp/htdocs
file_get_contents()读取文件
贴上出题的kaixin师傅的writeup:https://www.mrkaixin.top/posts/df9f633e/#5-0-hackme
没有复现环境,这里就主要说考察到的知识点:
profile.php
中session.serialize_handler
用的是php
,而init.php
中设置的是php_serialize
,这样就可以参考前面提到的PHP Session反序列化漏洞。
这个考点需要另外写一篇整理一下。
Title | Url |
---|---|
带你走进PHP session反序列化漏洞 | https://xz.aliyun.com/t/6640 |
PHP反序列化总结 | https://www.cnblogs.com/tr1ple/p/11156279.html#sna43nW4 |
PHP反序列化入门之session反序列化 | https://mochazz.github.io/2019/01/29/PHP反序列化入门之session反序列化/#PHP的session机制 |
GYCTF Hackme | http://www.pdsdt.lovepdsdt.com/index.php/2020/03/09/187/#Hackme |