我有一个完全供内部使用的应用程序,但我试图确保我已了解 SQL 注入的基础知识。除非其他人有一些信息可以指点我,否则无法动态指定语句中的列列表,而不会将查询开放给 SQL 注入,因为准备好的语句 bindparam 不能用于指定列名。
例如(不可能,会引发错误):
SELECT :column_name FROM tablename WHERE other_column_name = :value
为了仍然动态地允许调用列名,我必须直接传入一个变量(可以,但容易受到注入):
SELECT $column_name FROM tablename WHERE other_column_name = :value
为了防止注入,我使用 PHP 的 in_array 函数根据表内所有列名的白名单检查 $column_name 的值。如果它在列表中,我将继续查询,但如果不在,我会抛出异常。
由于应用程序中有许多不同的地方需要这样的查询,因此我向 PDO 包装器添加了一个函数,以使用 information_schema 数据库中的数据自动枚举这些列列表:
public function schema_list($database_name = NULL, $table_name = NULL, $column_name = NULL)
{
$query = 'SELECT table_schema AS database_name, table_name, column_name
FROM `information_schema`.`columns`
WHERE table_schema = :database_name ';
$parameters = array();
// Use this object's database name unless manually specified
if (!is_null($database_name)) {
$parameters[':database_name'] = $database_name;
} else {
$parameters[':database_name'] = $this->database_detail['dbname'];
}
// Add details for table_name if specified
if (!is_null($table_name)) {
$query .= 'AND table_name = :table_name ';
$parameters[':table_name'] = $table_name;
}
// Add details for column_name if specified
if (!is_null($column_name)) {
$query .= 'AND column_name = :column_name ';
$parameters[':column_name'] = $column_name;
}
$result = $this->query_select($query, $parameters);
if ($result['sth_count'] == 0) {
return FALSE;
} else {
return $result;
}
}
当然,这会返回一个多维记录数组,最终需要枚举列列表的对象的构造函数会执行以下操作:
protected $table_process_columnlist; // List of valid columns for the process table
public function __construct(dblink $global_dblink = NULL, job $global_job = NULL)
{
// other constructor secret sauce //
// Initialize valid column list
$this->table_process_columnlist = array();
$schema_list = $this->dblink->schema_list(NULL, $this->table_process);
foreach ($schema_list['sth_result'] as $table_column) {
$this->table_process_columnlist[] = $table_column['column_name'];
}
然后被以下对象使用:
public function __get($column)
{
if (in_array($column, $this->table_process_columnlist)) {
$query = 'SELECT '.$column.'
FROM '.$this->table_process.'
WHERE idprocess = :idprocess
LIMIT 1';
$parameters = array (
':idprocess' => $this->idprocess,
);
$result = $this->dblink->query_select($query, $parameters);
return $result['sth_result'][0][$column];
} else {
$e_message = 'Could not get specified column';
throw new ACException($e_message, 99);
}
}
请记住,我实际上并没有使用准备好的语句来提高性能,它们只是用于 SQL 注入保护。从我在测试中发现的情况来看,information_schema 表将仅返回数据库句柄用户实际有权访问的项目的结果集,因此 schema_list 函数不能用于枚举数据库用户尚无权访问的数据库的架构布局。
我正在使用一个公共的__get魔法函数,因为这个类仅在应用程序内部使用,并且被应用程序中上层的其他类包装。
这是防止 SQL 注入的明智方法吗?同时还能动态指定列名?这种方法是否存在安全问题?
答案1
我没有读过你的代码,但是是的,白名单是这里的正确解决方案。
答案2
您可以手动创建白名单数组,从而利用查询自身的漏洞来获取白名单。