Przede wszystkim filtrowanie danych wejściowych i stosowanie mysql_real_escape_string. Również wspomniane przez @kilas88 mechanizmy prekompilowanych zapytań z podstawianymi zmiennymi
- prepared statements (predefiniowane zapytania, MySQL, MS-SQL, PostgreSQL) więcej informacji
- bind variables (zmienne bindowane, Oracle) więcej informacji
- stored procedures czyli funkcje definiowane wewnątrz bazy SQL
Te zabezpieczenia pozwalają na dokładną deklarację typu zmiennej podawanej do zapytania.
Dodatkowo prepared statements daje znacznie większą wydajność wykonywania zapytań w większości systemów bazodanowych.
W ograniczonym stopniu można stosować również settype() do podstawiania zmiennych do SQL'a czyli coś takiego:
settype($offset, 'integer');
$query = "SELECT id, nazwa FROM produkty ORDER BY nazwa LIMIT 10 OFFSET $offset;";
Dodatkowym zabezpieczeniem może być mod_rewrite poprzez ukrycie oryginalnego adresu URL i zastąpienie go innym, czyli zwykłe (zaczerpnięte gdzieś z internetu):
RewriteEngine On
RewriteBase /
RewriteRule ^produkty,([0-9]+),([0-9]+)\.html$ produkty.php?kategoria=$1&produkt=$2 [L]
Tak jak już ktoś napisał walidować dane wprowadzane przez użytkowników, aby nie można było wpisywać apostrofa i słów które są składnią języka SQL (select, delete itd).
Można też użyć strip_tags - wycina wszystkie tagi html z tekstu.
Takie rzeczy jak sprawdzanie siły hasła wprowadzonego przez użytkownika do waszego serwisu oraz nadawanie użytkownikom minimalnych praw w korzystaniu z serwisu i kilka innych dobrych praktyk na pewno zniweluje podatność na ataki SQL Injection. Warto też być na bieżąco z nowymi atakami typu SQL Injection i szybko wprowadzać modyfikacje do swoich serwisów.