本系列文章主旨在于介绍一些漏洞类型产生的基本原理,探索最基础的解决问题的措施,不排除有些语言或者系统提供的安全的API可以更好地更直接地解决问题,也不排除可以严格地输入验证来解决。
根据IT研究与顾问咨询公司Gartner统计数据显示,75%的黑客攻击发生在应用层。而由NIST的统计显示92%的漏洞属于应用层而非网络层,52%的网站应用程序都存在SQL注入、XSS跨站脚本和输入验证问题。注入攻击产生的后果比较严重,轻则导致数据泄露,严重的会导致数据被篡改,根伟严重的可能会导致DOS攻击或者主机被接管。由于注入攻击容易被利用而且导致的后果是非严重,因此,注入攻击已经连续多次排在OWASP TOP 10的第一名,在CWE的排名这种,也一直名列前茅。
OWASP TOP 10对注入攻击的定义:注入攻击漏洞,例如SQL,OS以及LDAP注入。这些攻击发生在当不可信的数据作为命 令或者查询语句的一部分,被发送给解释器的时候。攻击者发送的恶意数据可以欺骗解释器,以执行计划外的命令或者在未被恰当授权时访问数据。首先需要确认一下,SQL注入产生的原因:第一、输入的参数可以被攻击者控制,第二,输入的参数被作为SQL语句的一部分用来组装SQL语句,此两种条件缺一不可。
根据SQL的作用,我把SQL注入攻击分成两种类型,第一种是数据操作注入攻击, 第二种类型是查询注入攻击;。之所以分成这两种,主要还是从产生的原因和预防措施的不同来分别讲解。
第一种,数据操作注入攻击
数据操作攻击主要是就是进行增(insert)删(delete)改(Update)操作时导致的注入攻击。在进行数据操作时,例如修改,一个典型的修改语句如下:
update product set price=9.0 where productid='1'
这里的productid和price的值都是从客户端发过来的,攻击者可以通过各种拦截或者代理工具拦截对值进行修改。如果将prodcutid的值修改为:1‘ or '1'='1 ,再将price的值改为1.0,SQL语句就会变为如下:
update product set price=1.0 where productid='1' or '1'='1'
这样就可以把所有的产品的价格修改,而且修改为更便宜的产品。
之所以,数据操作注入攻击单独列出来,是因为它会很容易地篡改数据。不过,预防的方法也比较简单。 由于增删改数据时,一般不会使用特别复杂的条件语句,一般也不会使用模糊的条件语句,所以预防就很简单,只要把标示字符串的开始和结束的字符单引号(')转义即可。不过,不同类型的数据,转义的方法会不一样。下面表格列出几种常用的数据库和转义方法:
攻击类型 | 危险字符 | 编码方法 | 例子 | 数据库类型 |
SQL注入 | ‘ | 添加一个单引号转义 | 例如:' -> '' | Oracle |
' \ | 添加一个\转义 | 例如:‘ -> \' | MySQL | |
' | 添加一个单引号转义 | 例如:' -> '' | SQL Server |
经过转义处理过的值,再组装SQL语句,就可以保证SQL语句的安全性了。 对于一些数据库,如:Oracle和MySQL,可以通过escape语句定义转义的字符,则需要使用escape语句中的字符进行转义,一般不建议这么用。
第二种,查询注入攻击
顾名思义,查询攻击就是输入的参数作为查询SQL语句的一部分用于查询数据。最经典的查询操作的例子,是根据输入的条件查询符合条件的记录,例如,在产品列表页面根据输入的产品名称查找所有符合产品,可能的SQL语句如下:
精确查询:
select * from product where name = '输入的值'
关于精确查询,注入和典型的注入类似,需要注入攻击向量:name' or '1'='1, 就可以查找到所有产品,也可以通过注入额外的SQL语句进行额外的破坏,不过这样做需要花更多的功夫和时间,即使有了SQLMAP这样的工具。
不过,这种精确查询的预防原理和数据操作注入攻击的原理很像,只要将单引号(')进行转义处理就可以了。
模糊查询:
select * from product where name like '输入的值+%'
模糊查询和精确查询不同的地方就是,除了要处理单引号(')这个特殊字符之外,模糊查询有额外的特殊的字符,例如:
字符 | 描述 |
% | 表示零个或多个字符的任意字符串,例如:LIKE 'AA%',可以匹配AA以及AA开头的字符串。 |
_ | 表示任何单个字符,例如:LIKE'_aa',只匹配包含aa工三个字符字符串,例如:Aaa,1aa。 Oracle也可以使用?表示任何单个字符。 |
[ ] | 表示指定范围,例如: ([a-z]) 是指所有小写字母,或者 集合 [abc])中的任何单个字符,结合LIKE使用 LIKE '[Hh]ello [Ww]orld' |
* | 表示零个或者多个字符,和%的区别是,当需要两端都匹配时,要用%而不能用*,例如:LIKE '%aaaa%',而不能使用'*aaaa*' ,一般都用于查询内容而又不列出具体字段,例如:select * from product。 |
# | 表示单个数字,例如:LIKE ‘a#b’ 匹配 'a1b'. |
{} | 有的数据库使用的是大括号,作用同[] |
针对这些特殊的字符的处理和单引号的处理是不同的,处理的主要方式使用一对括号将特殊字符括起来,例如:查询的字符串中含有%,假设查询字符串是a%b,就可以使用a[%]b,这样查询的内容就会含有'a%b' 而不是a开头和b结尾的所有串。不同的数据库使用不同的括号,统计如下表:
攻击类型 | 危险字符 | 编码方法 | 例子 | 数据库类型 |
SQL注入 | # % _ [ ] | 使用中括号[]转义 | 例如:% -> [%] | MySQL |
# % _ { } | 使用大括号{}转义 | 例如:% ->{%} | Oracle | |
# % _ [ ] | 使用中括号[]转义 | 例如:% -> [%] | SQL Server |
最后,还有一种SQL注入的地方是排序时 group by的字段的排序,一般这里都比较好处理,因为排序的字段都是数据库表的字段,都有固定的值的范围,通过输入验证是比较好的方法。当然,如果客户端传的不是字符串,而是,通过数字对应某个字段,例如:1->name,2->address等,就更容易做输入验证了,只要验证是否是数字或者数字List即可,然后,再映射成具体的字段。关于升序还是降序,那就就只有ASC或者DESC,无论怎么处理都比较方便。
总之,无论何时何地,想要把内容作为SQL的一部分,都不必须小心。即使在使用框架的时候,例如:MyBatis或者iBatis,有些情况下(使用$带入变量而不是使用#时),把输入的内容作为SQL的一部分也还是会有SQL注入。
综上所述,可以了解SQL注入之所以发生的根本原理和最基本的预防原理。 当然,预防的方法有多种,使用编译的SQL、输入验证等,这些预防方法都有一定的缺陷不一定适用所有的情况。 如果掌握了SQL注入的最基本的原理,了解预防发生的基本原理,就可以使用这一招处理所有的SQL注入的情况。
如果有不妥之处,希望可以留言指出。谢谢!
参考:
http://www.owasp.org.cn/owasp-project/OWASPTop102017RC1V1.2.pdf