最近在编程中遇到一个SQL注入防护的问题。在这里顺便把SQL注入小结一下。以MYSQL(5.1)为例。
一、常规的SQL语句
观察SQL语句
SELECT * FROM [tableName] WHERE col1='value1'
这个语句由3个部分组成,SELECT子句筛选得到结果,FROM子句指定了筛选的范围,WHERE子句指定了条件。当在其中进行如下置换:
SELECT * FROM [tableName] WHERE 1
之后,选出了[tableName]表中的全部内容。分析其语法不难发现,WHERE将其后的真值表达式求值,然后作为筛选的条件。
二、WHERE子句的理解
现在(在MYSQL5.1)中测试以下语句
SELECT * FROM [tableName] WHERE false or 1=‘1’ and '1'='1' and "1"="1"
同样得到了[tableName]表种的全部内容。
这个测试说明了两点:
1、WHERE对后面的表达式进行真值计算
2、不带符号、‘与"都可以通过真值测试,可能不是个别特殊符号的问题
三、WHERE子句在编程中的使用
通常是采用字符串拼接的方式來构建WHERE子句。一般有:
SELECT * + FROM [tableName] + WHERE col1=' + value1 + '
其中的value1常常是由输入所得。然而当WHERE子句具备(二)中的某种特征时就可能存在意外了。如果只是按照拼接來构建WHERE子句的话,那么WHERE子句可能会演化成这样的形式:
SELECT * FROM [tableName] WHERE col1='badvalue' or 1='1'
也就是说,用户熟悉SQL语句并精心构造了一个包含SQL特殊字符的输入{badvalue‘
or 1=‘1},这时候会返回整张表。
当程序员采用"工作时,类似也有:
SELECT * FROM [tableName] WHERE col1="badvalue" or 1="1"
中的{badvalue" or 1="1}。
总之,一旦程序员采用直接将用户输入拼接进SQL语句时就存在偷换WHERE真值的风险。像这样,用户通过简单地改变终端输入而改变了应用逻辑的情况,叫做注入。
四、更加奔放的情况
观察上面大括号里面的内容,在MYSQL(5.1)中还存在这样的情况:
SELECT * FROM [tableName] WHERE col1='badvalue' ; select * from [another] where 1='1'
也就是说,填入{badvalue‘ ; select * from [another] where 1=‘1}。完全可以把填入括号中的select换成drop、update等等。起作用的前提是程序员不作任何处理地将用户输入接入了SQL查询语句中。
可以做一个简单的结论,SQL特殊字符在概念上是嵌套的,然而由于在处理中使用了直接拼接的方式,使得用户参与了SQL语句的编写,输入特殊字符让SQL截短解析,最终导致意料以外的后果。
五、解决问题的现状
完全没有必要搞得高深莫测。但是解决的方法通常是会引起争论的。
我用的最久的语言是C#。因为某种二缺的原因,我第一门认真学习的语言是C#。在这里给出一个C#的解决方法吧。
六、解决方法构思与部署
主要是要隔离开用户输入对“构建”SQL语句的参与权。用户输入只能作为数据提供。只要满足这个原则,WHERE子句的组装还是比较清晰的。
我们可以尽量把非用户输入的部分写完整,然后把用户输入的特殊字符全部转义。这样,就有了示意的Where函数
/** * affirmNodePair 示例: * {"or col1=","value1"} * {"and col2>","value2"} * * / public string Where (Dictionary<string,string> affirmNodePair) { StringBuilder builder=new StringBuilder(); // 预处理 builder.Append(" where 1 "); foreach (var item in affirmNodePair) { builder.Append(item.Key); builder.Append("'"); //过滤用户输入:将用户输入的特殊字符转义 foreach (var c in item.Value) { switch (c) { case '\'': builder.Append(@"\"); break; case '\"': builder.Append(@"\"); break; case ';': builder.Append(@"\"); break; default: break; } builder.Append(c); } builder.Append("'"); builder.Append(" "); } return builder.ToString(); }
这样形成的Where子句类似于:
Where 1 or col1='value1' and col2>'value2'
当填入{badvalue‘ or 1=‘1}后得到转义后的结果
Where 1 or col1='badvalue\' or 1=\'1'
在我测试使用的MYSQL(5.1)中能够符合预期地报错。具体的部署要结合实际情况,限于工作条件,这里只提供了一个基础性思路。谢谢大家耐心看完。