看以下三种SQL语句
String sql1 = "select * from user where username = '"+username+"' and password = '"+password+"'";String sql2 ="select * from user where username = :username and password = :password"; String sql3 = "select * from user where username = ? and password = ?";</pre>
推荐写法是sql2、sql3使用的别名或者通配符的形式进行传值,可以避免SQL注入,sql1就有了常见的典型SQL注入问题,假设username随便传个值,比如'张三',传入的password的值为
' or 1 = 1' 则 拼接的SQL语句成了
select * from user where username ='张三' and password = '' or 1 = 1 ''很明显无论前面的and为真还是假,只要or后面为真,则就可使查询为true,导致SQL注入 所以尽可能避免字符串拼接的形式进行SQL传值,但有些场景可能必须采用字符串拼接的形式,比如Mybatis在按要求排序时:
order by #{order} #{sort}用#{}的形式取值就相当于字符串拼接,但是因为排序DESC、AESC这种是SQL关键字,不能进行转义,所以就必须采用字符串拼接的形式了
如果采用这种形式还要避免SQL注入的话,就需要对传入的值进行SQL过滤,来避免SQL注入
那么OWASP提供了一个防御sql注入的Esapi包,这个包中的encodeForSQL方法能对sql注入进行很好的防御。
//防止Oracle注入 ESAPI.encoder().encodeForSQL(new OracleCodec(),queryparam) //防止mysql注入 ESAPI.encoder().encodeForSQL(new MySQLCodec(Mode.STANDARD),queryparam) //Mode.STANDARK为标准的防注入方式,mysql一般用使用的是这个方式 //防止DB2注入
ESAPI.encoder().encodeForSQL(new DB2Codec(),queryparam)使用不同的数据库,使用的过滤方法不同
下面我们就用MySQL为例字分析encodeForSQL函数做了什么防御。具体函数过程就不跟踪了,直接分析最后调用了哪个方法。根据代码可知最后调用的是encodeCharacter方法。
public String encodeCharacter( char[] immune, Character c ) { char ch = c.charValue();// check for immune characters if ( containsCharacter( ch, immune ) ) { return ""+ch; } // check for alphanumeric characters String hex = Codec.getHexForNonAlphanumeric( ch ); if ( hex == null ) { return ""+ch; } switch( mode ) { case ANSI: return encodeCharacterANSI( c ); case STANDARD: return encodeCharacterMySQL( c ); } return null; } </pre>
上述方法中containsCharacter函数是不进行验证的字符串白名单,Codec.getHexForNonAlphanumeric函数查找字符传中是否有16进制,没有返回空值。
而encodeCharacterANSI和encodeCharacterMySQL才是防御的重点,我们看一下这两个函数的不同,如果选择的我们选择是Mode.ANSi模式,则字符串则进入下面的函数,可以看到这个函数对单撇号和双撇号进行了转义。
private String encodeCharacterANSI( Character c ) { if ( c == '\'' ) return "\'\'"; if ( c == '\"' ) return ""; return ""+c; }
如果选择的是Mode.STANDARD模式,则字符串则进入下面的函数,可以看到这个函数对单撇号和双撇号、百分号、反斜线等更多的符号进行了转换,所以使用时推荐使用标准模式。
private String encodeCharacterMySQL( Character c ) { char ch = c.charValue(); if ( ch == 0x00 ) return "\\0"; if ( ch == 0x08 ) return "\\b"; if ( ch == 0x09 ) return "\\t"; if ( ch == 0x0a ) return "\\n"; if ( ch == 0x0d ) return "\\r"; if ( ch == 0x1a ) return "\\Z"; if ( ch == 0x22 ) return "\\\""; if ( ch == 0x25 ) return "\\%"; if ( ch == 0x27 ) return "\\'"; if ( ch == 0x5c ) return "\\\\"; if ( ch == 0x5f ) return "\\_"; return "\\" + c; }
写个单元测试:
@org.junit.Test public void testESAPI() { String username = "zhangsan"; String password = "' or 1=1'";String sql1 = "select * from user where username = '"+username+"' and password = '"+password+"'";
// String sql2 ="select * from user where username = :username and password = :password";
// String sql3 = "select * from user where username = ? and password = ?";
System.out.println("过滤前:"+sql1);username = ESAPI.encoder().encodeForSQL(new MySQLCodec((Mode.STANDARD)), username); password = ESAPI.encoder().encodeForSQL(new MySQLCodec((Mode.STANDARD)), password); sql1 = "select * from user where username = '"+username+"' and password = '"+password+"'"; System.out.println("过滤后:"+sql1); }</pre>
我们介绍了利用绑定变量、通配符和利用esapi三种方式对sql注入进行防御,我的建议是尽量使用绑定变量或者通配符的是形式进行防注入,安全性能都比较好,如果不得不使用字符串拼接的形式,则使用esapi进行sql过滤
最后给个esapi的maven支持形式
<dependency> <groupId>org.owasp.esapi</groupId> <artifactId>esapi</artifactId> <version>2.1.0.1</version> </dependency>
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于