我最近在向我的朋友解释参数化及其优点,他问它比mysqli_escape_string
安全性更好。具体来说,您能想到任何 SQL 注入示例,尽管输入字符串被转义(使用 mysqli_escape_string),但仍会成功?
更新:
对于我最初的问题不够清楚,我深表歉意。这里要问的一般问题是,尽管转义了输入字符串,SQL 注入是否可能?
我最近在向我的朋友解释参数化及其优点,他问它比mysqli_escape_string
安全性更好。具体来说,您能想到任何 SQL 注入示例,尽管输入字符串被转义(使用 mysqli_escape_string),但仍会成功?
更新:
对于我最初的问题不够清楚,我深表歉意。这里要问的一般问题是,尽管转义了输入字符串,SQL 注入是否可能?
该问题已被编辑(在我的答案发布后)专门针对 target mysqli_escape_string
,这是一个别名,mysql_real_escape_string
因此考虑了连接编码。这使得原始答案不再适用,但为了完整起见,我将其保留。
简而言之,新答案在mysqli_escape_string
安全方面与参数化查询一样好,前提是您不要误伤自己。
具体来说,PHP文档页面上的巨大警告中突出显示了您不能做的事情:
字符集必须在服务器级别设置,或者使用 API 函数
mysqli_set_charset()
设置才能影响mysqli_real_escape_string()
.
如果您不注意此警告(即,如果您使用直接SET NAMES
查询更改字符集)并且将字符集从单字节编码更改为“方便”(从攻击者的角度来看)多字节编码,您将拥有实际上模仿了哑巴mysql_escape_string
的做法:在不知道输入的编码的情况下尝试转义字符。
这种情况使您可能容易受到 SQL 注入的攻击,如下面的原始答案所述。
重要提示:我记得在某处读到最近的 MySql 版本已经在其末端(在客户端库中?)堵住了这个漏洞,这意味着即使使用切换到易受攻击的多字节编码,您也可能是完全安全的。SET NAMES
但请不要相信我的话。
相比之下mysql_real_escape_string
,baremysql_escape_string
不考虑连接编码。这意味着它假设输入采用单字节编码,而实际上它可以合法地采用多字节编码。
一些多字节编码具有对应于单个字符的字节序列,其中一个字节是单引号 ( 0x27
) 的 ASCII 值;如果输入这样一个字符串,mysql_escape_string
将很高兴地“转义引号”,这意味着0x27
用0x5c
0x27
. 根据编码规则,这可能会导致将多字节字符变为另一个包含 的字符,0x5c
并将“剩余”0x27
作为独立的单引号留在 input 中。瞧,您在 SQL 中注入了一个未转义的引号。
有关更多详细信息,请参阅此博客文章。
具体来说,您能想到任何 SQL 注入示例,尽管输入字符串被转义(使用 mysql_escape_string),但仍会成功?
不可靠。您指的是mysql_escape_string()
,它没有考虑连接编码(虽然mysql_real_escape_string()
)。
因此,也许一个精心制作的字符串,前面有一个精心制作的不完整 UTF8 代码点,可能会导致,比如说,引号被转义,mysql_escape_string()
但转义本身被 MySQL 忽略,因为它会将其“视为”为 UTF8 字符。
例如:
0xC2' OR 1=1 ;--
会被mysql_escape_string()
as逃脱
0xC2\' OR 1=1 ;--
这将被组装到
WHERE password='0xC2\' OR 1=1 ;--';
并被 MySQL 视为(如果正确的连接编码有效),例如,
WHERE password='€' OR 1=1 ;[--';] <-- the bracketed part is considered a comment and ignored
这将是一个经典的 SQL 注入。
但这取决于您指定的事实,也许是通过分心,一个双重弃用的功能。如果你真的指的是mysql_real_escape_string()
,那么它就行不通了。
此外,这假定服务器和应用程序层(例如 PHP)在填充输入时都没有使用任何类型的字符集验证。如果他们这样做了,无效的 UTF8 将在到达时被删除,甚至永远不会被 看到mysql_escape_string
,这当然就足够了。
根本不要使用mysql_escape_string
(或mysql_whatever
)。它们已被弃用,您的代码可能会停止工作。请改用 PDO 函数。