一、环境搭建
- 安装phpstudy:- 下载并安装phpstudy并启动Apache、MySQL等服务。
- 配置数据库:- 在phpmyadmin创建一个名为
test_db的数据库。CREATE DATABASE test_db;USE test_db;CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL, password VARCHAR(50) NOT NULL);INSERT INTO users (username, password) VALUES ('admin', 'admin123'), ('user1', 'password1'); - Web页面的配置:- 在phpstudy的
WWW目录中,创建一个名为sql_injection的文件夹并在该文件夹内创建一个名为login.php的文件,并添加以下代码:<?php$conn = new mysqli("localhost", "root", "123456", "test_db");if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error);}if ($_SERVER["REQUEST_METHOD"] == "POST") { $username = $_POST['username']; $password = $_POST['password']; $sql = "SELECT * FROM users WHERE username='$username' AND password='$password'"; $result = $conn->query($sql); if ($result->num_rows > 0) { echo "Login successful!"; } else { echo "Invalid credentials!"; }}?><form method="POST"> Username: <input type="text" name="username"><br> Password: <input type="password" name="password"><br> <input type="submit" value="Login"></form> - 测试: 访问
http://localhost/sql_injection/login.php,输入用户名和密码进行测试,确保页面工作正常。
二、SQL注入漏洞复现
- 常见注入攻击:- 在用户名字段中输入:
' OR 1=1; --,输入密码123456- 点击登录,显示“Login successful!”,则注入成功。 - Union查询注入:- 在用户名字段中输入:
' UNION SELECT null, table_name FROM information_schema.tables; --,输入密码123456- 输出结果,会看到数据库中的表名。
三、SQL注入防御——PDO
在上面的php代码中,输入的用户名和密码是直接嵌入到SQL查询的字符串中的,所以会引发SQL注入。比如,如果用户输入以下内容作为用户名:
' OR '1'='1
则,生成的SQL查询如下:
SELECT * FROM users WHERE username='' OR '1'='1' AND password='password'
这个SQL查询总是为真,因此攻击者可以绕过登录验证。
解释PDO的防御机制
1. 预处理语句
预处理语句的优势在于,它将 SQL 结构与参数分开,这样即使用户输入了恶意的 SQL 代码,也不会影响原始的 SQL 语句结构。
$sql = "SELECT * FROM users WHERE username = :username";
$stmt = $pdo->prepare($sql);
正如以上,
:username
是一个占位符,它表示在执行查询时将由一个实际的值来替代。
2. 参数绑定
参数绑定是指在执行预处理语句时,将用户输入的数据绑定到特定的占位符上。PDO 会自动对输入的数据进行转义,确保其不会被当作 SQL 代码来执行,从而有效防止 SQL 注入。
$username = $_POST['username'];
$stmt->bindParam(':username', $username);
$stmt->execute();
在这段代码中,用户输入的
username
会被绑定到
:username
占位符上。PDO 会确保这个输入值被视作字符串,而不是 SQL 语句的一部分。
防止 SQL 注入
SQL 注入是一种常见的网络攻击方式,攻击者通过将恶意的 SQL 代码插入到输入字段中,试图操纵数据库查询。例如:
如果直接将用户输入嵌入到 SQL 查询中:
$sql = "SELECT * FROM users WHERE username = '" . $_POST['username'] . "'";
如果用户输入
username
为
'; DROP TABLE users; --
,则查询将变成:
SELECT * FROM users WHERE username = ''; DROP TABLE users; --';
这可能导致用户表被删除。而使用 PDO 的预处理语句和参数绑定,即使输入了这样的恶意字符串,也不会被执行,因为它仅作为字符串参数被处理。 所以我们可以,
- 修改
login.php来使用PDO:<?phptry { $conn = new PDO("mysql:host=localhost;dbname=test_db", "root", "123456"); // 设置PDO错误模式为异常模式 $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); echo "Connected successfully<br>";} catch(PDOException $e) { die("Connection failed: " . $e->getMessage());}if ($_SERVER["REQUEST_METHOD"] == "POST") { $username = $_POST['username']; $password = $_POST['password']; // 使用预处理语句防止SQL注入 $stmt = $conn->prepare("SELECT * FROM users WHERE username = :username AND password = :password"); $stmt->bindParam(':username', $username); $stmt->bindParam(':password', $password); $stmt->execute(); $result = $stmt->fetchAll(); if (count($result) > 0) { echo "Login successful!"; } else { echo "Invalid credentials!"; }}?><form method="POST"> Username: <input type="text" name="username"><br> Password: <input type="password" name="password"><br> <input type="submit" value="Login"></form> - 重测:- 重复上述的SQL注入攻击测试,因为用户输入不再直接嵌入SQL语句中,而是通过参数传递,所以防止了SQL注入。
四、绕过PDO
1.错误使用传递
使用了PDO,但是不正确的使用参数绑定传递,二是直接拼接用户的输入,也依旧存在注入风险,就像:
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$stmt = $pdo->query($sql);
$user = $stmt->fetch();
if ($user) {
echo "Login successful!";
} else {
echo "Invalid username or password.";
}
$username
和
$password
是用户输入的数据,直接拼接到 SQL 查询字符串中。假设一个攻击者在用户名字段中输入以下内容:
' OR '1'='1
生成的 SQL 查询会变成:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '';
OR '1'='1'
是一个总为真的条件,查询将返回数据库中的所有用户记录。即使密码不正确,也可能成功登录。
2. 宽字节注入
假设目前的PHP 应用使用 GBK 编码,且开启了
magic_quotes_gpc
(已在 PHP 5.4 中移除)或手动使用了
addslashes()
函数来转义用户输入的单引号
'
。
$input = "\xdf"; // 用户输入的恶意数据
$escaped_input = addslashes($input); // 转义后的结果:\xdf
// 最终的 SQL 查询
$query = "SELECT * FROM users WHERE username = '$escaped_input'";
// 结果:SELECT * FROM users WHERE username = '\xdf'
在 GBK 编码下,
\xdf
是一个半字符,当与反斜杠
\
结合在一起时,MySQL 会将其视为一个完整的宽字符,从而忽略了转义。这可能导致单引号
'
被有效关闭,形成 SQL 注入。
$input = "\xdf' OR '1'='1"; // 用户输入的恶意数据
$escaped_input = addslashes($input); // 转义后的结果:\xdf\' OR \'1\'=\'1
// 最终的 SQL 查询
$query = "SELECT * FROM users WHERE username = '$escaped_input'";
// 结果:SELECT * FROM users WHERE username = '縗' OR '1'='1'
这也将导致 SQL 查询返回所有用户记录。
五、总结
虽然PDO提供了很强大的防御机制,但前提是正确地使用,包括正确参数绑定,避免直接拼接。还有字符集的指定,及时更新php·························O(∩_∩)O
版权归原作者 @DKX 所有, 如有侵权,请联系我们删除。