我刚刚开始做我的第一个项目(很有趣)。我正在学习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语句,这几乎将是50个源文件中包含的每个SQL语句。
好消息 是从切换到准备好的语句所获得的收益是无价的,例如:
1-您永远不会担心“ SQL注入攻击”
PHP 手册说
如果应用程序仅使用准备好的语句, 则开发人员可以确保不会发生SQL注入 (但是,如果使用未转义的输入来构建查询的其他部分,则仍然可以进行SQL注入)。
对我来说,这个理由-放心-足以支付更改我的源代码的费用。,现在您的客户可以在表单名称字段中输入内容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语句来应用Sql Injection攻击。往哪里去是INSERT,SELECT,DELETE,UPDATE。例如
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注入攻击,这值得您更改源文件。