动态列枚举以防止 SQL 注入

动态列枚举以防止 SQL 注入

我有一个完全供内部使用的应用程序,但我试图确保我已了解 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

您可以手动创建白名单数组,从而利用查询自身的漏洞来获取白名单。

相关内容