Как разрешить запросы 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 вместо.