这个函数容易受到 SQL 注入的攻击吗?
Is this function vulnerable to SQL injection?
我构建了一个函数来检查 table 是否存在于使用 PDO 的数据库中,但我不确定我是否已正确保护它。
public function tableExists($table){
try{
$this->query('SELECT 1 FROM `'.str_replace('`', '', $table).'` LIMIT 1');
}catch(\PDOException $e){
if($e->errorInfo[1] == 1146){
return false;
}
throw $e;
}
return true;
}
如果直接从用户输入提供 $table
,攻击者是否有可能破坏查询? (极端情况)
没有。您已删除给定上下文中唯一的有害字符。
所以很安全。
至于合理,嗯……没有。 Table 名称通常不应来自用户输入。
不,您的代码不容易受到 SQL 注入攻击
然而,这也许更多的是侥幸。
为了正确处理用户提供的值,通常希望转义任何具有特殊含义的字符(而不是简单地删除它们)。如 Schema Object Names 中所述:
Identifier quote characters can be included within an identifier if you quote the identifier. If the character to be included within the identifier is the same as that used to quote the identifier itself, then you need to double the character. The following statement creates a table named a`b
that contains a column named c"d
:
mysql> <strong>CREATE TABLE `a``b` (`c"d` INT);</strong>
那么我们如何允许 任何 table 名称,包括那些包含反引号的名称?
允许转义——尝试 1
您可能想修改您的函数以使用以下内容:
$this->query('SELECT 1 FROM `'.str_replace('`', '``', $table).'` LIMIT 1');
不要!上面的代码 是 易受攻击的(在模糊的边缘情况下)。
str_replace()
是一个简单的字节函数(它不是字符集感知的,因此使用具有多字节字符的字符串编码是不安全的)。
如果您的数据库连接使用多字节字符集,例如 GBK,那么恶意 table 名称将无法正确转义:
// malicious user-provided value
$_POST['tableName'] = "\x8c`; DROP TABLE users; -- ";
// call your function with that value
tableExists($_POST['tableName']);
以上将导致使用以下字符串参数调用 query()
:
SELECT 1 FROM `宍`; DROP TABLE users; -- ` LIMIT 1
这是因为当 str_replace()
逐字节遍历输入的字符串时——天真地替换任何出现的 '`'
字符,即字节 0x60
——它这样做时没有理解MySQL会认为字符串使用GBK编码,其中0x8c60
是单个字符'宍'
;因此它将这些字符变成 0x8c6060
代表 '宍`'
。那就是 str_replace()
引入了一个以前没有的终止反引号字符!
允许转义——尝试 2
不过,这个问题可以通过使用字符集识别替换功能来解决。 PHP 默认情况下没有,尽管一些可选扩展如 Multibyte String 在典型的托管环境中相当普遍。
如果采用这种方法,您必须确保使用数据库连接的字符编码.[=32 执行替换=]
允许转义——尝试 3
从 MySQL v5.7.6 开始,您可以使用 mysql_real_escape_string_quote()
使用数据库连接的字符集正确转义 SQL 标识符。然而遗憾的是,PDO API 没有(还?)提供这个 C 函数的接口......
白名单呢?
白名单通常被认为比转义更安全可靠。然而(除非事先知道它们不包含特殊字符),仍然必须转义白名单值——所以这并不能真正帮助推进大多数一般情况,尽管它确实如果转义被证明是错误的,限制可以造成的损害。
那么结论是什么?
实际上很难以安全的方式使用用户提供的任意SQL标识符。
幸运的是,这不是一个常见的要求。作为一般规则,一个人的架构应该都是静态的(没有任何需要修改架构的代码库更改) 和 符合 principle of orthogonal design:如果这两个条件都满足, SQL 标识符将始终是一个人的静态代码的一部分,并且不需要在其位置使用用户输入。
我构建了一个函数来检查 table 是否存在于使用 PDO 的数据库中,但我不确定我是否已正确保护它。
public function tableExists($table){
try{
$this->query('SELECT 1 FROM `'.str_replace('`', '', $table).'` LIMIT 1');
}catch(\PDOException $e){
if($e->errorInfo[1] == 1146){
return false;
}
throw $e;
}
return true;
}
如果直接从用户输入提供 $table
,攻击者是否有可能破坏查询? (极端情况)
没有。您已删除给定上下文中唯一的有害字符。
所以很安全。
至于合理,嗯……没有。 Table 名称通常不应来自用户输入。
不,您的代码不容易受到 SQL 注入攻击
然而,这也许更多的是侥幸。
为了正确处理用户提供的值,通常希望转义任何具有特殊含义的字符(而不是简单地删除它们)。如 Schema Object Names 中所述:
Identifier quote characters can be included within an identifier if you quote the identifier. If the character to be included within the identifier is the same as that used to quote the identifier itself, then you need to double the character. The following statement creates a table named
a`b
that contains a column namedc"d
:mysql> <strong>CREATE TABLE `a``b` (`c"d` INT);</strong>
那么我们如何允许 任何 table 名称,包括那些包含反引号的名称?
允许转义——尝试 1
您可能想修改您的函数以使用以下内容:
$this->query('SELECT 1 FROM `'.str_replace('`', '``', $table).'` LIMIT 1');
不要!上面的代码 是 易受攻击的(在模糊的边缘情况下)。
str_replace()
是一个简单的字节函数(它不是字符集感知的,因此使用具有多字节字符的字符串编码是不安全的)。
如果您的数据库连接使用多字节字符集,例如 GBK,那么恶意 table 名称将无法正确转义:
// malicious user-provided value
$_POST['tableName'] = "\x8c`; DROP TABLE users; -- ";
// call your function with that value
tableExists($_POST['tableName']);
以上将导致使用以下字符串参数调用 query()
:
SELECT 1 FROM `宍`; DROP TABLE users; -- ` LIMIT 1
这是因为当 str_replace()
逐字节遍历输入的字符串时——天真地替换任何出现的 '`'
字符,即字节 0x60
——它这样做时没有理解MySQL会认为字符串使用GBK编码,其中0x8c60
是单个字符'宍'
;因此它将这些字符变成 0x8c6060
代表 '宍`'
。那就是 str_replace()
引入了一个以前没有的终止反引号字符!
允许转义——尝试 2
不过,这个问题可以通过使用字符集识别替换功能来解决。 PHP 默认情况下没有,尽管一些可选扩展如 Multibyte String 在典型的托管环境中相当普遍。
如果采用这种方法,您必须确保使用数据库连接的字符编码.[=32 执行替换=]
允许转义——尝试 3
从 MySQL v5.7.6 开始,您可以使用 mysql_real_escape_string_quote()
使用数据库连接的字符集正确转义 SQL 标识符。然而遗憾的是,PDO API 没有(还?)提供这个 C 函数的接口......
白名单呢?
白名单通常被认为比转义更安全可靠。然而(除非事先知道它们不包含特殊字符),仍然必须转义白名单值——所以这并不能真正帮助推进大多数一般情况,尽管它确实如果转义被证明是错误的,限制可以造成的损害。
那么结论是什么?
实际上很难以安全的方式使用用户提供的任意SQL标识符。
幸运的是,这不是一个常见的要求。作为一般规则,一个人的架构应该都是静态的(没有任何需要修改架构的代码库更改) 和 符合 principle of orthogonal design:如果这两个条件都满足, SQL 标识符将始终是一个人的静态代码的一部分,并且不需要在其位置使用用户输入。