手把手复现BUUCTF Ezsql漏洞环境:在Docker里搭建你的第一个PHP+MySQL靶场
2026/6/9 17:06:43 网站建设 项目流程

从零构建PHP+MySQL漏洞靶场:Docker实战BUUCTF Ezsql环境复现

在网络安全学习过程中,理论知识的吸收固然重要,但真正能让你快速成长的往往是动手实践。今天,我将带你从零开始,用Docker完整复现BUUCTF Ezsql这道经典的SQL注入题目环境。不同于简单的WriteUp阅读,我们将亲手搭建一个可攻击、可修复的实战环境,让你在安全可控的环境中体验从漏洞发现到修复的全过程。

1. 环境准备与Docker基础配置

在开始之前,我们需要准备好基础环境。Docker作为轻量级的容器技术,非常适合用来构建这种一次性的实验环境。如果你还没有安装Docker,可以参考官方文档进行安装,这里我们假设你已经具备了基本的Docker使用知识。

首先创建一个项目目录,我们将所有相关文件都放在这个目录下:

mkdir buuctf-ezsql && cd buuctf-ezsql

接下来,我们需要准备三个核心文件:

  • Dockerfile:定义我们的容器环境
  • docker-compose.yml:编排PHP和MySQL服务
  • 漏洞PHP文件:模拟题目中的有漏洞登录页面

2. 编写Docker配置文件

我们先从Dockerfile开始,这个文件定义了PHP环境的构建规则:

FROM php:7.4-apache RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli RUN apt-get update && apt-get install -y \ libzip-dev \ && rm -rf /var/lib/apt/lists/* COPY src/ /var/www/html/

这个配置做了以下几件事:

  1. 基于官方PHP 7.4镜像构建
  2. 安装mysqli扩展(用于PHP连接MySQL)
  3. 复制我们的PHP源代码到容器中

接下来是docker-compose.yml,它将协调PHP和MySQL两个服务:

version: '3' services: web: build: . ports: - "8080:80" depends_on: - db environment: - MYSQL_HOST=db - MYSQL_USER=root - MYSQL_PASSWORD=root - MYSQL_DATABASE=ctf db: image: mysql:5.7 environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=ctf volumes: - mysql_data:/var/lib/mysql volumes: mysql_data:

这个配置定义了两个服务:

  • web:我们的PHP应用,映射到宿主机的8080端口
  • db:MySQL数据库服务,使用MySQL 5.7版本

3. 构建有漏洞的PHP应用

现在我们来创建漏洞页面。在src目录下创建index.php

<?php error_reporting(0); $conn = new mysqli(getenv('MYSQL_HOST'), getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'), getenv('MYSQL_DATABASE')); if ($conn->connect_error) { die("连接失败: " . $conn->connect_error); } $username = $_GET['username'] ?? ''; $password = $_GET['password'] ?? ''; if (!empty($username) && !empty($password)) { $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; $result = $conn->query($sql); if (!$result) { die("查询错误: " . $conn->error); } if ($result->num_rows > 0) { echo "<h2>登录成功!</h2>"; } else { echo "<h2>用户名或密码错误</h2>"; } } ?> <!DOCTYPE html> <html> <head> <title>登录系统 - Ezsql漏洞演示</title> <style> body { font-family: Arial, sans-serif; max-width: 500px; margin: 0 auto; padding: 20px; } .login-form { margin-top: 30px; } input, button { padding: 8px; margin: 5px 0; width: 100%; } </style> </head> <body> <h1>登录系统</h1> <div class="login-form"> <form method="GET"> <input type="text" name="username" placeholder="用户名" required> <input type="password" name="password" placeholder="密码" required> <button type="submit">登录</button> </form> </div> </body> </html>

这个文件的关键漏洞在于直接拼接用户输入到SQL查询中,没有进行任何过滤或参数化处理。

4. 初始化数据库

我们需要创建一个数据库初始化脚本src/init.sql

CREATE TABLE IF NOT EXISTS 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'), ('test', 'test123');

为了让这个脚本在容器启动时自动执行,我们需要修改docker-compose.yml中的db服务部分:

db: image: mysql:5.7 environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=ctf volumes: - mysql_data:/var/lib/mysql - ./src/init.sql:/docker-entrypoint-initdb.d/init.sql

5. 启动环境并测试漏洞

现在我们可以启动整个环境了:

docker-compose up --build

等待所有服务启动完成后,访问http://localhost:8080,你应该能看到登录页面。

漏洞测试: 尝试使用经典的SQL注入payload:

  • 用户名:admin' OR '1'='1
  • 密码:任意值(如123

你应该会看到"登录成功"的提示,这说明我们的漏洞环境搭建成功了。

6. 漏洞分析与修复

让我们分析一下这个漏洞。问题出在PHP代码中的这一行:

$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

当用户输入admin' OR '1'='1时,实际执行的SQL语句变成了:

SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = '123'

由于'1'='1'永远为真,这个查询会返回users表中的所有记录,导致绕过认证。

修复方案

有几种方法可以修复这个漏洞:

  1. 使用预处理语句(推荐)
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?"); $stmt->bind_param("ss", $username, $password); $stmt->execute(); $result = $stmt->get_result();
  1. 使用过滤函数
$username = $conn->real_escape_string($username); $password = $conn->real_escape_string($password);
  1. 使用PHP的PDO扩展
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password"); $stmt->execute(['username' => $username, 'password' => $password]);

7. 加固后的完整代码

让我们实现第一个方案(预处理语句),这是最安全的方式:

<?php error_reporting(0); $conn = new mysqli(getenv('MYSQL_HOST'), getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'), getenv('MYSQL_DATABASE')); if ($conn->connect_error) { die("连接失败: " . $conn->connect_error); } $username = $_GET['username'] ?? ''; $password = $_GET['password'] ?? ''; if (!empty($username) && !empty($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 "<h2>登录成功!</h2>"; } else { echo "<h2>用户名或密码错误</h2>"; } } ?> <!DOCTYPE html> <html> <!-- 保持原有的HTML部分不变 --> </html>

8. 验证修复效果

修改代码后,我们需要重启服务使更改生效:

docker-compose down && docker-compose up

再次尝试之前的SQL注入payload,现在应该会看到"用户名或密码错误"的提示,说明我们的修复生效了。

额外测试用例

  1. 正常登录:admin/admin123
  2. 错误的登录:admin/wrongpassword
  3. 再次尝试SQL注入:admin' OR '1'='1/任意密码

9. 环境扩展与进阶学习

现在你已经成功复现并修复了这个SQL注入漏洞,可以进一步扩展这个环境:

  1. 添加更多漏洞类型

    • XSS漏洞
    • 文件包含漏洞
    • 命令注入漏洞
  2. 实现自动化测试: 使用Python脚本自动化测试漏洞:

import requests url = "http://localhost:8080" payload = {"username": "admin' OR '1'='1", "password": "123"} response = requests.get(url, params=payload) print("Vulnerable!" if "成功" in response.text else "Fixed!")
  1. 集成到CTF比赛中: 你可以把这个环境打包成一个CTF题目,添加flag机制和重置功能。

10. 最佳实践与安全建议

在开发实际的PHP应用时,除了修复这个特定的SQL注入漏洞外,还应该遵循以下安全实践:

  1. 始终使用预处理语句处理所有数据库查询
  2. 最小权限原则:数据库用户只应拥有必要的最小权限
  3. 错误处理:生产环境中不要显示详细的错误信息
  4. 输入验证:对所有用户输入进行严格的验证
  5. 定期更新:保持PHP和MySQL等组件的更新

安全防护措施对比表

防护方法安全性易用性适用场景
预处理语句★★★★★★★★★所有数据库操作
转义函数★★★★★★★遗留代码快速修复
存储过程★★★★★★复杂业务逻辑
ORM框架★★★★★★★★★现代应用开发

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询