在我们的应用程序中,用户可以以SQL语句的形式创建自定义导出功能。像这样:
SELECT name, age, date_birth FROM users WHERE group_id = 2
我不希望他们通过插入DELETE语句来清除整个数据库。 我的想法是:
我们正在使用PHP PDO。
答案 0 :(得分:19)
我认为,有三种选择可供选择:
创建一个工具,在后台为用户创建查询。只需单击按钮并输入表格名称即可。通过这种方式,您可以在后台捕获所有奇怪的行为,使您不会因为不想执行的查询而处于危险之中。
创建一个只允许SELECT
次查询的MySQL用户。我相信你甚至可以决定允许用户选择哪些表。使用该用户执行用户输入的查询。创建一个单独的用户,该用户拥有您希望其执行UPDATE
,INSERT
和DELETE
查询的权限。
在执行查询之前,请确保其中没有任何有害内容。扫描查询是否有错误的语法。
示例:
// 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 解析器(here 和 here)的经验,我可以说这个解决方案非常可靠,无需使用功能齐全的查询解析器。
截至撰写本文时,所有其他答案都有这些限制中的一个或多个。
INSERT
、UPDATE
、DELETE
、RENAME
、DROP
、CREATE
、{{1} }, TRUNCATE
, ALTER
, COMMIT
, ROLLBACK
, MERGE
, CALL
, EXPLAIN
, LOCK
, GRANT
、REVOKE
、SAVEPOINT
或 TRANSACTION
则是“危险”查询,不应运行。此函数不验证 SQL,它仅确保 SQL 不会以任何方式更改您的数据库。您仍然需要在 try/catch 中运行查询以确保查询有效。
SET
答案 6 :(得分:0)
还有另一种方法,如果您使用的是C API,则使用mysql_stmt_prepare()方法将允许您查询field_count,在select语句中该字段将为非零值。
它还允许您使用适当的sql准备。