文章总结: 本文为SQL注入入门靶场实战教程,通过Docker搭建LAMP环境,演示了从数据库基础操作到注入探测与UNION注入利用的完整流程,并讲解了日志分析特征与参数化查询等防护措施。文章强调注入根源是字符串拼接,核心防护是使用PDO预处理,并建议结合白名单校验与最小权限原则进行纵深防御。
综合评分: 75
文章分类: WEB安全,渗透测试,安全开发,安全工具
靶场实战03|搞懂数据库,才能玩转注入(附docker靶场部署步骤)
原创
安全值班室
安全值班室
安全值班室
2026年9月19日 09:00
北京
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
你访问的每个网站,几乎都是三层结构:浏览器里的页面是前端,背后处理请求的是后端程序,存账号、订单、商品的是数据库。攻击者盯上的,正是这三层之间来回传递的参数。
这一期我们把地基打牢:HTML/JS是什么、MySQL怎么增删改查、联合查询为什么是后面注入拖库的钥匙。全程在本地Docker里动手,零风险。
01 / 环境搭建:5分钟搭起LAMP三层架构
本章靶场是标准的LAMP环境(Linux+Apache+PHP+MySQL),全部用官方镜像,一条条命令启动,完全隔离在本机:
# 创建容器网络docker network create lab-net# 启动MySQL5.7docker run -d --name lab-db --network lab-net \-e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=shop \-p 3307:3306 docker.1ms.run/library/mysql:5.7# 启动PHP7.4-Apache,挂载网站目录docker run -d --name lab-web --network lab-net \-p 8080:80 -v $PWD/www:/var/www/html \docker.1ms.run/library/php:7.4-apache# 安装数据库扩展并重启web容器docker exec lab-web docker-php-ext-install mysqli pdo_mysqldocker restart lab-web
靶场说明:为什么选LAMP?因为它是全球部署最广的Web架构,也是SQL注入、文件上传、反序列化等绝大多数Web漏洞的第一现场。把”数据从浏览器到数据库的完整旅程”看明白,后面学漏洞会非常顺。php:7.4-apache和mysql:5.7都是官方镜像,稳定、干净、行为可预期。
避坑指南(新手必看):
① MySQL连不上报”Can’t connect”:不是命令错了,是MySQL首次启动要初始化数据目录,约30-60秒。等1分钟,或执行 docker logs lab-db 看到”ready for connections”再连。
② 端口冲突报”bind: address already in use”:8080/3307被占用了。换端口,比如 -p 8081:80,或先 docker ps 找到占用容器停掉。
③ PHP页面报”Call to undefined function mysqli_connect”:忘了装扩展。必须执行第4步 docker-php-ext-install 并重启容器,扩展才会生效。
④ 老教程里的 –link 参数已标记废弃,统一用 docker network create 建网,更规范也少踩坑。
02 / 模拟攻击:从数据库基础到注入前夜
先别急着打打杀杀。这一课的”攻击”是预习:在本地靶场里把数据库和联合查询玩熟,到第9章学SQL注入时直接就能上手。分5步走:
步骤1|初始化数据表——进入数据库容器,建一张商品表:
docker exec -it lab-db mysql -uroot -proot shop
CREATE TABLE goods ( id INT PRIMARY KEY, name VARCHAR(50), price DECIMAL(10,2));INSERT INTO goods VALUES (1,'路由器',199.00),(2,'摄像头',89.00),(3,'硬盘',459.00);SELECT * FROM goods;
说明:goods表模拟电商商品表,后面所有实验都围绕它。SELECT是查询,WHERE id=2就是”查ID为2的商品”。
步骤2|写一个有”坏味道”的查询页面——新建 www/goods.php:
<?php$conn = new mysqli('lab-db','root','root','shop');$id = $_GET['id'];$sql = "SELECT name, price FROM goods WHERE id = $id";$result = $conn->query($sql);while($row = $result->fetch_assoc()){ echo $row['name'].' '.$row['price'].'<br>';}?>
注意第3、4行:用户输入被直接拼进SQL。这是教科书级的错误写法,但很多老系统里真实存在。浏览器打开 http://localhost:8080/goods.php?id=1,正常返回”路由器 199.00″。
步骤3|正常访问,记住基线——curl验证:
curl -s "http://localhost:8080/goods.php?id=1"# 输出:路由器 199.00
步骤4|加一个单引号,探一探——把id改成 1′ :
# 传入单引号,触发SQL语法错误curl -s "http://localhost:8080/goods.php?id=1'"# 响应特征:页面返回数据库语法报错# 拼接后的SQL语句:# SELECT name, price FROM goods WHERE id = 1'# 多出一个单引号,SQL语法断裂# 注:报错返回的库名、账号等敏感信息已打码 [REDACTED]
为什么?因为SQL是拼出来的,多出来的单引号破坏了语句结构。这就是注入点的”探针”:只要参数拼接SQL,单引号就会让它现形。注意:这一步只在本地靶场做,真实系统没有授权就是违法。
步骤5|用UNION预习”拖库”——在MySQL里直接执行:
-- UNION 联合查询,读取数据库用户与版本信息SELECT name, price FROM goods WHERE id = 1 UNION SELECT user(), version();# 返回结果多出一行:root@[REDACTED] | 5.7.xx
UNION把两条查询的结果上下拼接,硬性要求两边列数一致(这里都是2列)。user()返回当前数据库用户,version()返回版本号。如果页面把这两列原样输出,攻击者就能用同样的语法把数据库里的用户名、密码哈希一行行”借”出来——这就是注入拖库的原理雏形。
响应包特征总结:正常请求200+两列商品数据;加单引号变500/语法错误;UNION请求200且多出一行非商品数据。三步特征串起来,就是一条完整的注入探测链。
03 / 日志分析:数据库在日志里留下的脚印
攻击特点:在Apache访问日志里,注入探测长这样(脱敏):
192.168.1.10 - - [17/Aug/2026:14:03:22 +0800] "GET /goods.php?id=1 HTTP/1.1" 200 451192.168.1.10 - - [17/Aug/2026:14:03:25 +0800] "GET /goods.php?id=1%27 HTTP/1.1" 500 320192.168.1.10 - - [17/Aug/2026:14:03:28 +0800] "GET /goods.php?id=1%20UNION%20SELECT%20user(),version() HTTP/1.1" 200 128
逐行解读:
第1行:id=1,正常请求,200,返回45字节——这是基线。
第2行:id=1%27(%27是单引号的URL编码),返回500且字节数飙到320——异常参数引发服务器报错,第一条警报。
第3行:id=1%20UNION%20SELECT…(%20是空格),200但返回128字节——参数里出现SQL关键字,返回内容异常。
判断标准(真实攻击,不是误报/爬虫):
① 参数编码异常:%27、%20、%2C(引号、空格、逗号)密集出现在参数里,正常用户绝不会这么输入。
② SQL关键字命中:URL里出现 union、select、sleep、benchmark 等,直接指向注入尝试。
③ 状态码节奏异常:200→500→200,同一IP几秒内连续请求同一页面——这是”探测-确认-利用”的典型节奏。
④ 响应字节突变:同一页面正常45字节,攻击时128+字节,说明返回了额外数据。
注意区分:搜索引擎爬虫路径规范、无参数探测、频率稳定,不是攻击;而上述四条同时出现,基本可以断定是人工注入尝试。
04 / 如何防护:把代码变回数据
先讲原理:SQL注入能成功,根本原因不是数据库不安全,而是开发者在拼接SQL时把用户输入当成了代码。输入里的单引号和关键字被数据库当成SQL语句的一部分执行了。只要输入永远是”数据”,注入就不存在。
基础级(必须做):参数化查询。用PDO预处理,SQL结构提前固定,输入只作为参数传递:
<?php$pdo = new PDO('mysql:host=lab-db;dbname=shop','root','root');$stmt = $pdo->prepare('SELECT name, price FROM goods WHERE id = ?');$stmt->execute([$_GET['id']]); // 用户输入只当做数据,不能篡改SQL结构$rows = $stmt->fetchAll();?>
推荐:白名单校验+最小权限。id只允许数字,用 (int) 强转;给Web应用单独建一个只有SELECT权限的数据库账号,即使被注入也拿不到表结构、改不了数据。
纵深防御:WAF拦截SQL特征请求、开启数据库审计日志、异常参数告警联动;代码上线前用静态扫描找出所有”字符串拼接SQL”;敏感操作双人复核。分层设防,让攻击者每一层都要付出代价。
05 / 总结复盘
本章要点(新手必须记住):
① Web是三层架构:前端、后端、数据库,攻击发生在参数传递处。
② SQL注入的根源是字符串拼接,不是数据库本身。
③ UNION注入要求两边列数一致,这是拖库的关键语法。
④ 参数化查询是防注入的黄金标准,写第一行代码就要用。
⑤ 状态码节奏+参数编码异常,是日志里发现探测的第一道线索。
面试可能怎么问:
“SQL注入的原理?” 答:用户输入未过滤直接拼接进SQL,输入变成代码执行,破坏语句结构并窃取数据。
“UNION注入的前提?” 答:两边列数一致,且结果能回显到页面。
“预处理为什么能防注入?” 答:SQL语句结构在绑定参数前已由服务端编译确定,输入只作为数据传递,无法改变语句结构。
下一期进入”安全测试方法论与工具环境”:Burp Suite代理、浏览器DevTools、完整测试流程。
关注我,下期不迷路
MORE
往期回顾
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:安全值班室 安全值班室
安全值班室《靶场实战03|搞懂数据库,才能玩转注入(附docker靶场部署步骤)》