PHP Prepared Statements – podejście do baz danych

2

Tworzysz kod i zastanawiasz się, czy jest bezpieczny? Czy wprawiony hacker może łatwo obejść Twoje zabezpieczenia? Naucz się, w jaki sposób zastosować w PHP Prepared Statements. Pozwoli to podejść poważnie do tematu łączenia PHP z Bazą Danych.

W tym artykule nauczysz się stosować Prepared Statements w PHP przy łączeniu z bazą danych SQL.

Jeśli łączysz argumenty podawane przez użytkownika z resztą kwerendy SQL, budując z tego jedną instrukcję, narażasz się na niebezpieczeństwo. Jako programista, musisz dołożyć wszelkich starań, by nikt złośliwie nie był w stanie popsuć bazy. Tym bardziej nie możesz dopuścić, by dane z bazy zostały wykradzione.

W jednym z wpisów pisałem o zagrożeniu SQL Injection. Jak może ucierpieć baza w przypadku braku odpowiednich zabezpieczeń oraz jak się przed tym bronić.

Jednym z dodatkowych, niemal niezawodnych, zabezpieczeń jest używanie tzw. Prepared statements. Ten artykuł stanowi niejako uzupełnienie kursu SQL, który znajdziesz na tej stronie.

Przygotowane instrukcje (z ang. Prepared Statements) polega to na tym, że najpierw przygotowujemy instrukcję dla bazy z miejscami przygotowanymi dla argumentów, po czym osobno przypisujemy argumenty w kolejnym poleceniu.

W ten sposób nie ma mowy o ingerencji w wykonywany kod SQL, gdyż nie łączymy wielu stringów w jeden i nie wykonujemy go w całości. Jedynie dokładamy dodatkowe argumenty w bezpiecznie przygotowane miejsca.

Zobacz, jak uzyskać efekt Prepared Statements przy użyciu obiektu MySQLi oraz PHP.

Najpierw utwórz sobie nowy obiekt MySQLi przy pomocy dostępnych danych:

$conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_SOCKET);
	$conn->set_charset("utf8");
	// Check connection
	if ($conn->connect_error) {
		die("Connection failed: " . $conn->connect_error);
	}

Do tworzenia obiektu użyłem zestawu stałych, np.:

define('DB_NAME', 'dbName');
define('DB_USER', 'dbUser');
define('DB_PASSWORD', 'dbUserPass1234');
define('DB_HOST', 'localhost');
define('DB_SOCKET', 12345);

Następnie, przygotujmy sobie naszą instrukcję przy pomocy metody prepare:

$stmt = $conn->prepare("select u.FirstName, u.LastName from Users u where u.Email=?");

Teraz…

Baza danych już wie, że wykona instrukcję przygotowaną w poprzednim kroku. W kolejnym kroku musimy jedynie przyporządkować wartość dla podanego warunku.

Jak pewnie zauważyłeś, w Prepared Statements każde miejsce na zmienną oznaczane jest znakiem zapytania ?.

Znaków zapytania możemy wstawić tyle, ile jest potrzebne. Później w odpowiedniej kolejności przypiszemy zmienne. Pod koniec artykułu znajdziesz przykład z większą ilością przypisanych argumentów. Póki co jedynie podamy adres e-mail.

$stmt->bind_param('s', $email);
$stmt->execute();

Powyższa linijka przypisuje do pierwszego znaku zapytania wartość zmiennej $email, po czym metoda execute wykonuje wcześniej przygotowaną instrukcję.

Żeby zobaczyć rezultat, należy najpierw przypisać zwracane kolumny do odpowiednich zmiennych. W naszym przykładzie pobieramy FirstName i LastName z tabeli Users. Potrzebujemy zatem dwóch zmiennych, by przypisać do nich dwie kolumny.

Robimy to za pomocą instrukcji bind_result.

Po jej zastowosaniu możemy przeiterować po rezultatach i dostać wartość każdego wiersza. Przykład poniżej:

$stmt->bind_result($firstName, $lastName);
 
$returnArray = array();
while ($stmt->fetch())
{
	echo $firstName . " " . $lastName;
}
 
$stmt->close();
$conn->close();

Na sam koniec zamykamy przygodowaną instrukcję oraz połączenie z bazą.

Zobacz teraz, jak połączyć kilka warunków (dodać kilka zmiennych) do przygotowanej instrukcji:

$stmt = $conn->prepare("INSERT INTO Users (Id, FirstName, LastName, Email) VALUES (?, ?, ?, ?);");
$stmt->bind_param('isss', $id, $firstName, $lastName, $email);
$stmt->execute();

Zauważasz pewnie pierwszy argument metody bind_params, który jest stringiem. Służy on do poinstruowania, jakiego typu argumenty będą wstawiane w instrukcję. Tak oto, d oznacza liczbę całkowitą, a s oznacza ciąg znaków. dsss to liczba, string, string i string.

Poczytaj o bind_params w oficjalnej dokumentacji.

Jeśli chcesz skopiować cały fragment kodu z dzisiejszego tematu, teraz masz na to szansę:

define('DB_NAME', 'dbName');
define('DB_USER', 'dbUser');
define('DB_PASSWORD', 'dbUserPass1234');
define('DB_HOST', 'localhost');
define('DB_SOCKET', 12345);
 
$conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_SOCKET);
	$conn->set_charset("utf8");
	// Check connection
	if ($conn->connect_error) {
		die("Connection failed: " . $conn->connect_error);
	}
 
$stmt = $conn->prepare("select u.FirstName, u.LastName from Users u where u.Email=?");
 
$stmt->bind_param('s', $email);
$stmt->execute();
 
$stmt->bind_result($firstName, $lastName);
 
$returnArray = array();
while ($stmt->fetch())
{
	echo $firstName . " " . $lastName;
}
 
$stmt->close();
$conn->close();

Mam nadzieję, że zdobyta wiedza sprawi, że będziesz lepszym programistą.

Prepared Statements pomoże Ci uniknąć nieprzyjemności włamania i wycieku danych lub ich całkowitej utracie.

Najlepiej będzie, gdy ich stosowanie wejdzie Ci w nawyk. Powodzenia!

Share.
  • Marcin

    Czy w tym miejscu:
    echo $firstName + ” ” + $lastName;
    nie powinno być kropek do łączenia stringów, zamiast plusików z JavaScriptu?

  • Marcin Wesel

    Jasne, że tak. Moje przeoczenie. Zaraz poprawię.

    Dzięki!