2016-06-25 2 views
2

Я читал статьи о SQL Injection и решил изменить свой код, чтобы предотвратить SQL-инъекцию.Обертка подготовленного оператора в функции

Например, у меня есть ввод, который я вставляю значение в свою базу данных. Первоначально мой охранник против введения был такой:

function test_input($data) { 
    $data = trim($data); 
    $data = stripslashes($data); 
    $data = htmlspecialchars($data); 
    // $data = addslashes($data); 
    $data = mysql_real_escape_string($data); 
    return $data; 
} 

$artist = $_POST["artist"];  // can be anything 
$artist = test_input($artist); // escaped chars are &, quotes, <, >, \n, \r, etc. 

if ($mysqli->query("SELECT * FROM `my_table` WHERE `artist` = '$artist'")->num_rows == 0) { 
    $mysqli->query("INSERT INTO my_table (artist) VALUES ('$artist')"); 
    echo "New artist is added."; 
} else { 
    echo "Artist already exists."; 
} 

В статьях, которые я читал, было высказано предположение о том, что следует использовать подготовленные заявления. Я изменил свой код и использовал его:

$artist = $_POST["artist"]; // can be anything 

$query = $mysqli->prepare("SELECT * FROM my_table WHERE artist = ?"); 
$query->bind_param("s", $artist); 
$query->execute(); 
$result = $query->get_result(); 
$query->close(); 

if ($result->num_rows == 0) { 
    echo "Artist doesn't exist in the DB." . PHP_EOL; 
    $query = $mysqli->prepare("INSERT INTO my_table (artist) VALUES (?)"); 
    $query->bind_param("s", $artist); 
    $query->execute(); 
    if ($query->affected_rows > 0) { 
     echo "Artist is added to the DB." . PHP_EOL; 
    } 
    $query->close(); 
} else { 
    echo "Artist already exists in the DB." . PHP_EOL; 
} 

Хотя это предотвращает внедрение SQL, оно ничего не делает о XSS. Поэтому я решил изменить test_input (удалено $data = mysql_real_escape_string($data);) и использовать его, чтобы предотвратить инъекцию скрипта.

function test_input($data) { 
    $data = trim($data); 
    $data = stripslashes($data); 
    $data = htmlspecialchars($data); 
    return $data; 
} 

$artist = $_POST["artist"]; // can be anything 
$artist = test_input($artist); 

Теперь моя проблема заключается в использовании подготовленных инструкций. Я буду вставлять три элемента; исполнителя, альбома и песни. Повторение одного и того же процесса (подготовка, привязка, выполнение, закрытие) снова и снова кажется излишним для меня. Я хочу создать функцию и перенести с ней подготовленный процесс утверждения. Что-то вроде этого:

function p_statement($mysqli, $query_string = "", $type = "", $vars = []) { 
    $query = $mysqli->prepare($query_string); 
    $query->bind_param($type, $vars); 
    $query->execute(); 
    $result = null; 
    preg_match("/^[A-Z]+/", $query_string, $command); 
    switch ($command[0]) { 
     case "SELECT": 
      $result = $query->get_result(); 
      break; 
     case "INSERT": 
      $result = $query->affected_rows; 
      break; 
    } 
    $query->close(); 
    return $result; 
} 

Хотя, это представляет проблему: $vars массив. Поскольку число переменных, которые будут переданы в mysqli_stmt::bind_param(), будет переменным/динамическим, я использовал массив в основной функции p_statement. Я не знаю, как мне передать элементы в массиве на mysqli_stmt::bind_param(). bind_param ожидает (type, var1, var2, varn,), и у меня есть массив.

Как это сделать?

ответ

1

Вы ищете implode()

Глядя на Manpage и она показывает свет об использовании call_user_func_array. И я редактирую часть вашего фрагмента.

function p_statement($mysqli, $query_string = "", $type = "", $vars = []) { 

    $query = $mysqli->prepare($query_string); 

    //assign $type to first index of $vars 
    array_unshift($vars, $type); 

    //Turn all values into reference since call_user_func_array 
    //expects arguments of bind_param to be references 
    //@see mysqli::bind_param() manpage 
    foreach ($vars as $key => $value) { 
     $vars[$key] =& $vars[$key]; 
    } 

    call_user_func_array(array($query, 'bind_param'), $vars); 
    $query->execute(); 

    //INSERT, SELECT, UPDATE and DELETE have each 6 chars, you can 
    //validate it using substr() below for better and faster performance 
    if (strtolower(substr($query_string, 0, 6)) == "select") { 
     $result = $query->get_result(); 
    } else { 
     $result = $query->affected_rows; 
    } 

    $query->close(); 
    return $result; 
} 
+0

Неа. 'implode'" склеивает "элементы массива и возвращает строку. Мне нужно передать элементы массива в качестве аргументов функции 'bind_param'. – akinuri

+0

Вы пробовали? Второй аргумент bind_param похож на вызов func_get_args, но со ссылкой. Заблокировать ее - это единственный способ сделать это. Пока вы указываете количество совпадений и его тип (первые аргументы) – Chay22

+0

Да. Он вызывает mysqli_stmt :: bind_param(): количество элементов в определении типа ... '. Как я уже сказал, implode возвращает одну строку, содержащую элементы массива. Элементы должны передаваться индивидуально в 'bind_param'. 'bind_param (type, string1, string2)', а не 'bind_param (type," string1, string2 ")'. Можете ли вы переписать функцию 'p_statement' и показать ее здесь? Может быть, я делаю что-то неправильно. – akinuri

1

Я нашел способ решить проблему, используя call_user_func_array.

function p_statement($mysqli, $query_string = "", $type = "", $vars = []) { 
    $query = $mysqli->prepare($query_string); 
    // create an empty array 
    $parameters = array(); 
    // push the type string into the array by reference 
    $parameters[] = & $type; 
    // push the items from $vars array into the array by reference 
    for ($i = 0; $i < count($vars); $i++) { 
     $parameters[] = & $vars[$i]; 
    } 
    // call mysqli_stmt::bind_param with the $parameters array, which contains [type, var1, var2, ...] 
    call_user_func_array(array($query, "bind_param"), $parameters); 
    $query->execute(); 
    $result = null; 
    preg_match("/^[A-Z]+/", $query_string, $command); 
    switch ($command[0]) { 
     case "SELECT": 
      $result = $query->get_result(); 
      break; 
     case "INSERT": 
     case "UPDATE": 
     case "DELETE": 
      $result = $query->affected_rows; 
      break; 
    } 
    $query->close(); 
    return $result; 
} 

$artist = "3 Doors Down"; 
$year = 2000; 

$artist_select = p_statement($mysqli, "SELECT * FROM albums WHERE artist = ? AND year = ?", "si", [$artist, $year]); 

var_dump($artist_select->fetch_all(MYSQLI_ASSOC)); 

Выходы:

array(1) { 
    [0]=> 
    array(3) { 
    ["album"]=> 
    string(15) "The Better Life" 
    ["year"]=> 
    int(2000) 
    ["artist"]=> 
    string(12) "3 Doors Down" 
    } 
}