Я только начал работать над своим первым проектом (для удовольствия). Я изучаю PHP и MySQL и имею свое завершенное мое первое рабочее приложение. Он работает, но теперь я изучаю, как защитить свое приложение и тем самым предотвратить инъекции SQL. У меня есть примерно 50+ PHP-файлов, которые управляют взаимодействием с моей базой данных MySQL. Все они выглядят примерно так:
<?php $inputvalues = $_POST; $errors = false; $result = false; session_start(); $uid = $_SESSION['usr_id']; $mysqli = new mysqli('localhost', "root", "", "testdb"); if (mysqli_connect_errno()) { printf("Connect failed: %s\n", mysqli_connect_error()); exit(); } foreach ($inputvalues as $key => $value) { if(isset($value) && !empty($value)) { $inputvalues[$key] = $mysqli->real_escape_string( $value ); } else { $errors[$key] = 'The field '.$key.' is empty'; } } if( !$errors ) { $addresult = " SELECT a.firstnames, a.surname, a.schoolrole, a.datejoined FROM teachers a LEFT JOIN schools b ON a.schoolid = b.id WHERE b.id = '".$inputvalues['schoolid']."' "; if( $result = $mysqli->query($addresult) ) { while($row = $result->fetch_all()) { $returnResult = $row; } } } mysqli_close($mysqli); echo json_encode(['result' => $returnResult, 'errors' => $errors]); exit; ?>
Это формат, который я использовал во время моего приложения для чтения и записи данных в / из базы данных. Если мне нужно изменить их на подготовленные заявления, где я не вставляю какую-либо информацию, а просто получаю их, как бы мне это сделать?
Кроме того, если я не вхожу ни в какие данные в БД, он все еще уязвим для инъекций?
Не могли бы вы предоставить мне пример того, как я могу адаптировать мой текущий код к подготовленным заявлениям, я бы очень признателен.
Я был в той же ситуации. Я также использовал конкатенированные утверждения, затем я переключил свое приложение на подготовленные заявления.
плохая новость заключается в том, что вы собираетесь изменить каждый оператор SQL, созданный путем объединения данных клиента в оператор SQL, что почти будет каждым оператором SQL, который у вас есть в ваших 50 исходных файлах.
хорошая новость – выигрыш от перехода к подготовленным заявлениям бесценен, например:
1 – вы никогда не будете беспокоиться о чем-то, называемом «SQL Injection attack»,
в руководстве php говорится:
Если приложение использует исключительно подготовленные операторы, разработчик может быть уверен, что SQL-инъекция не произойдет (однако, если другие части запроса создаются с несвязанным вводом, SQL-инъекция по-прежнему возможна).
Для меня эта причина – образ ума – достаточно, чтобы заплатить стоимость изменения моего исходного кода. , теперь ваши клиенты могут ввести имя поля формы robert; DROP table students; -- ;)
robert; DROP table students; -- ;)
robert; DROP table students; -- ;)
и вы чувствуете себя в безопасности, что ничего не случится
2- вам больше не нужно выходить из параметров клиента. вы можете напрямую использовать их в инструкции SQL, например:
$query = "SELECT FROM user WHERE id = ?"; $vars[] = $_POST['id'];
вместо
$id = $mysqli->real_escape_string($_POST['id']); $query = "SELECT FROM user WHERE id = $id";
что вам нужно было сделать, прежде чем использовать подготовленные заявления, что помешало вам забыть избежать одного параметра как обычного человека. и все, что требуется для того, чтобы злоумышленник мог испортить вашу систему, – это всего лишь 1 неэкранированный параметр.
обычно изменение исходных файлов всегда опасно и имеет боль, особенно если ваш дизайн программного обеспечения плох и если у вас нет очевидного плана тестирования. но я скажу вам, что я сделал, чтобы сделать его как можно проще.
Я сделал функцию, которую будет использовать каждый код взаимодействия с базой данных, поэтому вы можете изменить то, что хотите позже, в одном месте – эта функция – вы можете сделать что-то вроде этого
class SystemModel { /** * @param string $query * @param string $types * @param array $vars * @param \mysqli $conn * @return boolean|$stmt */ public function preparedQuery($query,$types, array $vars, $conn) { if (count($vars) > 0) { $hasVars = true; } array_unshift($vars, $types); $stmt = $conn->prepare($query); if (! $stmt) { return false; } if (isset($hasVars)) { if (! call_user_func_array(array( $stmt, 'bind_param'), $this->refValues($vars))) { return false; } } $stmt->execute(); return $stmt; } /* used only inside preparedQuery */ /* code taken from: https://stackoverflow.com/a/13572647/5407848 */ protected function refValues($arr) { if (strnatcmp(phpversion(), '5.3') >= 0) { $refs = array(); foreach ($arr as $key => $value) $refs[$key] = &$arr[$key]; return $refs; } return $arr; } }
Теперь вы можете использовать этот интерфейс в любом месте в исходных файлах, например, давайте сменим текущие SQL-запросы, предоставленные вами в вопросе. Давайте изменим это
$mysqli = new mysqli('localhost', "root", "", "testdb"); $addresult = " SELECT a.firstnames, a.surname, a.schoolrole, a.datejoined FROM teachers a LEFT JOIN schools b ON a.schoolid = b.id WHERE b.id = '".$inputvalues['schoolid']."'"; if( $result = $mysqli->query($addresult) ) { while($row = $result->fetch_all()) { $returnResult = $row; } }
В это
$mysqli = new mysqli('localhost', "root", "", "testdb"); $sysModel = new SystemModel(); $addresult = " SELECT a.firstnames, a.surname, a.schoolrole, a.datejoined FROM teachers a LEFT JOIN schools b ON a.schoolid = b.id WHERE b.id = ?"; $types = "i"; // for more information on paramters types, please check : //https://php.net/manual/en/mysqli-stmt.bind-param.php $vars = []; $vars[] = $inputvalues['schoolid']; $stmt = $sysModel->preparedQuery($addresult, $types, $vars, $mysqli); if (!$stmt || $stmt->errno) { die('error'); // TODO: change later for a better illustrative output } $result = $stmt->get_result(); $returnResult = []; while ($row = $result->fetch_array(MYSQLI_ASSOC)) { $returnResult[] = $row; }
Кроме того, если я не вхожу ни в какие данные в БД, он все еще уязвим для инъекций?
Да, атака Sql Injection применяется путем объединения плотной строки в ваш оператор SQL. где это INSERT
, SELECT
, DELETE
, UPDATE
. например
$query = "SELECT * FROM user WHERE name = '{$_GET['name']}' AND password = '{$_GET['pass']}'"
что-то подобное можно использовать
// exmaple.com?name=me&pass=1' OR 1=1; --
что приведет к выражению SQL
$query = "SELECT * FROM user WHERE name = 'me' AND password = '1' OR 1=1; -- '" //executing the SQL statement and getting the result if($result->num_rows){ //user is authentic }else{ //wrong password } // that SQL will always get results from the table which will be considered a correct password
Удачи в том, чтобы переключить свое программное обеспечение на подготовленные заявления и помните, что душевное спокойствие, которое вы получите, зная, что, что бы ни случилось, вы безопасны от атак SQL-инъекций, стоит затрат на изменение исходных файлов