session反序列化
先了解什么是session
默认看文章的都有反序列化基础,否则请看[PHP反序列化(一)](https://unjoke.github.io/2024/08/18/PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%EF%BC%88%E4%B8%80%EF%BC%89/)<font style="color:rgb(221, 17, 68);">Session</font>
一般称为“会话控制“,简单来说就是是一种客户与网站/服务器更为安全的对话方式。一旦开启了 <font style="color:rgb(221, 17, 68);">session</font>
会话,便可以在网站的任何页面使用或保持这个会话,从而让访问者与网站之间建立了一种“对话”机制。不同语言的会话机制可能有所不同,这里仅讨论<font style="color:rgb(221, 17, 68);">PHP session</font>
机制。
会话:就是客户端浏览器和服务器的一次数据交互
出现会话的原因:我们知道客户端浏览器访问网站使用的是http(https)协议,http协议是一种无状态的协议,意思就是说不会储存任何东西,每一次的请求都是没有关联的,这样做的好处就是速度快,但是现在就出来了一个问题,比如我们向login.php发送了一个登录请求,并完成了登录,但是由于http的无状态,这个登录只是在login.php上面进行了,但是并没有在index.php上面登录,那我们的登录是没有意义的,所以就产生了cookie,cookie是一个缓存用于一定时间的身份验证,在同一域名下面是全局的,所以说在同一域名下的页面都可以访问到cookie,这样http协议的无状态产生的问题就解决了,但是由于cookie保存在客户端浏览器,这样的话我们就可以去修改cookie,这样的话就很不安全,在这种情况下产生了session,session的本质和cookie一样,但是session保存在服务端。
<font style="color:rgb(221, 17, 68);">PHP session</font>
可以看做是一个特殊的变量,且该变量是用于存储关于用户会话的信息,或者更改用户会话的设置,需要注意的是,<font style="color:rgb(221, 17, 68);">PHP Session</font>
变量存储单一用户的信息,并且对于应用程序中的所有页面都是可用的,且其对应的具体 <font style="color:rgb(221, 17, 68);">session</font>
值会存储于服务器端,这也是与 <font style="color:rgb(221, 17, 68);">cookie</font>
的主要区别,所以<font style="color:rgb(221, 17, 68);">seesion</font>
的安全性相对较高。
session工作原理剖析
session工作机制
当我们开启一个会话时,php会尝试在请求中查找sessio_id,如果在请求中的cookie,GET,POST里面没有找到session_id,这个时候php会调用php_session_create_id函数创造一个新的会话并且在`http response`中通过`set-cookie`头部发送给客户端保存session_start()函数
这里解释一下`session`的创造过程,`session_statrt()`这个函数,这个函数的作用是开启会话,初始化`session`数据1 |
|
刚才说过session
的作用是开启会话,也就是打开session
,也就是说如果我们想要使用session
功能,可以使用session_start
来开启,这个函数既不会成功也不会报错,它的作用是打开Session
,并且随机生成一个32位的session_id,session
的全部机制也是基于这个session_id
,服务器就是通过这个唯一的session_id
来区分出这是哪个用户访问的
session储存
上面说了session_id的产生,下面我们来看一下session的储存,测试payload1 |
|
也是可以看到随机生成了一个session_id
并且被储存在网页的cookie里面,查看目录同样存在
可以看到我们生成的session的储存名称是以sees_+sesion_id组成的
前面我们也说过session会保存在cookie中,那我们是否可以通过修改cookie中的phpsession来修改session_id呢?
尝试一下:
将他修改成woaini
,看看目录是否被修改了
可以看到生成了一个全新的session_id,而不是修改原先文件,现在我们尝试写入新的文件
1 |
|
文件有了内容,而且是序列化的内容,分析一下过程
1 |
|
session在php.ini配置
session的保存位置是由php.ini文件控制的,那我们接下来看一下php.ini中于session有关的配置1 |
|
1 |
|
1 |
|
1 |
|
session.serialize_handler是定义序列化/反序列化的处理器名字,我们可以看到我们测试环境的处理器是php,而在session文件中经过php处理器处理过的以”|”把键名和键值分开了,这就是php处理器的特性,下面我们来看一下序列化/反序列化常用处理器得特性和作用
session.serialize_handler处理器
| 处理器 | 对应储存格式 | | --- | --- | | php | 键名 + 竖线 + 经过 serialize() 函数反序列处理的值 | | php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值 | | php_serialize (php>=5.5.4) | 经过 serialize() 函数反序列处理的数组 |看看别人给出来的三个处理器的区别
php
![](https://raw.githubusercontent.com/unjoke/imjoke/img/1730425834517-8ee55fe3-e546-4108-9f2d-743e643d8df4.png)php_binary
![](https://raw.githubusercontent.com/unjoke/imjoke/img/1730425823856-96487e78-29e8-461a-ac5d-7033d0cce9ab.png)php_serialize
![](https://raw.githubusercontent.com/unjoke/imjoke/img/1730425818362-4e88aa31-fcbf-49f1-b195-1bf8889cffb9.png)session反序列化
重头戏来了!!!!!!session不需要unserialize()就能够进行反序列化,但是究竟是怎么进行反序列化呢?
我们来看一下session_start()函数的官方文档PHP: Hypertext Preprocessor
可以看到官方文档的一句话
1 |
|
展示一下个人理解
1 |
|
既然这样那我们如果把序列化后的内容提前写入到sess文件中,然后刷新页面,就会调用read函数返回现有会话数据,php会把我们之前已经传入的数据进行反序列化操作,这样就会触发反序列化漏洞。
但是现在还有一个问题要解决,因为我们传入的是键值对,那么session
序列化存储所用的处理器肯定也是将这个键值对写了进去,怎么才能让它正好反序列化到我们传入的内容。
这里就要用到我们上面介绍到的不同序列化处理器的特性,我们可以在我们传入的序列化内容前面加一个|,在php_serialize
处理后会返回一个序列化后的数组,但是在使用php处理器会以竖线|作为一个分隔符,前面的为键名,后面的为键值,然后将键值进行反序列化操作,这样就能够实现我们session反序列化操作
看看本地测试
漏洞页面1 |
|
session传参页面
1 |
|
传输?unjoke=O:6:"unjoke":1:{s:4:"code";s:10:"phpinfo();";}
|之前的会被认定为键名,|之后会被认定为键值,所以unjoke
被当成了一个字符,并不会触发反序列化
传入?unjoke=|O:6:"unjoke":1:{s:4:"code";s:10:"phpinfo();";}
这个时候键名不再是unjoke
了,而是a:1:{s:6:"unjoke";s:45:"
,回到漏洞触发的代码块,可以看到反序列化漏洞成功触发
题目试试水
bestphp's revenge
进来就是代码审计,注释版本1 |
|
存在flag.php,访问flag.php
1 |
|
思路梳理
1 |
|
由于SoapClient
类的__call
方法,当 __call
方法被触发后,它可以发送 HTTP 和 HTTPS 请求,我们就可以利用这个类来进行ssrf(结合CRLF注入),伪造脚本如下
1 |
|
这里一般情况下用ini_set
的,但是ini_set
不能识别数组,所以换成了session_start
,然后POST传入serialize_handler=php_serialize
接下来就是要想办法引用SoapClient
类的__call
方法了,这个我们需要调用$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
,这个写法可以调用类里面的方法,可以学习一下,但是现在$b = 'implode';
所以我们需要将$b
覆盖,使用函数<font style="color:#000000;">extract()</font>
并没有得到预期的结果,放到本地发现是无法动态调用extract
,推测题目也是因为这个,如果动态调用了只要把cookie
修改成伪造的cookie
就可以得到flag
参考文章
1 |
|