2009-08-17 10 views
25

Jestem nowy w użyciu przygotowanych oświadczeń w mysql z php. Potrzebuję pomocy w przygotowaniu instrukcji do pobrania kolumn.Jak stworzyć bezpieczne przygotowanie mysql w php?

Potrzebuję uzyskać informacje z różnych kolumn. Obecnie dla pliku testowego, używam całkowicie niezabezpieczonych oświadczenie SQL:

$qry = "SELECT * FROM mytable where userid='{$_GET['userid']}' AND category='{$_GET['category']}'ORDER BY id DESC" 
$result = mysql_query($qry) or die(mysql_error()); 

Czy ktoś może mi pomóc stworzyć oświadczenie bezpieczne mysql używając metody z parametrami adresu URL (jak wyżej), który jest przygotowany?

BONUS: Przygotowane instrukcje mają również zwiększać prędkość. Czy zwiększy ogólną prędkość, jeśli użyję tylko przygotowanego oświadczenia trzy lub cztery razy na stronie?

+1

możliwe duplikat [ Najlepszy sposób na zatrzymanie SQL Injection w PHP] (http://stackoverflow.com/questions/60174/best-way-to-stop-sql-injection-in-php) – outis

Odpowiedz

45

Oto przykład przy użyciu mysqli (object-składnia - dość łatwy do przetłumaczenia funkcjonować składni jeśli chcecie):

$db = new mysqli("host","user","pw","database"); 
$stmt = $db->prepare("SELECT * FROM mytable where userid=? AND category=? ORDER BY id DESC"); 
$stmt->bind_param('ii', intval($_GET['userid']), intval($_GET['category'])); 
$stmt->execute(); 

$stmt->store_result(); 
$stmt->bind_result($column1, $column2, $column3); 

while($stmt->fetch()) 
{ 
    echo "col1=$column1, col2=$column2, col3=$column3 \n"; 
} 

$stmt->close(); 

Ponadto, jeśli chcesz w łatwy sposób chwycić tablic asocjacyjnych (do użytku z wybranymi *) zamiast dokładnie określić, jakie zmienne do wiązania, oto poręczne funkcja:

function stmt_bind_assoc (&$stmt, &$out) { 
    $data = mysqli_stmt_result_metadata($stmt); 
    $fields = array(); 
    $out = array(); 

    $fields[0] = $stmt; 
    $count = 1; 

    while($field = mysqli_fetch_field($data)) { 
     $fields[$count] = &$out[$field->name]; 
     $count++; 
    } 
    call_user_func_array(mysqli_stmt_bind_result, $fields); 
} 

aby go użyć, wystarczy powołać się go zamiast dzwonić bind_result:

$stmt->store_result(); 

$resultrow = array(); 
stmt_bind_assoc($stmt, $resultrow); 

while($stmt->fetch()) 
{ 
    print_r($resultrow); 
} 
+0

dzięki. Jak wyświetlić wyniki z tego oświadczenia? Generalnie używam mysql_fetch_row, czy to działa tutaj? – chris

+0

Zaktualizowano w celu dodania przykładu, jak uzyskać wyniki. – Amber

+0

Zauważ również, że mój przykład zakłada, że ​​oba parametry są liczbami całkowitymi - jeśli są ciągami, musisz odpowiednio zmienić argumenty na bind_param. – Amber

2

Bezpieczeństwo z MySQL w PHP (lub w jakimkolwiek innym języku) jest w dużym stopniu omawianym problemem. Oto kilka miejsc, aby podnieść kilka cennych wskazówek:

Dwa najbardziej Najistotniejsze pozycje w moim zdaniem są:

  • Injection SQL: Pamiętaj, aby uciec wszystkie zmienne zapytania za pomocą funkcji PHP mysql_real_escape_string() (lub czegoś podobnego).
  • Walidacja danych wejściowych: Nigdy nie ufaj wprowadzaniu danych przez użytkownika. Zapoznaj się z instrukcją this dotyczącą samouczenia i sprawdzenia poprawności danych wejściowych.
+1

mysql_real_escape_string nie jest świetny i może być używany tylko w kontekst tekstowy. Nigdy nie używaj go, aby chronić pole nie będące ciągiem. Proszę, proszę, użyj ograniczonych parametrów zamiast tej czarnej listy, która służy do filtrowania pasm. – Cheekysoft

+1

@James: bardzo ważna jest funkcja sprawdzania poprawności danych wejściowych. dzięki! – chris

11

Można napisać to zamiast:

$qry = "SELECT * FROM mytable where userid='"; 
$qry.= mysql_real_escape_string($_GET['userid'])."' AND category='"; 
$qry.= mysql_real_escape_string($_GET['category'])."' ORDER BY id DESC"; 

Ale użycie przygotowanych sprawozdań lepiej użyć rodzajowe biblioteki, jak PDO

<?php 
/* Execute a prepared statement by passing an array of values */ 
$sth = $dbh->prepare('SELECT * FROM mytable where userid=? and category=? 
         order by id DESC'); 
$sth->execute(array($_GET['userid'],$_GET['category'])); 
//Consider a while and $sth->fetch() to fetch rows one by one 
$allRows = $sth->fetchAll(); 
?> 

Lub przy mysqli

<?php 
$link = mysqli_connect("localhost", "my_user", "my_password", "world"); 

/* check connection */ 
if (mysqli_connect_errno()) { 
    printf("Connect failed: %s\n", mysqli_connect_error()); 
    exit(); 
} 

$category = $_GET['category']; 
$userid = $_GET['userid']; 

/* create a prepared statement */ 
if ($stmt = mysqli_prepare($link, 'SELECT col1, col2 FROM mytable where 
         userid=? and category=? order by id DESC')) { 
    /* bind parameters for markers */ 
    /* Assumes userid is integer and category is string */ 
    mysqli_stmt_bind_param($stmt, "is", $userid, $category); 
    /* execute query */ 
    mysqli_stmt_execute($stmt); 
    /* bind result variables */ 
    mysqli_stmt_bind_result($stmt, $col1, $col2); 
    /* fetch value */ 
    mysqli_stmt_fetch($stmt); 
    /* Alternative, use a while: 
    while (mysqli_stmt_fetch($stmt)) { 
     // use $col1 and $col2 
    } 
    */ 
    /* use $col1 and $col2 */ 
    echo "COL1: $col1 COL2: $col2\n"; 
    /* close statement */ 
    mysqli_stmt_close($stmt); 
} 

/* close connection */ 
mysqli_close($link); 
?> 
1

Przygotowane oświadczenia nie są " t obsługiwane przez zwykły stary interfejs Mysql/PHP. Będziesz potrzebował PDO lub mysqli. Ale jeśli chcesz tylko zastąpić symbole zastępcze, check this comment na stronie podręcznika php's mysql_query.

1

Jeśli zamierzasz używać mysqli - co wydaje mi się najlepszym rozwiązaniem - gorąco polecam pobranie kopii klasy codesense_mysqli.

Jest to schludny trochę klasy, która owija się i ukrywa większość cruft że gromadzi się przy użyciu surowego mysqli taki, że za pomocą przygotowanych sprawozdań zajmuje tylko wiersz lub dwa dodatkowe w stosunku do starego interfejsu mysql/php

+0

Ta klasa jest wyjątkowo łatwa w użyciu. Zastanawiam się, dlaczego coś takiego nie jest bardziej popularne. – chris

7

zgadzam się z kilka innych odpowiedzi:

  • PHP ext/mysql nie obsługuje sparametryzowanych instrukcji SQL.
  • Parametry zapytania są uważane za bardziej niezawodne w ochronie przed problemami z iniekcją SQL.
  • mysql_real_escape_string() może być również efektywny, jeśli używa się go poprawnie, ale jest bardziej szczegółowy w stosunku do kodu.
  • W niektórych wersjach międzynarodowe zestawy znaków mają przypadki znaków, które nie są poprawnie usunięte, pozostawiając subtelne luki. Korzystanie z parametrów zapytania pozwala uniknąć tych przypadków.

Należy również pamiętać, że nadal należy zachować ostrożność podczas wstrzykiwania SQL, nawet jeśli używane są parametry zapytania, ponieważ parametry zastępują tylko wartości literalne w zapytaniach SQL. Jeśli budujesz zapytania SQL dynamicznie i używasz zmiennych PHP do nazwy tabeli, nazwy kolumny lub jakiejkolwiek innej części składni SQL, żadne parametry zapytania ani mysql_real_escape_string() nie pomogą w tym przypadku. Na przykład:

$query = "SELECT * FROM $the_table ORDER BY $some_column"; 

Odnośnie wydajności:

  • Korzyść wydajność przychodzi po wykonaniu przygotowanym zapytaniu wielokrotnie z różnymi wartościami parametrów. Unikniesz narzutu na przetwarzanie i przygotowanie zapytania. Ale jak często trzeba wykonać to samo zapytanie SQL wiele razy w tym samym żądaniu PHP?
  • Nawet jeśli można wykorzystać tę zaletę wydajności, jest to zwykle tylko niewielka poprawa w porównaniu do wielu innych rzeczy, które można wykonać w celu poprawy wydajności, na przykład efektywnego buforowania kodu lub buforowania danych.
  • Istnieje nawet kilka przypadków, w których przygotowane zapytanie szkodzi wydajności.Na przykład w poniższym przypadku, optymalizator nie można zakładać, może użyć indeksu do poszukiwania, ponieważ musi on przyjąć wartość parametru może rozpocząć asterisk:

    SELECT * FROM mytable WHERE textfield LIKE ? 
    
+0

Doskonała odpowiedź. W jaki sposób radziłbyś sobie z instrukcją typu "SELECT * FROM mytable WHERE textfield LIKE?". Czy powinienem użyć przygotowanego oświadczenia lub czegoś innego? – chris

+0

W tym konkretnym przypadku musisz wstawić wzór do ciągu zapytania: "SELECT * FROM mytable GDZIE pole tekstowe LIKE" słowo% "' ". W ten sposób optymalizator może stwierdzić, że może korzystać z indeksu (oczywiście, jeśli twój wzór zaczyna się od symbolu wieloznacznego, indeks i tak nie daje żadnych korzyści). –