有许多出色的方法来保护应用程序免受SQL注入的攻击,例如,此问答表示某些方式How to prevent SQL injection in PHP?。
这个问题是关于已经编写的申请。我们不想修改它以提高其安全性。我们想知道所使用的方法是否容易受到注入攻击。
我们需要知道这部分代码是否是泄漏数据的原因?
这是一个PHP函数,可以消除一些无用的(对我们而言)和可能有害的字符:
function removeBadCharacters($s)
{
return str_replace(array('&','<','>','/','\\','"',"'",'?','+'), '', $s);
}
它抛出危险的字符以避免SQL注入攻击(不要担心删除的字符,例如&<>/\"'?+
应用程序不需要它们):
$x = removeBadCharacters($_POST['data']);
mysql_query("insert into table (x) values ('".$x."');");
mysql_query("select * from into where name = '".$x."';");
是否足以使查询安全?
有没有办法绕过removeBadCharacters
?
如何破解?
答案 0 :(得分:9)
为了能够从string literal的上下文中注入任意SQL,需要保留该字符串文字。这只能通过引入字符串结束分隔符(在本例中为单个'
)或通过将字符串文字扩展为前面的'
来实现,例如,通过使用转义字符\
:
$a = '\\';
$b = ' OR 1=1 OR ';
$c = ' --';
$query = "SELECT * FROM t1 WHERE a='$a' AND b='$b' AND c='$c'";
// result:
// SELECT * FROM t1 WHERE a='\' AND b=' OR 1=1 OR ' AND c=' --'
// \_________/ \_______/
现在,当您的函数删除任何'
和\
时,似乎无法离开或扩展字符串文字,因此无法注入任意SQL。< / p>
但是,由于您的函数没有考虑实际的字符编码,如果MySQL的字符编码是GBK,可以利用它,类似于how it can be exploited when using addslashes
instead of mysql_real_escape_string
:
$a = "\xbf";
$b = " OR 1=1 OR ";
$c = " --";
$query = "SELECT * FROM t1 WHERE a='$a' AND b='$b' AND c='$c'";
// result:
// SELECT * FROM t1 WHERE a='縗 AND b=' OR 1=1 OR ' AND c=' --'
// \_________/ \_______/
为了安全起见,use mysql_real_escape_string
or other proven methods to prevent SQL injections。
答案 1 :(得分:5)
这就是你的做法本质上是错误的。
您必须正确格式化您的SQL文字,而不是破坏它们。
想象一下,这个网站正在使用这样的“保护”:你无法发布你的问题!
从字面上回答你的问题 - 是的,在某些情况下,它很容易注入。仅仅因为一次性清理的想法被打破了。 PHP有一个类似的功能,称为“魔术引号”。这是一个艰难的教训,但现在它终于从语言中删除了。由于我告诉你的原因:
根据其角色,每个SQL文字都必须经过个人处理。
答案 2 :(得分:3)
防止SQL注入的正确方法是使用参数化查询。这意味着定义将使用参数值的占位符执行的SQL代码,以编程方式添加参数值,然后执行查询。这样做允许服务器为查询创建执行计划,从而阻止执行任何“注入的” SQL。一个例子将有助于解释这一点。我们使用相同的脚本,但是我将使用参数占位符定义SQL查询:
$sql = "SELECT * FROM UserTbl WHERE Username = ? and Password = ?";
现在,我将定义一个包含参数值的数组:
$params = array($_POST['Username’], $_POST['Password’]);
执行查询时,我将$ params数组作为参数传递:
$stmt = sqlsrv_query($conn, $sql, $params);
调用sqlsrv_query时,将在执行查询之前在服务器上创建执行计划。该计划仅允许执行我们的原始查询。参数值(即使它们是注入的SQL)也不会执行,因为它们不是计划的一部分。因此,如果我像上面的示例一样提交密码(“或1 = 1--”),它将被视为用户输入,而不是SQL代码。换句话说,查询将查找具有此密码的用户,而不是执行意外的SQL代码。
上面的脚本经过修改以防止SQL注入,如下所示:
<form method="post" action="injection.php" enctype="multipart/form-data" >
Username:<input type="text" name="Username" id="Username"/></br>
Password:<input type="text" name="Password" id="Password"/></br>
<input type="submit" name="submit" value="Submit" />
</form>
<?php
$params = array($_POST['Username'], $_POST['Password']);
$server = "MyServer\sqlexpress";
$options = array("Database"=>"ExampleDB", "UID"=>"MyUID", "PWD"=>"MyPWD");
$conn = sqlsrv_connect($server, $options);
$sql = "SELECT * FROM UserTbl WHERE Username = ? and Password = ?";
$stmt = sqlsrv_query($conn, $sql, $params);
if(sqlsrv_has_rows($stmt))
{
echo "Welcome.";
}
else
{
echo "Invalid password.";
}
?>
注意:如果希望使用不同的参数值多次执行查询,请使用sqlsrv_prepare和sqlsrv_execute函数。 sqlsrv_prepare函数在服务器上创建一次执行计划,并且sqlsrv_execute函数每次调用时都会使用不同的参数值执行查询。
答案 3 :(得分:2)
您的功能不处理不同的编码。
不要试图自己想出卫生方法,使用已经制作的东西。对于mysql_*
,它将是mysql_real_escape_string
,但是,您不应再使用mysql_*
,而是使用PDO或mysqli。
有关详细信息,请参阅:How can I prevent SQL injection in PHP?。
答案 4 :(得分:2)
是的,可以使用Unicode字符和操纵字符集来解决这个问题。