安全数据库查询
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()
方法,因为这些方法默认也会转义提供的输入。