java每日一学--数据校验20131008

 数据校验

规则1.1 禁止直接使用外部输入来拼接SQL语句以防止SQL注入

说明:如果对于外部输入的数据未经处理,直接用于拼接SQL语句,攻击者可以通过构造特殊形式的输入来改变程序中原本要执行的SQL逻辑,形成SQL注入攻击,导致系统功能异常、信息泄露、数据被非法修改等安全问题。
错误示例:

public void doPrivilegedAction(String username, char[] password) throws SQLException 
    { 
        /* … */ 
        try 
        { 
            String pwd = hashPassword(password); 
            String sqlString = "SELECT * FROM db_user WHERE username = '" 
                    + username + "' AND password = '" + pwd + "'"; 
            Statement stmt = connection.createStatement(); 
            ResultSet rs = stmt.executeQuery(sqlString); 
            // Authenticate … 
        } 
        /* … */ 
    } 

  上面的代码使用用户输入来拼接SQL语句,但又没有对用户的输入做校验,存在SQL注入风险。当用户为username输入值:jack' OR '1' = '1
SQL语句变成了:SELECT * FROM db_user WHERE username='jack' OR '1'='1'
AND

password=<PASSWORD>
如果jack是有效用户,这样便绕开了对口令的验证。

推荐做法1校验输入长度,并且使用PreparedStatement来防范SQL注入。

public void doPrivilegedAction(String username, char[] password) 
            throws SQLException 
    { 
        /* … */ 
        try 
        { 
            String pwd = hashPassword(password); 
            // Ensure that the length of user name is legitimate 
            if ((username.length()) > 8) 
            { 
                // Handle error 
            } 
            String sqlString = "select * from db_user where username=? and password=?"; 
            PreparedStatement stmt = connection.prepareStatement(sqlString); 
            stmt.setString(1, username); 
            stmt.setString(2, pwd); 
            ResultSet rs = stmt.executeQuery(); 
            // Authenticate … 
        } 
        /* … */ 
    } 

  使用PreparedStatement,输入的参数无法改变原始的SQL语义。上例中,即使攻击者插入类似 jack or 1=1的字符串,也只会将此字符串
当做username来查询。
推荐做法2使用存储过程

public void doPrivilegedAction(String username, 
char[] password) 
            throws 
SQLException 
    { 
        /* … */ 
        try 
        { 
            
String pwd = hashPassword(password); 
            // Ensure that the length of user name is 
legitimate 
            
if ((username.length()) > 
8) 
            
{ 
                
// Handle error 

            } 
            
String sqlString = "select * from db_user where 
username=? and password=?"; 

            CallableStatement cs = connection.prepareCall("{call sp_getUser(?,?)}"); 
            
cs.setString(1, username); 
            
cs.setString(2, pwd); 
            
ResultSet rs = cs.executeQuery(); 
            // Authenticate … 

        } 
        /* … */ 
  
  } 

使用存储过程的效果和使用PreparedStatement类似,其区别是存储过程需要先将SQL语句定义在数据库中。但需要注意的是,存储过程中也可能

存在注入问题,因此应该尽量避免在存储过程内使用动态的SQL语句。
推荐做法3对外部输入进行转义

public void doPrivilegedAction(String username, char[] password) 
            throws SQLException 
    { 
        /* … */ 
        try 
        { 
            String pwd = hashPassword(password); 
            Codec oe = new OracleEncoder(); 
            String susername = oe.encode(username); 
            String spwd = oe.encode(pwd); 
            String sqlString = "SELECT * FROM db_user WHERE username = '" 
                    + susername + "' AND password = '" + spwd + "'"; 
            Statement stmt = connection.createStatement(); 
            ResultSet rs = stmt.executeQuery(sqlString); 
            // Authenticate … 
        } 
        /* … */ 
    } 

  对于PreparedStatement无法适用或者存储过程内部也存在动态构造SQL语句的情形,则可以考虑对外部输入做转义。每个DBMS都有一个字符转义机制
来告知DBMS输入的是数据而不是代码,如果将所有用户的输入都进行转义,那么DBMS就不会混淆数据和代码,也就不会出现SQL注入了。针对每种数据
的转义机制实现,可以使用现有的API工具,比如OWASP ESAPIescaping
routines
,也可以使用自己的实现。