SQL防注入:使用base64构建安全查询

Ver 2013.8.30


这几天看了一些关于SQL注入和防注入的文章

谈SQL注入的:

http://www.cnblogs.com/hkncd/archive/2012/03/31/2426274.html

谈防注入的:

http://cyrilwang.blogspot.com/2010/10/sql-injection-no.html

http://www.uml.org.cn/safe/201303045.asp

SQL注入,主要是通过加入特殊字符,使用特殊编码将非法SQL语句注入到查询中,并得到返回数据

看到了一些防SQL注入的手段,自己也经过思考总结了一下,得到了一个流程:

1.查询前强制转换输入

对于提交的输入:

特定类型int, date等:进行强制类型转换,如intval, date_create;

字符串一律使用mysql_real_escape_string过滤

有格式的使用正则表达式进行验证

对于不需要模糊查询的字段,建议使用base64编码后再进一步处理并存入数据库,下文详述

2.使用预定义查询,不使用动态查询

预定义查询可以通过重用参数和执行计划来破坏SQL注入。不过它的深层次原理我还是没太明白。。。以后有空再研究下。。。关键问题是,参数在进行查询时是不是参与了SQL语义的识别,参数是否二进制安全。

3.查询后关闭错误输出

关闭错误输出,即使注入成功也不输出错误信息,让注入得不到结果。

4.针对不同查询,使用不同权限的SQL用户

最小化权限,使查询只能针对指定的表,指定的操作(select, update, insert, delete, alter)。防止注入后SQL代码获取权限之外的数据。这一点估计很少有注意的,甚至很多系统都用dbo甚至管理员权限的账户来进行数据库操作。

5.针对关键操作添加触发器记录日志

对于关键表关键字段的操作,添加触发器,并在日志中加以记录,做到被SQL注入后也可以追踪。亡羊补牢,为时未晚。

6.使用SQL语义安全的编码处理字符串

做到了上面五步,在整个查询过程中,就像加了5道牢固的锁,已经非常安全了。估计全世界没有几个人还能用SQL注入方式入侵这样的数据库拿到数据。不过,还是有SQL注入风险存在的,以php+mysql为例讲,主要问题出在经过mysql_real_escape_string过滤的字符串上。因为输入经过过滤,并不能100%保证过滤后的安全性,虽然出问题的概率很小,但在根本上还是有风险的而对于经过intval或者date强制转换的字段,则在语义上保证了安全性。使用强制转换的数据再进行查询,可以完全避免SQL注入问题。

针对这个问题,我想到了一个使用base64编码不需要模糊查询字段再进行处理的方法。

例如登录时验证用户名密码这个注入的重灾区,这样处理:

接下来通过SQL查询验证的步骤完全一样。

那么为什么通过base64编码方式来避免SQL注入?

因为base64编码使用的字符包括大小写字母各26个,加上10个数字,和加号“+”,斜杠“/”,一共64个字符,等号“=”用来作为后缀用途。不管你输入任何内容,包括注入非法的字符,都会被转化成上述这些对SQL语义绝对不会产生任何影响的安全字符。

看一个例子,如果在username里输入”or 1=1″会变成什么?结果是”b3IgMT0x”。这样在查询用户名时,不管输入了任何非法字符,经过base64都会变成对SQL语义不产生影响的安全字符,再经过查询或者进一步处理就十分安全了。而密码经过了加盐和md5处理,也会产生安全字符,所以查询起来也是安全的。

那我们可以不可以把所有内容都使用base64编码呢?答案是否定的。因为base64编码后的内容难以进行模糊查询,这跟base64使用4字节表示3字节内容导致的字节无法对齐有关。所以,base64可以用来处理长度不是很长的不需要模糊查询的字符串字段。例如用户名。为什么建议存储长度不长的内容呢?因为使用base64存储,占空间一般也会增长40%左右。

那么有没有一劳永逸既从根本上避免字符串的SQL注入(使用安全字符)又能进行模糊查询的方法呢?

我想了好久发现只有使用16进制编码最合适了,因为能让字符串每个字节对齐的最高效的产生安全编码的方式,只有使用a-e和0-9进行16进制编码了,不过代价高昂,需要多使用一倍的存储空间。

根据utf8编码的定义,如果使用汉字字符和全角标点,每个字的编码长度正好是3字节。所以,针对只有全角字符的字符串,就可以用base64编码来达到既可以防止SQL注入又能模糊查询的目的了,而且空间开销增加也不是很大。因为每个字都会转换成对应的4字节base64代码,查询时每4字节对齐查询即可。

防止SQL注入和处理很多其他安全问题一样,最根本的一条,永远不要相信从用户端提交的数据是正确的,永远都要验证用户提交的数据。最根本的验证不是模式匹配或者过滤,而是强制类型转换。做好这一点,网站安全性就进了一大步。

Ver 2018.5.27


今天突然想起来这篇文章,5年前自己的思考确实是有问题的。文中通过base64的方式,解决的问题是防止注入,也就是防止SQL语句被破坏,但是为了保障安全,最终需求是否是为了保障不被注入呢? 其实不是的,最终是为了保障页面运行安全,一般通过注入目的都是显示一些数据到页面上,从而在浏览器端拿到许可之外的用户数据。在针对这一点上,base64化起不到任何效果,因为显示到页面上的东西就是注入的内容,如果存储到数据库时没有做合理的过滤,仍然存在风险,并且base64化后反而对于敏感字的检测带来极大的难度,所以这并不是一个合理的方案。现在考虑,做任何防护都是在成本和抗攻击程度之间做一个平衡,很多时候简单的增加一点点攻击成本,就能阻挡住绝大多数攻击。根据防护要求,选取合适的方案才是合理的。如果真的被盯上了,一定有一万种方法入侵,没有被入侵只是你没有被入侵的价值。不过站在现在的角度看之前的思考,还是蛮有趣的~