Bezpieczeństwo PHP

Styk baz danych z WWW to często miejsce, gdzie łatwo o poważną lukę bezpieczeństwa, tzw SQL Injection. Popatrzmy na prosty formularz HTML, pobierający od użytkownika nazwę przedmiotu. Otrzymana nazwa jest przekazywana do programu w PHP, który zadba o wydobycie z bazy danych średniej oceny z tego przedmiotu.

<form action="srednie.php" method="post">
Podaj nazwę przedmiotu: <input type="text" name="przedmiot"><br>
<br>
<input type=submit value="Szukaj">
</form>
Podaj nazwę przedmiotu:

Prosty program srednie.php mogłby wyglądać tak

<?php
$przedmiot = $_POST["przedmiot"];

$link = pg_connect("host=lkdb dbname=usos user=scott password=tiger");
$wynik = pg_query($link,
                  "SELECT AVG(ocena) AS srednia 
                   FROM Oceny
                   WHERE przedmiot = '" . $przedmiot . "'");
$ile = pg_numrows($wynik);

if ($ile == 0) {
  echo "<center><strong>Nie ma nic</strong></center>";
  $imie = "";
}
else {
  $oceny = pg_fetch_array($wynik, 0);
  $srednia = $oceny["srednia"];
}
?>

<form action="zoo2.php" method="post">

<?php
echo "Średnia: " . $srednia . "<br><br>";
echo "Podaj inny przedmiot: <input type=text name='przedmiot' value='$przedmiot'><br><br>";
pg_close($link);
?>

<input type=submit value="Szukaj">
</form>

Ale ciekawski użytkownik mógłby wpisać taką wartość dla przedmiotu

Bazy danych' and indeks = '123456

Po sklejeniu napisów dostaniemy

SELECT AVG(ocena) AS srednia 
FROM Oceny
WHERE przedmiot = 'Bazy danych' and indeks = '123456';

Bardzo przyzwoite zapytanie, tyle że narusza prywatność osoby o indeksie 123456, ponieważ wyświetli jej ocenę z tego przedmiotu.

Złośliwy użytkownik mógłby wpisać następującą wartość dla przedmiotu

Bazy danych'; delete from Oceny where przedmiot ='IPP

Po sklejeniu napisów dostajemy

SELECT AVG(ocena) AS srednia
FROM Oceny
WHERE przedmiot = 'Bazy danych'; delete from Oceny where przedmiot ='IPP';

Ogólna zasada bezpieczeństwa mówi: ,,Nigdy nie ufaj danym otrzymanym od użytkownika. Sprawdzaj!''.

W PHP są dwa sposoby. Pierwszy to w każdym otrzymanym napisie wszystkie znaki, które nie są literami ani cyframi, poprzedzić prefiksem '\', odbierającym im specjalny character. Można to zrobić samemu, ale lepiej użyć którejś funkcji bibliotecznej, na przykład pg_escape_string(). Są one dostosowane do konkretnych systemów baz danych --- w tym przypadku do Postgresa

$wynik = pg_query($link,
                  "SELECT AVG(ocena) AS srednia 
                   FROM Oceny
                   WHERE przedmiot = '" . pg_escape_string($przedmiot) . "'");

Drugi sposób to zamiast pg_query użyć funkcji pg_query_params. Ma ona dodatkowy argument: tablicę wartości dla parametrów. W treści zapytania parametry representowane są jako $1, $2, ...

$wynik = pg_query($link,
                  "SELECT AVG(ocena) AS srednia 
                   FROM Oceny
                   WHERE przedmiot = $1",
                  array($przedmiot));

Na stronie laboratorium (u dołu) mamy dwa przykłady takiej interakcji z bazą danych. W pierwszym dla podanego gatunku odnajdujemy informacje o jakimś zwierzaku z tego gatunku.

Drugi przykład to wstawianie nowego gatunku do bazy danych. Pokazuje, także obsługę błędów.