sql >> Databáze >  >> RDS >> Mysql

Jak mohu zabránit vkládání SQL v PHP?

Správné způsob, jak se vyhnout útokům SQL injection, bez ohledu na to, jakou databázi používáte, je oddělit data od SQL , takže data zůstanou daty a nebudou nikdy interpretována jako příkazy analyzátoru SQL. Je možné vytvořit SQL příkaz se správně naformátovanými datovými částmi, ale pokud to neuděláte úplně porozumět detailům, měli byste vždy použít připravené příkazy a parametrizované dotazy. Jedná se o příkazy SQL, které jsou odesílány a analyzovány databázovým serverem odděleně od jakýchkoli parametrů. Tímto způsobem je nemožné, aby útočník vložil škodlivý SQL.

V zásadě máte dvě možnosti, jak toho dosáhnout:

  1. Pomocí CHOP (pro jakýkoli podporovaný databázový ovladač):

     $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
     $stmt->execute([ 'name' => $name ]);
    
     foreach ($stmt as $row) {
         // Do something with $row
     }
    
  2. Pomocí MySQLi (pro MySQL):

     $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
     $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
     $stmt->execute();
    
     $result = $stmt->get_result();
     while ($row = $result->fetch_assoc()) {
         // Do something with $row
     }
    

Pokud se připojujete k jiné databázi než MySQL, existuje druhá možnost specifická pro ovladač, na kterou se můžete obrátit (například pg_prepare() a pg_execute() pro PostgreSQL). PDO je univerzální možnost.

Správné nastavení připojení

Pamatujte, že při použití CHOP pro přístup k databázi MySQL skutečné připravené příkazy se ve výchozím nastavení nepoužívají . Chcete-li to opravit, musíte zakázat emulaci připravených příkazů. Příklad vytvoření připojení pomocí PDO je:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');

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

Ve výše uvedeném příkladu není chybový režim nezbytně nutný, ale doporučuje se jej přidat . Tímto způsobem se skript nezastaví s Fatal Error když se něco pokazí. A dává to vývojářům šanci catch všechny chyby, které jsou throw n jako PDOException s.

Co je povinné , je však první setAttribute() řádek, který říká PDO, aby zakázalo emulované připravené příkazy a použilo skutečné připravené výpisy. Tím zajistíte, že příkaz a hodnoty nebudou analyzovány PHP před jejich odesláním na server MySQL (nedává možnému útočníkovi šanci vložit škodlivý SQL).

I když můžete nastavit charset v možnostech konstruktoru je důležité poznamenat, že „starší“ verze PHP (před 5.3.6) tiše ignoroval parametr charset v DSN.

Vysvětlení

Příkaz SQL, který předáte k prepare je analyzován a zkompilován databázovým serverem. Zadáním parametrů (buď ? nebo pojmenovaný parametr jako :name ve výše uvedeném příkladu) sdělíte databázovému stroji, kde chcete filtrovat. Poté, když zavoláte execute , připravený příkaz je zkombinován s hodnotami parametrů, které určíte.

Důležité je, že hodnoty parametrů jsou kombinovány s kompilovaným příkazem, nikoli s řetězcem SQL. SQL injection funguje tak, že při vytváření SQL k odeslání do databáze oklame skript, aby zahrnul škodlivé řetězce. Takže odesláním skutečného SQL odděleně od parametrů omezíte riziko, že skončíte s něčím, co jste nezamýšleli.

Všechny parametry, které odešlete při použití připraveného příkazu, budou považovány pouze za řetězce (ačkoli databázový stroj může provést určitou optimalizaci, takže parametry mohou samozřejmě skončit také jako čísla). Ve výše uvedeném příkladu, pokud $name proměnná obsahuje 'Sarah'; DELETE FROM employees výsledkem by bylo jednoduše hledání řetězce "'Sarah'; DELETE FROM employees" a neskončíte s prázdným stolem .

Další výhodou použití připravených příkazů je, že pokud stejný příkaz provedete mnohokrát ve stejné relaci, bude analyzován a zkompilován pouze jednou, což vám poskytne určité zvýšení rychlosti.

Jo, a protože jste se zeptali, jak to udělat pro vložku, zde je příklad (pomocí PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute([ 'column' => $unsafeValue ]);

Lze připravené příkazy použít pro dynamické dotazy?

I když můžete stále používat připravené příkazy pro parametry dotazu, strukturu samotného dynamického dotazu nelze parametrizovat a nelze parametrizovat některé vlastnosti dotazu.

Pro tyto konkrétní scénáře je nejlepší použít filtr seznamu povolených, který omezuje možné hodnoty.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}


  1. Funkce MySQL PI() – vrátí hodnotu π (pi)

  2. Export databáze MySQL do databáze SQLite

  3. Jak zřetězit řetězce v SQL

  4. Jak zobrazit řazení sloupce v MySQL