Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Jak mogę zapobiec wstrzykiwaniu SQL w PHP?

Właściwe sposobem na uniknięcie ataków typu SQL injection, niezależnie od używanej bazy danych, jest oddzielenie danych od SQL , dzięki czemu dane pozostają danymi i nigdy nie będą interpretowane jako polecenia parsera SQL. Możliwe jest utworzenie instrukcji SQL z poprawnie sformatowanymi częściami danych, ale jeśli nie całkowicie zrozumieć szczegóły, zawsze należy używać przygotowanych instrukcji i sparametryzowanych zapytań. Są to instrukcje SQL, które są wysyłane i analizowane przez serwer bazy danych niezależnie od jakichkolwiek parametrów. W ten sposób atakujący nie może wstrzyknąć złośliwego kodu SQL.

Zasadniczo masz dwie możliwości, aby to osiągnąć:

  1. Korzystanie z PDO (dla dowolnego obsługiwanego sterownika bazy danych):

     $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
     $stmt->execute([ 'name' => $name ]);
    
     foreach ($stmt as $row) {
         // Do something with $row
     }
    
  2. Korzystanie z MySQLi (dla 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
     }
    

Jeśli łączysz się z bazą danych inną niż MySQL, istnieje druga opcja specyficzna dla sterownika, do której możesz się odwołać (na przykład pg_prepare() i pg_execute() dla PostgreSQL). PDO to uniwersalna opcja.

Poprawna konfiguracja połączenia

Pamiętaj, że używając PDO aby uzyskać dostęp do bazy danych MySQL rzeczywistej przygotowane oświadczenia domyślnie nie są używane . Aby to naprawić musisz wyłączyć emulację przygotowanych instrukcji. Przykład tworzenia połączenia za pomocą PDO jest:

$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);

W powyższym przykładzie tryb błędu nie jest bezwzględnie konieczny, ale zaleca się jego dodanie . W ten sposób skrypt nie zatrzyma się z Fatal Error kiedy coś pójdzie nie tak. Daje też programiście szansę catch wszelkie błędy, które są throw n jako PDOException s.

Co jest obowiązkowe , jednak jest pierwszym setAttribute() wiersz, który mówi PDO, aby wyłączyć emulowane przygotowane instrukcje i użyć real przygotowanych zestawień. Zapewnia to, że instrukcja i wartości nie są analizowane przez PHP przed wysłaniem ich na serwer MySQL (nie dając potencjalnemu napastnikowi szansy na wstrzyknięcie złośliwego kodu SQL).

Chociaż możesz ustawić charset w opcjach konstruktora należy zauważyć, że „starsze” wersje PHP (przed 5.3.6) po cichu zignorował parametr charset w DSN.

Wyjaśnienie

Instrukcja SQL, którą przekazujesz do prepare jest analizowany i kompilowany przez serwer bazy danych. Określając parametry (albo ? lub nazwany parametr, taki jak :name w powyższym przykładzie) mówisz silnikowi bazy danych, gdzie chcesz filtrować. Następnie, gdy wywołasz execute , przygotowana instrukcja jest łączona z określonymi przez Ciebie wartościami parametrów.

Ważną rzeczą tutaj jest to, że wartości parametrów są łączone ze skompilowaną instrukcją, a nie z ciągiem SQL. Wstrzykiwanie SQL działa poprzez nakłanianie skryptu do włączenia złośliwych ciągów podczas tworzenia kodu SQL do wysłania do bazy danych. Tak więc wysyłając rzeczywisty kod SQL oddzielnie od parametrów, ograniczasz ryzyko uzyskania czegoś, czego nie zamierzałeś.

Wszelkie parametry, które wyślesz podczas korzystania z przygotowanej instrukcji, będą traktowane jako ciągi znaków (chociaż silnik bazy danych może przeprowadzić pewną optymalizację, więc parametry mogą oczywiście również zostać podane jako liczby). W powyższym przykładzie, jeśli $name zmienna zawiera 'Sarah'; DELETE FROM employees wynikiem byłoby po prostu wyszukanie ciągu "'Sarah'; DELETE FROM employees" i nie skończysz z pustą tabelą .

Kolejną korzyścią płynącą z używania przygotowanych instrukcji jest to, że jeśli wykonasz tę samą instrukcję wiele razy w tej samej sesji, zostanie ona przeanalizowana i skompilowana tylko raz, co zapewni Ci pewien wzrost szybkości.

Aha, a skoro pytałeś, jak to zrobić dla wstawki, oto przykład (przy użyciu PDO):

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

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

Czy przygotowane instrukcje mogą być używane w zapytaniach dynamicznych?

Chociaż nadal możesz używać przygotowanych instrukcji dla parametrów zapytania, struktura samego zapytania dynamicznego nie może być sparametryzowana, a niektórych funkcji zapytania nie można sparametryzować.

W przypadku tych konkretnych scenariuszy najlepszą rzeczą do zrobienia jest użycie filtra białej listy, który ogranicza możliwe wartości.

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


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Przykłady ASCII() – MySQL

  2. Jak utworzyć bazę danych MySQL w cPanel?

  3. Jak obliczyć procent kolumny w MySQL?

  4. Nieprawidłowa wartość ciągu:'\xF0\x9F\x8E\xB6\xF0\x9F...' MySQL

  5. Jak skonfigurować automatyczne przełączanie awaryjne dla bazy danych Moodle MySQL?