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

时间:2015-05-07 08:34:08

标签: php mysql security

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

SELECT name, age, date_birth FROM users WHERE group_id = 2

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

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

我们正在使用PHP PDO。

7 个答案:

答案 0 :(得分:19)

我认为,有三种选择可供选择:

选项1

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

选项2

创建一个只允许SELECT次查询的MySQL用户。我相信你甚至可以决定允许用户选择哪些表。使用该用户执行用户输入的查询。创建一个单独的用户,该用户拥有您希望其执行UPDATEINSERTDELETE查询的权限。

选项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
    }
}

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

结论

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

答案 1 :(得分:12)

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

答案 2 :(得分: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]);

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

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

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

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

答案 3 :(得分:3)

对我来说,我更喜欢使用仅允许SELECT的MYSQL帐户。

如果你想要一个正则表达式,我认为所有的SELECT SQL都是以" select"开头的,其他的?这些是我的代码:

$regex = "/^select/i";
if(preg_match($regex,$sql)){
    //do your sql
}

答案 4 :(得分:2)

您可以通过以下代码检查查询中是否有任何DELETE语句。

if (preg_match('/(DELETE|DROP|TRUNCATE)/',strtoupper($query)) == 0){
    /*** Run your query here ***/
}
else {
    /*** Do something ***/
}

请注意,彼得指出最好有一个单独的MySql用户。

答案 5 :(得分:1)

我想要一个更安全、更强大的解决方案,它不涉及完全标记化查询。根据我编写 SQL 解析器(herehere)的经验,我可以说这个解决方案非常可靠,无需使用功能齐全的查询解析器。

这是最佳答案,因为:

  • 它不需要具有有限权限的新 SQL 用户
  • 它不需要具有有限权限的新数据库连接
  • 如果查询包含字符串或带有“删除”一词的注释,它不会中断
  • 它允许使用嵌套查询进行复杂查询
  • 它允许用户输入任意 SQL
  • 很安全

截至撰写本文时,所有其他答案都有这些限制中的一个或多个。

这是它的工作原理

  • 删除所有行内和多行注释
  • 删除所有单引号和双引号字符串
  • 删除所有符号和数字
  • 创建一个唯一的剩余(关键字)词数组
  • 如果剩下的任何关键字是 INSERTUPDATEDELETERENAMEDROPCREATE、{{1} }, TRUNCATE, ALTER, COMMIT, ROLLBACK, MERGE, CALL, EXPLAIN, LOCK, GRANTREVOKESAVEPOINTTRANSACTION 则是“危险”查询,不应运行。

注意:

此函数不验证 SQL,它仅确保 SQL 不会以任何方式更改您的数据库。您仍然需要在 try/catch 中运行查询以确保查询有效。

SET

答案 6 :(得分:0)

还有另一种方法,如果您使用的是C API,则使用mysql_stmt_prepare()方法将允许您查询field_count,在select语句中该字段将为非零值。

它还允许您使用适当的sql准备。