如何允许SELECT查询并阻止其他人?

Chr*_*ler 16 php mysql security

在我们的应用程序中,用户可以以SQL语句的形式创建自定义导出功能.像这样的东西:

SELECT name, age, date_birth FROM users WHERE group_id = 2
Run Code Online (Sandbox Code Playgroud)

我不希望他们通过插入DELETE语句来清除整个数据库.我的想法是:

  • 使用SQL帐户,只允许SELECT.(如果有其他选择,我不想这样做.)
  • 使用魔术正则表达式,检查查询是否危险.(这会好吗?有这样的正则表达式吗?)

我们正在使用PHP PDO.

Pet*_*ter 20

在我看来,有三种选择可供选择:

选项1

创建一个工具,在后台为用户创建查询.只需单击按钮并输入表格名称即可.通过这种方式,您可以在后台捕获所有奇怪的行为,使您不会因为不想执行的查询而面临危险.

选项2

创建仅允许执行SELECT查询的MySQL用户.我相信你甚至可以决定允许用户选择哪些表.使用该用户执行用户输入的查询.创建有你想让它做你的权限一个单独的用户UPDATE,INSERTDELETE查询.

选项3

在执行查询之前,请确保其中没有任何有害内容.扫描查询以查找错误的语法.

例:

// Check if SELECT is in the query
if (preg_match('/SELECT/', strtoupper($query)) != 0) {
    // Array with forbidden query parts
    $disAllow = array(
        'INSERT',
        'UPDATE',
        'DELETE',
        'RENAME',
        'DROP',
        'CREATE',
        'TRUNCATE',
        'ALTER',
        'COMMIT',
        'ROLLBACK',
        'MERGE',
        'CALL',
        'EXPLAIN',
        'LOCK',
        'GRANT',
        'REVOKE',
        'SAVEPOINT',
        'TRANSACTION',
        'SET',
    );

    // Convert array to pipe-seperated string
    // strings are appended and prepended with \b
    $disAllow = implode('|',
        array_map(function ($value) {
            return '\b' . $value . '\b';
        }
    ), $disAllow);

    // Check if no other harmfull statements exist
    if (preg_match('/('.$disAllow.')/gai', $query) == 0) {
        // Execute query
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:在执行此检查之前,您可以添加一些PHP代码来过滤掉注释

结论

您要做的事情很有可能,但是您永远不会100%保证它是安全的.而不是让用户进行查询,最好使用API​​向用户提供数据.

  • 例如,如果列名称为"delete_id",该怎么办?它不会运行?也许更安全的方法是做一些像''DELETE'`这样的东西,两边都有空格来确保匹配这个词? (2认同)

Fro*_*oid 12

不要这样做,总会有创造性的方法来进行危险的查询.创建一个手动构建查询的API.


tie*_*anx 6

既然你说过,如果有其他选择,你不愿意使用只读SQL帐户.如果你运行PHP 5.5.21+或5.6.5+:我建议检查查询的第一个语句是否是SELECT语句并禁用PDO连接中的多个查询.

首先禁用PDO对象上的多语句...

$pdo = new PDO('mysql:host=hostname;dbname=database', 'user', 'password', [PDO::MYSQL_ATTR_MULTI_STATEMENTS => false]);
Run Code Online (Sandbox Code Playgroud)

然后检查查询中的第一个语句是否使用SELECT并且没有子查询.第一个正则表达式忽略前导空格是可选的,第二个正则表示检测将用于创建子查询的括号 - 这确实会产生阻止用户使用SQL函数的副作用,但根据您的示例我不认为这是一个问题.

if (preg_match('/^(\s+)?SELECT/i', $query) && preg_match('/[()]+/', $query) === 0) {
    // run query
}
Run Code Online (Sandbox Code Playgroud)

如果您运行的是旧版本,则可以禁用模拟准备以防止执行多个语句,但这依赖于正在使用的PDO :: prepare().

FWIW:使用预准备语句/为用户生成安全查询或使用只读SQL帐户会好得多.如果你正在使用MySQL并拥有管理员权限/远程访问权限,我建议使用SQLyog社区版(https://github.com/webyog/sqlyog-community/wiki/Downloads)创建只读用户帐户.它非常用户友好,因此您无需学习GRANT语法.