常见漏洞防御 之 防SQL注入的三种方式 有更新!

  |   0 评论   |   558 浏览

    看以下三种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 = ?";

     

    推荐写法是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;  
        }  

     

    上述方法中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);
    		
    	}

     

    SQL注入

     

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

    最后给个esapi的maven支持形式

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

     

     

     

     

     

    评论

    发表评论

    validate