SQL注入笔记
前言
把sql注入所学到的东西做个总结吧
产生注入的原理和条件
SQL注入的原理
- 官方点的:
1 |
|
自己的理解:
- SQL注入就是输入的恶意语句拼接到了原有语句中,破坏了原有语句的结构,并且被执行。
SQL注入产生的条件
- 参数可控:即前端传给后端的参数用户可以控制
- 参数带入了数据库并执行:可以简要理解为你输入的恶意语句要被执行
SQL注入分类
按变量类型分
字符型注入
' or 1=1#
当输入参数为字符串时,称为字符型。数字型与字符型注入最大的区别在于:数字型不需要单引号闭合,而字符串类型一般要使用单引号来闭合。
数字型注入
or 1=1
当输入的参数为整型时,如ID、年龄、页码等,如果存在注入漏洞,则可以认为是数字型注入。这种数字型注入最多出现在ASP、PHP等弱类型语言中,弱类型语言会自动推导变量类型,
例如,参数id=8,PHP会自动推导变量id的数据类型为int类型,那么id=8 and 1=1,则会推导为string类型,这是弱类型语言的特性。
而对于Java、C#这类强类型语言,如果试图把一个字符串转换为int类型,则会抛出异常,无法继续执行。所以,强类型的语言很少存在数字型注入漏洞。
搜索型注入:
%xxx% or 1=1 #%’
当在搜索框搜索的时候,称为搜索型。搜索类型一般要使用百分号来闭合。也是字符型的一种
1 |
|
按照http提交方式
- GET型注入
- POST型注入
- Cookie注入
按照注入方式分
报错注入
盲注
时间盲注
bool盲注
联合注入(union)
堆叠注入:有的应用可以加入
;
后一次执行多条语句OOB注入
编码问题
- 宽字节注入
注入常用函数
基础信息函数
基础信息函数 | 功能 |
---|---|
system_user() | 系统用户名 |
user() | 用户名 |
current_user() | 当前用户名 |
session_user() | 连接数据库的用户名 |
database() | 数据库名 |
version() | 数据库版本 |
@@datadir | 数据库路径 |
@@basedir | 数据库安装路径 |
@@version_compile_os | 操作系统 |
count() | 返回执行结果数量 |
sleep() | 不用解释 |
常用处理函数
字符处理函数 | 功能 | 举例 |
---|---|---|
重点 concat() | 没有分隔符地连接字符串 | select concat(c1,c2) from xxx |
重点 concat_ws() | 指定分隔符地连接字符串 | select concat_ws(‘:’,c1,c2) from xxx |
重点 group_concat() | 以逗号分隔某列/组的数据 | select group_concat(c1,c2) from xxx |
load_file() | 读取服务器文件 | select loadfile(‘/tmp/a.txt’) |
into outfile | 写入文件到服务器 | select ‘xxxx’ into outfile ‘/tmp/a.txt’ |
ascii() | 字符串的ASCII代码值 | select ascii(‘a’) |
ord() | 返回字符串第一个字符的ASCII值 | select ord(‘abc’) |
char() | 返回ASCII值对应的字符串 | select char(97) |
mid() | 返回一个字符串的一部分 | select mid(‘abcde’,1,1) |
substr() | 返回一个字符串的一部分 | select substr(‘abcde’,1,1) |
length() | 返回字符串的长度 | select length(‘abc’) |
left() | 返回字符串最左面几个字符 | select left(‘mysql’,2) |
floor() | 返回小于或等于X的最大整数 | select floor(5.1) |
rand() | 返回0-1间的一个随机数 | select rand() |
if() | 三目运算 | select if(1>2,’A’,’B’) |
strcmp() | 比较字符串ASCII大小 | select strcmp(‘c’,’b’) |
ifnull() | 参数1为不null则返回参数1,否则参数2 | select ifnull(null,2) |
常见语言接受参数的函数或变量
1 |
|
一般手工注入流程
注入前准备工作
也就是信息收集,收集网站架构,所使用的语言,数据库类型,以及web中间件
寻找注入点
一般在什么url里如:
1 |
|
还有搜索框、cookie、以及表单提交等任何出现数据交互的地方都有可能出现注入点
判断是否存在注入
一般两种方法:
单引号法:直接在数据或url后面添加
'
,如果页面不能正常显示,浏览器返回一些异常信息, 则说明该链接可能存在注入漏洞。1=1
和1=2
法很多时候检测提交包含引号的链接时,会提示非法字符,或直接不返回任何信息,这种情况下可以如下尝试:
1 |
|
更多方法
- 两者组合如:
1 |
|
更多组合加闭合:
在插入的注入语句后面,增加
--+
或#
,用以注释掉后面的正常语句#
有时会被过滤,因此可以使用url编码,将#表文%23
单引号也有可能会被过滤,可以换成双引号可以在引号后加一两个括号,用以实现可能的语句闭合,如
1 |
|
如果存在注入
- 首先看是否有回显
- 有回显的话判断显示位置和列数
- 通过order by num (num=1,2,3···)当报错时的前一个数就是列数
- 通过 select 1,2,3···来查询显示位置(sql语句中 select 1,2,3这样的语句会直接将1,2,3 输出)
- 无回显可以考虑盲注入、报错注入、
再通过各个注入方式来进行下一步注入
如mysql >= 5.0的联合注入就有
- 0x01 获取字段数
通过order by num (num=1,2,3···)当报错时的前一个数就是列数
0x02 获取系统数据库
select null,null,schema_name from information_schema.schemata
0x03 获取当前数据库
select null,null,database()
0x04 获取数据库表
select null,null,...,group_concat(table_name) from information_schema.tables where table_schema=database()
select null,null,...,table_name from information_schema.tables where table_schema=database() limit 0,1
0x05 获取字段名
select null,null,...,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'
0x06 获取数据
select group_concat(字段名,字段名) from (tableName)
各个注入方式的原理
union注入
所谓联合查询注入即是使用union合并两个或多个SELECT语句的结果集,第二个语句中就包含我们想要查询的语句
注意:union查询的子语句的字段数要与它前面主语句的字段数要要一致
- 如一个sql语句为
1 |
|

当使用union查询时:
1 |
|

所以一般的union注入拼接为
1 |
|
union 操作符一般与 order by 语句配合使用来确定可以查询的字段个数
因为查询的字段不能超过主查询的字段,可以先在 SQL 语句后面加 order by 进行排序,通过报错内容确定到底有几个字段可供使用
1 |
|
时间盲注/BOOL盲注
当我们的注入语句被带入数据库查询,页面确不能回显我们的结果,如应用程序就会返回一个“通用的”的页面或特定的语句,我们不能以此来判断注入是否成功,这种情况下就要用到sql盲注的技巧了。
布尔盲注原理
只返回布尔值的sql注入漏洞,通过构造语句,来判断数据库信息的正确性,再通过页面反回的布尔值,来判断正确与否
布尔盲注方法
1 |
|
时间盲注原理
语句执行后,不回显,不报错,不提示真假的sql注入漏洞。可以通过构造语句,通过条件语句判断,为真则立即执行,否则延时执行
1 |
|
当过滤sleep时还有其他延时方法如:
get_lock()
get_lock(key,time)
,该函数的意思是给一个key
上锁,如果上锁失败就等待time
秒,然后回滚事务。利用方式:先使用
get_lock(key,time)
将key
上锁,然后再开一个进程使用get(key,time)
,这时候就会因为原来的key
已经被上锁了而会等待time
秒benchmark()
benchmark(count,expr)
,该函数会重复计算expr
表达式count
次从而消耗大量时间。if((布尔盲注语句),BENCHMARK(10000000,md5(‘a’)),1)
笛卡尔积
这里的笛卡尔积和离散数学里的笛卡尔积的意思是一样的,因为mysql支持多表查询,笛卡尔积可以将多个表合并成一个表,但是如果多个表中的数据较多就会导致运算过程中耗费大量的时间。if((布尔盲注语句),(select count(*) from information_schema.columns A,information_schema.columns B, information_schema.columns C),1)
RLIKE
语法规则:
A RLIKE B
,表示B是否再A里面。- B中的表达式可以使用标准的正则语法。
利用方式:
if((布尔盲注语句),concat(rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’),rpad(1,999999,’a’)) RLIKE ‘(a.)+(a.)+(a.)+(a.)+(a.)+(a.)+(a.*)+b’,1)
报错注入
当数据库的函数被传入错误参数时,会发生语法错误,正常情况下这类错误不应该回显到前端页面,但当中间件或程序未正常屏蔽时导致错误信息输出到屏幕上,就能用来进行报错注入了。
select/insert/update/delete都可以被用来报错注入
报错注入条件:
- 后台没有屏蔽数据库报错信息,在语法发生错误时会输出在前端
- 运维人员设置中间件的时候,错误输出没有屏蔽
- 程序员写网站代码的时候,没有屏蔽错误信息输出
优缺点:
- 优点是注入速度快
- 缺点是语句较为复杂,而且只能用limit依次进行猜解
总体来说,报错注入其实是一种公式化的注入方法,主要用于在页面中没有显示位,但是用mysql输出了错误信息时使用。
常用函数
floor()
:MYSQL 中用来取整的函数
1 |
|
extractvalue():MYSQL 对 XML 文档数据进行查询的 XPATH 函数
1 |
|
updatexml()
:MYSQL 对 XML 文档数据进行查询和修改的 XPATH 函数
最常用的报错注入函数
1 |
|
updatexml报错实战
select
、delete
语句注入
1 |
|
insert
、update
、注入
增、改同样可以用来进行报错注入,只是需要前后分别用or语句连接,使语句完整,一般需要抓包用bp改参数
1 |
|
head
和cookie
注入
如果代码调用了head或cookie中的信息拿到数据库进行拼接,也可以用报错注入尝试
同样是先用单引号找到注入点,在进行注入
1 |
|
其他常用exp
- floor()和rand()
1 |
|
- 2 extractvalue()
1 |
|
- 3 updatexml()
1 |
|
- 4 geometrycollection()
1 |
|
- 5 multipoint()
1 |
|
- 6 polygon()
1 |
|
- 7 multipolygon()
1 |
|
- 8 linestring()
1 |
|
- 9 multilinestring()
1 |
|
- 10 exp()
1 |
|
其他注入方式
都是面对php代码或配置,对输入的单引号进行转义时,在处理用户输入数据时存在问题,可以绕过转义
宽字节注入
GBK处理编码的过程存在问题,可以构造数据消灭转义符\
宽字节注入原理
在对单双引号进行了转义过滤的情况下,前面的注入方式都不好使,但可以在引号前加上%df
再进行sql注入尝试
它的原理是
\'
编码后的值为%5C%27
- 使用GBK编码数据库时,两个字符为一个汉字
- ASCII码只有128个,大于128的,就会和第二个字符组成一个汉字
- 使用
%df\'
,编码后为%df%5C%27
第一个码大于128,因此会使用前两个字符運
,最后单剩一个引号
即编码后的值为運'
- 然后就可以正常进行sql注入了
宽字节注入方法
黑盒测试的话:在可能的注入点后,键入%df'
后,进行测试
白合测试的话:
- 查看mysql编码是否为GBK
- 是否使用
preg_replace
转换单引号为\'
- 是否使用addslashes进行转义
- 是否使用mysql_real_escape_string进行转义
宽字节注入防范
- 使用utf-8,可以避免宽字节注入
不仅在gbk中,韩文、日文等都是宽字节 - 使用
mysql_real_escape_string
方法转义
需同时设置mysql_set_charset('gbk',$conn)
- 可以设置mysql连接参数:
character_set_clinet=binary
二次编码注入
php代码中用了urldecode()等编码函数,对url中的特殊字符进行编码,可以利用此函数与php自身编码转换,产生漏洞
二次编码注入原理
- 用户输入
id=1%27
,会被php转码为id=1'
- 转义代码发现有单引号,转义为
id=1\'
,无法sql注入 - 用户输入
id=1%2527
,由于%25转码后就是%,因而会转码为id=1%27
- 转义代码没有发现单引号,故不转义
- 但后续urldecode等函数,处理url时,会将
id=1%27
转码为id=1'
,就可以注入
注意:如果做白盒测试,要看urldecode函数 是否在转义方法之后
二次注入
#####二次注入原理
- 插入恶意数据
有些程序在进行数据库插入的时候,仅仅对特殊字符进行了转义,但仍然将数据写入了数据库,如果插入的数据包含恶意内容的话 - 引用恶意数据
在插入到数据库中后,在另外的地方查询该数据的时候,如果没有对取出的数据做校验处理(即认为可信),直接使用该数据,就会造成sql二次注入
二次注入举例
举例1如下:
- 新建用户
admin'#
,有特殊字符,但写入成功,并能使用该用户登录 - 正常修改用户密码时
sql语句如是:update user set password='1234' where username='x' and psssword='xx'
- 但当用户为
admin'#
时
sql语句变为:update user set password='1234' where username='admin'#' and xxxxx
明显井号后的语句都被注释掉了 - 结果就是会修改掉原有用户admin的密码
举例2如下:
- 有两个页面,一个页面写入数据,另一个页面可以有办法查看该数据
- 如果写入时,写入了
xx' union select 1,database(),3
- 如果存在sql注入漏洞,写入的数据应该为
1,库名,3
- 在另一个页面就可以看到该库名数据
二次注入防御
- 对外部提交的数,需要更加谨慎处理,特殊字符不写入
- 程序内部的数据调用,也要进行严格检查,不要认为可信
注入防范方法
解决SQL注入问题的关键是对所有可能来自用户输入的数据进行严格的检查、对数据库配置使用最小权限原则。通常修复使用的方案有:
代码层面:
- 对输入进行严格的转义和过滤
- 使用参数化(Parameterized):目前有很多ORM框架会自动使用参数化解决注入问题,但其也提供了”拼接”的方式,所以使用时需要慎重!
- PDO预处理 (Java、PHP防范推荐方法:)
没有进行PDO预处理的SQL,在输入SQL语句进行执行的时候,web服务器自己拼凑SQL的时候有可能会把危险的SQL语句拼凑进去。但如果进行了PDO预处理的SQL,会让MYSQL自己进行拼凑,就算夹带了危险的SQL语句,也不会进行处理只会当成参数传进去,而不是以拼接进SQL语句传进去,从而防止了SQL注入。
PDO数据库抽象层学习:http://www.php.cn/course/868.html
网络层面:
- 通过WAF设备启用防SQL Inject注入策略(或类似防护系统)
- 云端防护(360网站卫士,阿里云盾等)