跳至主要内容
版本:5.1

安全数据库查询

SQL 注入攻击是一种漏洞,攻击者可以通过注入用户控制的内容来操纵 SQL 查询。

请考虑以下代码片段

$query = $this->db->getQuery(true);
$username = $user->username;
$newPassword = password_hash($newPassword, PASSWORD_DEFAULT);

$query->update('#__users')->set('password = "' . $newPassword . '"')->where('username = "' . $username . '"');

$this->db->setQuery($query)->execute();

该代码旨在更新当前已登录用户的密码,该用户由其用户名标识,对于用户“foobar”的查询结果如下所示

UPDATE jos_users SET password = "{HASH}" WHERE username = "foobar";

现在假设用户选择foobar" OR username="admin作为其用户名。这将导致一个非常不同的查询

UPDATE jos_users SET password = "{HASH}" WHERE username = "foobar" OR username="admin";

因此,攻击者已将用户控制的命令注入到查询中,不仅重置了自己的密码,还重置了管理员用户的密码。

预防

使用预处理语句

这是防止 SQLi 攻击的金标准。

基本原理很简单:而不是在 PHP 代码中将用户提供的值集成到查询中,查询和输入值将通过单独的调用发送到数据库服务器

Prepared Statements
Query: SELECT foobar FROM bar WHERE foo = ?
Data: [? = 'bar']

基本原理很简单:而不是在 PHP 代码中将用户提供的值集成到查询中,查询和输入值将通过单独的调用发送到数据库服务器。然后,数据库服务器将查询和值相互组合,并在必要时处理转义和/或引号。

因此,通过将查询和注入的值彼此分离,注入变得不可能。

通过 JDatabaseDriver 实现预处理语句

在 Joomla 中实现预处理语句非常简单,并且跨平台。

$query = $this->db->getQuery(true)
->select($this->db->quoteName(array('id', 'password')))
->from($this->db->quoteName('#__users'))
->where($this->db->quoteName('username') . ' = :username')
->bind(':username', $credentials['username']);

在您的查询中,您定义了一个所谓的命名占位符,以双冒号为前缀。然后,使用bind()方法将该占位符的实际替换值传递给数据库服务器。

以下函数接受数组以减少函数调用的开销

  • bind()
  • bindArray()
  • whereIn()
  • whereNotIn()

!如果可能,您应该使用预处理语句!

了解更多

转义用户控制的输入

通过转义在 SQL 查询中被视为控制字符的字符,也可以防止攻击者“逃脱”您在查询中构建的双引号监狱。

$query = $this->db->getQuery(true);
$username = $user->username;
$newPassword = password_hash($newPassword, PASSWORD_DEFAULT);

$query->update('#__users')->set('password = "' . $this->db->escape($newPassword) . '"')->where('username = "' . $this->db->escape($username) . '"');

$this->db->setQuery($query)->execute();

您还可以删除查询中手动添加的双引号,并使用quote()quoteName()方法,因为这些方法默认也会转义提供的输入。