SQL注入之防御篇(一)
防御的基本原则是「外部数据不可信任」,其实在Web安全领域的各种攻击方式中,大多数都是因为开发者违反了这个原则而导致的, 所以保持“数据与代码分离”是原则性的解决方案。
一、检查数据类型
如果你的SQL语句是类似where id={$id}
这种形式,数据库里所有的id都是数字,那么就应该在SQL被执行前,检查确保变量id是int类型;如果是接受邮箱,那就应该检查并严格确保变量一定是邮箱的格式。其他的类型比如日期、时间等也是一个道理,必须严格按照相关格式。总结起来:只要是有固定格式的变量,在SQL语句执行前,应该严格按照固定格式去检查,确保变量是我们预想的格式,这样很大程度上可以避免SQL注入攻击。
2、过滤特殊符号
对于无法确定固定格式的变量,一定要进行特殊符号过滤或转义处理。以PHP为例,通常是采用addslashes
函数,它会在指定的预定义字符前添加反斜杠转义,这些预定义的字符是:单引号 (') 双引号 (") 反斜杠 (\) NULL
。有时候不使用空格也会注入成功,比如:
eg1: SELECT/**/username/**/from/**/users eg2: SELECT(username)from(users)
有些时候,不需要括号引号也可以:
SELECT passwd from users where user=0x61646D696E /*0x61646D696E是“admin”的十六进制*/
对于PHP程序+MySQL构架的程序,在动态的SQL语句中,使用单引号把变量包含起来配合addslashes
函数是应对SQL注入攻击的有效手段。
3、使用预编译语句
前面说了,SQL之所以能被注入,最主要的原因就是它的数据和代码(指令)是混合的。
其实我们想一下,C程序为什么从来没听说过注入这种说法,有的也是溢出。这是因为C是一种编译型语言,你没法在语义上欺骗它,语义解析这步提前做了,都生成二进制了。所以攻击C的方式大多是溢出,通过溢出让数据覆盖指令段。
数据库也提供了这种分离数据和代码(指令)的方式,就是SQL预编译。而SQL语句在程序运行前已经进行了预编译,在程序运行时第一次操作数据库之前,SQL语句已经被数据库分析,编译和优化,对应的执行计划也会缓存下来并允许数据库已参数化的形式进行查询,当运行时动态地把参数传给预编译语句(PreprareStatement)时,即使参数里有敏感字符如 or ‘1=1’也数据库会作为一个参数一个字段的属性值来处理而不会作为一个SQL指令。
MySQL的mysqli
驱动提供了预编译语句的支持,不同的程序语言,都分别有使用预编译语句的方法,我们这里仍然以PHP为例:
//使用问号替代变量位置 $sql = "SELECT uid,username FROM user WHERE username=?"; $stmt = $mysqli->prepare($sql); //绑定变量 $stmt->bind_param("s", $username); $stmt->execute();
通过绑定变量的方式,使用预编译语句是预防SQL注入的最佳方式,使用预编译的SQL语句语义不会发生改变。在SQL语句中,变量用问号?
表示,黑客即使本事再大,也无法改变SQL语句的结构,像上面例子中,username变量传递的admin' AND 1=1-- hack
参数,也只会当作username字符串来解释查询,从根本上杜绝了SQL注入攻击的发生。
对于不同的语言有不同的预编译的函数和方法,具体的相关函数可以查看相关的官方文档。
从开发的角度上看,采用预编译处理可以尽可能的提高访问数据库的性能。数据库在处理SQL语句时都有一个预编译的过程,而预编译对象就是把一些格式固定的SQL编译后,存放在内存池中即数据库缓冲池,当我们再次执行相同的SQL语句时就不需要预编译的过程了,只需DBMS运行SQL语句。这样就可以大大降低运行时间,有效的加快访问数据库的速度。
4、编码问题
除了使用上面的几种方法验证应用程序的输入外,通常还需要对在应用程序的不同模块或者部分间传递的内容进行编码。通常会被忽视的情况是对来自数据库的信息进行编码,尤其是当正在使用的数据未经过严格检查或者审查,这在XSS中也是一个必须严肃对待的问题。
在Oracle中,由于Oracle使用单引号作为字符串的结束符,因为有必要对包含在字符串(动态SQL中将包含该字符串)中的单引号进行编码。例如:sql = sql.replace(” ‘ “,” ‘ ‘ “)。有时也可以使用字符编码表示一些特殊的字符:
sql = replace(sql,'''',''''''); 使用下面的形式表示: sql = replace(sql, CHR(39),CHR(39)||CHR(39));
在MySQL中,由于MySQL使用单引号作为字符串字面量的结束符,因为有必要对包含在字符串(动态SQL语句中包含该字符串)中的单引号进行编码。
在PHP中提供了mysql_real_escape()函数。该函数会自动使用反斜线来引用单引号及其他具有潜在危险的字符,如0x00(NULL)、换行符(\n)、回车符(\r)、双引号(”)、反斜线(\)和0x1A(Ctrl+Z)。