Pytanie Jak mogę zapobiec iniekcji SQL w PHP?


Jeśli dane wprowadzane przez użytkownika są wstawiane bez modyfikacji do zapytania SQL, wówczas aplikacja staje się podatna na ataki SQL injection, podobnie jak w poniższym przykładzie:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

To dlatego, że użytkownik może wprowadzić coś takiego value'); DROP TABLE table;--, a zapytanie staje się:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Co można zrobić, aby temu zapobiec?


2781


pochodzenie




Odpowiedzi:


Użyj przygotowanych instrukcji i sparametryzowanych zapytań. Są to instrukcje SQL, które są wysyłane i analizowane przez serwer bazy danych niezależnie od parametrów. W ten sposób napastnik nie może wstrzykiwać złośliwego kodu SQL.

Zasadniczo masz dwie opcje, aby to osiągnąć:

  1. Za pomocą CHNP (dla dowolnego obsługiwanego sterownika bazy danych):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. Za pomocą 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, zależna od sterownika, opcja, do której możesz się odnosić (np. pg_prepare() i pg_execute() dla PostgreSQL). PDO jest opcją uniwersalną.

Prawidłowe konfigurowanie połączenia

Należy pamiętać, że podczas korzystania PDO uzyskać dostęp do bazy danych MySQL real przygotowane oświadczenia są nie jest używane domyślnie. Aby to naprawić, musisz wyłączyć emulację przygotowanych instrukcji. Przykład tworzenia połączenia za pomocą PDO to:

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

$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ę go dodać. W ten sposób skrypt nie zakończy się na Fatal Error kiedy coś pójdzie nie tak. Daje to szansę deweloperowi catch wszelkie błędy, które są thrown jako PDOExceptions.

Co jest obowiązkowyjest jednak pierwszy setAttribute() line, która mówi PDO, aby wyłączyć emulowane przygotowane instrukcje i użyć real przygotowane oświadczenia. Dzięki temu oświadczenie i wartości nie są analizowane przez PHP przed wysłaniem go na serwer MySQL (co daje potencjalnemu atakującemu szansę na wstrzyknięcie złośliwego kodu SQL).

Chociaż możesz ustawić charset w opcjach konstruktora należy zauważyć, że "starsze" wersje PHP (<5.3.6) po cichu zignorowano parametr charset w DSN.

Wyjaśnienie

Co się dzieje, jest to, że instrukcja SQL przekazujesz prepare jest analizowany i kompilowany przez serwer bazy danych. Poprzez określenie parametrów (a ? lub nazwany parametr jak :name w powyższym przykładzie) informujesz silnik bazy danych, w którym chcesz filtrować. Potem, kiedy zadzwonisz execute, przygotowana instrukcja jest połączona z określonymi wartościami parametrów.

Ważną rzeczą jest to, że wartości parametrów są łączone ze skompilowaną instrukcją, a nie z ciągiem SQL. SQL injection działa, nakłaniając skrypt do włączenia złośliwych ciągów znaków, gdy tworzy on kod SQL, aby wysłać je do bazy danych. Zatem wysyłając rzeczywisty SQL oddzielnie od parametrów, ograniczasz ryzyko zakończenia z czegoś, czego nie zamierzałeś. Wszelkie parametry wysyłane podczas korzystania z przygotowanej instrukcji będą traktowane tylko jako łańcuchy znaków (chociaż silnik bazy danych może przeprowadzić pewne optymalizacje, aby parametry mogły się również kończyć jako liczby). W powyższym przykładzie, jeśli $namezmienna zawiera 'Sarah'; DELETE FROM employees wynikiem byłoby po prostu wyszukanie ciągu znaków "'Sarah'; DELETE FROM employees", a nie skończysz pusty stolik.

Inną 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 tylko przeanalizowana i skompilowana tylko raz, co da ci pewne przyrosty prędkości.

Aha, a ponieważ pytałeś o to, jak to zrobić dla wstawki, oto przykład (używając PDO):

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

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

Czy przygotowane zapytania mogą być użyte w zapytaniach dynamicznych?

Chociaż nadal można używać gotowych instrukcji dla parametrów zapytania, struktura samej kwerendy dynamicznej nie może zostać sparametryzowana, a niektóre funkcje zapytania nie mogą być parametryzowane.

W przypadku tych konkretnych sytuacji najlepiej jest użyć filtru 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';
}

7954



Aby dodać, ponieważ nie widziałem go nigdzie indziej tutaj, kolejną linią obrony jest zapora aplikacji internetowych (WAF), które mogą mieć reguły, aby szukać ataków sql injection: - jkerak
Oficjalna dokumentacja mysql_query pozwala tylko na wykonanie jednego zapytania, a więc na dowolne inne zapytanie; jest ignorowany. Nawet jeśli jest to już przestarzałe, w PHP 5.5.0 istnieje wiele systemów, które mogą korzystać z tej funkcji. php.net/manual/en/function.mysql-query.php - Randall Valenciano
Jest to zły nawyk, ale jest rozwiązaniem po wprowadzeniu problemu: nie tylko w przypadku iniekcji SQL, ale również w przypadku wszelkiego rodzaju wstrzyknięć (na przykład w przypadku wtyczki do szablonu w v3 zastosowano odwzorowanie widoku szablonu), jeśli masz już gotową starą witrynę lub aplikację cierpi od defektów iniekcyjnych, jednym z rozwiązań jest ponowne przypisanie wartości predefiniowanych początkowych klatek, takich jak $ _POST z wartościami zmienionymi podczas ładowania początkowego. Według PDO nadal można uciec (również dla dzisiejszych frameworków): substr ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1) - Alix
W tej odpowiedzi brakuje wyjaśnienia, co jest przygotowanym oświadczeniem - jedna rzecz - jest to trafienie wydajnościowe, jeśli podczas zgłoszenia masz dużo przygotowanych stwierdzeń, a czasami odpowiada za 10-krotne trafienie skuteczności. Lepszym przypadkiem byłoby użycie PDO z wyłączonym parametrem, ale przygotowanie instrukcji wyłączone. - donis
Używając PDO jest lepiej, w przypadku, gdy używasz bezpośredniego zapytania, użyj mysqli :: escape_string - Kassem Itani


Ostrzeżenie:   Ten przykładowy kod odpowiedzi (jak przykładowy kod zapytania) używa PHP mysql rozszerzenie, które zostało wycofane w PHP 5.5.0 i całkowicie usunięte w PHP 7.0.0.

Jeśli używasz najnowszej wersji PHP, mysql_real_escape_string Opcja opisana poniżej nie będzie już dostępna (chociaż mysqli::escape_string jest nowoczesnym odpowiednikiem). Te dni mysql_real_escape_string opcja ma sens tylko dla starszego kodu na starej wersji PHP.


Masz dwie opcje - unikanie znaków specjalnych w twoim unsafe_variablelub za pomocą sparametryzowanego zapytania. Oba chronią przed iniekcją SQL. Sparametryzowane zapytanie jest uważane za lepszą praktykę, ale będzie wymagało zmiany na nowsze rozszerzenie mysql w PHP, zanim będzie można z niego korzystać.

Omówimy dolny ciąg uderzeniowy, który uszedł pierwszy.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Zobacz także szczegóły mysql_real_escape_string funkcjonować.

Aby użyć sparametryzowanego zapytania, musisz użyć MySQLi niż MySQL Funkcje. Aby przepisać twój przykład, potrzebowalibyśmy czegoś takiego.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

Kluczowa funkcja, którą będziesz chciał przeczytać tam będzie mysqli::prepare.

Ponadto, jak sugerowali inni, może ci się przydać / ułatwić zwiększenie warstwy abstrakcji za pomocą czegoś takiego CHNP.

Przypominamy, że sprawa, o którą pytasz, jest dość prosta i że bardziej złożone przypadki mogą wymagać bardziej złożonych podejść. W szczególności:

  • Jeśli chcesz zmienić strukturę SQL na podstawie danych wprowadzanych przez użytkownika, sparametryzowane zapytania nie pomogą, a wymagana zmiana nie jest objęta mysql_real_escape_string. W takim przypadku lepiej byłoby przekazać dane użytkownika przez białą listę, aby zapewnić, że tylko "bezpieczne" wartości są dozwolone.
  • Jeśli używasz liczb całkowitych z danych wprowadzonych przez użytkownika w warunku i weź mysql_real_escape_string podejście, będziesz cierpieć z powodu problemu opisanego przez Wielomian w komentarzach poniżej. Ta sprawa jest trudniejsza, ponieważ liczby całkowite nie byłyby otoczone cudzysłowami, więc możesz sobie z tym poradzić, sprawdzając, czy dane wprowadzane przez użytkownika zawierają tylko cyfry.
  • Są prawdopodobnie inne przypadki, o których nie wiem. Możesz znaleźć to jest użytecznym źródłem informacji na temat niektórych subtelniejszych problemów, które możesz napotkać.

1514



za pomocą mysql_real_escape_string czy wystarczy, czy muszę też sparametryzować? - peiman F.
@peimanF. Zachowaj dobrą praktykę używania sparametryzowanych zapytań, nawet w lokalnym projekcie. Dzięki sparametryzowanym zapytaniom gwarantowane że nie będzie iniekcji SQL. Należy jednak pamiętać, że należy zdezynfekować dane, aby uniknąć fałszywego pobierania (np. Wstrzyknięcia XSS, na przykład umieszczenia kodu HTML w tekście) htmlentities na przykład - Goufalite
@peimanF. Dobra praktyka dla sparametryzowanych zapytań i wartości wiązania, ale prawdziwy ciąg znaków ucieczki jest na razie dobry - Richard


Każda odpowiedź tutaj obejmuje tylko część problemu.
W rzeczywistości są cztery różne części zapytania, które możemy dodać dynamicznie:

  • ciąg znaków
  • numer
  • identyfikator
  • słowo kluczowe składni.

a przygotowane oświadczenia obejmują tylko 2 z nich

Ale czasami musimy uczynić nasze zapytanie jeszcze bardziej dynamicznym, dodając operatory lub identyfikatory.
Potrzebujemy więc różnych technik ochrony.

Ogólnie rzecz biorąc, takie podejście do ochrony opiera się na Biała lista. W takim przypadku każdy parametr dynamiczny powinien zostać zakodowany w skrypcie i wybrany z tego zestawu.
Na przykład, aby wykonać dynamiczne porządkowanie:

$orders  = array("name","price","qty"); //field names
$key     = array_search($_GET['sort'],$orders)); // see if we have such a name
$orderby = $orders[$key]; //if not, first one will be set automatically. smart enuf :)
$query   = "SELECT * FROM `table` ORDER BY $orderby"; //value is safe

Istnieje jednak inny sposób zabezpieczenia identyfikatorów - ucieczka. Dopóki masz cytowany identyfikator, możesz uciec od tyłu, podwajając je.

Jako kolejny krok możemy pożyczyć naprawdę świetny pomysł użycia jakiegoś symbolu zastępczego (proxy reprezentującego rzeczywistą wartość w zapytaniu) z przygotowanych instrukcji i wynaleźć symbol zastępczy innego typu - symbol zastępczy identyfikatora.

Tak więc, aby zrobić krótką historię: jest to symbol zastępczy, nie przygotowane oświadczenie można uznać za srebrną kulę.

Tak więc ogólne zalecenie może być sformułowane jako
Dopóki dodajesz dynamiczne części do zapytania za pomocą symboli zastępczych (i te symbole zastępcze są prawidłowo przetwarzane), możesz mieć pewność, że twoje zapytanie jest bezpieczne.

Nadal istnieje problem ze słowami kluczowymi składni SQL (takimi jak AND, DESC i takie), ale biała lista wydaje się jedyną metodą w tym przypadku.

Aktualizacja

Chociaż istnieje ogólna zgoda na najlepsze praktyki dotyczące ochrony wstrzykiwania SQL, istnieją wciąż wiele złych praktyk. A niektóre z nich są zbyt głęboko zakorzenione w umysłach użytkowników PHP. Na przykład na tej samej stronie są (choć niewidoczne dla większości odwiedzających) ponad 80 usuniętych odpowiedzi - wszystkie usunięte przez społeczność ze względu na złą jakość lub promowanie złych i przestarzałych praktyk. Co gorsza, niektóre złe odpowiedzi nie są usuwane, ale raczej rozwijają się.

Na przykład, tam (1)  są (2)  wciąż (3)  wiele (4)  odpowiedzi (5), włącznie z druga najgłośniejsza odpowiedź sugerując ręczne przeciąganie stringów - przestarzałe podejście, które okazało się być niezabezpieczone.

Lub jest nieco lepsza odpowiedź, która sugeruje właśnie inna metoda formatowania ciągów a nawet szczyci się nim jako ostatecznym panaceum. Oczywiście tak nie jest. Ta metoda nie jest lepsza od zwykłego formatowania ciągów, ale zachowuje wszystkie jej wady: ma zastosowanie tylko do ciągów znaków i, jak każde inne ręczne formatowanie, jest w zasadzie opcjonalna, nieobowiązkowa, podatna na wszelkiego rodzaju błędy ludzkie.

Myślę, że to wszystko z powodu jednego bardzo starego przesądu, wspieranego przez takie autorytety jak OWASP lub Instrukcja PHP, która głosi równość między "ucieczką" a ochroną przed iniekcjami SQL.

Niezależnie od tego, co instrukcja PHP mówi od wieków, *_escape_string w żaden sposób nie czyni danych bezpiecznymi i nigdy nie było przeznaczone. Poza tym, że jest bezużyteczne dla jakiejkolwiek części SQL innej niż łańcuch, ręczne zamykanie jest złe, ponieważ jest ręczne, przeciwnie niż automatyczne.

A OWASP czyni to jeszcze gorszym, kładąc nacisk na ucieczkę dane wejściowe użytkownika co jest kompletnym nonsensem: nie powinno być takich słów w kontekście ochrony iniekcyjnej. Każda zmienna jest potencjalnie niebezpieczna - bez względu na źródło! Innymi słowy - każda zmienna musi być odpowiednio sformatowana, aby umieścić ją w zapytaniu - bez względu na źródło. Liczy się cel. W momencie, gdy deweloper zaczyna oddzielać owce od kozłów (zastanawiając się, czy jakaś konkretna zmienna jest "bezpieczna" lub nie), robi pierwszy krok w kierunku katastrofy. Nie wspominając już o tym, że nawet sformułowanie sugeruje masowe ucieczkę w punkcie wejścia, przypominające bardzo magiczną funkcję cytowania - już pogardzaną, przestarzałą i usuniętą.

Tak więc, w przeciwieństwie do "uciekających", przygotowanych oświadczeń jest miara, która rzeczywiście chroni przed iniekcją SQL (jeśli dotyczy).

Jeśli nadal nie jesteś przekonany, oto wyjaśnienie krok po kroku, które napisałem, Hitchhiker's Guide to SQL Injection prevention, gdzie szczegółowo wyjaśniłem wszystkie te kwestie, a nawet przygotowałem sekcję poświęconą złym praktykom i ich ujawnieniu.


946



Świetny, przemyślany artykuł. Dodam, że użycie filtrów odkażających w PHP jest (ale nie do końca) białą listą sortów. Na przykład, FILTER_SANITIZE_NUMBER_INT dopuszcza tylko znaki liczbowe, a więc białe znaki, a nie całe ciągi znaków. W połączeniu z przygotowanymi wypowiedziami, stanowi dobre podejście "pas i szelki". - Sablefoste
@Sablefoste nie potrzebujesz tutaj białej listy. Wszelkie sanityzacje będą zbędne. Mniej zasad do naśladowania, mniej błędów popełnisz. Mimo że można wykonać dowolne zatwierdzenia, zrób to ze względu na logikę aplikacji, ale nie dla bazy danych. - Your Common Sense


Zalecam używanie CHNP (PHP Data Objects) do uruchamiania sparametryzowanych zapytań SQL.

Nie tylko chroni to przed iniekcją SQL, ale także przyspiesza zapytania.

I za pomocą PDO zamiast mysql_, mysqli_, i pgsql_ funkcje sprawiają, że twoja aplikacja jest trochę bardziej abstrakcyjna z bazy danych, w rzadkich przypadkach, gdy musisz zmienić dostawców baz danych.


770



czy PDO nie zawiera mysqli dla baz danych MySQL? W takim przypadku na pewno nie może być szybszy niż mysqli. Wciąż jednak polecam. Jest to znacznie lepszy interfejs niż interfejs API mysqli. - Peter Bagnall
Używanie sparametryzowanych zapytań przyspiesza zapytania. Technicznie mysqli może być jeszcze szybszy o bardzo mały margines. Rzeczywisty czas potrzebny na odpowiedź przez serwer eliminuje wszelkie różnice w czasie, które mogą wystąpić, ponieważ używasz opakowania. Ale mysqli jest związany z bazą danych. Jeśli chcesz użyć innego silnika bazy danych, musisz zmienić wszystkie połączenia korzystające z mysqli. Nie dotyczy to ChNP. - Kibbee
PDO nie obsługuje dynamicznego order by Niestety :( - Horse


Posługiwać się PDO i przygotowane zapytania.

($conn jest PDO obiekt)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

567



Od wikipedia: Przygotowane instrukcje są odporne na iniekcję SQL, ponieważ wartości parametrów, które są przesyłane później przy użyciu innego protokołu, nie muszą być poprawnie znakowane. Jeśli oryginalny szablon wyciągu nie pochodzi z zewnętrznego źródła, SQL injection nie może wystąpić. - Imran


Jak widać, ludzie sugerują, że najczęściej używasz przygotowanych oświadczeń. Nie jest źle, ale kiedy twoja kwerenda zostanie wykonana tylko raz w każdym procesie wystąpiłaby niewielka kara za wykonanie.

Stawiłem czoła temu problemowi, ale myślę, że go rozwiązałem bardzo wyrafinowany sposób - sposób, w jaki hakerzy używają, aby uniknąć używania cytatów. Użyłem tego w połączeniu z emulowanymi przygotowanymi instrukcjami. Używam go, aby zapobiec wszystko rodzaje możliwych ataków typu SQL injection.

Moje podejście:

  • Jeśli oczekujesz, że wejście będzie liczbą całkowitą, upewnij się, że jest naprawdę liczba całkowita. W języku zmiennoprzecinkowym, takim jak PHP, jest to bardzo ważny. Możesz użyć na przykład tego bardzo prostego, ale potężnego rozwiązania: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input); 

  • Jeśli spodziewasz się czegoś innego z liczby całkowitej szykuj to. Jeśli ją szesnastkowo, doskonale unikniesz wszystkich danych wejściowych. W C / C ++ jest funkcja o nazwie mysql_hex_string(), w PHP możesz użyć bin2hex().

    Nie przejmuj się, że zmieniony łańcuch znaków będzie miał 2x pierwotną długość, ponieważ nawet jeśli użyjesz mysql_real_escape_string, PHP musi przydzielić tę samą pojemność ((2*input_length)+1), to jest to samo.

  • Ta metoda szesnastkowa jest często używana podczas przesyłania danych binarnych, ale nie widzę powodu, dla którego nie użyłbym go do wszystkich danych, aby zapobiec atakom SQL injection. Zauważ, że musisz poprzedzić dane za pomocą 0x lub użyj funkcji MySQL UNHEX zamiast.

Na przykład zapytanie:

SELECT password FROM users WHERE name = 'root'

Stanie się:

SELECT password FROM users WHERE name = 0x726f6f74

lub

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex to idealna ucieczka. Nie ma możliwości wstrzyknięcia.

Różnica między funkcją UNHEX a prefiksem 0x

W komentarzach była dyskusja, więc w końcu chcę to wyjaśnić. Te dwa podejścia są bardzo podobne, ale pod pewnymi względami różnią się nieco:

Prefiks ** 0x ** może być używany tylko dla kolumn danych, takich jak char, varchar, text, block, binary, etc.
Ponadto jego użycie jest nieco skomplikowane, jeśli masz zamiar wstawić pusty ciąg. Musisz całkowicie go zastąpić '', albo dostaniesz błąd.

UNHEX () działa dalej każdy kolumna; nie musisz się martwić o pusty ciąg.


Metody heksadecymalne są często stosowane jako ataki

Zauważ, że ta metoda heksadecymalna jest często używana jako atak typu SQL injection, gdzie liczby całkowite są podobne do łańcuchów znaków i są unikane mysql_real_escape_string. Wtedy możesz uniknąć używania cytatów.

Na przykład, jeśli po prostu zrobić coś takiego:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

atak może cię bardzo wstrzyknąć z łatwością. Rozważ następujący wstrzyknięty kod zwrócony z twojego skryptu:

SELECT ... WHERE id = -1 połączenie wszystkie wybierz table_name z information_schema.tables

a teraz po prostu wyodrębnij strukturę tabeli:

SELECT ... WHERE id = -1 union wszystkie wybrane nazwa_kolumny z pola informacja_schemat.kolumn gdzie nazwa_tabeli = 0x61727469636c65

A następnie wybierz dowolne dane, które chcesz. Czy to nie jest fajne?

Ale jeśli koder strony wstrzykiwanej mógłby ją sformatować, nie byłoby możliwe żadne wstrzyknięcie, ponieważ zapytanie wyglądałoby tak: SELECT ... WHERE id = UNHEX('2d312075...3635')


501



@SumitGupta Yea, zrobiłeś. MySQL nie łączy się z + ale z CONCAT. A do wydajności: nie sądzę, że ma wpływ na wydajność, ponieważ mysql musi analizować dane i nie ma znaczenia, czy pochodzenie jest łańcuchem, czy szesnastkiem - Zaffy
@ YourCommonSense Jakie błędy napotykasz? Być specyficznym. - Zaffy
@ YouCommonSense Nie rozumiesz pojęcia ... Jeśli chcesz mieć ciąg w mysql, cytujesz go w ten sposób 'root' lub możesz to zrobić heksadecymalnie 0x726f6f74 ALE jeśli chcesz numer i wysłać go jako ciąg, prawdopodobnie napiszesz "42", a nie CHAR (42) ... "42" na heksie będzie 0x3432 nie 0x42 - Zaffy
@ YourCommonSense Nie mam nic do powiedzenia ... po prostu lol ... jeśli nadal chcesz wypróbować hex na polach numerycznych, zobacz drugi komentarz. Założę się, że to zadziała. - Zaffy
@YourCommonSense nadal nie rozumiesz? Nie można używać 0x i konkatować, ponieważ jeśli ciąg znaków jest pusty, zakończy się błędem. Jeśli chcesz prostej alternatywy dla swojego zapytania, wypróbuj tę SELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ') - Zaffy


WAŻNY

Najlepszym sposobem, aby zapobiec SQL Injection jest użycie Przygotowane oświadczenia  zamiast uciekać, tak jak zaakceptowana odpowiedź demonstruje.

Istnieją biblioteki takie jak Aura.Sql i EasyDB które pozwalają programistom łatwiej korzystać z przygotowanych instrukcji. Aby dowiedzieć się więcej o tym, dlaczego przygotowane instrukcje są lepsze zatrzymanie SQL injection, odnosić się do to mysql_real_escape_string() objazd i niedawno naprawiono usterki Unicode SQL Injection w WordPress.

Zapobieganie wtryskom - mysql_real_escape_string ()

PHP ma specjalnie przygotowaną funkcję, aby zapobiec tym atakom. Wszystko, co musisz zrobić, to użyć łyk funkcji, mysql_real_escape_string.

mysql_real_escape_string pobiera ciąg znaków, który zostanie użyty w zapytaniu MySQL i zwróci ten sam ciąg, w którym wszystkie próby wstrzyknięcia SQL zostały bezpiecznie usunięte. Zasadniczo zastąpi te kłopotliwe cytaty ('), które użytkownik może wprowadzić z substytutem bezpiecznym dla MySQL, wyciętą cytat \'.

UWAGA: musisz być podłączony do bazy danych, aby korzystać z tej funkcji!

// Połącz się z MySQL

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

Możesz znaleźć więcej szczegółów w MySQL - SQL Injection Prevention.


455



Jest to najlepsze, co możesz zrobić ze starszym rozszerzeniem mysql. W przypadku nowego kodu zaleca się przełączenie na mysqli lub PDO. - Álvaro González
Nie zgadzam się z tym "specjalnie stworzoną funkcją zapobiegania tym atakom". Myślę, że mysql_real_escape_string celem jest zbudowanie poprawnej kwerendy SQL dla każdego wejściowego ciągu danych. Zapobieganie iniekcji sql jest efektem ubocznym tej funkcji. - sectus
nie używaj funkcji do pisania poprawnych łańcuchów danych wejściowych. Napiszesz tylko te poprawne, które nie potrzebują ucieczki lub zostały już usunięte. mysql_real_escape_string () może być zaprojektowany z myślą o celu, o którym wspominasz, ale jego jedyną wartością jest zapobieganie iniekcjom. - Nazca
OSTRZEŻENIE!  mysql_real_escape_string()  nie jest nieomylny. - eggyal
mysql_real_escape_string jest teraz przestarzałe, więc nie jest już realną opcją. Zostanie usunięty w przyszłości z PHP. Najlepiej przejść na to, co polecają użytkownicy PHP lub MySQL. - jww


Ostrzeżenie bezpieczeństwa: Ta odpowiedź nie jest zgodna z najlepszymi praktykami bezpieczeństwa. Ucieczka jest niewystarczająca, aby zapobiec iniekcjom SQL, posługiwać się przygotowane oświadczenia zamiast. Skorzystaj ze strategii opisanej poniżej na własne ryzyko. (Również, mysql_real_escape_string() został usunięty w PHP 7.)

Możesz zrobić coś podstawowego:

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

To nie rozwiąże każdego problemu, ale jest to bardzo dobry krok. Pominąłem oczywiste elementy, takie jak sprawdzenie istnienia zmiennej, jej format (liczby, litery itp.).


413



Próbowałem twojego przykładu i to działa dobrze dla mnie. Czy możesz wyjaśnić "to nie rozwiąże każdego problemu" - Chinook
Jeśli nie zacytujesz łańcucha, nadal można go wstrzykiwać. Brać $q = "SELECT col FROM tbl WHERE x = $safe_var"; na przykład. Oprawa $safe_var do 1 UNION SELECT password FROM users działa w tym przypadku z powodu braku ofert. Możliwe jest również wstrzykiwanie ciągów do zapytania za pomocą CONCAT i CHR. - Polynomial
@Polynomial Zupełnie w porządku, ale widziałbym to jedynie jako niewłaściwe użycie. Dopóki użyjesz go poprawnie, na pewno zadziała. - glglgl
OSTRZEŻENIE!  mysql_real_escape_string()  nie jest nieomylny. - eggyal
mysql_real_escape_string jest teraz przestarzałe, więc nie jest już realną opcją. Zostanie usunięty w przyszłości z PHP. Najlepiej przejść na to, co polecają użytkownicy PHP lub MySQL. - jww


Cokolwiek użyjesz, upewnij się, że twoje dane wejściowe nie zostały już zmanipulowane magic_quotes lub inne śmieci o dobrym znaczeniu i jeśli to konieczne, przeprowadź je stripslashes lub cokolwiek, żeby go odkażić.


347



W rzeczy samej; uruchomienie z włączonymi magic_quotes po prostu zachęca do złej praktyki. Czasami jednak nie zawsze możesz kontrolować środowisko do tego poziomu - albo nie masz dostępu do zarządzania serwerem, albo twoja aplikacja musi współistnieć z aplikacjami, które (dreszcz) zależą od takiej konfiguracji. Z tych powodów dobrze jest pisać aplikacje przenośne - chociaż oczywiście wysiłek zmarnuje się, jeśli kontrolujesz środowisko wdrażania, np. ponieważ jest to aplikacja wewnętrzna lub może być używana tylko w określonym środowisku. - Rob
Począwszy od PHP 5.4, ohyda znana jako "magiczne cytaty" była zabity martwy. I dobre pozbywanie się złych śmieci. - BryanH