web-dev-qa-db-fra.com

Comment créer une instruction sécurisée mysql en PHP?

Je suis novice dans l'utilisation d'instructions préparées dans mysql avec php. J'ai besoin d'aide pour créer une instruction préparée pour récupérer des colonnes.

J'ai besoin d'obtenir des informations de différentes colonnes. Actuellement, pour un fichier de test, j'utilise l'instruction complètement non sécurisée 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()); 

Quelqu'un peut-il m'aider à créer une instruction secure mysql en utilisant les paramètres d’entrée (comme ci-dessus) préparés?

BONUS: Les déclarations préparées sont supposées augmenter également la vitesse. La vitesse globale augmentera-t-elle si je n'utilise qu'une déclaration préparée trois ou quatre fois par page?

26
chris

Voici un exemple utilisant mysqli (object-syntax - assez facile à traduire en syntaxe de fonction si vous le souhaitez):

$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();

De plus, si vous voulez un moyen simple de récupérer des tableaux associatifs (à utiliser avec SELECT *) au lieu de spécifier exactement les variables auxquelles vous souhaitez vous connecter, voici une fonction pratique:

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);
}

Pour l'utiliser, il suffit de l'invoquer au lieu d'appeler bind_result:

$stmt->store_result();

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

while($stmt->fetch())
{
    print_r($resultrow);
}
46
Amber

Vous pouvez écrire ceci à la place:

$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";

Mais pour utiliser des instructions préparées, il vaut mieux utiliser une bibliothèque générique, comme 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(); 
?>

Ou, en utilisant 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);
?>
11
Vinko Vrsalovic

Je suis d'accord avec plusieurs autres réponses:

  • Le ext/mysql de PHP ne prend pas en charge les instructions SQL paramétrées.
  • Les paramètres de requête sont considérés comme plus fiables pour la protection contre les problèmes d'injection SQL.
  • mysql_real_escape_string() peut également être efficace si vous l'utilisez correctement, mais le code est plus détaillé.
  • Dans certaines versions, les jeux de caractères internationaux contiennent des cas de caractères qui ne sont pas correctement échappés, ce qui laisse des vulnérabilités subtiles. L'utilisation de paramètres de requête évite ces cas.

Vous devez également noter que vous devez toujours faire attention à l'injection SQL même si vous utilisez des paramètres de requête, car ceux-ci ne remplacent que les valeurs littérales des requêtes SQL. Si vous construisez des requêtes SQL de manière dynamique et utilisez des variables PHP pour le nom de la table, le nom de la colonne ou toute autre partie de la syntaxe SQL, ni les paramètres de requête ni mysql_real_escape_string() ne vous aident dans ce cas. Par exemple:

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

En ce qui concerne la performance:

  • L'avantage en termes de performances survient lorsque vous exécutez une requête préparée plusieurs fois avec différentes valeurs de paramètre. Vous évitez la surcharge liée à l'analyse et à la préparation de la requête. Mais combien de fois avez-vous besoin d'exécuter la même requête SQL plusieurs fois dans la même requête PHP?
  • Même lorsque vous pouvez tirer parti de cet avantage en termes de performances, il ne s'agit généralement que d'une légère amélioration par rapport à de nombreuses autres choses que vous pourriez faire pour améliorer les performances, telles que la mise en cache opcode ou la mise en cache de données.
  • Il existe même des cas où une requête préparée nuit - performance. Par exemple, dans le cas suivant, l'optimiseur ne peut pas supposer qu'il peut utiliser un index pour la recherche, car il doit assumer la valeur du paramètre might begin avec un caractère générique:

    SELECT * FROM mytable WHERE textfield LIKE ?
    
8
Bill Karwin

La sécurité avec MySQL dans PHP (ou tout autre langage) est un sujet largement débattu. Voici quelques endroits où vous pouvez trouver de bons conseils:

Les deux éléments les plus importants à mon avis sont:

  • Injection SQL: Assurez-vous d'échapper à toutes vos variables de requête avec la fonction mysql_real_escape_string() de PHP (ou quelque chose de similaire).
  • Validation d'entrée: Ne faites jamais confiance à l'entrée de l'utilisateur. Voir this pour un tutoriel sur la manière de nettoyer et de valider correctement vos entrées.
2
James Skidmore

Si vous utilisez mysqli - ce qui me semble la meilleure solution - je vous recommande fortement de télécharger une copie de la classe codesense_mysqli .

C'est une petite classe soignée qui cache et cache la plupart des difficultés accumulées lors de l'utilisation de mysqli brut, de telle sorte que l'utilisation d'instructions préparées ne prend qu'une ligne ou deux de plus sur l'ancienne interface mysql/php

1
Cruachan

Les instructions préparées ne sont pas prises en charge par l'ancienne interface Mysql/PHP. Vous aurez besoin de PDO ou mysqli . Mais si vous voulez simplement substituer des espaces réservés, consultez ce commentaire sur la page de manuel de php mysql_query.

1
inerte

Assez tard, mais cela pourrait aider quelqu'un:

/**
* Execute query method. Executes a user defined query
*
* @param string $query the query statement
* @param array(Indexed) $col_vars the column variables to replace parameters. The count value should equal the number of supplied parameters
*
* Note: Use parameters in the query then supply the respective replacement variables in the second method parameter. e.g. 'SELECT COUNT(*) as count FROM foo WHERE bar = ?'
*
* @return array
*/
function read_sql($query, $col_vars=null) {
    $conn = new mysqli('hostname', 'username', 'user_pass', 'dbname');
    $types = $variables = array();
    if (isset($col_vars)) {
        for ($x=0; $x<count($col_vars); $x++) {
            switch (gettype($col_vars[$x])) {
                case 'integer':
                    array_Push($types, 'i');
                        break;
                case 'double':
                    array_Push($types, 'd');
                    break;
                default:
                    array_Push($types, 's');
            }
            array_Push($variables, $col_vars[$x]);
        }
        $types = implode('', $types);
        $sql = $conn->prepare($query);
        $sql -> bind_param($types, ...$variables);
        $sql -> execute();
        $results = $sql -> get_result();
        $sql -> close();
    }else {
        $results = $conn->query($query) or die('Error: '.$conn->error);
    }
    if ($results -> num_rows > 0) {
        while ($row = $results->fetch_assoc()) {
            $result[] = $row;
        }
        return $result;
    }else {
        return null;
    }
}

Vous pouvez ensuite invoquer la fonction comme suit:

read_sql('SELECT * FROM mytable where userid = ? AND category = ? ORDER BY id DESC', array($_GET['userid'], $_GET['category']));
0
Adera