看以下三种 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>

 

SQL注入

 

我们介绍了利用绑定变量、通配符和利用 esapi 三种方式对 sql 注入进行防御,我的建议是尽量使用绑定变量或者通配符的是形式进行防注入,安全性能都比较好,如果不得不使用字符串拼接的形式,则使用 esapi 进行 sql 过滤

最后给个 esapi 的 maven 支持形式

    <dependency>
            <groupId>org.owasp.esapi</groupId>
            <artifactId>esapi</artifactId>
            <version>2.1.0.1</version>
        </dependency>

 

 

 

 

 

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品: PipeSoloSymWide 等,欢迎大家加入,贡献开源。

    3018 引用 • 3729 回帖 • 660 关注
  • SQL注入
    1 引用
  • 安全问题
    1 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...