安全

学习网络安全 — 安全开发实践活动

安全开发实践练习:校验和净化用户输入、用预处理语句防范 SQL 注入、用 htmlspecialchars() 防范 XSS,附 PHP 与 MySQL 参考答案和逐步说明。

· 8 分钟阅读 · 难度 中级

开发中的安全是网络安全的一大支柱。它指的是从应用程序设计与开发的最初阶段就纳入安全实践,以预防漏洞并保护敏感数据。在当前网络攻击日益复杂的背景下,理解并在软件开发中落实安全措施是必不可少的。

这些练习将通过实际场景,引导您识别并修复代码中的常见漏洞。您将学习如何保护应用程序免受 SQL 注入和 XSS(跨站脚本)等攻击。每个练习都针对一种具体技术,为您提供开发健壮、安全的应用程序所需的知识。

Illustration 1 — Apprendre la cybersécurité - Activités pratiques en développement sécurisé

用户输入的校验

目标 :

学习校验和净化用户输入,以预防 SQL 注入、XSS 漏洞等基于不安全输入的常见攻击。

前置条件:

  • PHP 与 HTML 的基础知识。
  • 本地 Web 服务器(如 LAMP、XAMPP 或 WAMP)。

操作步骤:

  • 创建一个简单的 HTML 表单,让用户输入姓名和电子邮件地址。
  • 在 PHP 中处理数据,以避免混入恶意字符。

参考答案与说明参考答案
创建一个用于输入姓名和电子邮件的表单。
html <form method="post" action=""> Nom : <input type="text" name="nom"><br> Email : <input type="email" name="email"><br> <input type="submit" value="Envoyer"> </form>
在 PHP 中处理数据,以避免混入恶意字符。
<?php if ($_SERVER["REQUEST_METHOD"] == "POST") { $nom = $_POST['nom']; $email = $_POST['email']; // Validation et assainissement des données $nom = trim($nom); $nom = stripslashes($nom); $nom = htmlspecialchars($nom); $email = filter_var($email, FILTER_SANITIZE_EMAIL); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { echo "Adresse email invalide."; } else { echo "Nom : $nom<br>"; echo "Email : $email<br>"; } } ?>
说明
数据的校验与净化
trim($nom):删除字符串首尾的空白字符。stripslashes($nom) :删除某些 PHP 函数为转义特殊字符而添加的反斜杠( \ )。htmlspecialchars($nom):将特殊字符转换为 HTML 实体(例如 < 变成 &lt;),从而阻止 HTML 或 JavaScript 代码被执行。
电子邮件的校验
filter_var($email, FILTER_SANITIZE_EMAIL):删除电子邮件地址中的非法字符。filter_var($email, FILTER_VALIDATE_EMAIL) :校验该电子邮件地址格式是否正确。
详细的参考答案与说明
删除空格和无用字符:$nom = trim($nom);
该函数会删除字符串首尾的空白字符或其他预定义字符(如换行符)。
删除反斜杠:$nom = stripslashes($nom);
stripslashes() 会去掉在某些 PHP 配置中为转义引号而添加的反斜杠,从而防范某些形式的注入。
转换特殊字符:$nom = htmlspecialchars($nom);
htmlspecialchars() 将特殊字符转换为 HTML 实体,以阻止 XSS 攻击。例如 < 变成 &lt;,> 变成 &gt;,依此类推。
电子邮件的净化:$email = filter_var($email, FILTER_SANITIZE_EMAIL);
FILTER_SANITIZE_EMAIL 会删除电子邮件地址中所有不允许出现的字符。
电子邮件的校验:if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { echo "Adresse email invalide."; }
FILTER_VALIDATE_EMAIL 按照电子邮件地址标准检查该地址格式是否正确。如果邮件地址无效,则显示一条错误信息。


防范 SQL 注入

目标:理解并实施安全措施,以预防 SQL 注入攻击。

操作步骤

  • 创建一个简单的数据库,其中包含一张用户表。
  • 编写一条存在 SQL 注入漏洞的 SQL 查询。
  • 修改代码,改用预处理语句以预防 SQL 注入。

步骤

1. 创建数据库和数据表:

  • 使用一个数据库管理系统(例如 MariaDB、MySQL 或 PostegreSQL)创建一个数据库和一张 users 表。
  • 随后在该表中至少插入一个用户(例如 admin)

2. 存在漏洞的 SQL 查询:

  • 在一个 PHP 文件(例如 login.php)中编写一条存在 SQL 注入漏洞的 SQL 查询。
  • 打开浏览器并访问 login.php 文件。
  • 注入恶意的 SQL 查询,查看该应用是否存在漏洞。

3. 使用预处理语句:

  • 修改代码,改用预处理语句以预防注入。
  • 使用与前面相同的 SQL 注入重新测试该应用。
Illustration 2 — Apprendre la cybersécurité - Activités pratiques en développement sécurisé

参考答案与说明1. 创建数据库和数据表
创建数据库和数据表的 SQL 查询参考答案:
CREATE DATABASE security_test; USE security_test; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50), password VARCHAR(50) );
插入数据:
INSERT INTO users (username, password) VALUES ('admin', 'password123'), ('user1', 'pass1');
MySQL 命令说明:
CREATE DATABASE security_test; :该命令创建一个名为 security_test 的新数据库。
USE security_test; :该命令选中 security_test 数据库,使后续命令在此上下文中执行。
CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50), password VARCHAR(50));:该命令创建一张 users 表,包含三列:id(自增主键)、username 和 password(两者均为 VARCHAR 类型,最大长度 50 个字符)。
INSERT INTO users (username, password) VALUES ('admin', 'password123'), ('user1', 'pass1'); :该命令向 users 表中插入两条记录。
这些步骤为 SQL 注入练习搭建了一个简单的测试环境。
2. 存在漏洞的 SQL 查询
login.php 文件的内容:
<?php $conn = new mysqli('localhost', 'root', '', 'security_test'); if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } if ($_SERVER["REQUEST_METHOD"] == "POST") { $username = $_POST['username']; $password = $_POST['password']; $query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; $result = $conn->query($query); if ($result->num_rows > 0) { echo "Login successful!"; } else { echo "Invalid credentials."; } } ?> <form method="post" action=""> Username: <input type="text" name="username"> Password: <input type="password" name="password"> <input type="submit" value="Login"> </form>
代码说明:
连接数据库:new mysqli('localhost', 'root', '', 'security_test'); 以用户 root 且无密码的方式,在 localhost 上建立到 security_test 数据库的连接。
SQL 查询:$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; 根据用户输入动态拼接一条 SQL 查询。
检查结果:if ($result->num_rows > 0) 检查该查询是否返回了记录行,返回则表示登录成功。
漏洞:
只要在输入框中注入恶意的 SQL 查询,例如用户名填 admin' OR '1'='1、密码填 anything,攻击者就能绕过身份验证。
SQL 注入的细节
攻击示例
用户名:admin' OR '1'='1密码:anything
SQL 查询的拼接:
当这些值通过网页表单提交时,存在漏洞的 PHP 代码会拼接出下面这条 SQL 查询:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'anything';
为便于理解,我们把这条查询拆开来看:
WHERE 子句:username = 'admin':检查用户名是否为 admin。OR '1'='1':恒为真,因为 '1' 等于 '1'。
AND password = 'anything':检查密码是否为 anything。
WHERE 子句的求值:
在 SQL 中,AND 和 OR 运算符遵循确定的逻辑:
只要条件 A 或 B 中有一个为真,A OR B 就为真。只有当条件 A 和 B 都为真时,A AND B 才为真。
在本例中:条件 username = 'admin' 可能为真也可能为假。条件 '1'='1' 恒为真。条件 password = 'anything' 可能为真也可能为假。由于存在 OR '1'='1',无论 username 字段的内容是什么,整个 username = 'admin' OR '1'='1' 子句都恒为真。
查询的结果:
由于 OR '1'='1' 恒为真,该查询可以简化为:
SELECT * FROM users WHERE TRUE AND password = 'anything';这等价于:
SELECT * FROM users WHERE password = 'anything';
不过,一个恒为真的条件与一个无关紧要的条件(password = 'anything')通过 AND 相连,意味着如果有某一行与所提供的密码匹配,MySQL 就会返回该表的第一行;若其中某个条件为假,甚至可能返回所有行。但由于 OR '1'='1' 恒为真,该查询会返回全部用户。
影响:该查询返回了 users 表中的一行,使 PHP 脚本误以为身份验证已经成功。
攻击者由此在不知道真实密码的情况下成功通过了身份验证。使用预处理语句
修改后的 login.php 文件:<?php $conn = new mysqli('localhost', 'root', '', 'security_test'); if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } if ($_SERVER["REQUEST_METHOD"] == "POST") { $username = $_POST['username']; $password = $_POST['password']; $stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?"); $stmt->bind_param("ss", $username, $password); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { echo "Login successful!"; } else { echo "Invalid credentials."; } } ?> <form method="post" action=""> Username: <input type="text" name="username"> Password: <input type="password" name="password"> <input type="submit" value="Login"> </form>
代码说明:
1. mysqli_stmt 类
PHP 中的 mysqli_stmt 类表示一条预处理的 SQL 语句。它能够以安全的方式执行 SQL 查询,从而避免 SQL 注入。该类与 mysqli 对象(表示一个数据库连接)配合使用。
2. 创建预处理语句
代码行:
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$conn->prepare() 是 mysqli 对象的一个方法,其参数是一个字符串,内含使用问号 ? 作为参数占位符的 SQL 查询。
该方法返回一个表示预处理语句的 mysqli_stmt 对象。问号 ? 标明变量的值将以安全方式插入的位置。
3. 绑定参数
代码行:
$stmt->bind_param("ss", $username, $password);
bind_param() 是 mysqli_stmt 对象的一个方法,用于将 PHP 变量绑定到预处理语句中的占位符 ?。
第一个参数 "ss" 是一个字符串,用于指定各参数的类型:"s" 表示 string(字符串)"i" 表示 integer(整数)"d" 表示 double(浮点数)"b" 表示 blob(二进制数据)
在本例中,"ss" 表示这两个参数都是字符串。
后面的参数就是要绑定的变量:$username 和 $password。
4. 执行查询
代码行:
$stmt->execute();
execute() 是 mysqli_stmt 对象的一个方法,用于以绑定的参数值执行预处理语句。调用 execute() 时,SQL 查询会连同替换占位符的变量值一起发送给数据库服务器。
5. 获取结果
代码行:
$result = $stmt->get_result();
get_result() 是 mysqli_stmt 对象的一个方法,返回一个表示查询结果的 mysqli_result 对象。可以通过该对象访问 SQL 查询返回的数据。
预处理语句的重要性
使用预处理语句能够阻止此类注入,因为用户的输入不会被直接拼接进 SQL 查询。预处理语句把输入当作纯数据处理,与 SQL 命令相互分离。

防范 XSS 漏洞

目标:通过理解攻击者如何向 Web 应用注入恶意脚本,学习识别并预防 XSS 漏洞。

前置条件:

  • PHP 与 HTML 的基础知识。
  • 本地 Web 服务器(如 XAMPP 或 WAMP)。

步骤:

1. 创建一个存在漏洞的 Web 应用

创建一个简单的 HTML 表单,让用户提交一条评论,然后在同一页面上不加校验地显示这条评论。

<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $comment = $_POST['comment'];
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Commentaires</title>
</head>
<body>
    <form method="post" action="">
        Commentaire: <input type="text" name="comment">
        <input type="submit" value="Soumettre">
    </form>

    <h2>Commentaires:</h2>
    <p><?php echo $comment; ?></p>
</body>
</html>

2. 注入一段恶意脚本:

提交表单,并以一段恶意脚本作为评论内容:

<script>alert('XSS Attack!');</script>

当这条评论被显示时,该脚本将被执行,弹出一个内容为 "XSS Attack!" 的提示框。

3. 理解该请求的行为:

该表单通过一个 POST 请求发送评论数据。

$_POST['comment'] 的值被直接显示而未经校验,因此可以注入 JavaScript 代码。

4. 修复 XSS 漏洞

修改该 PHP 脚本,使用 htmlspecialchars() 将特殊字符转换为 HTML 实体,从而阻止脚本被执行。

<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $comment = htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Commentaires</title>
</head>
<body>
    <form method="post" action="">
        Commentaire: <input type="text" name="comment">
        <input type="submit" value="Soumettre">
    </form>
    <h2>Commentaires:</h2>
    <p><?php echo $comment; ?></p>
</body>
</html>

5. 验证防护效果

再次提交表单并附上那段恶意脚本:

<script>alert('XSS Attack!');</script>

这一次,脚本不会被执行,评论将原样显示为 <script>alert('XSS Attack!');</script>。