1. 从“血性”到“理性”:为什么说“是人创造出来的东西就会有漏洞”
几年前我在某项目的上线前安全评审里,第一次亲眼见证“人写的代码必然有缺陷”变成现实。当时团队连续迭代了三个月,所有人都认为逻辑已经收敛得足够干净,结果内部演练时,一个不起眼的边界条件还是被发现了——不是什么高深技巧,就是某个接口在没有校验空值的情况下,被一梭子数据打穿。事后复盘,写代码的人苦恼地问:“怎么会漏掉这种地方?”答案很简单:因为你是一个人,人的注意力会被习惯和惯性牵引,而隐藏缺陷恰恰藏在那些“你根本不会去多想”的缝隙里。
如果把“计算机系统”当作一个城市,那么它所有的功能模块,就是城市里的道路、桥梁和下水道系统。设计者规划了主干道,预算了人流密度,却很难预测几十年后的人口增长、极端天气、管线老化。“是人创造出来的东西就会有漏洞”这句话,本质上是承认了万物皆有“衰老”与“意外”的可能。在这个前提下,安全行业的任务就不再是“消灭所有缺陷”——那是不可能完成的任务——而是建立一套机制,让缺陷在被真实伤害影响用户之前就被看见、被修复。
这也是所谓的“红蓝对抗”能成为一种成熟方法论的根本原因。它把安全研究变成了反复演兵:红队站在攻击者视角,思考“如果我是个坏蛋,我会怎么打进这座城市”;蓝队站在防御者视角,思考“如果出现险情,我的应急通道、隔离墙、报警系统是否真的能扛住”。最初我听到“血性决斗场”这种说法时,觉得它把技术比拼描述得太像竞技赛事,但后来明白了,这个词强调的其实是“高强度、高仿真、 высокий对抗”。在日常开发环境里,系统就像风平浪静的操场;在红蓝对抗中,系统则被投入到模拟的雷暴环境里,所有门窗都要经过一次压力测试。
可能有读者会问:我们不是经常听开发团队说“线上跑得好好的,没有暴露任何问题”吗?这正是“正常环境与对抗环境”的区别。平时系统的流量路径是固定的、用户行为是可预测的,缺陷就像埋在堆满稻草的仓库里的那颗钉子,只要没人去用力踩,它就只是刺破空气的静物。而红蓝对抗要做的事,就是专门派人在仓库里各种翻找,不只是找钉子,还要找那些“看似无关、但组合起来能引发连锁反应”的隐患。所以与其说这是一种技术手段,不如说它是一种“对系统保持怀疑”的态度。
大多数人对“漏洞”这个词的直觉认知,停留在“程序员写错了某行代码”上。但真实的漏洞形态远比这丰富:
- 业务逻辑漏洞:代码语法完全正确,程序也能正常运行,但逻辑上的“顺序漏洞”或“信任边界缺失”会被利用。比如某金融 App 转账时,前端隐藏了手续费字段,后端又没有校验,攻击者就能构造请求绕过计费。
- 配置脆弱性:系统代码本身没问题,但部署时端口暴露过多、默认口令未改、日志级别过高等,都会形成防守薄弱的“城门”。
- 人机交互缺陷:用户界面引导不清晰,导致操作者习惯性将敏感信息粘贴到不该粘贴的位置,或者被钓鱼页面诱导输入密码。
- 依赖组件风险:现代软件几乎不可能从零造轮子,外部库、开源框架、第三方 SDK 就像城市里的外包工程,它们自带的问题会成为整条链路的短板。
举个最贴近生活的类比:你花巨资买了最好的智能门锁,但把备用钥匙放在门槛的地毯下面。这一行为不是“门锁有漏洞”,而是整个“回家链路”存在一个几乎与门锁无关的破绽。红蓝对抗中讨论的“漏洞”,恰恰是这种系统性的破绽,而不是某一个孤立的故障。
因此,我对“是人创造出来的东西就会有漏洞”这句话的理解是:它不是在给失败找借口,而是在呼吁一种谦逊和清醒。你在设计和构建系统的时候,必须假设“我可能会犯错”“我的队友可能会疏忽”“第三方依赖可能隐藏风险”,然后在这个假设之上,建立可验证、可度量、可持续改进的安全流程。今天这篇博文,我会用一个贴近真实项目的思路,把“红蓝对抗”如何运行、如何帮助我们发现并修复漏洞、以及“漏洞发现之后要做什么”的完整链路,按一个从业者的口吻讲清楚。如果你是初入行的安全工程师、想建立安全意识的开发者,或者只是对“攻防”好奇的爱好者,这篇内容都会给你一个比较踏实的框架。
2. 红蓝对抗的运行逻辑:从模拟攻击中拿到“真实损伤清单”
2.1 为什么“平时测试过”不足以证明安全
每一个吃饱了“测试没问题”苦头的工程师,大概都体验过那种“测试环境一片绿,生产环境一片红”的割裂感。单元测试覆盖了核心函数,自动化测试跑了成百上千条用例,Code Review 也过了三轮,但真正进入高并发、误操作、人为恶意的环境时,系统还是会现出原形。原因在于:普通测试是"按预期设计测试",而红蓝对抗是“按非预期设计试探”。
常规测试的逻辑是:我有需求规格说明书,我知道系统应该干什么,所以我顺着功能路径去验证它有没有干好。红蓝对抗的逻辑则是:我拿到一张系统地图,但我只想找到那些“地图上没有标出来、却可以通过其他路径到达的地方”。前者关注“正门能不能开”,后者关注“窗户有没有锁、排水管能不能爬、通风管道里有没有隔断”。这两者的信息量和思考强度完全不同。
我还记得某次内部评审,开发团队领导笃定地说:“我们对所有用户输入都做了校验,不存在注入问题。”红队的报告出来之后,大家才发现,某条消息队列的消费处理逻辑里,数据来源被认为是“内部可信”,因此没有做任何过滤。结果是在特定的消息内容操控下,队列里的字段可以被渲染成一段指令。这个缺陷在常规测试阶段很难被发现,因为测试人员只从外部 API 发起请求,而消息队列的内部流转被视为“高可信区”。红队却会专门去检查“信任边界”,这是测试用例里最容易被忽略的维度。
所以,红蓝对抗真正的价值不是“找几个 Bug”,而是在有限的时间内,把所有可能被利用的攻击面都拉到一个清单里,哪怕只是粗略的。这个清单,业务方把它称为“真实损伤清单”:分清哪些漏洞能被远程利用、哪些需要内部权限、哪些需要用户交互,从而排出风险处理的优先级。
2.2 一次完整演练的四个阶段
在我经历过的若干次红蓝对抗项目中,流程基本可以固定为四个阶段——虽然每次侧重点不同,但骨架一致。
阶段一:信息收束与攻击面测绘
红队出发前,会尽可能收集目标系统的公开信息。这里不牵扯任何特殊渠道,仅仅是域名、证书透明度日志、GitHub 上意外泄露的注释、招聘广告里的技术栈描述等。这些信息拼凑起来,就能大致推断出系统用了什么框架、哪类网关、什么前端脚手架、可能存在的老旧依赖。攻击面的测绘有点像拿到一张模糊的城市地图:有些道路清晰,有些街区需要现场踩点,但至少能规划出一条大概率可行的路线。这个阶段的工作直接决定后续测试效率,如果连“门朝哪开”都不知道,后面就是在胡打。
阶段二:漏洞探测与利用验证
红队开始对目标进行动态测试和静态代码分析。动态测试指的是用种种手段向系统喂入异常数据、观察回应;静态分析则是直接阅读代码、审查配置文件,寻找逻辑漏洞。两者互为补充:动态测试能发现运行期才暴露的问题,而静态分析能定位那些因为“当前数据没触发”而潜伏的缺陷。在这个阶段,红队不只是“找到一个可疑点”,而是会尝试将可疑点串起来,形成一条完整的攻击链。比如单独看某个文件上传点,它只是能传一个文件名,但如果后续文件名拼接进一个系统命令,就可能升级为高危事件。攻击链的思维是“组合漏洞”,一套系统中单个漏洞的危害往往有限,但组合起来就可能打通整个内网。
阶段三:蓝队监测与应急响应
与红队并行,蓝队会开启监控大屏、梳理告警规则、制定应急角色分工。真实演练里,蓝队并不提前知道红队会从哪里进来,所以他们的工作是在日常安全运营的基础上,模拟“突发事件已经发生”时如何最快发现、如何定位、如何抑制。这个阶段最容易暴露的问题是:告警规则设得太严导致误报刷屏,或者设得太松导致有效告警被淹没;应急手册写得太抽象,实际操作时不知道谁来执行封禁指令;日志审计链路太长,无法在短时间内回溯攻击路径。蓝队的成熟度直接影响“损失评估”——有的团队明明系统已经被打穿,却因为监控缺失,演练结束后才后知后觉。
阶段四:复盘与修复计划
演练结束后,红队出具报告,蓝队给出防守日志,双方坐在一起,拿着“真实损伤清单”逐条对质:这个漏洞您觉得风险不高?那您看看这条攻击链能够连锁引发到哪一步;那条告警当时没响应?当时值班人手确实不够,但我们的自动化预案可以这样调整。复盘的产出不能只是一份“问题汇总”,而是要转化为具体的修复项和责任人,并给出验证周期。红蓝对抗的重心从来不是“谁赢谁输”,而是“我们发现了什么、以后如何减少同类问题”。
2.3 一个值得参考的验证指标
不少人问我:“怎么判断一次红蓝对抗值不值?”我给出的通用思路是:不要只看“发现漏洞的数量”,而要看“高危漏洞从发现到确认再到修复的闭环周期”以及“同类漏洞在其他模块的复发检查”。漏洞数量在一个准备充分的系统上可能本来就很少,但如果发现的 3 个漏洞都是同一类根因(比如“外部输入未过滤”),那么这次演练最大的价值是定位到了一个系统性的编码习惯问题,而不是三个孤立 Bug。做安全,先把根因挖出来,比追求“多抓几个虫”重要得多。
3. 漏洞里最常见的三类“人味”:逻辑、信任、懒惰
3.1 逻辑漏洞:程序按照你写的做,但没按你设想的做
逻辑漏洞的特点是:代码执行完全符合语法规则,只是结果与设计意图相悖。我曾经见过一个优惠券系统,规则是“同一个用户可以领取三次”。开发者在设计表结构时,用“用户ID + 优惠券类型”做了唯一索引,用来防止重复领取。表面上看没问题,但忽略了“领取失败”状态也会写入一条记录。于是用户先故意触发一次失败(比如在请求时传一个非法参数),那么这条失败记录就占了一个名额,真正的领取入口只剩下两次。攻击者可以构造请求,反复触发失败式写入,把优惠券池占满,导致其他正常用户无法领取。
这类问题的根源在于:程序员在写代码时,对“中间状态”和“边界情况”的建模不够完整。数据库约束解决了“重复成功”的问题,却没解决“重复失败”的问题。红队在这种地方特别容易得手,因为他们的思路不是“把一个功能做完”,而是“把一个功能的状态穷举完”。防御者想明白这一点后,最好的方式是在设计阶段做一张“状态迁移图”,明确每一个输入可能导致的状态变化,而不是等到代码写完才去测试。
3.2 信任漏洞:默认相信“内部的东西是安全的”
现代架构中,微服务之间通信、内网服务调用、消息队列投递,都默认处在“内部可信”环境。这种信任在开发阶段很省事,但一旦某个内部组件被外部攻击者拿到了一丝控制权,整个信任链就会崩盘。一个典型场景是某公司的运维系统里有一个“配置同步接口”,只允许内网调用,所以没有鉴权。攻击者通过一个稍早前发现的员工终端 RCE(远程代码执行)漏洞,打进内网,再横向扫描,发现这个“无鉴权配置接口”,就能篡改所有服务器的配置文件。这里的关键不是那个 RCE 本身有多厉害,而是“信任边界”太脆:外部和内部的访问一旦被打通,内网服务就不再是免检产品。
我从这些案例里学到的最重要一课是:安全策略不能建立在“来源可信”上,而应该建立在“权限最小”和“必须验证”上。哪怕两个服务住在一个容器网络里,每一次调用也要有身份证明。虽然这会让开发多写几行代码、多一点延迟,但这是必要的成本。红蓝对抗之所以能逼出这类问题,是因为演练期间可以允许红队模拟“已经突破第一道防线”的场景——一个普通人不会做的测试假设,恰恰是真实攻击者被拦下后一定会继续推进的方向。
3.3 懒惰漏洞:默认配置、弱口令和“以后再改”
如果说逻辑漏洞和信任漏洞还需要一点“思考深度”才能利用,那么“懒惰漏洞”简直是安全从业者的友好福利。我甚至见过一台内部数据仓库,管理员为了方便,把数据库的访问密码设置为“123456”,并且对全网开放 3306 端口。你说这不是个技术难题,它就是人躺在舒适区里的结果。红队扫描到这样一个入口,十分钟内就能拖走大量数据。
你可能会想:“这种低级错误,正规团队不可能犯。”但现实是,在业务迭代压力极大的时候,很多部署工作会交给脚本、模板和自动化工具,研发人员只关心“功能跑起来”,很少去复审“默认参数是不是安全的”。我自己的习惯是:每接一个新系统,第一件事就是全局搜索所有硬编码的密钥、令牌、默认口令,把它们清理成环境变量,并启用密钥管理系统。这件事看起来琐碎,但价值极高——至少能减少一半“随手暴露”类型的风险。
4. 让漏洞“现形”的关键技术:代码审计 + 威胁建模
红蓝对抗结束了,报告出了,但真正决定一场安全活动有没有后劲的,是“漏洞是否被理解得足够深”。团队可以不立刻修复所有问题(业务排期有限),但必须理解“为什么会发生”。而理解漏洞来源的两大利器,就是代码审计与威胁建模。
4.1 代码审计:像读侦探小说一样读产品代码
代码审计不是“通读所有代码”,而是带着问题去精读高危区。这里有三个比较实用的切入点:
- 优先审计“外部边界”代码:所有接受用户输入的地方——HTTP 请求参数、文件上传、消息队列、第三方回调——都是最高优先级。先看它们有没有校验、过滤、白名单限制。
- 审查数据流而不是函数流:不要一个函数一个函数读源码,而是跟着一条数据走:从用户输入开始,经过哪些转换、存储、输出,最终流向哪里。哪一步缺少约束,哪一步就是漏洞。
- 检查“差异逻辑”:一个系统在迭代过程中,会出现“接口 A 做了完整校验,接口 B 是 A 的简化兼容版,所以校验不全”的情况。这些“复制后改了一部分”的代码,往往是漏洞的高发区。
实用性最高的做法,是把代码审计的结果反向标注在威胁建模图上,看看“预想中的防御边界的哪些点实际存在缺口”。这样修复的时候就不是“跟着问题跑”,而是“补齐整个防线的结构性缺失”。
4.2 威胁建模:在攻击发生之前画出“坏蛋的路线图”
威胁建模本质上是一种结构化的想象练习:你把系统拆成“资产、入口、信任边界、流程”,再问自己:“如果一个坏蛋想获取最核心的资产,他有哪几条路可以走?”比如一个电商系统,核心资产是订单数据和用户支付信息。可能的攻击路线包括:
- 通过 Web 前端注入,篡改查询参数获取他人订单;
- 通过优惠券系统的逻辑漏洞,批量生成恶意折扣;
- 通过供应链管理后台的弱口令,直接读取订单数据库;
- 通过第三方物流回调查接口缺陷,伪造发货状态触发退款。
把这些路线画出来之后,你就能针对每一条路线设置检查点:哪些地方需要更严格的鉴权、哪些输出需要加密、哪些日志需要保留更长时间。红蓝对抗本质上是“用实战检验威胁建模的质量”:如果你的模型漏画了一条路线,红队就可能真的从那条路线打进来。所以我一直强调,威胁建模不是“画完就完”的文档工作,它是整个安全团队对系统理解的静态体现,必须随着系统迭代持续更新。
5. 防御侧的核心动作:不只是“堵漏洞”,更是“缩短暴露时间”
当红队报告里的漏洞清单被转交到蓝队和研发部门后,很多团队的惯性思维是“赶紧把洞补上、然后写个复盘、宣布战斗胜利”。但从实际攻防经验看,修复单个漏洞只是最低线,真正让系统越打越硬的关键是:缩短漏洞从出现到被发现、再到被修复的“暴露窗口”。
5.1 建设“可观测性”以缩短发现时间
“可观测性”这个词听起来很高大上,落到日常动作就是三件事:日志记得全、指标看得见、告警能拉到人。很多漏洞之所以能在系统里潜伏数月甚至数年,是因为根本没有日志记录它被访问过的痕迹。红队进来扫描时,如果蓝队的访问日志连“哪些 IP 在持续触发错误”这种最基本的摘要都拉不出来,那防守就是聋子的耳朵。
我建议团队至少做到三个“凡是”:
- 凡是涉及认证、授权、支付、数据导出等核心操作,必须记录完整审计日志;
- 凡是外部输入进入内部处理之前,都要有入口监控;
- 凡是出现错误率突增、异常忙时、陌生 IP 高频探测,都要有对应的告警规则。
这些规则不复杂,但能在真实攻击发生时,把响应时间从“几天”压缩到“几十分钟”。红蓝对抗演练里最容易见到的差距就是:有的蓝队 5 分钟就能定位攻击源并完成封禁,而有的蓝队直到演练结束还不知道红队是从哪条路上来的。差异往往不在工具,而在日志规范与运营流程。
5.2 用“补丁 + 回归用例”解决复发问题
单个漏洞修复之后,必须配套一个回归测试用例,防止同样的问题在下一个迭代里被无意中重新引入。比如某系统修好了一个 IDOR(不安全的直接对象引用)漏洞,那么在自动化测试里就要加入类似“访问不属于当前用户的资源 ID,必须得到 403/404”的用例。下次有人重构了权限逻辑,测试跑不过,就能及时发现。
很多团队觉得“补丁打完就完事了”,结果半年后重构,新代码又把旧的逻辑错误带回来,红蓝对抗又查出同样的问题。这种循环消耗极大,根本原因就是缺乏“修复的持续校验机制”。
5.3 制定“悍跳式”应急演练
这里说的悍跳式,指的是红队不按常理出牌,随机挑选时间、随机挑选入口,打蓝队一个措手不及。日常的例行演练会提前通知,大家有心理准备,漏洞很容易被掩盖;而真正考验反应速度的,是“挑某个周一的下午,突然出现告警风暴”的场景。这种演练不该频繁搞(太消耗资源),但一年做两三次很有价值。它能暴露出团队在疲劳、压力下的真实决策能力:谁负责通知道路、谁负责封禁操作、谁负责安抚业务部门,都是提前定好的分工,而不是临时推诿。
6. 红蓝对抗之后:把“漏洞清单”升级为“改进路线图”
一场完整的红蓝对抗,交付的不应该只是一份“漏洞报告”,而应该是系统安全能力的体检报告和改进路线图。我在实际项目里通常会把后续工作拆成三个递进层次:
第一层次:紧急止血。对于能够被远程利用、直接导致敏感数据泄露或服务异常的高危漏洞,立即制定修复计划,必要时先上线临时缓解措施(比如 WAF 规则、禁用功能入口、收紧网络策略),再在下一个发布周期内彻底修复。紧急止血讲究一个“快”字,不要追求完美修复,先阻断进一步利用。
第二层次:系统性加固。针对报告里反复出现的同类问题,推动研发规范更新。比如多次发现“输入校验不完整”,那就应该启动统一参数校验框架的推广;多次发现“内部接口无鉴权”,就应该引入服务间身份认证体系。这个层面的改动往往涉及架构改造,周期较长,但收益也最明显。
第三层次:安全意识与流程更新。把演练中暴露的“人的因素”纳入培训计划:开发者的安全编码习惯、运维的部署合规纪律、运营人员的敏感信息保护意识。很多低级漏洞不是不会写安全的代码,而是没有在“习惯层面”把安全做成默认动作。
在这个闭环里面,我最深刻地体会到“是人创造出来的东西就会有漏洞”并不是悲观论调,而是一种务实的工程哲学:
- 因为你承认人一定会犯错,所以你不把安全寄托在“大家细心一点”上,而是引入自动化检查;
- 因为你承认人的注意力有限,所以你在发布流程里强制加入安全审查环节;
- 因为你承认攻击者会不断寻找新角度,所以你不把某一次演练的“胜利”当成终点,而是保持对系统的持续怀疑。
红蓝对抗的意义正在于此:它让我们能够在“漏洞造成真实损失之前”,预先知道自己的薄弱环节。而那些在演练中被发现、被修复、被沉淀为防御能力的经验,才是真正属于团队的“血性”所在——不是在模拟战斗中争一时高低,而是在真实风险面前多一分从容。