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 ESAPI的escaping
routines,也可以使用自己的实现。