1. 榜单不是考试大纲:先把 OWASP Top 10 的定位弄清楚
我在几年里做过不少安全相关的评审和复盘,发现一个挺普遍的现象:一提到 Web 应用安全,几乎所有团队的第一个动作都是把 OWASP Top 10 搬出来。有人拿它当合规检查单,有人拿它当新人培训教材,还有人直接对着榜单让扫描器跑一遍,扫出来没几个问题,就说自己“已经按 Top 10 整改过了”。说实话,这几种用法都不算错,但都没切中要害。
OWASP Top 10 本质上是社区对 Web 应用安全风险的一次排名,它回答的问题很现实:在今天这个技术环境下,你的应用最可能因为哪些弱点出事,出事之后影响有多大。它不是“十大漏洞清单”,而是“十大风险清单”。同一个漏洞,落在不同行业、不同系统上,风险等级可能千差万别,所以榜单里的每一项都在权衡发生率、可利用性、可检测性和影响面,而不是单纯告诉你“这个 bug 有多危险”。
1.1 它统计的是“风险”,不是“漏洞”
我见过不少团队把 Top 10 当成漏洞字典来对照,比如检查自家代码里有没有 SQL 拼接、有没有明文密码,查完就完事。但实际上,Top 10 的价值在于帮你建立风险视角。以榜单第一名“访问控制失效”为例,它不是一个具体的漏洞,而是“功能层面的授权检查没做好”这一类问题的总称,下面可以衍生出水平越权、垂直越权、对象级授权缺失、强制浏览等等一大堆具体形态。你如果只盯着“漏洞”去找,很容易漏掉背后那一整片出问题的方式。
换句话说,正确的姿势应该是把榜单当成一张风险地图。你先看现有系统是否存在这些风险类别,再往下钻到更细粒度的问题模式,然后结合自己的业务场景评估:哪些类别最可能被攻击者利用、数据敏感度如何、被利用后会有什么后果。很多团队把顺序搞反了,拿榜单去套功能点,最后套出一个“改完列表就安全”的假象。
这里举一个很日常的例子。同样是“未授权访问”问题,在一个完全不对外、只有内网几个人用的工具系统里,和在一个存着大量用户隐私的公网系统里,风险等级完全不同。Top 10 榜单统计的是整体趋势,落到你自己头上,还得自己做二次判断。
1.2 从 2017 到 2021:榜单变化背后是攻击面的转移
OWASP Top 10 每次更新间隔都比较长,2017 版到 2021 版隔了四年。这四年里变化非常大,我强烈建议每个做安全的同学都把两个版本逐项对比一遍,因为每一次条目变动都在透露攻击面的迁移趋势。
我先把两个版本的对应关系列出来,方便对照:
| 排名 | 2017 版 | 2021 版 |
|---|---|---|
| A01 | 注入 | 访问控制失效 |
| A02 | 失效的身份认证 | 加密失败 |
| A03 | 敏感数据泄露 | 注入 |
| A04 | XXE | 不安全设计 |
| A05 | 访问控制失效 | 安全配置错误 |
| A06 | 安全配置错误 | 易受攻击和过时的组件 |
| A07 | XSS | 身份识别与认证失败 |
| A08 | 不安全的反序列化 | 软件与数据完整性失败 |
| A09 | 已知漏洞组件 | 安全日志与监控失败 |
| A10 | 日志与监控不足 | 服务端请求伪造(SSRF) |
看到这张表,你可能会觉得“好多老朋友被挤下去了,好多新面孔进来了”。其实不是新面孔,而是排名规则变了。2021 版统计了超过五十万个真实应用样本,把“发生率”和“被利用情况”合并打分,再加入社区投票的专家意见。XSS 被并进注入大类,敏感数据泄露被扩成了加密失败,XXE 因为主流框架默认配置趋严不再单列,这些都说明一个问题:行业对威胁的归类方式,正在从“技术现象”转向“风险成因”。你越早理解这种视角切换,后面做安全设计时就越不容易走偏。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 访问控制与加密失败:两个“看似基础却常年霸榜”的重灾区
2021 版把访问控制失效放到了第一名,加密失败放到第二名。很多人不理解,觉得这两个问题太“初级”了,怎么还能排这么高。这是对榜单数据来源的误解:统计样本覆盖了大量传统行业系统、内部系统和外包项目,不是只有大型互联网公司的产品。恰恰因为这类问题遍布各处,才让它们稳稳占据了榜首和榜眼。
2.1 越权访问:不是公网入口的问题,是对象级授权的账
访问控制失效最典型的表现是越权,尤其是对象级越权。很多团队把安全重点全放在登录、防火墙、WAF 上,结果攻击者登录一个普通账号,改一下 URL 里的参数就能看到别人的订单、病历或者合同。这类漏洞根本不需要多高深的技术,一个普通用户就能临时客串攻击者。
我拿一个很常见的场景举例。假设接口设计成这样:
code复制GET /api/order/10086
后端如果只验证“用户是否已登录”,却没有验证“该订单是否属于当前用户”,那么攻击者只需要把参数从 10086 改成 10087,就能访问另一个用户的订单详情。这就是对象级授权的缺失,业内常叫水平越权。另一个方向是垂直越权,比如普通用户直接请求管理员接口,后端没有校验角色。
这类问题为什么难根治?因为授权检查通常要落在每一个业务接口上,属于分散式责任。业务一多,接口一多,开发容易漏,测试也容易漏。我的经验是,解决它不能靠“提醒大家注意”,要改成默认拒绝:所有接口默认不允许访问,只有显式声明了权限规则才放行。同时把授权逻辑收敛到统一的过滤器或中间件里,而不是让每个业务模块自己写一遍。
还有一点特别容易被忽略:前端“隐藏按钮”不等于后端“限制权限”。很多年后端只在前端把某些操作按钮藏起来,接口本身却没有任何校验,攻击者只要会抓包,绕开前端直接发请求就能做他想做的事。做评审的时候,我会让测试团队把所有前端隐藏按钮对应的接口用脚本直接打一遍,往往一打一个准。
2.2 加密失败:不是没有加密,是加密用错了地方
加密失败这个类别是从上一版的“敏感数据泄露”改名扩大的。改名不是文字游戏,它把该加密的地方没加密、用了不安全的算法、密钥管理混乱、传输链路有缺口这些成因全部纳入进来。换句话说,问题不只是“数据泄露”,而是“保护数据的手段本身失败了”。
常见的翻车姿势有这么几类:
- 敏感字段明文落库,数据库被拖库后直接裸奔;
- 密码用 MD5、SHA1 这类快速摘要算法存储,且没有加盐,撞库成本极低;
- 登录接口走明文传输,账号密码在公共网络里一抓一个准;
- 用了“看起来很安全”的自定义加密算法,实际经不起分析;
- 密钥硬编码在代码或配置文件里,能看懂代码的人都能拿到。
2021 版特别强调,加密失败不仅包括数据加密,还包括传输链路。现在浏览器基本都会对不安全的连接给出提示,但很多内部接口、微服务之间的调用仍然在走明文。内部链路不加密,一旦内网被渗透者横向移动,流量里的隐私数据全都会暴露。
正确的做法其实不复杂:先做数据分类,分清哪些是敏感数据;存储侧给敏感数据选现代算法,比如带自适应成本的密码哈希方案;传输侧统一用现代 TLS 协议并关闭脆弱套件;密钥放进专门的密钥管理系统而不是代码仓库。加密不是“加了就行”,而是算法、密钥管理、传输链路、日志脱敏四个环节都要闭环。任何一个环节断掉,加密这件事就等于没做。
3. 注入攻击:为什么一个“老掉牙”的漏洞仍然稳居前列
第三名是注入。看到这里有人会疑惑:都 202x 年了,怎么 SQL 注入还能排这么靠前?答案有点反直觉:技术圈认为老掉牙的东西,在实际行业里的覆盖率远没有想象中那么低。2021 版统计下来,注入类问题在样本应用中的发生率仍有约 4%,绝对发生量超过二十七万次,而且 XSS 也被并入这个大目录,所以它依然是一个必须正视的大类。
3.1 注入的本质:把输入当成代码
注入类漏洞的根因,可以一句话概括:不可信的用户输入,被拼进了可以被解释执行的代码或命令里。最经典的 SQL 注入长这样:
sql复制SELECT * FROM user WHERE name = 'admin' OR '1'='1';
如果在后端代码里是字符串拼接出来的,攻击者就能通过闭合引号改变整条 SQL 的语义。轻则绕过登录,重则把数据库里的订单、手机号、密码哈希全部拖走。SQL 注入在极限情况下还能变成操作系统命令执行——比如利用数据库写文件的能力,或者结合某些扩展函数调起系统命令,那就已经不是数据泄露的问题了。
注入不只是 SQL 一种。同一个原理可以迁移到好多场景:NoSQL 查询注入、操作系统命令注入、LDAP 查询注入、模板引擎注入、表达式注入,以及 2021 版并入的 XSS。XSS 的本质也是“浏览器把用户输入当成了 HTML 或 JavaScript 来执行”。原理完全一致,只是解释执行的位置不同。
我建议每个负责写业务代码的同学,不要只看 SQL 注入的案例代码,要看懂“拼接导致语义被改变”这件事。看懂了这个,你会对输入校验、输出编码、语句参数化等一系列手段真正想解决的问题有体感,而不是死记硬背。
3.2 防御为什么总是落不了地
防御注入的方案早就是共识了:参数化查询(预编译语句)、对象关系映射提供安全的查询方式、输入校验只允许合法格式、数据库账号遵循最小权限。可为什么还是年年上榜?
我踩过和见过的原因主要有三个。第一,历史遗留代码太多,老项目里到处是硬拼接 SQL,改造工作量巨大,业务方又不愿意停下来专门处理。第二,ORM 给了人虚假安全感,不少团队以为用了 ORM 就永远不会注入,结果项目里还是能看到直接执行原生 SQL 的地方,而且这些地方往往夹带着用户传进来的参数。第三,测试阶段没有把注入当作必测项,很多功能“点通就算完”,根本没有恶意输入的正向验证。
所以我的建议很直接:先从“找硬拼接”开始,把代码里所有涉及字符串拼接查询的地方列出来,逐个排查参数来源;再定一条铁律,新代码一律不允许拼 SQL,评审时发现拼 SQL 直接打回。至于已经跑着的存量系统,按风险排序分批改造,同时用数据库的最小权限账号作为兜底——就算被注入了,也注不出多少东西。
4. 设计缺陷、配置错误和组件过期:安全债的三种滚法
榜单中间的位置是 A04 不安全设计、A05 安全配置错误、A06 易受攻击和过时的组件。这三类放在一起看特别有意思:它们都不是“某个具体的漏洞”,而是安全债在不同阶段的形态。我把它们统称为“安全债三兄弟”,因为整改它们需要的不是修一个函数,而是从流程和意识上改变。
4.1 安全设计缺陷:问题出在需求阶段
A04 不安全设计是 2021 版新增的类别,也是我最喜欢跟产品经理聊的一类。它想表达的是:有些系统从需求、设计和架构层面就没有把安全考虑进去,后面再怎么打补丁都补不干净。
举几个设计层的典型例子。比如一个系统把“是否允许提现”完全交给前端状态来判断,后端不校验;比如一个上传功能没有任何对文件类型的限制和内容安全策略;比如同一个服务既管读又管写,权限无法拆分;再比如登录接口设计成无限次重试,导致任何人都可以跑字典爆破。这些问题在画原型和定接口的时候就已经埋下了,代码写得再规范也挡不住。
对这类问题的解法是提前做威胁建模:在功能设计阶段就画出数据流向,识别可信边界,标注哪些数据敏感、哪些操作影响面大,然后针对每个高风险点设计防线。安全需求不能靠“上线前让安全同事看一眼”,那样只能发现显性问题。越早把安全纳入需求验收条件的团队,后期返工越少。我甚至会在评审时直接问一句:这个功能最坏情况下被恶意使用,会造成什么后果?答不上来的需求,说明还没想清楚。
4.2 配置错误:最泛滥也最好修的一类
A05 安全配置错误在 2021 版是发生率最高的类别,统计里接近 4.5% 的应用存在这类问题。典型表现包括:保持默认口令不修改、云存储对象对外开放读权限、目录列表开启、报错信息把完整堆栈返回给用户、开发环境的调试接口没关、多余的服务端口暴露在公网。
这类问题不“惊艳”,甚至有点无聊,但它造成的破坏一点不比注入小。我见过一个内部系统把管理后台挂在默认端口上,口令还是初始化的默认弱口令,扫描器一扫就进去了。修复配置问题通常很简单——改掉配置就行,难的是找不到到底哪些地方配错了。
所以我的建议是,把配置管理纳入自动化流程:基础设施用代码管理,安全基线写成检查脚本定期扫描,云存储权限用统一规则约束,账号口令定期清理,开发和生产环境严格隔离。这里最怕的是“人肉维护”,一旦系统多起来,配置漂移是迟早的事,安全问题也就跟着四处漏风。每次事故复盘,我都建议先查“配置变更记录”,很多问题根本不是代码逻辑写错了,而是配置某天被人随手改了一下。
4.3 有缺陷的旧组件:第三方依赖成了火药桶
A06 也是老熟人了,只是这版排名往上挪了。现代应用几乎不可能不用第三方库和框架,但这些依赖一旦过期,就相当于在火药桶旁边打火。最典型的案例是某款被广泛使用的开源日志库被爆出远程代码执行漏洞,当时大量应用紧急排查,就是因为它的覆盖面太广。
这里的关键不是“不用第三方依赖”,而是“知道自己在用什么,并且及时更新”。很多项目的依赖管理是失控的:项目文件里写了一大串依赖,其中一半是传递依赖,没人知道它们具体是什么版本,也没人知道它们各自有什么已知漏洞。
所以我一直强调两件事:一是建立软件物料清单,把直接依赖和传递依赖的版本、许可证、已知漏洞都记录清楚;二是把依赖安全扫描接进流水线,新依赖引入时先扫一遍再合并,旧依赖定期扫描并按风险确定修复优先级。另外还要养成“删依赖”的习惯,不再使用的库及时移除,不为了一个小功能引入一个大框架。依赖越少,暴露面越小,这句话永远成立。
5. 身份认证失败与完整性破坏:攻击者最爱的两个突破口
再往下是 A07 身份识别与认证失败、A08 软件与数据完整性失败。这两个类别看着名字挺长,其实就是两类最常见的问题:账号口令防线太弱,以及代码和数据在流转过程中被人动了手脚后,系统依然信任它们。
5.1 身份识别与认证失败:凭什么账号是第一目标
身份认证这块的问题,大家应该都不陌生。弱口令、默认口令、没有登录失败次数限制、会话没有超时、会话标识直接放在 URL 里、注销后后端不清理会话……这些问题任何一个单拿出来都不算新鲜,但它们组合在一起,就是一条非常顺畅的爆破链路。
现在比较理想的加固方式是“三层配套”。第一层,登录环节要求强口令并接入多因素认证,强口令不是为了难为用户,而是要尽量拦住批量撞库。第二层,登录行为要做限速和风控,连续失败要降级、临时锁定或要求附加验证。第三层,会话管理要规范,登录成功后重新生成会话标识,注销时把服务端会话同步销毁,让“退出登录”真正退出。很多系统只做了第一层,第二三层完全没有,攻击者拿已泄露的账号密码列表去撞库,一撞一个准。
这里额外说一个容易被忽视的点:不要只盯着“登录”环节。密码重置流程、找回账号流程、Cookie 里的身份标识、第三方登录的回调逻辑,都算在认证链路里。我见过不止一次,登录页面防得固若金汤,密码重置接口却可以直接被请求重定向到攻击者留的邮箱。
5.2 软件与数据完整性:从反序列化到供应链篡改
A08 是 2021 版的新类别,它把不安全的反序列化、软件更新包被篡改、构建发布流程缺乏完整性校验这些问题放在了一起,核心思想是:系统必须在真正执行之前,确认代码和数据没有被篡改过。
反序列化是这里最有代表性的技术问题。很多语言和框架在把序列化数据“还原”成对象的过程中,会自动触发一些能力,攻击者精心构造一段序列化数据,就可能让服务器实例化任意对象、调用任何方法,最终变成远程代码执行。防御思路并不复杂:不要对不可信来源做反序列化;如果必须要做,用签名验证或类型白名单把输入边界卡死。
供应链完整性是另一个现实困境。现在的开发流程里,依赖拉取、镜像构建、自动发布都是流水线完成的,如果流水线本身被渗透,或者拉取的依赖包被人投毒,最终运行的程序可能带着恶意。要守住这条线,至少要做好三件事:代码和构建产物要签名校验、依赖包来源要固定可溯源、流水线平台的账号权限要做严格管控。完整性问题和配置错误一样,平时很难感知,一旦出问题就是大事故。
6. 日志监控缺位与SSRF:两个容易被忽视的“隐蔽大坑”
榜单最后两名,A09 安全日志与监控失败、A10 服务端请求伪造,放在一起很耐人寻味。一个是你不看攻击记录,一个是你被利用去访问不该访问的地方。这两类问题在平时开发里最没存在感,但事故发生后,它们往往是决定损失边界的关键。
6.1 安全日志与监控失效:看不见就防不住
很多团队把日志当成排障工具,出了线上问题才去翻,但安全日志的关注点和排障日志不完全一样。它关心的是:谁、在什么时间、从哪个地址、访问了哪些资源、做了什么敏感操作、触发了什么访问控制异常。没有这些信息,攻击者进来逛一圈你都不知道,更别提响应和止损。
我遇到过一个很典型的场景:某系统的管理接口被暴力破解成功,攻击者用管理员账号导出了大量客户数据,整个过程持续了将近一周。之所以能持续这么久,就是因为系统虽然记录了登录日志,但没有针对“多次登录失败后突然成功”“非工作时间管理员登录”这类异常行为做告警。等到发现时,数据早就流走了。反过来,如果日志能第一时间推送告警,响应时间从“周”压缩到“分钟”,损失是完全不同的数量级。
安全日志最怕两件事:一是没记录,二是记录了但保护不住。没记录通常是因为开发阶段没有定义“什么事件必须打日志”;保护不住则是因为日志权限管理太松,攻击者进来先把日志清了,或者日志系统本身存在注入漏洞。所以做日志方案的时候,除了定义事件类型、上下文信息、告警规则,还必须考虑日志的完整性保护和访问控制。重要日志要能支撑事后审计,甚至作为证据使用。
6.2 SSRF:从“帮你访问网址”到“帮你内网漫游”
SSRF 是 2021 版新挤进前十的类别,它在云时代的位置越来越重要。简单说,当应用根据用户提供的地址去“代取资源”时,如果对目标地址没有严格限制,攻击者就可以把目标指向内网地址,甚至是云平台内部的元数据服务地址,让应用替自己发起内网探测和访问。
我举个最容易理解的场景。不少系统有“在线预览链接”“图片代理”“URL 检测”之类的功能,用户提交一个网址,服务端去访问它,然后把内容返回。如果后端直接把这个网址当请求目标,攻击者提交的就不再是外部网页,而可能是内网的管理后台、数据库端口、监控面板。
这类请求的可怕之处在于,它发自应用服务器本身,天然带着内网信任,防火墙通常也不会拦“自己人”的流量。防御的核心是建立白名单:只允许访问预先定义好的合法域名或 IP 范围,拒绝私网地址段和链路本地地址段,重定向要自己处理而不是无脑跟随,对返回内容做类型限制。网络层面,如果条件允许,可以把这类“代取资源”的功能拆到专用服务里,单独用出口防火墙约束它能访问的网络范围。SSRF 特别考验架构的细心程度,因为攻击者可玩的花样很多,防的不只是一个维度。
7. 从榜单到落地整改:一份可以参考的执行路径
前面把十个类别过了一遍,如果只看不行动,那这个榜单对你来说还是别人的清单。最后这部分,我结合自己在项目里的实际操作,说说从榜单到整改到底怎么落地。不管是什么体量的团队,这套思路基本都能用上。
7.1 先做差距盘点,别急着全量整改
我见过有人一听说要按 OWASP Top 10 整改,立刻列了一个大表,把十个类别全部铺开,每个类别下写一堆任务,结果团队还没开始就被淹没在任务里。正确的做法应该是先做差距盘点:按系统梳理资产清单,明确每个系统存了什么数据、谁在用、对外的暴露面有多大,然后把十个类别逐个对照,判断当前系统分别处于什么状态。
盘点阶段不需要追求“证明我们有多安全”,反而要找证据反着问:数据能越权访问吗?敏感数据有加密吗?登录有没有防爆破?依赖有没有定期扫?日志有没有人看?这些问题每一条都可以用很简单的测试来回答,比如用手工请求改一下资源 ID,用扫描器看常见口令,查一下依赖清单有没有高危版本。盘点完了,你会得到一份“哪些类别在我们这边确实高发、哪些相对稳健”的清单,而不是一份虚假的全员达标报告。
7.2 修复优先级、验收方式和一点个人体会
拿到差距清单之后,优先级怎么定?我的经验是按“被利用的容易程度 × 出事后影响面”来排,而不是按榜单顺序照抄。比如你的系统是纯工具类应用,不存敏感数据,那加密失败的优先级就可以往后放一放;如果系统直接暴露在公网且涉及资金操作,那认证失效和访问控制一定是第一优先级。排序之后,每一项修复都要有明确的验收方式:改完不是“代码提交就算完”,而是重新做一次相同的攻击验证,确认原路径打不通了,再做回归。
最后说点个人体会。我用了这么多年 OWASP Top 10,最大的感触是:它真正的价值不在于这十个条目本身,而在于它逼着整个行业用统一的语言去讨论风险。无论你是开发、测试、运维还是管理者,当大家能对着同一张榜单说“我们这次重点解决访问控制和配置错误”,沟通成本会低非常多。它当然不是安全工作的终点,很多深度问题还需要结合更细的规范去查,但如果你连这份榜单落实起来都觉得困难,那大概率不是榜单的问题,而是团队的安全水位需要整体往上抬一抬了。
