В настоящее время я работаю над серверным скриптом NodeJS, который анализирует входящие HTTP-запросы для записи и чтения из базы данных MySQL для работы. Я попытался защитить его от SQL-инъекций, применив своего рода двухуровневую защиту.

Для записи в базу данных пользователю необходимо предоставить действительный JSON. Вот как должны быть ключи JSON, чтобы сценарий мог писать в БД. В противном случае запрос будет отменен.

{
    "uuid": "1234-5678-abcd-efgh",
    "product_id": 6,
    "product_extras": "color=red",
    "product_count": 2,
    "buyer_name": "X Y",
    "shipping_address": "XY Street 5"
}

Первый уровень, который проверяет этот ввод, - это очень простой черный список. Здесь переменная USER_INPUT - это указанный выше JSON, который уже прошел процесс проверки.

let BLACKLIST = ["DROP ", "DELETE ", "INSERT ", "UPDATE ", "SELECT ", "WHERE ", "ALTER "];
let dont_execute = false;

for(var i = 0; i < BLACKLIST.length; i++) {   
    if(JSON.stringify(USER_INPUT).toUpperCase().includes(BLACKLIST[i].toUpperCase())) {
        console.log("\x1b[31mreceived blacklisted command - aborting");
        dont_execute = true;
        return false;
    }
}

После этой проверки, если dont_execute по-прежнему ложно, будет вызван второй уровень, и запрос будет экранирован и отправлен следующим образом:

// setting.sqlconnection.table_name is equal to "orders" in this case. Also this variable can't be changed or specified by the user. It's pulled from a settings.json file

sql.sendQuery("INSERT INTO " + setting.sqlconnection.table_name + " (uuid, productid, orderid, productextras, productcount, buyername, shippingaddress) VALUES (" + sqlconnection.escape(uuid) + ", " + sqlconnection.escape(parseddata.product_id) + ", " + null + ", " + sqlconnection.escape(parseddata.product_extras) + ", " + sqlconnection.escape(parseddata.product_count) + ", " + sqlconnection.escape(parseddata.buyer_name) + ", " + sqlconnection.escape(parseddata.shipping_address) + ")");





Я перепробовала много разных инъекций, таких как:

{
    "uuid": "1234-5678-abcd-efgh",
    "product_id": 6,
    "product_extras": "color=red",
    "product_count": 2,
    "buyer_name": "X Y",
    "shipping_address": "');DROP orders;--"
}

И как я и ожидал, это не сработало, потому что в первую очередь это было обнаружено в первом слое черного списка. Но после отключения его просто для проверки весь SQL-запрос был интерпретирован как строка, поскольку апостроф был экранирован (' стал \'), что как раз то, что я хотел. Тогда, насколько мне известно, запрос, который будет отправлен с зараженным SQLI JSON, в конце будет выглядеть так:

INSERT INTO orders (guid, productid, orderid, productextras, productcount, buyername, shippingaddress) VALUES ("1234-5678-abcd-efgh", 6, null, "color=red", 2, "X Y", "\');DROP orders--")

Но проблема в том, что после того, как я показал это моему боссу, он сказал, что это небезопасно и все еще уязвимо для SQLI. Поэтому мой вопрос в том, прав ли он, и если да, то что я могу сделать, чтобы его улучшить.




Дополнительная информация:
Я использую пакет npm mysql для подключения к базе данных
Я использую XAMPP с MySQL для локального размещения базы данных




Извините, если я не смог предоставить дополнительную информацию, но я почти уверен, что мне не разрешено публиковать больше материалов, чем это.

1
Sv443 12 Ноя 2018 в 13:09

1 ответ

Лучший ответ

Подход денилистов неизбежно упускает из виду некоторые случаи. Вам нужно будет намного больше узнать о том, как формируются запросы, и вы должны написать подробные модульные тесты для своего кода, чтобы любой, кто просматривает ваш код, мог видеть, какие случаи вы тестировали.

Подход денилистов также приведет к ложным срабатываниям. Похоже, что вы не можете вставить какие-либо данные, например, содержащие слово "DROP". Это заблокирует некоторые допустимые значения данных.

Обе эти проблемы решаются с помощью параметризованных запросов, как указано в комментариях выше. Вы сказали, что «взглянете» на использование параметров, но вы должны рассматривать его как основное решение для защиты от SQL-инъекций, а не какое-либо дополнительное или расширенное использование.

Однако параметры полезны только вместо скалярных значений (строк, чисел, дат). Вы не можете использовать параметры для имен таблиц, имен столбцов или других идентификаторов, выражений SQL или ключевых слов SQL. Эти случаи могут быть менее распространенными, но у вас есть хотя бы один пример в имени вашей динамической таблицы.

Вредоносный ввод исходит не только от пользователей. Он может поступать из файлов, из веб-сервисов, из документов JSON. Это может быть даже ваша собственная база данных! Любой контент, который может содержать странные символы, может вызвать SQL-инъекцию.

SQL-инъекция не обязательно вредоносная. Это может быть просто ошибка, которая с большей вероятностью приведет к тому, что ваш SQL-запрос станет недействительным, вместо того, чтобы вызвать утечку данных.

Решением для дополнения параметризованных запросов часто является список разрешений . Например, если вам интересно, является ли имя таблицы из вашего файла конфигурации допустимым, сравните его со списком известных имен таблиц. Некоторые приложения хранят это как постоянный массив. Некоторые приложения запрашивают INFORMATION_SCHEMA, чтобы получить актуальный список таблиц.

Вы когда-нибудь видели таблицу ORDER в базе данных электронной коммерции? Это могло бы запутать SQL, потому что ORDER - зарезервированное слово. Вы должны разделять имена таблиц в обратных тиках на тот случай, если они являются зарезервированными словами SQL или содержат знаки препинания или пробелы.

Вот пример добавления обратных тиков вокруг имени таблицы:

sql.sendQuery("INSERT INTO `" + setting.sqlconnection.table_name + "` (uuid, ...
1
Bill Karwin 9 Ноя 2020 в 22:41