Как разрешить запросы SELECT и предотвратить другие?
В нашем приложении пользователи могут создавать пользовательские функции экспорта в виде операторов SQL. Что-то вроде этого:
SELECT name, age, date_birth FROM users WHERE group_id = 2
Я не хочу, чтобы они очищали всю базу данных, вставив инструкцию DELETE.
Мои идеи были бы следующими:
- Используйте учетную запись SQL, которой разрешено только SELECT. (Я не хочу этого делать, если есть альтернативы.)
- Используйте волшебное регулярное выражение, которое проверяет, является ли запрос опасным или нет. (Это было бы хорошо? Есть ли уже такое регулярное выражение?)
Мы используем PHP PDO.
Ответы
Ответ 1
На мой взгляд, есть три варианта на выбор:
Опция 1
Создайте инструмент, который будет создавать запрос для пользователя в фоновом режиме. Просто нажимая кнопки и вводя имена таблиц. Таким образом, вы можете уловить все странное поведение в фоновом режиме, что избавит вас от опасности для запросов, которые вы не хотите выполнять.
Вариант 2
Создайте пользователя MySQL, которому разрешено выполнять только запросы SELECT
. Я считаю, что вы даже можете решить, из каких таблиц пользователю разрешено выбирать. Используйте этого пользователя для выполнения запросов, которые вводит пользователь. Создайте отдельного пользователя, у которого есть разрешения, которые вы хотите, чтобы он выполнял ваши запросы UPDATE
, INSERT
и DELETE
.
Вариант 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 для предоставления данных вашим пользователям.
Ответ 2
Не делайте этого, всегда будут творческие способы сделать опасный запрос. Создайте API, который будет вручную создавать ваши запросы.
Ответ 3
Поскольку вы сказали, что предпочитаете не использовать учетные записи 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, но на основании вашего примера я не думаю, что это проблема.
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.
Ответ 4
Для меня я предпочитаю использовать учетную запись MYSQL, разрешенную только SELECT.
Если вы хотите регулярное выражение, я думаю, что все SELECT SQL запускаются с помощью "select", любых других? Это мои коды:
$regex = "/^select/i";
if(preg_match($regex,$sql)){
//do your sql
}
Ответ 5
Вы можете проверить, есть ли какой-либо оператор DELETE в запросе по следующему коду.
if (preg_match('/(DELETE|DROP|TRUNCATE)/',strtoupper($query)) == 0){
/*** Run your query here ***/
}
else {
/*** Do something ***/
}
Обратите внимание, что, как указано peter, лучше иметь отдельного пользователя MySql.
Ответ 6
Есть другой метод, если вы используете C API, использование метода mysql_stmt_prepare() позволит вам запросить field_count, который будет ненулевым в операторе select.
Это также позволяет вам использовать правильную подготовку SQL вместо.