Какво е SQL инжектиране и как да се предотврати в PHP приложения?

Значи смятате, че вашата SQL база данни е производителна и защитена от незабавно унищожаване? Е, SQL Injection не е съгласен!

Да, говорим за моментално унищожаване, защото не искам да отварям тази статия с обичайната куца терминология за „затягане на сигурността“ и „предотвратяване на злонамерен достъп“. SQL Injection е толкова стар трик в книгата, че всеки, всеки разработчик, знае за него много добре и е наясно как да го предотврати. С изключение на този един странен момент, когато се подхлъзнат и резултатите могат да бъдат нищо друго освен катастрофални.

Ако вече знаете какво е SQL Injection, не се колебайте да преминете към втората половина на статията. Но за тези, които тепърва прохождат в областта на уеб разработката и мечтаят да поемат по-високи позиции, малко въведение е подходящо.

Какво е SQL инжектиране?

Ключът към разбирането на SQL Injection е в името му: SQL + Injection. Думата „инжекция“ тук няма никакви медицински конотации, а по-скоро е употребата на глагола „инжектирам“. Заедно тези две думи предават идеята за поставяне на SQL в уеб приложение.

Поставяне на SQL в уеб приложение. . . Хммм . . . Не е ли това, което правим все пак? Да, но ние не искаме нападател да управлява нашата база данни. Нека разберем това с помощта на пример.

Да приемем, че изграждате типичен PHP уебсайт за местен магазин за електронна търговия, така че решавате да добавите формуляр за контакт като този:

<form action="record_message.php" method="POST">
  <label>Your name</label>
  <input type="text" name="name">
  
  <label>Your message</label>
  <textarea name="message" rows="5"></textarea>
  
  <input type="submit" value="Send">
</form>

И нека приемем, че файлът send_message.php съхранява всичко в база данни, така че собствениците на магазина да могат да четат потребителските съобщения по-късно. Може да има код като този:

<?php

$name = $_POST['name'];
$message = $_POST['message'];

// check if this user already has a message
mysqli_query($conn, "SELECT * from messages where name = $name");

// Other code here

Така че първо се опитвате да видите дали този потребител вече има непрочетено съобщение. Заявката SELECT * от съобщения, където name = $name изглежда достатъчно проста, нали?

ГРЕШНО!

В нашата невинност ние отворихме вратите за незабавното унищожаване на нашата база данни. За да се случи това, нападателят трябва да отговаря на следните условия:

  • Приложението работи на SQL база данни (днес почти всяко приложение е)
  • Текущата връзка с базата данни има разрешения за „редактиране“ и „изтриване“ на базата данни
  • Имената на важните таблици могат да се познаят

Третата точка означава, че сега, когато атакуващият знае, че управлявате магазин за електронна търговия, вие много вероятно съхранявате данните за поръчките в таблица с поръчки. Въоръжен с всичко това, всичко, което нападателят трябва да направи, е да предостави това като свое име:

  Как да добавите бутон Shazam към вашия център за управление на iPhone

Джо; съкращаване на поръчки;? Да сър! Нека да видим какво ще стане заявката, когато се изпълни от PHP скрипта:

SELECT * FROM съобщения WHERE име = Джо; съкращаване на поръчки;

Добре, първата част от заявката има синтактична грешка (няма кавички около „Джо“), но точката и запетая принуждава MySQL двигателя да започне да интерпретира нова: съкращаване на поръчки. Точно така, с един замах, цялата история на поръчките изчезва!

След като вече знаете как работи SQL Injection, е време да разгледаме как да го спрете. Двете условия, които трябва да бъдат изпълнени за успешно SQL инжектиране са:

  • PHP скриптът трябва да има привилегии за промяна/изтриване на базата данни. Мисля, че това е вярно за всички приложения и няма да можете да направите вашите приложения само за четене. 🙂 И познайте какво, дори ако премахнем всички привилегии за модифициране, SQL инжектирането пак може да позволи на някой да изпълнява SELECT заявки и да преглежда цялата база данни, включително чувствителните данни. С други думи, намаляването на нивото на достъп до базата данни не работи и вашето приложение така или иначе се нуждае от него.
  • Въведеното от потребителя се обработва. Единственият начин, по който SQL инжектирането може да работи, е когато приемате данни от потребители. Още веднъж, не е практично да спирате всички входове за вашето приложение само защото се притеснявате от SQL инжектиране.
  • Предотвратяване на SQL инжектиране в PHP

    Сега, като се има предвид, че връзките към бази данни, заявките и потребителските данни са част от живота, как да предотвратим SQL инжектиране? За щастие, това е доста просто и има два начина да го направите: 1) да дезинфекцирате въведеното от потребителя и 2) да използвате подготвени изрази.

    Дезинфекцирайте въведеното от потребителя

    Ако използвате по-стара версия на PHP (5.5 или по-ниска, и това се случва често при споделен хостинг), е разумно да стартирате целия си потребителски вход чрез функция, наречена mysql_real_escape_string(). По принцип това, което прави, премахва всички специални символи в низ, така че да загубят значението си, когато се използват от базата данни.

    Например, ако имате низ като I’m a string, символът с единични кавички (‘) може да бъде използван от нападател, за да манипулира създаваната заявка към базата данни и да предизвика SQL инжекция. Пускането му през mysql_real_escape_string() произвежда I’m низ, който добавя обратна наклонена черта към единичните кавички, избягвайки го. В резултат на това целият низ сега се предава като безвреден низ към базата данни, вместо да може да участва в манипулирането на заявката.

      Кои функции на монитора за игри всъщност имат значение?

    Има един недостатък на този подход: това е наистина, наистина стара техника, която върви заедно с по-старите форми на достъп до бази данни в PHP. От PHP 7 тази функция дори вече не съществува, което ни води до следващото ни решение.

    Използвайте подготвени твърдения

    Подготвените отчети са начин да направите заявките към базата данни по-безопасни и надеждни. Идеята е, че вместо да изпращаме необработената заявка към базата данни, първо казваме на базата данни структурата на заявката, която ще изпратим. Това е, което имаме предвид под „подготвяне“ на изявление. След като изявлението е подготвено, ние предаваме информацията като параметризирани входове, така че базата данни да може да „запълни празнините“ чрез включване на входовете в структурата на заявката, която изпратихме преди. Това отнема всяка специална сила, която входовете може да имат, карайки ги да бъдат третирани като обикновени променливи (или полезни товари, ако искате) в целия процес. Ето как изглеждат подготвените отчети:

    <?php
    $servername = "localhost";
    $username = "username";
    $password = "password";
    $dbname = "myDB";
    
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    
    // Check connection
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    
    // prepare and bind
    $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $firstname, $lastname, $email);
    
    // set parameters and execute
    $firstname = "John";
    $lastname = "Doe";
    $email = "[email protected]";
    $stmt->execute();
    
    $firstname = "Mary";
    $lastname = "Moe";
    $email = "[email protected]";
    $stmt->execute();
    
    $firstname = "Julie";
    $lastname = "Dooley";
    $email = "[email protected]";
    $stmt->execute();
    
    echo "New records created successfully";
    
    $stmt->close();
    $conn->close();
    ?>

    Знам, че процесът звучи ненужно сложен, ако сте нов в подготвените отчети, но концепцията си заслужава усилието. Ето го хубаво въведение към него.

    За тези, които вече са запознати с PDO разширението на PHP и го използват за създаване на подготвени отчети, имам малък съвет.

    Предупреждение: Бъдете внимателни, когато настройвате PDO

    Когато използваме PDO за достъп до база данни, можем да бъдем всмукани от фалшиво чувство за сигурност. „А, добре, използвам PDO. Сега няма нужда да мисля за нищо друго” — така обикновено протича нашето мислене. Вярно е, че PDO (или изготвени от MySQLi оператори) е достатъчно, за да предотврати всякакви атаки чрез SQL инжектиране, но трябва да внимавате, когато го настройвате. Обичайно е просто да копирате и поставите код от уроци или от предишни проекти и да продължите напред, но тази настройка може да отмени всичко:

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

    Това, което прави тази настройка, е да укаже на PDO да емулира подготвени изрази, вместо действително да използва функцията за подготвени отчети на базата данни. Следователно PHP изпраща прости низове на заявки към базата данни, дори ако вашият код изглежда така, сякаш създава подготвени изрази и настройва параметри и всичко това. С други думи, вие сте толкова уязвими към SQL инжектиране, колкото и преди. 🙂

      Как да използвате функцията за разделен екран на Excel

    Решението е просто: уверете се, че тази емулация е зададена на false.

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    Сега PHP скриптът е принуден да използва подготвени изрази на ниво база данни, предотвратявайки всякакъв вид SQL инжектиране.

    Предотвратяване на използването на WAF

    Знаете ли, че можете също да защитите уеб приложенията от SQL инжектиране с помощта на WAF (защитна стена за уеб приложения)?

    Е, не само SQL инжектиране, но и много други уязвимости на слой 7, като междусайтови скриптове, нарушена автентификация, междусайтово фалшифициране, излагане на данни и т.н. Или можете да използвате самостоятелно хоствано като Mod Security или базирано на облак, както следва.

    SQL инжектиране и модерни PHP рамки

    SQL инжекцията е толкова често срещана, толкова лесна, толкова разочароваща и толкова опасна, че всички съвременни PHP уеб рамки идват с вградени противодействия. В WordPress, например, имаме функцията $wpdb->prepare(), докато ако използвате MVC рамка, тя върши цялата мръсна работа вместо вас и дори не е нужно да мислите за предотвратяване на SQL инжектиране. Малко е досадно, че в WordPress трябва изрично да подготвите отчети, но хей, говорим за WordPress. 🙂

    Както и да е, моята гледна точка е, че съвременната порода уеб разработчици не трябва да мислят за SQL инжектиране и в резултат на това те дори не са наясно с възможността. По този начин, дори ако оставят една задна врата отворена в своето приложение (може би това е $_GET параметър на заявка и стари навици за стартиране на мръсна заявка), резултатите могат да бъдат катастрофални. Така че винаги е по-добре да отделите време, за да се потопите по-дълбоко в основите.

    Заключение

    SQL Injection е много неприятна атака срещу уеб приложение, но лесно се избягва. Както видяхме в тази статия, внимаването при обработката на въведеното от потребителя (между другото, SQL инжектирането не е единствената заплаха, която носи обработката на въведеното от потребителя) и запитването към базата данни е всичко, което трябва да направите. Въпреки това, ние не винаги работим със сигурността на уеб рамка, така че е по-добре да сте наясно с този тип атака и да не се поддавате на нея.

    x