A najmniejszą pomocą byłaby funkcja, którą opracowałeś. PHP: „Cytaty”. Pisanie zapytań mysql, ukośników, cudzysłowów Wyszukiwanie w kodzie

ciąg znaków cudzysłowu (11)

Próbuję znaleźć najlepszy sposób pisania zapytań. Rozumiem też, jak ważne jest bycie konsekwentnym. Do tej pory losowo korzystałem z pojedynczych cudzysłowów, podwójnych cudzysłowów i odwrotnych znaków, bez większego zastanowienia.

$query = "WSTAW DO tabeli (id, kol1, kol2) WARTOŚCI (NULL, wartość1, wartość2)";

W powyższym przykładzie weź pod uwagę, że „table”, „col[n]” i „val[n]” mogą być zmiennymi.

Jaki jest tego standard? Co robisz?

Czytam odpowiedzi na podobne pytania od około 20 minut, ale wydaje się, że nie ma ostatecznej odpowiedzi na to pytanie.

Odpowiedzi

Załóżmy teraz, że używasz zmiennej postu bezpośredniego w zapytaniu MySQL, a następnie użyj jej w ten sposób:

$query = "WSTAW DO `tabeli` (`id`, `name`, `email`) WARTOŚCI (" ".$_POST["id"]." ", " ".$_POST["nazwa"]." ", " ".$_POST["e-mail"].." ")";

Jest to najlepsza praktyka używania zmiennych PHP w MySQL.

Głównie w Mysql tego typu identyfikatory są używane w zapytaniach ` , " , " i ().

    " lub " użyj, aby dołączyć ciąg znaków jako wartość "01/26/2014 00:00:00" lub "01/26/2014 00:00:00" . Ten identyfikator jest używany tylko w funkcji łańcuchowej „01/26/2014 00:00:00”, takiej jak now() lub sum ,max .

    ` użyj, aby dołączyć tabelę lub tabelę, np. wybierz nazwę_kolumny z nazwy_tabeli, gdzie id = "2"

    () służą jedynie do prostego uwzględnienia części zapytania, na przykład wybierz nazwę_kolumny z nazwa_tabeli, gdzie (id = „2” i płeć = „mężczyzna”) lub nazwa = „rakesh.

Oprócz wszystkich (dobrze wyjaśnionych) odpowiedzi, nie było żadnej wymienionej poniżej i często przychodzę do tych pytań i odpowiedzi.

W skrócie; MySQL myśli, że chcesz zająć się matematyką na własnej tabeli/kolumnie i interpretuj łączniki, takie jak „e-mail”, jako e-mail .

Odmowa odpowiedzialności. Pomyślałem więc, że dodam to jako odpowiedź „FYI” dla tych, którzy są zupełnie nowicjuszami w pracy z bazami danych i którzy mogą nie rozumieć już opisanych terminów technicznych.

(Powyżej znajdują się dobre odpowiedzi dotyczące natury SQL Twojego pytania, ale może to być również istotne, jeśli dopiero zaczynasz korzystać z PHP.)

Warto zauważyć, że PHP inaczej traktuje pojedyncze i podwójne cudzysłowy...

Ciągi ujęte w pojedyncze cudzysłowy to „literały” i jest ich sporo w trybie WYSIWYG. Łańcuchy w cudzysłowie są interpretowane przez PHP w celu ewentualnego zastąpienia zmiennych (odniesienia zwrotne w PHP nie są dokładnie ciągami znaków, wykonują polecenie w powłoce i zwracają wynik).

$foo = "pasek"; echo "jest $foo"; // Jest $foo echo "jest $foo"; // Występuje echo słupkowe `ls -l`; // ... lista katalogów

Jeśli tabele i wartości cols są zmiennymi, istnieją dwa sposoby:

W przypadku podwójnych cudzysłowów „” pełne zapytanie wygląda następująco:

$query = "WSTAW DO $nazwa_tabeli (id, $col1, $col2) WARTOŚCI (NULL, "$val1", "$val2"";

$query = "WSTAW DO ".$nazwa_tabeli." (id, ".$col1.", ".$col2.") WARTOŚCI (NULL, ".$val1.", ".$val2."") ";

Z pojedynczymi cudzysłowami „”:

$query = "WSTAW DO ".$nazwa_tabeli." (id, ".$col1.", ".$col2.") WARTOŚCI (NULL, ".$val1.", ".$val2.")";

Użyj backticków ``, gdy nazwa kolumny/wartości jest podobna do słowa kluczowego zastrzeżonego MySQL.

Notatka. Jeśli określisz nazwę kolumny z nazwą tabeli, użyj backticków w następujący sposób:

`nazwa_tabeli` . `nazwa_kolumny`<- Примечание: исключить. из задних клещей.

Backticków należy używać w przypadku identyfikatorów tabel i kolumn, ale są one potrzebne tylko wtedy, gdy identyfikator jest zarezerwowanym słowem kluczowym MySQL lub gdy identyfikator zawiera spacje lub znaki spoza ograniczonego zestawu (patrz poniżej). Często zaleca się, aby w miarę możliwości unikać używania zastrzeżonych słów kluczowych jako identyfikatorów kolumn lub tabel, aby uniknąć problemu z cytatami.

Pojedyncze cudzysłowy należy stosować w przypadku wartości łańcuchowych, np. na liście WARTOŚCI(). Podwójne cudzysłowy są obsługiwane przez MySQL również dla wartości łańcuchowych, ale pojedyncze cudzysłowy są szerzej akceptowane przez inne RDBMS, dlatego dobrym pomysłem jest używanie pojedynczych cudzysłowów zamiast podwójnych cudzysłowów.

MySQL oczekuje również, że wartości literałów DATE i DATETIME będą ujęte w pojedyncze cudzysłowy jako ciągi znaków, np. „2001-01-01 00:00:00”. Aby uzyskać więcej informacji, zobacz dokumentację dotyczącą daty i godziny, a zwłaszcza alternatywy dla używania łącznika jako separatora segmentów w ciągach dat.

Używając więc twojego przykładu, podwójnie rzuciłbym ciąg PHP i użyłbym pojedynczych cudzysłowów dla wartości „val1”, „val2” . NULL jest słowem kluczowym MySQL i nie jest wartością, dlatego nie jest używane.

Żaden z tych identyfikatorów tabel lub kolumn nie jest słowami zastrzeżonymi ani nie używa znaków wymagających cytowania, ale i tak zacytowałem je od tyłu (więcej o tym później...).

Funkcje związane z RDBMS (takie jak NOW() w MySQL) nie powinny być cytowane, chociaż ich argumenty podlegają tym samym regułom lub regułom cytowania, o których już wspomniano.

Backtick(`) tabela i kolumna ┬──── ┬──┬───────┐ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ $zapytanie = " WSTAW DO `tabeli` (`id`, `col1`, `col2`, `data`, `updated`) WARTOŚCI (NULL, "val1", "val2", "2001-01-01", NOW())"; Słowo kluczowe bez cudzysłowu ─────┴┴┴┘ │ │ │ │ │ │ │││││ Ciągi w pojedynczym cudzysłowie (”) ───────────────┴─ ───┴── ┴────┘ │ │ │││││ Pojedynczy cudzysłów (") DATA ──────────────────────────── ───┴── Funkcja niecytowana ───────────────────────── ───────── ───── ──┴┴┴┴┘

Interpolacja zmienna

Wzorce cytowania zmiennych nie zmieniają się, chociaż jeśli zamierzasz interpolować zmienne bezpośrednio w ciągu znaków, należy je umieścić w podwójnym cudzysłowie w PHP. Tylko upewnij się, że poprawnie ucieczki zmiennych do użycia w SQL. (Zamiast tego zaleca się użycie interfejsu API obsługującego przygotowane instrukcje w celu ochrony przed iniekcją SQL).

// To samo dotyczy zamiany niektórych zmiennych // Tutaj nazwa tabeli zmiennej $table jest ujęta w cudzysłów, a zmienne // na liście WARTOŚCI są ujęte w pojedynczy cudzysłów $query = "INSERT INTO `$stół`(`id`, `col1`, `col2`, `data`) WARTOŚCI (NULL, „$wart1”, „$wart2”, „$data”)";

Przygotowane oświadczenia

Pracując z przygotowanymi zestawieniami, należy zapoznać się z dokumentacją, aby określić, czy należy uwzględnić wypełniacze zestawień. Najpopularniejsze API dostępne w PHP, PDO i MySQLi obejmują nieautoryzowany symbole zastępcze, jak większość przygotowanych interfejsów API instrukcji w innych językach:

// Przykład PDO z nazwanymi parametrami, bez cudzysłowu $query = "WSTAW DO `tabeli` (`id`, `col1`, `col2`, `data`) WARTOŚCI (:id, :col1, :col2, :data)" ; // Przykład MySQLi z ? parametry, bez cudzysłowu $query = "WSTAW DO `tabeli` (`id`, `col1`, `col2`, `data`) WARTOŚCI (?, ?, ?, ?)";

Symbole zwracające odwołanie wstecz w identyfikatorach:

Na przykład:

To samo można zrobić z nazwami tabel i nazwami pól. To jest bardzo dobry zwyczaj jeśli powiążesz identyfikator bazy danych z tylnymi oknami.

Sprawdź tę odpowiedź, aby dowiedzieć się więcej o wnioskach odwrotnych.

Teraz o podwójnych cudzysłowach i pojedynczych cudzysłowach (Michael już o tym wspomniał).

Aby jednak zdefiniować wartość, należy użyć pojedynczych lub podwójnych cudzysłowów. Zobaczmy inny przykład.

WSTAW DO `nazwa_tabeli` (`id, `tytuł`) WARTOŚCI (NULL, tytuł 1);

Tutaj celowo zapomniałem owinąć tytuł 1 w cudzysłów. Serwer zaakceptuje teraz tytuł1 jako nazwę kolumny (tj. identyfikator). Aby więc wskazać, że jest to wartość, należy użyć podwójnych lub pojedynczych cudzysłowów.

WSTAW DO `nazwa_tabeli` (`id, `tytuł`) WARTOŚCI (NULL, "tytuł1");

Teraz, w połączeniu z PHP, cudzysłowy i pojedyncze cudzysłowy znacznie ułatwiają pisanie zapytań. Przyjrzyjmy się zmodyfikowanej wersji zapytania w Twoim pytaniu.

$query = "WSTAW DO `tabeli` (`id`, `col1`, `col2`) WARTOŚCI (NULL, "$val1", "$val2"");

Teraz, używając podwójnych cudzysłowów w PHP, sprawisz, że zmienne $val1 i $val2 użyją swoich wartości, tworząc w ten sposób prawidłowe zapytanie. tak jak

$val1 = „moja wartość 1”; $val2 = „moja wartość 2”; $query = "WSTAW DO `tabeli` (`id`, `col1`, `col2`) WARTOŚCI (NULL, "$val1", "$val2"");

WSTAW DO `tabeli` (`id`, `col1`, `col2`) WARTOŚCI (NULL, „moja wartość 1”, „moja wartość 2”)

Było tu wiele pomocnych odpowiedzi, których kulminacją były zazwyczaj dwa punkty.

  1. TYŁY (`) są używane wokół nazw identyfikatorów.
  2. POJEDYNCZE CYTATY (") są używane wokół wartości.

I jak powiedział @MichaelBerkowski

Backticków należy używać w przypadku identyfikatorów tabel i kolumn, ale są one potrzebne tylko wtedy, gdy identyfikator jest zarezerwowanym słowem kluczowym MySQL lub gdy identyfikator zawiera spacje lub znaki spoza ograniczonego zestawu (patrz poniżej). Często zaleca się, aby w miarę możliwości unikać używania zastrzeżonych słów kluczowych jako identyfikatorów kolumn lub tabel, aby uniknąć problemu z cytatami.

Istnieje przypadek, w którym identyfikator nie może być zastrzeżone słowo kluczowe lub zawierać białe znaki Lub znaków spoza ograniczonego zestawu ale zdecydowanie wymagają linków zwrotnych wokół nich.

123E10 jest poprawną nazwą identyfikatora, ale także prawidłowym literałem INTEGER.

[Bez wchodzenia w szczegóły, jak uzyskać taką nazwę identyfikatora] Powiedzmy, że chcę utworzyć tabelę tymczasową o nazwie 123456e6.

Brak BŁĘDU w przypadku backticków.

DB > utwórz tabelę tymczasową `123456e6` (`id` char (8)); Zapytanie OK, wpływ na 0 wierszy (0,03 s)

BŁĄD, jeśli nie używasz wywołań zwrotnych.

DB > utwórz tabelę tymczasową 123451e6 („id” char (8)); BŁĄD 1064 (42000): Wystąpił błąd w składni SQL; sprawdź instrukcję odpowiadającą Twojej wersji serwera MariaDB, aby znaleźć właściwą składnię do użycia w pobliżu „123451e6 (`id` char (8))” w linii 1

Jednak 123451a6 to dobra nazwa identyfikacyjna (bez backticków).

DB > utwórz tabelę tymczasową 123451a6 („id” char (8)); Zapytanie OK, wpływ na 0 wierszy (0,03 s)

Dzieje się tak całkowicie dlatego, że 1234156e6 jest również liczbą wykładniczą.

Pojedyncze cudzysłowy należy stosować w przypadku wartości łańcuchowych, np. na liście WARTOŚCI().

Backticki są zwykle używane do wskazania identyfikatora i mogą być również bezpieczne ze względu na sporadyczne użycie zastrzeżonych słów kluczowych.

W połączeniu z PHP i MySQL cudzysłowy i pojedyncze cudzysłowy znacznie upraszczają czas pisania zapytań.

W MySQL istnieją dwa typy cudzysłowów:

  1. ", aby uwzględnić literały łańcuchowe
  2. `, aby uwzględnić identyfikatory, takie jak nazwy tabel i kolumn

A potem jest „to szczególny przypadek. Można go wykorzystać jeden powyższych celów na raz, w zależności od sql_mode serwera:

  1. Domyślny„znak” może być używany do zagnieżdżania literałów łańcuchowych „
  2. W trybie ANSI_QUOTES " symbol może być używany do dołączania identyfikatorów ANSI_QUOTES

Poniższe zapytanie zwróci różne wyniki (lub błędy) w zależności od trybu SQL:

WYBIERZ „kolumnę” Z tabeli GDZIE foo = „bar”

ANSI_QUOTES wyłączone

Zapytanie wybierze literał ciągu „kolumnę”, gdzie kolumna foo jest równa ciągowi „bar”

Włączono ANSI_QUOTES

Zapytanie wybierze kolumnę kolumnową, gdzie kolumna foo jest równa kolumnie

Kiedy użyć

  • Sugeruję unikanie używania „, aby Twój kod nie zależał od trybów SQL
  • Zawsze dołączaj identyfikatory, ponieważ jest to dobra praktyka (kilka pytań na temat SO omawia tę kwestię)

Istnieje wyraźna różnica pomiędzy użyciem „” i „”.

Kiedy w całym tekście używane jest „ ”, nie ma mowy o „transformacji ani tłumaczeniu”. Jest wydrukowany tak, jak jest.

Dzięki „ ” wszystko, co otacza, zostaje „przetłumaczone lub przekształcone” na jego wartość.

Mam na myśli tłumaczenie/konwersję: wszystko zawarte w pojedynczych cudzysłowach nie zostanie „przetłumaczone” na ich wartości. Zostaną zaakceptowane, ponieważ znajdują się w cudzysłowie. Przykład: a=23 , następnie echo "$a" wygeneruje $a na standardowym wyjściu. Podczas gdy echo „$a” wygeneruje 23 na standardowym wyjściu.

(PHP 4 >= 4.3.0, PHP 5)

mysql_real_escape_string — Ucieka od znaków specjalnych w ciągu znaków do użycia w instrukcji SQL

Opis

mysql_real_escape_string (ciąg $unescaped_string [, zasób $link_identifier = NULL]): strunowy

Usuwa znaki specjalne w unescaped_string , biorąc pod uwagę bieżący zestaw znaków połączenia, dzięki czemu można bezpiecznie umieścić go w mysql_query(). Jeśli mają zostać wstawione dane binarne, należy skorzystać z tej funkcji.

mysql_real_escape_string() wywołuje funkcję biblioteczną MySQL mysql_real_escape_string, która dodaje ukośniki odwrotne do następujących znaków: \x00, \N, \R, \ , " , " I \x1a.

Ta funkcja musi być zawsze (z nielicznymi wyjątkami) używana, aby zabezpieczyć dane przed wysłaniem zapytania do MySQL.

Ostrożność

Bezpieczeństwo: domyślny zestaw znaków

Zestaw znaków należy ustawić na poziomie serwera lub za pomocą funkcji API mysql_set_charset()żeby to miało wpływ mysql_real_escape_string() . Aby uzyskać więcej informacji, zobacz sekcję dotyczącą pojęć dotyczącą zestawów znaków.

Parametry

ciąg_ucikowy

Ciąg znaków, który ma zostać zmieniony.

Identyfikator_linku

Połączenie MySQL. Jeśli identyfikator łącza nie jest określony, ostatni link otwarty przez mysql_connect() zakłada się. Jeśli nie zostanie znalezione takie łącze, spróbuje je utworzyć mysql_connect() został wywołany bez argumentów. Jeśli nie zostanie znalezione lub nawiązane żadne połączenie, an E_OSTRZEŻENIE generowany jest błąd poziomu.

Zwracane wartości

Zwraca ciąg znaków ucieczki lub FAŁSZ na błędzie.

Błędy/wyjątki

Wykonanie tej funkcji bez połączenia MySQL również spowoduje emisję E_OSTRZEŻENIE Poziom błędów PHP. Wykonuj tę funkcję tylko przy obecności prawidłowego połączenia MySQL.

Przykłady

Przykład nr 1 Prosty mysql_real_escape_string() przykład

//Łączyć
$link = mysql_connect("mysql_host" , "mysql_user" , "mysql_password" )
LUB umrzyj(mysql_error());

//Zapytanie
$zapytanie = sprintf ( „WYBIERZ * OD użytkowników GDZIE użytkownik="%s" ORAZ hasło="%s"",
mysql_real_escape_string($użytkownik),
mysql_real_escape_string($hasło));
?>

Przykład nr 2 mysql_real_escape_string() wymaga przykładu połączenia

Ten przykład pokazuje, co się stanie, jeśli podczas wywoływania tej funkcji nie będzie połączenia MySQL.

Powyższy przykład wyświetli coś podobnego do:

Ostrzeżenie: mysql_real_escape_string(): Brak takiego pliku lub katalogu w /this/test/script.php w linii 5 Ostrzeżenie: mysql_real_escape_string(): Nie można ustanowić łącza do serwera w /this/test/script.php w linii 5 bool(false) string(41) "WYBIERZ * OD aktorów GDZIE nazwisko = """

Przykład nr 3 Przykładowy atak typu SQL Injection

// Nie sprawdziliśmy $_POST["hasło"], może to być cokolwiek, czego chce użytkownik! Na przykład:
$_POST [ „nazwa użytkownika” ] = „aidan”;
$_POST ["hasło"] = "" LUB ""="" ;

// Zapytanie do bazy danych, aby sprawdzić, czy istnieją pasujący użytkownicy
$query = ( $_POST [ "nazwa użytkownika" ]) " AND hasło=" ( $_POST [ "hasło" ]) "";
mysql_query($zapytanie);

// Oznacza to, że zapytanie wysłane do MySQL będzie brzmiało:
echo $zapytanie;
?>

Zapytanie wysłane do MySQL:

Umożliwiłoby to każdemu zalogowanie się bez ważnego hasła.

Notatki

Przed użyciem wymagane jest połączenie MySQL mysql_real_escape_string() w przeciwnym razie błąd poziomu E_OSTRZEŻENIE jest generowany i FAŁSZ jest zwracany. Jeśli identyfikator_linku nie jest zdefiniowany, używane jest ostatnie połączenie MySQL.

Notatka: mysql_real_escape_string() nie ucieka % I _ . Są to symbole wieloznaczne w MySQL, jeśli są połączone z TAK JAK, DOTACJA, Lub UNIEWAŻNIĆ.

8 lat temu

Tylko mała funkcja, która naśladuje oryginalny mysql_real_escape_string, ale która nie wymaga aktywnego połączenia mysql.Można ją zaimplementować jako funkcję statyczną w klasie bazy danych.Mam nadzieję, że komuś to pomoże.

funkcja mysql_escape_mimic ($inp) (
if(is_array($inp))
zwróć mapę_tablicy (__METODA__, $inp);

If(!empty($inp ) && is_string ($inp )) (
return str_replace (array("\\" , "\0" , "\n" , "\r" , """ , """ , "\x1a" ), array("\\\\" , "\ \0" , "\\n" , "\\r" , "\\"" , "\\"" , "\\Z" ), $inp );
}

Zwróć $inp ;
}
?>

13 lat temu

Należy zauważyć, że mysql_real_escape_string nie dodaje ukośników odwrotnych do \x00, \n, \r i \x1a, jak wspomniano w dokumentacji, ale w rzeczywistości zastępuje ten znak akceptowalną reprezentacją MySQL dla zapytań (np. \n jest zastępowane przez „\ n" literał). (\, " i " są oznaczone znakami ucieczki, zgodnie z dokumentacją) Nie zmienia to sposobu, w jaki powinieneś używać tej funkcji, ale myślę, że warto wiedzieć.

6 lat temu

Żadna dyskusja na temat ucieczki nie jest kompletna bez poinformowania wszystkich, że zasadniczo nigdy nie należy używać zewnętrznych danych wejściowych do generowania zinterpretowanego kodu. Dotyczy to instrukcji SQL lub czegokolwiek, co można nazwać dowolną funkcją „eval”.

Zamiast więc używać tej strasznie zepsutej funkcji, użyj zamiast tego przygotowanych instrukcji parametrycznych.

Szczerze mówiąc, wykorzystywanie danych dostarczonych przez użytkownika do tworzenia instrukcji SQL należy uznać za zaniedbanie zawodowe i pracodawca lub klient powinien pociągnąć Cię do odpowiedzialności za niestosowanie przygotowanych instrukcji parametrycznych.

Co to znaczy?

Oznacza to, że zamiast budować instrukcję SQL w następujący sposób:

„WSTAWIĆ WARTOŚCI X (A).(.$_POST[„a”].)”

Powinieneś użyć funkcji przygotowania() () mysqli, aby wykonać instrukcję wyglądającą tak:

„WSTAWIĆ WARTOŚCI X (A) (?)”

Uwaga: nie oznacza to, że nigdy nie powinieneś generować dynamicznych instrukcji SQL. Oznacza to, że nigdy nie powinieneś używać danych dostarczonych przez użytkownika do generowania tych instrukcji. Wszelkie dane dostarczone przez użytkownika powinny zostać przekazane jako parametry do instrukcji po jej zostało przygotowane.

Na przykład, jeśli budujesz mały framework i chcesz wstawić do tabeli na podstawie identyfikatora URI żądania, w twoim najlepszym interesie jest nie przyjmowanie wartości $_SERVER["REQUEST_URI"] (ani żadnej innej jego część) i bezpośrednio połączyć to z zapytaniem. Zamiast tego powinieneś wyodrębnić żądaną część wartości $_SERVER["REQUEST_URI"] i zmapować ją za pomocą jakiejś funkcji lub tablicy asocjacyjnej na wartość niebędącą użytkownikiem podana wartość. Jeśli mapowanie nie generuje żadnej wartości, wiesz, że coś jest nie tak z danymi dostarczonymi przez użytkownika.

Niezastosowanie się do tego było przyczyną szeregu problemów z wstrzykiwaniem SQL w frameworku Ruby On Rails, mimo że wykorzystuje on przygotowane parametrycznie instrukcje. W ten sposób w pewnym momencie zhakowano GitHub. Zatem żaden język nie jest odporny na ten problem. Dlatego jest to ogólna najlepsza praktyka, a nie coś specyficznego dla PHP i dlatego NAPRAWDĘ powinieneś ją zastosować.

Ponadto nadal powinieneś przeprowadzać jakąś weryfikację danych dostarczonych przez użytkowników, nawet jeśli używasz przygotowanych zestawień parametrycznych. Dzieje się tak dlatego, że dane dostarczone przez użytkownika często stają się częścią wygenerowanego kodu HTML, a Ty chcesz mieć pewność, że dane dostarczone przez użytkownika nie spowodują problemów z bezpieczeństwem w przeglądarce.

9 lat temu

W przykładzie nr 2 jest interesujące dziwactwo dotyczące wstrzyknięcia SQL: AND ma pierwszeństwo przed OR, więc wstrzyknięte zapytanie faktycznie jest wykonywane jako WHERE (user="aidan" AND hasło="") OR ""="", więc zamiast tego zwrócenie rekordu bazy danych odpowiadającego dowolnej nazwie użytkownika (w tym przypadku „aidan"), w rzeczywistości zwróci WSZYSTKIE rekordy bazy danych. W dowolnej kolejności. Zatem osoba atakująca może zalogować się na dowolne konto, ale niekoniecznie na dowolne kontrolę nad tym, które to konto.

Oczywiście potencjalny atak może po prostu zmodyfikować ich parametry, aby atakować konkretnych użytkowników:

//Np. wartości atakującego
$_POST [ „nazwa użytkownika” ] = „”;
$_POST["hasło"] = "" LUB użytkownik = "administrator" ORAZ "" = "";

// Źle sformułowane zapytanie
$zapytanie = "WYBIERZ * OD użytkowników GDZIE użytkownik="$_POST [nazwa użytkownika] "AND hasło=" $_POST [hasło] "";

echo $zapytanie;

// Zapytanie wysłane do MySQL będzie brzmiało:
// WYBIERZ * OD użytkowników GDZIE użytkownik="" ORAZ hasło="" LUB użytkownik="administrator" ORAZ ""="";
// co umożliwiłoby każdemu uzyskanie dostępu do konta o nazwie "administrator"

?>

1 rok temu

@feedr
Rozwinąłem jego notatkę w następujący sposób:
$string = "asda\0sd\x1aas\\\\\\\\dasd\"asdasd\na\"\"sdasdad";
$array1 = array("\\\\\\\\\", "\0", "\n", "\r", """, """, "\x1a");
$array2 = array("\\\\\\\\\\\\\\\\\\", "\\\0", "\\\n", "\\\r", "\\\ " ", "\\\"", "\\\Z");
echo($ciąg);
echo(PHP_EOL);
dla($i=0; $i jeśli ($i==0)
$p = "/(?w przeciwnym razie
$p = "/(?echo($i);
echo($p);
echo($tablica2[$i]);
$string = preg_replace($p, $tablica2[$i], $string);
echo("\t");
echo($ciąg);
echo(PHP_EOL);
}
echo(PHP_EOL);
echo($ciąg);

2 lata temu

Cytując Sama z Numb Safari

[ „Żadna dyskusja na temat ucieczki nie jest kompletna bez poinformowania wszystkich, że w zasadzie nigdy nie należy używać zewnętrznych danych wejściowych do generowania zinterpretowanego kodu. Dotyczy to instrukcji SQL lub czegokolwiek, co można by nazwać jakąkolwiek funkcją „eval”.

Zamiast więc używać tej strasznie zepsutej funkcji, użyj zamiast tego przygotowanych instrukcji parametrycznych.

Szczerze mówiąc, używanie danych dostarczonych przez użytkownika do tworzenia instrukcji SQL powinno zostać uznane za zaniedbanie zawodowe i powinieneś zostać pociągnięty do odpowiedzialności przez swojego pracodawcę lub klienta za nieużywanie przygotowanych instrukcji parametrycznych." ]

Sam ma rację............

Jednakże nie sądzę, że rozsądne jest przerywanie całego czyszczenia i po prostu przekazanie tego zadania parametrycznym przygotowanym oświadczeniom.

Konkretny programista pracujący w określonej sytuacji zawsze będzie wiedział więcej na temat prawidłowych danych wejściowych (specyficznych dla tego kontekstu).

Jeśli poprosisz użytkownika o podanie wartości, którą już mu podałeś i wiesz, że wszystkie takie wartości zaczynają się od AB******, a ciąg powinien mieć długość 7 lub 11, ale nigdy innej długości, to masz podstawa dobrego narzędzia do wstępnej dezynfekcji — różne dopuszczalne długości ciągu mogą wskazywać na starsze dane.

Nigdy nie chciałbym po prostu przekazywać śmieci, które złośliwy użytkownik mógł przekazać za pośrednictwem formularza, do parametrycznych przygotowanych instrukcji. Zawsze wolałbym najpierw przeprowadzić własną kontrolę poprawności, a w niektórych przypadkach może to być błędem ostrożności i po prostu całkowicie przerwij operację bazy danych.

W ten sposób moja baza danych nie zostanie zatkana niebezpiecznymi instrukcjami i będzie bezpieczna - po prostu nie zostanie zatkana, co jest lepsze.

Bezpieczeństwo warstwowe – w każdej sytuacji należy rozważyć sanityzację i walidację PRZED użyciem przygotowanych zestawień.

Ponadto, o ile mogę przeczytać w oficjalnym dokumencie
==============================================

„Ucieczka i wstrzyknięcie SQL

Powiązane zmienne są wysyłane do serwera niezależnie od zapytania i dlatego nie mogą na niego wpływać. Serwer wykorzystuje te wartości bezpośrednio w momencie wykonania, po przeanalizowaniu szablonu wyciągu. Powiązane parametry nie muszą być zmieniane, ponieważ nigdy nie są bezpośrednio zastępowane w ciągu zapytania”

Sugeruje mi to, że niebezpieczeństw wewnętrznych można uniknąć poprzez alternatywne postępowanie, a nie przez unieważnienie.

Oznacza to, że duży projekt z niepełną konwersją na przygotowane zestawienia, starszy kod w różnych częściach organizacji lub serwery komunikujące się ze sobą mogą spowodować przekazanie złych wiadomości z niezabezpieczonej lokalizacji lub sytuacji do takiej, która nie jest odporna.

Dopóki odkażanie zostanie przeprowadzone kompetentnie, bez ponoszenia dodatkowego ryzyka, osobiście trzymałbym się pewnych warstw odkażania, a następnie wywołałbym przygotowane oświadczenia.


Najpierw trochę o tym, dlaczego te ukośniki są potrzebne w ogóle.
Jeżeli do zapytania podstawimy jakiekolwiek dane, to aby odróżnić te dane od poleceń SQL, należy je umieścić w cudzysłowie.
Na przykład, jeśli piszesz
WYBIERZ * Z tabeli GDZIE imię = Bill
wtedy baza danych uzna, że ​​Bill to nazwa innego pola, nie znajdzie jej i zgłosi błąd. Dlatego też podstawione dane (w tym przypadku imię Billa) należy ująć w cudzysłów – wówczas baza danych potraktuje je jako ciąg znaków, którego wartość należy przypisać do pola name:
WYBIERZ * Z tabeli GDZIE imię = „Rachunek”
Cytaty mogą jednak pojawiać się także w samych danych. Np,
WYBIERZ * Z tabeli GDZIE imię = "D"Artagnan"
Tutaj baza danych uzna, że ​​„D” to dane, a Artagnan to polecenie, którego nie zna, i również zgłosi błąd. Dlatego konieczne jest prześledzenie wszystkich danych, aby wyjaśnić bazie danych, że znalezione w nich cudzysłowy (i inne znaki specjalne) odnoszą się do danych.
W rezultacie otrzymamy poprawne żądanie, które nie spowoduje błędów:
WYBIERZ * Z tabeli GDZIE nazwa = "D\Artagnan"

Tym samym dowiedzieliśmy się, że podstawiając w zapytaniu dane stringowe należy kierować się dwiema zasadami:
- wszystkie wstawione dane ciągu muszą być ujęte w cudzysłów (pojedynczy lub podwójny, ale pojedyncze są wygodniejsze i częściej używane).
- znaki specjalne muszą być poprzedzone ukośnikami.

Należy szczególnie zaznaczyć: dodane ukośniki NIE trafiają do bazy danych. Są one potrzebne jedynie w żądaniu. Po uderzeniu w bazę, cięcia są odrzucane. W związku z tym częstym błędem jest używanie ukośników podczas pobierania danych z bazy danych.

Wszystkie powyższe dotyczą danych ciągowych i dat. Liczby można wstawiać bez ich kończenia lub otaczania cudzysłowami. Jeśli to zrobisz to KONIECZNIE! wymuś dane do żądanego typu przed wstawieniem ich do zapytania, na przykład:
$id = okres ($id);
Jednak dla uproszczenia (i niezawodności) możesz pracować z liczbami tak samo, jak z ciągami znaków (ponieważ mysql nadal konwertuje je na żądany typ). W związku z tym prześledzimy wszelkie dane wprowadzone do żądania i umieścimy je w cudzysłowie.

Jest jeszcze jedna zasada - opcjonalna, ale należy jej przestrzegać, aby uniknąć błędów:
Nazwy pól i tabel należy ująć w tylny cudzysłów - „`” (klawisz z tym symbolem znajduje się na standardowej klawiaturze po lewej stronie klawisza „1”). Wszak nazwa pola może pokrywać się z mysql słowa kluczowe, ale jeśli użyjemy cudzysłowu, MySQL zrozumie, że wszystko jest w porządku:
WYBIERZ * Z `tabeli` GDZIE `data` = "2006-04-04"
Należy rozróżnić te cudzysłowy i nie mylić ich ze sobą. Powinieneś także pamiętać, że ukośników nie można uniknąć ukośnikami.

Dowiedzieliśmy się więc, jak poprawnie zastąpić dane w żądaniu.
ALE! Dynamiczna konstrukcja zapytań nie ogranicza się do podstawienia danych. Często musimy podstawić w zapytaniu polecenia SQL i nazwy pól. I tu przechodzimy do tematu bezpieczeństwa:

SQL Injection to metoda ataku hakerskiego polegająca na modyfikacji danych przesłanych do skryptu w taki sposób, że zapytanie wygenerowane w tym skrypcie zaczyna wykonywać coś zupełnie innego niż było przeznaczone.
Zasady ochrony przed takimi atakami można podzielić na dwa punkty:
1. Praca z danymi.
2. Praca z kontrolkami zapytań.

Pierwszy punkt szczegółowo omówiliśmy powyżej. Można powiedzieć, że w istocie nie jest to obrona. Spełnienie zasad dodawania danych do zapytania podyktowane jest przede wszystkim wymogami SKŁADNI SQL. A jako efekt uboczny mamy również ochronę przed hakerami.

Drugi punkt jest znacznie trudniejszy, ponieważ nie ma jednej uniwersalnej zasady dotyczącej danych - backtick nie uchroni nazwy pola przed modyfikacją przez hakera. Nie można używać cudzysłowów do ochrony nazw tabel, instrukcji SQL, parametrów polecenia LIMIT i innych instrukcji.
Dlatego podstawową zasadą przy podstawiania elementów sterujących do zapytania jest:
Jeżeli zachodzi potrzeba dynamicznego wstawiania do zapytania instrukcji SQL lub nazw pól, baz danych, tabel, to pod żadnym pozorem nie należy ich wstawiać bezpośrednio do zapytania.
Wszystkie opcje takich dodatków muszą być zapisane z wyprzedzeniem w skrypcie i wybrane na podstawie tego, co wprowadził użytkownik.
Przykładowo, jeśli chcesz przekazać nazwę pola do zamówienia przez operatora, to pod żadnym pozorem nie zastępuj jej bezpośrednio. Musimy to najpierw sprawdzić. Na przykład utwórz tablicę prawidłowych wartości i podstaw ją w żądaniu tylko wtedy, gdy w tej tablicy znajduje się przekazany parametr:
$zamówienia =array("nazwa", "cena", "ilość");
$key = array_search($_GET["sort"], $zamówienia));
$orderby = $zamówienia [ $key ];
$zapytanie = "WYBIERZ * Z `tabeli` ZAMÓW PRZEZ$zamówienie";

Przeszukujemy tablicę wcześniej opisanych opcji pod kątem wprowadzonego przez użytkownika słowa i jeśli je znajdziemy, wybieramy odpowiedni element tablicy. Jeśli nie zostanie znalezione żadne dopasowanie, wybrany zostanie pierwszy element tablicy.
Zatem w żądaniu podstawiane jest nie to, co wprowadził użytkownik, ale to, co zostało zapisane w naszym skrypcie.
To samo należy zrobić we wszystkich innych przypadkach.
Na przykład, jeśli klauzula WHERE jest generowana dynamicznie:
if (!empty($_GET [ "cena" ])) $where .= "cena="" .mysql_real_escape_string ($_GET [ "cena" ]). """ ;
$query = "WYBIERZ * Z `tabeli` GDZIE $gdzie ";

Trudno mi sobie wyobrazić przypadek, w którym nazwę tabeli można wstawić do zapytania dynamicznie, ale jeśli tak się stanie, to nazwę również trzeba wstawić tylko ze zbioru predefiniowanego w skrypcie.
Parametry operatora LIMIT należy wymusić na typ całkowity za pomocą operacji arytmetycznych lub funkcji intval().
Nie myśl, że podane tutaj przykłady wyczerpują wszystkie możliwości dynamicznej konstrukcji zapytań. Wystarczy zrozumieć tę zasadę i zastosować ją we wszystkich takich przypadkach.

Ze względu na charakter mojej pracy muszę wykonywać audyty bezpieczeństwa kodu źródłowego aplikacji webowych.
Wiele aplikacji internetowych i dużo kodu...

Nie jest tajemnicą, że luki w zabezpieczeniach polegające na wstrzykiwaniu SQL są najczęstszymi ze wszystkich luk w aplikacjach internetowych po stronie serwera. Istnieją platformy i frameworki, gdzie takie rzeczy są prawie całkowicie wykluczone, np. ORM itp. Jednak statystyki uparcie mówią nam o absolutnej przewadze aplikacji internetowych z prostymi, połączonymi zapytaniami SQL w Internecie. Poza tym zdarzają się przypadki, gdzie ORM jest powszechnie obowiązujące Nie może to mieć miejsca np. wtedy, gdy od danych użytkownika muszą zależeć nie tylko parametry wyrażeń, ale także sama logika zapytań na poziomie operatora.

Zacznijmy więc.

Bezużyteczna ucieczka postaci
Występuje w 83% aplikacji internetowych PHP podatnych na zastrzyki SQL
Używanie funkcji ucieczki dla znaków takich jak
mysql_escape_string
mysql_real_escape_string
dodaje ukośniki
bez cudzysłowów. Najczęściej objawia się to parametrami numerycznymi (wszelkiego rodzaju *_id).
Przykład
$sql = "WYBIERZ użytkownika Z listy użytkowników GDZIE userid=".mysql_real_escape_string($_GET["uid"]);

Wydaje się, że jest to bezpieczny kod, ale tylko na pozór. Tutaj wkradł się najczęstszy schemat wstrzykiwania SQL w PHP w mojej praktyce. Aby zaatakować tę lukę, osoba atakująca musi po prostu unikać użycia znaków „ ” \x00 \r \n \x1a w wektorze ataku.
Na przykład:
/index.php?uid=-777 UNION WYBIERZ hasło Z listy użytkowników

Szukaj w kodzie
Skomplikowane przez semantykę języka. Do prostego wyszukiwania możesz użyć egrep:
egrep -Rin "(wybierz|aktualizuj|wstaw|usuń|zamień).*(z|ustaw|w).*(mysql_escape_string|mysql_real_escape_string|addslashes)" . | grep -v "[\""]["\"]"

Logika wyszukiwania jest następująca: znajdź wszystkie linie, w których po lewej stronie funkcji filtrujących nie ma sekwencji cudzysłowów („”, „”, „”, „”). Metoda oczywiście jest daleka od 100%, ale nie można wymagać wyrażenia regularnego do przeprowadzenia analizy semantycznej.
Aby ułatwić wyświetlanie informacji, możesz podświetlić funkcję kolorem w konsoli:
egrep -Rin "(wybierz|aktualizuj|wstaw|usuń|zamień).*(z|ustaw|w).*(mysql_escape_string|mysql_real_escape_string|addslashes)" . | grep -v "[\""]["\"]" | egrep --color "(mysql_escape_string|mysql_real_escape_string|addslashes)"

Aby zabezpieczyć się przed tą luką w zabezpieczeniach związaną z symbolami wieloznacznymi, najlepiej jest użyć rzutowania typów.
To zawsze działa szybciej i jest bardziej niezawodne niż wszelkiego rodzaju filtrowanie i przesiewanie.
W powyższym przykładzie łatka może wyglądać następująco:
$sql = "WYBIERZ użytkownika Z listy użytkowników GDZIE userid=".intval($_GET["uid"]);

Na tym kończy się krótki esej. Wzywam wszystkich twórców stron internetowych, aby spróbowali sprawdzić swoje źródła pod kątem takich projektów. Jeszcze lepiej, rozwiń podany skrypt wyszukiwania osób.

Zasadniczo zagłębiłem się w obszary MySQL i PHP... szczególnie w zakresie środków bezpieczeństwa, które powinienem podjąć podczas pracy z bazą danych i danymi wejściowymi z formularzy. Jak na razie uważam, że godne polecenia są:

  1. Przygotowane oświadczenia
  2. Używanie _real_escape_string()
  3. NIE używaj magicznych cudzysłowów, ponieważ myli to bazy danych i kończy się dawaniem takich rzeczy jak „Nie nazwałeś tego…”.

Wszystko jest super i obserwuję to. Zastanawiałem się jednak, czy powinienem unikać znaków takich jak znak dolara [$], znak procentu [%] i być może inne. Czy zapytanie mogło zinterpretować znak dolara jako zmienną PHP? A co ze składnią LIKE, o której słyszałem, że używa symbolu% lub nawet symbolu wieloznacznego? Przygotowane oświadczenia powinny technicznie zająć się tym wszystkim, ale chciałem się tylko upewnić i upewnić się, że wszystko zostało właściwie ujęte. W przypadkach, gdy zapomniałem użyć przygotowanych stwierdzeń lub po prostu je zaniedbałem, miałem nadzieję, że ta druga linia obrony podpowie mi, że mogę pozbyć się zawrotów głowy.

Oto, czego obecnie używam do ucieczki:

Funkcja escape($połączenie, $data)( $new_data = trim($data); $new_data = i_real_escape_string($connection, $new_data); $new_data = addcslashes($new_data, "%_$"); $new_data = htmlspecialchars ($new_data, ENT_NOQUOTES); zwróć $new_data; )

Czy to prawda? Czy robię coś strasznie złego? Pamiętaj, że podczas zwracania danych z bazy danych będę musiał usunąć ukośniki odwrotne przed znakami $,% i _.

Czy robię coś strasznie złego?

Najpierw o twoich badaniach.

Przygotowane zestawienia – jedyny cudowną rzecz, którą znalazłeś.

Chociaż użycie mysqli_real_escape_string (zakładając, że używasz przygotowanych instrukcji) byłoby bezużyteczne i szkodliwe(tworząc wynik, który sam zanotowałeś: „Zadzwoniłeś, czy nie…”).

A magiczne cytaty już dawno zostały usunięte z języka – a zatem nie są nic warte.

Zatem nawet większość Twoich początkowych założeń jest wyraźnie błędna.

A teraz twoje pytanie.

Czy zapytanie mogło zinterpretować znak dolara jako zmienną PHP?

A co ze składnią LIKE, o której słyszałem, że używa symbolu% lub nawet symbolu wieloznacznego?

Tak, dobrze słyszałeś. Dokładnym celem operatora LIKE jest wyszukiwanie wzorców. Wyłączenie tych znaków w LIKE nie miałoby najmniejszego sensu.

Za każdym razem, gdy zamierzasz użyć operatora LIKE, musisz zdecydować, którego konkretnego znaku użyć, a którego nie. Nie można skorzystać z rozwiązania jednorazowego. Nie wspominając, że we wszystkich innych interakcjach mysql znak % nie ma specjalnego znaczenia.

Przygotowane zestawienia powinny technicznie to wszystko załatwić

Przygotowane instrukcje nie mają nic wspólnego ze znakami $ ani %. Przygotowane instrukcje odnoszą się do iniekcji SQL, ale żaden znak nie może tego spowodować (nie można nazwać „wstrzyknięcia” poprawnie użytym operatorem LIKE, prawda?).

Wreszcie najgorsza część.

Jeżeli zapomnisz skorzystać z przygotowanych stwierdzeń lub po prostu zaniedbasz ich przestrzeganie,

nic cię nie uratuje.

A najmniejszą pomocą byłaby funkcja, którą opracowałeś.

Podsumować.

  1. Pozbądź się tej funkcji.
  2. Używać wypełniacze* do reprezentowania każdej indywidualnej zmiennej w zapytaniu.
  3. Uciekaj od znaków % i _ na wejściu tylko wtedy, gdy będą one użyte w operatorze LIKE i nie chcesz, aby były interpretowane.
  4. Użyj htmlspecialchars() jako wyjścia, a nie wejścia mysql.

*przeczytaj przygotowane oświadczenia, jeśli nie znasz tego terminu.

Nie musisz unikać znaku dolara. MySQL nie patrzy konkretnie na ten znak, a PHP rozpoznaje go tylko w kodzie źródłowym, a nie w wartościach łańcuchowych (chyba że wywołasz eval na łańcuchu, ale to zupełnie inny robak robaków).

Musiałbyś uciec przed % i _ tylko wtedy, gdy używasz danych wejściowych użytkownika jako argumentu LIKE i nie chcesz, aby użytkownik mógł używać symboli wieloznacznych. Może się to zdarzyć, jeśli przetwarzasz formularz wyszukiwania. Nie musisz go używać podczas przechowywania w bazie danych.

Nie musisz używać htmlspecialchars podczas uzyskiwania dostępu do bazy danych. Należy tego używać tylko podczas wyświetlania danych użytkownikowi na stronie HTML, aby zapobiec wstrzyknięciu XSS.

W zależności od tego, jakie dane i do czego służą.

Jeśli stwierdzisz, że domyślne, gotowe do użycia instrukcje PHP są zbyt duże i złożone, sugeruję zapoznanie się z niektórymi klasami dostępnymi na githubie, aby dać ci pojęcie o uproszczonych zapytaniach.

Przykład wstawiania zapytań z tą klasą

$data = Array („login” => „admin”, „active” => true, „firstName” => „John”, „lastName” => „Doe”, „password” => $db->func( "SHA1(?)",Array ("tajne hasło+sól")), // hasło = SHA1("tajne hasło+sól") "createdAt" => $db->now(), // utworzonyAt = TERAZ() " wygasa" => $db->now("+1Y") // wygasa = TERAZ() + interwał 1 rok // Obsługiwane interwały [s]sekunda, [m]inute, [h]godzina, [d]dzień, [Miesiąc, rok); $id = $db->insert("użytkownicy", $dane); if ($id) echo "Utworzono użytkownika. Id=" . $id; else echo "wstawienie nie powiodło się: ". $db->getLastError();