正确的防御SQL注入
sql注入的防御不是简单只做一些用户输入的escape处理,这样是不够的,只是提高了攻击者的门槛而已,还是不够安全。
例如 mysql_real_escape_string()函数会对输入的参数进行转义。
$sql="SELECT id,name,mail,cv,blog,twitter FROM register WHERE id= ".mysql_real_escape_string($_GET['id']);
当攻击者构造的注入代码如下时:
http://vuln.example.com/user.php?id=2,and,1=1,union,select,1,
concat(user,0x3a,password),3,4,5,6,from,mysql.user,where,
user=substring_index(current_user(),char(64),1)
将会绕过这个函数注入成功,其原因在于这个函数仅仅会转义: ' " \r \n NULL Control-Z
这种基于黑名单的方法,或多或少地存在些问题,是不可能排除所有恶意符号或者说能利用的符号。
围绕着SQL注入攻击产生的两个关键条件:1.用户控住的输入参数 2.拼接过程中的数据当做代码执行。
有下面几个防御措施:
一.使用预编译语句
一般来说,防御SQL注入的最好效果就是使用预编译语句,绑定变量。例如在Java中使用预编译的SQL语句:
String userName=request.getParameter("userName");
String query="SELECT account_balance FROM user_data WHERE
user_name=?";
PreparedStatement pstmt=connection.prepareStatament(query);
pstmt.setString(1,userName);
ResultSet results=pstmt.executeQuery();
使用这种预编译的SQL语句,SQL语句的语义不会发生改变,变量用?表示,攻击者无法改变SQL的结构。
在PHP中绑定变量的示例:
$query="INSERT INTO myCity(Name,CountCode,District)VALUES(?,?,?)";$stmt=$mysqli->prepare($query);$stmt->bind_param("sss",$var1,$var2,$var3);$var1="StuName";$var2="DEU";$var3="Bid";$stmt->execute();
在不同的语言中,都有着预编译语句的方法:
Java EE------ PreparedStatement()
.NET------SqlCommand() or OleDbCommand()
PHP------bind_Param()
Hibernate------createQuery()
SQLite------sqlite3_prepare()
二.使用存储函数
使用安全的存储过程对抗SQL注入。使用存储过程与预编译语句的区别在于,存储过程要先将SQL语句定义在数据库中,所以存储过程也可能存在注入问题,得尽量避免在存储过程中使用动态的SQL语句。如果业务要求,实在无法避免,则可以考虑加上严格的输入过滤或者是编码函数来处理用户的输入数据。
Java中存储过程的示例,其中sp_getAccount是预先在数据中定义好的存储过程。
String username=request.getParameter("userName");try{
CallableStatement cs=connection.prepareCall(" {call sp_getAccount(?)} ");
cs.setString(1,username);
ResultSet results=cs.executeQuery();
}catch(SQLException se){//....some error info and error handing
}
三.检查数据类型
检出数据类型,在很大程度上能够对抗SQL注入。
比如下面这段代码,就限制了输入数据只能为integer。
20 OFFSET ;";$query=sprintf("SELECT id ,name,FROM produces ORDER BY name LIMIT 20 OFFSET %d;");
?>
其他数据格式或者类型检查也是有益的,但是提交字符串,则需要依赖其他的方法防御SQL注入,而不能使用'%s"代换"%d"。
四.使用安全函数
安全函数,数据库厂商都往往对此都做出了“指导”。可以参考OWASP ESAPI中的实现。这个函数由安全专家编写的,更值得信赖。
ESAPI.encoder().encoderForSQL(new OracleCodec(),queryparam);
使用示例:
Codec ORACLE_CODEC=newOracleCode();
String query="SELECT user_id FROM user_data WHERE user_name=' "+ESAPI.encoder().encoderForSQL( ORACLE_CODEC,req.getParameter("userID"))+" ' and
user_password=' "+
ESAPI.encoder().encoderForSQL( ORACLE_CODEC,req.getParameter("pwd"))+" ' ";