我构建了一个函数来检查数据库中是否存在使用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;
}
Run Code Online (Sandbox Code Playgroud)
如果$table
直接从用户输入提供,攻击者是否有可能破坏查询?(极端情况)
然而,这可能更多来自侥幸而不是其他任何东西.
要正确处理用户提供的值,通常希望转义任何具有特殊含义的字符(而不是简单地删除它们).如架构对象名称中所述:
如果引用标识符,标识符引号字符可以包含在标识符中.如果要包含在标识符中的字符与用于引用标识符本身的字符相同,则需要将该字符加倍.以下语句创建一个名为的表
a`b
,其中包含一个名为的列c"d
:Run Code Online (Sandbox Code Playgroud)mysql> CREATE TABLE `a``b` (`c"d` INT);
那么我们如何允许任何表名,包括那些包含反引号字符的表名?
您可能想要修改您的函数以使用以下内容:
$this->query('SELECT 1 FROM `'.str_replace('`', '``', $table).'` LIMIT 1');
Run Code Online (Sandbox Code Playgroud)
str_replace()
是一个天真的,逐字节的函数(它不能识别字符集,因此使用具有多字节字符的字符串编码是不安全的).
如果数据库连接使用多字节字符集(如GBK),则恶意表名称将无法正确转义:
// malicious user-provided value
$_POST['tableName'] = "\x8c`; DROP TABLE users; -- ";
// call your function with that value
tableExists($_POST['tableName']);
Run Code Online (Sandbox Code Playgroud)
以上将导致query()
使用以下字符串参数调用:
SELECT 1 FROM `?`; DROP TABLE users; -- ` LIMIT 1
Run Code Online (Sandbox Code Playgroud)
这是因为当str_replace()
逐字节地逐字逐句地替换任何出现的'`'
字符时,即字节0x60
-it这样做而不理解MySQL会考虑使用GBK编码的字符串,其中0x8c60
是单个字符'?'
; 因此它将这些字符转化0x8c6060
为代表'?`'
.这str_replace()
已经引入了一个以前没有的终止反引号!
但是,可以通过使用字符集识别替换功能来解决该问题.PHP 默认情况下没有,但是在典型的托管环境中,一些可选的扩展(如Multibyte String)相当普遍.
如果遵循此方法,则必须确保使用数据库连接的字符编码执行替换.
从MySQL v5.7.6开始,您可以使用mysql_real_escape_string_quote()
数据库连接的字符集来正确地转义SQL标识符.但遗憾的是,PDO API(还没有?)为这个C函数提供了一个接口......
白名单通常被认为比逃避更安全可靠.但是(除非事先知道它们不包含特殊字符),否则必须逃避列入白名单的值 -这对于最常见的案例来说并没有真正帮助,尽管它确实限制了逃逸时可以造成的损害.证明是错误的.
实际上很难以安全的方式使用任意的,用户提供的SQL标识符.
然而,幸运的是,这不是一个常见的要求.作为一般规则,一个人的模式应该都是静态的(没有对代码库进行必要的模式修改的任何更改)并且符合正交设计的原则:如果满足这两个条件,则SQL标识符将始终是一个静态代码的一部分并且不需要在他们的位置使用用户输入.