问题描述:如果将用户输入的数据不经处理直接插入到SQL查询语句中,应用很可能遭受SQL注入攻击,如下例:$unsafe_variable=$_POST['user_input'];mysql_query("INSERTINTOtable(column)VALUES('".$unsafe_variable."')");因为用户的输入可能是这样的:value');删除表表;--那么SQL查询就会变成如下:INSERTINTOtable(column)VALUES('value');DROPTABLEtable;--')应该采取哪些有效措施来防止SQL注入?最佳答案(来自Theo):使用准备好的语句和参数化查询。准备好的语句和参数被发送到数据库服务器进行解析,参数将被视为普通字符。这种方法使攻击者无法注入恶意SQL。您有两种选择来实现此方法:1.使用PDO:$stmt=$pdo->prepare('SELECT*FROMemployeesWHEREname=:name');$stmt->execute(array('name'=>$name));foreach($stmtas$row){//dosomethingwith$row}2.使用mysqli:$stmt=$dbConnection->prepare('SELECT*FROMemployeesWHEREname=?');$stmt->bind_param('s',$name);$stmt->执行();$result=$stmt->get_result();while($row=$result->fetch_assoc()){//dosomethingwith$row}PDO请注意,默认情况下使用PDO不会使MySQL数据库执行真正的准备好的语句(原因见下文)。要解决此问题,您应该禁用PDO模拟准备好的语句。使用PDO正确创建数据库连接的例子如下:$dbConnection=newPDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8','user','pass');$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);$dbConnection->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);在上面的例子中,报错模式(ATTR_ERRMODE)不是必须的,但是建议加上。这样,当致命错误(FatalError)发生时,脚本不会停止运行,而是给程序员一个捕捉PDOExceptions的机会,以便正确处理错误。但是需要先调用setAttribute(),这样PDO就不能模拟preparedstatement,而是使用真正的preparedstatement,即让MySQL执行preparedstatement。这确保语句和参数在发送到MySQL之前不经过PHP处理,这将防止攻击者注入恶意SQL。了解原因可以参考这篇博文:PDO防注入原理分析及PDO使用注意事项。请注意,在旧版本的PHP(<5.3.6)中,您无法在PDO构造函数的DSN上设置字符集,请参阅:静默忽略字符集参数。解析当您将SQL语句发送到数据库服务器进行预处理和解析时会发生什么?通过指定占位符(上例中的?或命名的:name)告诉数据库引擎您要过滤的位置。当你调用execute时,preparedstatement会和你指定的参数值结合起来。重点就在这里:参数的值是和解析出来的SQL语句结合的,而不是SQL字符串。SQL注入是由脚本在构造SQL语句时包含恶意字符串触发的。因此,通过分离SQL语句和参数,您可以防止SQL注入的风险。您发送的任何参数值都将被视为普通字符串,不会被数据库服务器解析。回到上面的例子,如果$name变量的值为'Sarah';DELETEFROMemployees,那么实际的查询是查找employees中name字段值为'Sarah'的记录;从员工中删除。使用准备语句的另一个好处是:如果在同一个数据库连接会话中多次执行同一条语句,它只会被解析一次,这样可以稍微加快执行速度。如果你想问如何插入,请看下面的例子(使用PDO):$preparedStatement=$db->prepare('INSERTINTOtable(column)VALUES(:column)');$preparedStatement->execute(数组('列'=>$unsafeValue));
