未鉴权
- 含义:指接口或资源完全未做任何身份或权限校验,任何用户(包括未登录者)均可直接访问或操作。
- 示例 :一个获取用户个人信息的接口
/api/user/profile,未检查调用者是否已登录或是否为该用户本人,攻击者可直接请求任意用户ID的数据。 - 风险等级:极高,属于"零门槛"越权。
详细点来看
在 API 安全防御中,理解它**"为什么会发生"** 以及**"具体的攻击链路"**,比单纯知道它的定义更重要。
我们可以从以下四个维度来深度剖析:
1. 漏洞的本质:鉴权逻辑的"断层"
"未鉴权"的本质是后端代码的信任边界模糊 。
开发者往往认为"只要前端页面不显示这个按钮,用户就点不到",或者"只要用户登录了系统,就可以访问系统内的所有数据"。这种逻辑在后端代码中表现为只有"认证(Authentication)"没有"鉴权(Authorization)"。
- 认证:确认"你是谁"(比如通过 Token 确认你是用户 A)。
- 鉴权:确认"你能干什么"(比如确认用户 A 只能看订单 1001,不能看订单 1002)。
未鉴权漏洞,就是系统只做了第一步,直接跳过了第二步。
2. 常见的"未鉴权"场景分类
在实际业务中,它通常以以下几种隐蔽的形式出现:
- 场景一:完全"裸奔"的接口(最常见)
-
现象:开发测试时为了方便,把接口的鉴权中间件关掉了,上线时忘了打开。
-
代码逻辑:
// 错误示例:直接返回数据,没有任何 Token 校验 app.get('/api/user/info', (req, res) => { const userId = req.query.id; const user = db.find(userId); res.json(user); // 任何人访问都能拿到数据 });
-
- 场景二:被遗忘的"影子 API"
- 现象 :系统升级了,旧版本的接口(如
/v1/api/...)虽然前端不再调用,但后端服务没有下线。旧接口往往缺乏新系统的安全防护。 - 攻击方式:攻击者通过字典爆破或爬虫,发现了这些不在文档中的旧接口。
- 现象 :系统升级了,旧版本的接口(如
- 场景三:管理后台接口泄露
- 现象 :某些用于内部运维的接口(如
/api/admin/resetPassword),本应限制 IP 访问,但因配置错误暴露在公网,且未做二次身份校验。
- 现象 :某些用于内部运维的接口(如
3. 攻击者的视角:从"发现"到"利用"
攻击者通常不会盲目猜测,而是有一套标准化的探测流程:
- 被动收集:通过浏览器开发者工具(F12)查看前端请求,或者查看 JS 文件,寻找 API 路径。
- 主动探测 :使用工具(如 Burp Suite、Dirsearch)对目标域名进行路径爆破,寻找返回状态码为
200 OK的接口。 - 参数篡改 :
- 发现接口
/api/order/detail?id=100返回了自己的订单。 - 尝试修改为
/api/order/detail?id=101。 - 如果返回了别人的订单详情,且没有报错或跳转登录页,即确认为"未鉴权"漏洞。
- 发现接口
4. 深度修复方案(从代码到架构)
要彻底根治,不能只靠"加个 Token 校验",需要建立多层防御体系:
如果你正在负责相关的安全整改,建议优先使用自动化扫描工具(如 OWASP ZAP)对全站接口进行一次"未鉴权"专项扫描,快速定位这些"裸奔"的接口。
总结
"未鉴权"之所以风险极高,是因为它完全绕过了业务逻辑的防线。对于攻击者来说,这就像走进一家银行,发现金库门不仅没锁,甚至连门都没有。
-
L1 防御:强制全局中间件
不要依赖开发者在每个接口里手动写鉴权代码。在框架层(如 Spring Security, Express middleware)配置全局拦截器,默认拒绝所有请求,只有白名单接口(如登录、注册)放行。
-
L2 防御:基于角色的访问控制(RBAC)
在代码中明确绑定资源与用户的关系。
// 伪代码示例:正确的鉴权逻辑 public Order getOrder(String orderId, User currentUser) { Order order = orderRepository.findById(orderId); // 核心:必须校验当前用户是否是该订单的主人 if (order.getUserId() != currentUser.getId()) { throw new ForbiddenException("无权访问"); } return order; } -
L3 防御:零信任架构与最小权限原则
即使在内网环境,微服务之间的调用也必须携带身份凭证。不要假设"内网就是安全的"。
-
L4 防御:API 资产治理
定期扫描并下线所有废弃的接口,确保所有上线的 API 都有明确的文档和责任人,消除"影子 API"。
在"未鉴权漏洞"的上下文中,"返回数据标签"指的是安全扫描工具或API网关对接口响应体中敏感数据类型进行自动识别和分类后打上的标记 。它不是指HTML中的
<label>标签,也不是加密算法中的认证标签(如AES-GCM的Tag),而是数据安全治理中的一个核心概念。这个功能的核心价值在于:即使一个接口存在"未鉴权"漏洞,攻击者能访问到数据,系统也能通过"数据标签"快速判断泄露的是什么类型的数据,从而评估风险等级、触发告警、启动脱敏或阻断策略,并为后续的合规审计提供依据。
通过数据分类分级机制进行识别和标注
衍生知识
一、什么是"数据分类分级"?
这是数据安全治理的核心环节,目的是:
- 分类:按业务属性或数据类型(如个人信息、金融数据、企业数据)归类;
- 分级 :根据数据泄露后可能造成的危害程度(对个人、组织、社会、国家安全的影响),划分为不同安全等级,通常分为4级:
- 核心数据:关系国家安全、经济运行命脉,泄露将造成特别严重危害;
- 重要数据:影响公共利益、行业稳定或大规模人群权益;
- 敏感一般数据:涉及个人隐私或企业商业秘密,泄露会造成中等以上损害;
- 常规一般数据:公开或低敏感度信息,泄露影响较小。
| 字段名称 | 典型分类 | 典型分级 | 说明 |
|---|---|---|---|
| 密码 | 身份鉴别信息 | 敏感一般数据(部分场景为重要数据) | 直接关联账户安全,泄露可导致身份冒用、资金损失。 |
| 手机号 | 个人通信标识 | 敏感一般数据 | 可用于精准诈骗、骚扰、社工攻击,常与身份证号、地址组合使用。 |
| 地址、固定电话、手机号 | 个人位置与联系方式 | 敏感一般数据 | 组合后可构建完整个人画像,用于线下骚扰或精准营销。 |
| 统一社会信用代码 | 企业唯一标识 | 敏感一般数据(部分场景为重要数据) | 可用于企业冒名注册、税务欺诈、供应链攻击。 |
| 身份证号码 | 个人法定身份标识 | 敏感一般数据(高敏感) | 黑灰产中威胁等级最高,常用于"开盒"、贷款诈骗、身份盗用。 |
| 未成年人身份证号 | 特殊群体身份信息 | 敏感一般数据(高敏感) | 法律重点保护对象,泄露后果更严重,可能触发监管处罚。 |
| 车牌号 | 个人财产与行踪标识 | 敏感一般数据 | 可关联车主身份、行驶轨迹,用于跟踪、勒索或车辆盗抢。 |
| 银行卡号 | 金融账户标识 | 敏感一般数据(高敏感) | 与密码、CVV组合可直接盗刷资金,是黑灰产核心目标。 |
| 电子邮箱 | 网络身份标识 | 敏感一般数据 | 常用于钓鱼、撞库、重置密码,是攻击入口之一。 |
| 姓名 + 手机号 + 身份证 | 个人身份三要素 | 敏感一般数据(高敏感) | 三者组合可完成绝大多数实名认证,是"精准诈骗"的基础。 |
| 级别 | 定义 | 典型字段示例 |
|---|---|---|
| L1(常规一般数据) | 泄露后不会对个人权益、组织权益造成危害,多为公开或脱敏数据。 | 产品名称、公开地址、非敏感业务描述 |
| L2(低敏感一般数据) | 泄露后可能造成轻微影响,如骚扰电话、营销干扰。 | 手机号(单独)、电子邮箱、固定电话 |
| L3(敏感一般数据) | 泄露后可能导致身份盗用、资金损失、精准诈骗等中等以上损害。 | 身份证号、银行卡号、密码、车牌号、未成年人信息、统一社会信用代码 |
| L4(重要/核心数据) | 泄露后可能引发大规模社会风险、行业动荡或国家安全威胁。 | 国家人口库、金融交易流水、生物识别原始数据、关键基础设施配置 |
- 密码 → L3(高敏感),部分金融系统视为L4;
- 手机号 → L2(单独存在时),若与身份证、地址组合则升级为L3;
- 地址 + 固定电话 + 手机号 → L3(组合后可构建完整画像);
- 统一社会信用代码 → L3(企业级敏感数据,部分场景为L4);
- 身份证号码 → L3(高敏感),未成年人身份证号同样为L3,但法律要求更严格;
- 车牌号 → L3(可关联行踪与财产);
- 银行卡号 → L3(高敏感),与CVV、密码组合即为L4;
- 姓名 + 手机号 + 身份证 → L3(三要素组合,是实名认证基础,黑灰产核心目标);
- 电子邮箱 → L2(单独),若用于重置密码或钓鱼攻击则为L3。
CVV ,全称是 Card Verification Value ,中文常称为"信用卡安全码"或"卡片验证码",是印在信用卡背面签名栏末端的一组3位或4位数字。它并非银行卡号的一部分,而是由发卡银行通过加密算法,结合卡号、有效期等信息生成的动态验证值,主要用于在非面对面交易(如网购、电话支付)中确认持卡人确实持有实体卡片,从而防止盗刷和欺诈。
CVV 的核心作用 是作为一道独立于密码和卡号的"第二道防线"。即使攻击者窃取了你的银行卡号和有效期,若没有 CVV,绝大多数线上支付平台会拒绝交易。因此,它在金融安全体系中属于最高敏感级别的数据,与密码、磁条数据一样,被严格禁止存储或记录在系统中。
在很多国际支付场景(如 Visa/Mastercard 的境外网站)中,只要拥有"卡号 + 有效期 + CVV"这三样信息,就可以直接扣款,无需短信验证码。因此,CVV 本身就被视为具备直接造成资金损失能力的核心数据。
根据国内《个人金融信息保护技术规范》及国际 PCI DSS 标准,CVV 被归类为 C3 级(最高敏感等级)或 L4 级数据,其安全防护要求极为严苛:
- 严禁以任何形式明文存储;
- 不得出现在日志、调试信息或第三方组件的输出中;
- 传输过程必须加密;
- 商户和系统端无权收集或保存,仅支付机构或银行可在交易瞬间临时验证。(即使加密存储也是违规的)
只要涉及 CVV、磁道数据、支付密码 等直接用于资金交易的鉴权信息,都应毫不犹豫地将其定级为 L4,并采取最严格的加密传输、禁止落盘存储等防护措施。
需要注意的是,分级不是静态的,它会随着数据组合、使用场景、行业属性动态调整。例如:
- 单独的手机号是L2,但若与身份证号、住址组合,即构成"个人身份三要素",应定为L3;
- 企业在内部系统中存储的"统一社会信用代码"可能是L3,但若用于跨境数据传输或涉及国家经济命脉行业,则可能升级为L4;
- 医疗系统中的"诊断结果"属于L3,但若涉及大规模人群健康数据,则可能被认定为L4。
个人身份信息三要素通常指姓名、身份证号码和手机号码这三项核心信息,它们是当前国内实名认证、金融开户、网络注册等场景中用于验证用户真实身份的基础组合。
三、为什么这些字段被标记为"敏感"?
因为这些字段一旦泄露或被滥用,极易导致:
- 个人层面:身份盗用、精准诈骗、骚扰电话、网络暴力;
- 企业层面:客户信任崩塌、监管处罚、品牌声誉受损;
- 社会层面:大规模数据泄露引发公共安全事件(如"社工库"泛滥)。
尤其在金融、政务、医疗等行业,这些数据常被列为"重要数据"或"敏感个人信息",需采取加密、脱敏、访问控制、审计日志等强化防护措施。
四、如何进一步防护?
- 最小化采集:非必要不收集敏感字段;
- 动态脱敏 :展示时对身份证号、手机号等做掩码处理(如
138****1234); - 访问控制:仅授权人员可访问原始数据,且需记录操作日志;
- 加密存储:对密码、身份证号、银行卡号等字段加密存储;
- 定期审计:监控异常访问行为,及时发现未鉴权接口或越权操作。
参数可遍历
- 含义:接口虽做了基础鉴权,但对象标识符(如ID)可被枚举或猜测,攻击者通过修改参数值访问他人数据。
- 示例 :订单详情接口
/api/order/123,攻击者可尝试/api/order/124、/api/order/125...从而遍历所有订单。 - 风险等级:高,尤其当ID为连续数字或可预测时。
1. 漏洞本质:为什么"可遍历"如此危险?
"参数可遍历"的核心问题在于对象标识符(ID)的可预测性 。
在很多老旧或设计不严谨的系统中,数据库的主键(Primary Key)默认是自增整数(1, 2, 3...)。当后端接口只验证了"用户是否登录",却没有验证"当前用户是否有权访问这个ID对应的资源"时,攻击者只需要写一个简单的脚本,把ID从1循环到10000,就能把全平台的数据全部"爬"走。
2. 具体攻击场景拆解
攻击者通常不会手动去改ID,而是利用工具进行自动化攻击。以下是两种最常见的实战场景:
- 场景一:水平越权(同级用户互访)
- 接口 :
GET /api/user/profile?uid=1001 - 正常逻辑:用户A登录后,只能查看自己的资料(uid=1001)。
- 攻击逻辑 :攻击者B登录后,将请求参数修改为
uid=1002、uid=1003。如果后端没有校验"当前登录用户是否为1002",攻击者就能批量获取所有用户的手机号、邮箱等隐私信息。
- 接口 :
- 场景二:订单/发票信息泄露
- 接口 :
GET /api/order/detail?order_id=20230001 - 攻击逻辑 :订单号往往包含日期或连续编号。攻击者可以构造出
20230002、20230003等请求,获取他人的订单详情、收货地址甚至支付状态。
- 接口 :
3. 攻击者是如何发现的?(检测手段)
在实际的渗透测试或安全扫描中,攻击者通常通过以下步骤发现此类漏洞:
- 抓包分析 :使用 Burp Suite 等工具拦截正常的业务请求,观察URL或Body中是否存在
id、order_no、file_id等参数。 - 参数篡改 :手动修改参数值(例如把 id=1 改为 id=2),观察返回的数据是否发生了变化,且是否属于其他用户。
- 自动化爆破 :如果确认存在遍历可能,攻击者会使用 Burp Suite 的 Intruder 模块,导入一个包含大量ID的字典(如 1-10000),批量发送请求,筛选出返回状态码为 200 且数据长度异常的响应。
4. 深度修复方案(从根源解决)
要彻底修复"参数可遍历",不能只靠"加个校验",需要从数据设计和代码逻辑两方面入手:
-
方案一:使用不可预测的标识符(推荐)
放弃使用自增整数ID作为对外暴露的标识符,改用 UUID 或 NanoID。
- 效果 :即使攻击者知道你的ID格式,也无法猜测出下一个ID是什么(例如从
550e8400-e29b...根本猜不到下一个UUID),从而从根本上杜绝了"遍历"的可能性。
- 效果 :即使攻击者知道你的ID格式,也无法猜测出下一个ID是什么(例如从
-
方案二:强制实施对象级鉴权(BOLA防御)
在后端代码中,必须显式地校验"资源归属权"。
// 伪代码示例:正确的鉴权逻辑 public Order getOrder(String orderId, User currentUser) { Order order = orderRepository.findById(orderId); // 核心:必须校验当前用户是否是该订单的主人 if (order.getUserId() != currentUser.getId()) { throw new ForbiddenException("无权访问该订单"); } return order; }BOLA防御:防止"拿错别人的快递"
BOLA(Broken Object Level Authorization,失效的对象级授权),也被称为 IDOR(不安全的直接对象引用)。它是目前 API 安全中最常见、危害最大的漏洞之一。
- 什么是 BOLA 漏洞?
简单来说,就是系统只验证了"你是谁",却没验证"这个东西是不是你的"。
举个例子: 你登录了自己的邮箱,查看第一封邮件的链接是
mail.com/inbox/101。如果你手动把链接改成mail.com/inbox/102,结果竟然看到了别人的邮件。这就是典型的 BOLA 漏洞。
- 什么是 BOLA 防御?
BOLA 防御就是在代码层面强制校验"资源归属权" 。
当用户请求访问某个数据(对象)时,后端系统不仅要检查用户是否登录,还必须检查当前登录的用户,是否真的拥有访问或操作这个特定数据的权限。
- 通俗的例子:
你去快递站取件(登录成功,验证了身份)。
没有 BOLA 防御的快递站: 你只要报出一个取件码(比如 102),工作人员就直接把快递给你,不管这个快递是不是你的。
有 BOLA 防御的快递站: 工作人员不仅看取件码,还会核对你的身份证。如果发现 102 号快递的主人叫张三,而你叫李四,工作人员就会拒绝把快递给你。
国内的安全工程师和开发人员在日常交流时,为了省事,通常会直接用它的另一个"大名"------IDOR(Insecure Direct Object Reference,不安全的直接对象引用)。
IDOR 的读法也是按字母念(I-D-O-R),或者有时候大家会直接通俗地叫它**"越权漏洞"** 或**"平行越权"**。
- 方案三:引入随机盐值(Salt)
如果必须使用数字ID,可以在ID后面拼接一段随机字符串(Salt),例如 order_id=1001_a8f5f167。后端校验时先验证Salt的正确性,再处理业务逻辑。
盐值(Salt):给密码加把"随机锁"
在网络安全中,盐值是一个随机生成的字符串。它通常与用户的密码组合在一起,然后再进行加密(哈希运算),最后存入数据库。
为什么要用盐值?
如果系统只把密码加密后存储,黑客一旦拿到数据库,就可以利用"彩虹表"(一个预先计算好各种密码对应密文的巨大表格)瞬间反查出大量用户的原始密码。
加上盐值后,即使两个用户设置了完全相同的密码(比如都是
123456),因为系统给每个人加的"盐"是随机且唯一的,最终加密出来的密文也会完全不同。这直接让彩虹表失效,极大增加了黑客破解密码的难度。通俗的例子:
假设你的密码是"糖",黑客有本字典能查出"糖"的味道。
加了盐值后,你的密码变成了"糖+特定的盐A",别人的密码变成了"糖+特定的盐B"。黑客就算知道你们用的都是"糖",也因为尝不出具体的"盐"是什么,而无法还原出原始的"糖"。
总结
"参数可遍历"之所以被标记为高风险,是因为它往往伴随着大规模的数据泄露 。对于开发团队来说,最优先的修复动作是:排查所有涉及ID参数的接口,确保每一个接口都执行了"当前用户 == 资源所有者"的校验逻辑。
请求路径异常
- 含义 :攻击者通过构造非标准或畸形URL路径(如包含
../、特殊编码、多余斜杠等),绕过路径解析逻辑,访问本不应暴露的资源。 - 示例 :正常路径为
/api/files/report.pdf,攻击者尝试/api/files/../../etc/passwd读取系统文件。 - 风险等级:中高,常与路径遍历漏洞结合使用。
在安全圈里也常被称为目录遍历(Directory Traversal)
它之所以被归类在 BOLA(对象级授权失效)的"请求路径异常"下,是因为攻击者通过篡改请求路径,绕过了系统的权限控制,最终访问到了本不该被访问的"文件对象"。
下面为你详细拆解这个漏洞的原理、攻击手法以及防御思路:
1. 漏洞原理:系统"迷路"了
正常情况下,Web 服务器或应用程序会有一个"根目录"(比如 /var/www/html),所有的文件访问都应该被限制在这个目录内。
但是,在 Linux/Unix 系统中,..(两个点)代表"返回上一级目录"。如果后端代码在拼接文件路径时,没有对用户输入的参数进行严格过滤,攻击者就可以利用 ../ 不断"跳出"当前的限制目录,最终访问到服务器的系统文件。
2. 攻击手法详解
攻击者通常会使用以下几种"花招"来绕过简单的防御:
- 基础遍历 :
直接使用../。例如请求/api/files/../../../etc/passwd,系统解析后会变成/etc/passwd(Linux 系统的用户密码文件)。 - 编码绕过 :
如果系统过滤了../,攻击者会将其进行 URL 编码。.. 编码后是 %2e%2e,/ 编码后是 %2f。
攻击请求变成:/api/files/%2e%2e%2f%2e%2e%2fetc/passwd。如果后端在解码前就进行了过滤,这个请求就能成功绕过。 - 双重编码 :
对已经编码的字符再次编码,例如%252e%252e%252f。 - 多余斜杠与点 :
利用..//、....//或/../等畸形路径,欺骗不同层级的解析器(如 Web 服务器和后端应用对路径的解析逻辑不一致)。
URL编码(也叫百分号编码)的规则其实非常简单,核心逻辑就是把非字母数字的字符 转换成
%加上该字符 ASCII 码的十六进制表示。📝 URL编码规则
- 保留字符 :字母(A-Z, a-z)、数字(0-9)以及少数安全符号(如
-,_,.,~)通常不编码,保持原样。- 特殊字符 :空格、中文、符号(如
/,?,&,#)等,都会被转换成%XX的格式。
- 比如空格 ,会被编码为
%20(空格的十六进制 ASCII 码是 20)。- 比如斜杠
/,会被编码为%2F。🛠️ 怎么进行编码?
在实际操作或写代码时,你完全不需要手动去查 ASCII 码表,直接利用现成的工具或代码即可:
- 浏览器自动编码 :
你在浏览器地址栏输入中文或特殊符号,按下回车后,浏览器会自动帮你把地址栏里的内容变成 URL 编码。- 编程语言内置函数 :
几乎所有编程语言都有现成的库。比如:
- Python :
urllib.parse.quote("../")结果就是%2E%2E%2F- JavaScript :
encodeURIComponent("../")结果也是%2E%2E%2F- 在线工具 :
直接搜索"URL编码/解码",有很多网页工具可以一键转换。🕵️♂️ 结合你刚才问的"路径遍历"
在刚才提到的安全漏洞中,攻击者为什么要用 URL 编码?
因为很多安全扫描工具或后端代码,会直接拦截明文的 ../。但如果攻击者把它编码成 %2E%2E%2F,某些写得不够严谨的系统,在解码之前 就去检查路径,发现里面没有 ../,就放行了。等后续系统真正去读取文件时,又把它解码回了 ../,从而成功绕过了安全检查。
简单来说,URL编码就像是给特殊字符穿上了一件"马甲",让它们能安全地在网络上传输,但有时候也会被黑客利用这件"马甲"来伪装恶意请求。
3. 危害等级:中高(甚至极高)
- 读取敏感文件 :攻击者可以读取服务器的配置文件(如数据库密码)、系统文件(如
/etc/shadow)、应用程序源码等。 - 配合其他漏洞:如果攻击者不仅能读,还能写(虽然较少见),他们甚至可以向服务器上传 WebShell(木马脚本),从而彻底控制服务器。
"路径遍历"配合"文件写入(文件上传漏洞)"最终拿到服务器控制权,这是黑客非常经典的一条攻击链路。我们可以通过一个具体的"三步走"实战场景,来看看攻击者是怎么一步步把服务器"拿下"的:
第一步:寻找"上传入口"(写入漏洞)
假设你的网站有一个"用户头像上传"功能,或者"日志上传"功能。
正常的逻辑是:用户上传一张图片,服务器把它保存在
/var/www/uploads/目录下。但如果代码写得不严谨,允许用户自定义保存的文件名,这就埋下了隐患。
第二步:利用"路径遍历"跳出限制(越狱)
攻击者不上传正常的图片,而是准备了一个恶意的脚本文件(比如 PHP 写的 WebShell 木马),文件名叫
hack.php。在上传时,攻击者把文件名篡改成:
../../hack.php。此时,服务器的处理逻辑可能是这样的:
- 接收到文件名:
../../hack.php- 拼接保存路径:
/var/www/uploads/+../../hack.php- 系统解析路径:
/var/www/uploads/往上一级是/var/www/,再往上一级是/var/。- 最终文件被保存到了:
/var/hack.php。结果:攻击者成功把木马文件"扔"到了网站根目录之外的地方,甚至直接扔到了系统的关键目录里。
第三步:访问木马,彻底控制(接管)
文件上传成功后,攻击者只需要在浏览器里访问这个文件(比如
http://你的网站/var/hack.php)。因为这是一个 PHP 脚本,服务器会执行它。而这个脚本里写满了"执行系统命令"的代码(比如
system("ls")或system("rm -rf /"))。最终结局 :
攻击者通过这个网页,就能在你的服务器上执行任何命令------查看数据库密码、删除文件、甚至安装后门,彻底成为这台服务器的"主人"。
总结
这就是为什么"路径遍历"虽然看起来只是"读个文件",但一旦配合上"写入"功能,危害就会呈指数级上升。它就像是攻击者利用了一个"时空隧道"(
../),直接跳过了大门保安(权限校验),把炸弹(木马)直接安放在了你的金库(系统核心目录)里。所以,在开发涉及文件操作的功能时,绝对不要信任用户传来的文件名 ,一定要在后端重新生成随机文件名,并严格限制保存目录。
4. 深度修复方案
要彻底防御路径遍历,不能只靠"黑名单"过滤,建议采取以下多层防御策略:
- 方案一:拒绝直接引用(最推荐)
不要让用户直接输入文件名或路径。后端应该维护一个白名单 或映射表 。- 错误做法 :
/download?file=report.pdf - 正确做法 :
/download?file_id=1001,后端根据 ID 1001 去数据库查询对应的真实文件路径,而不是直接使用用户传入的字符串。
- 错误做法 :
核心思路:把文件名藏起来,用户只能拿到一个随机的"代号"(ID)。
具体操作:
- 建立映射表:在数据库里建一张表,把真实的文件路径和生成的随机ID对应起来。
- 前端只传ID :用户请求下载时,只传递
file_id=1001。 - 后端查库 :后端拿到ID后,去数据库查出真实的物理路径(比如
/data/files/2023/10/24/abc.pdf),然后再去读取文件。
伪代码示例:
# 用户请求:/download?id=1001
file_id = request.get('id')
# 从数据库查询真实路径,而不是拼接用户输入
real_path = db.query("SELECT path FROM files WHERE id = ?", file_id)
send_file(real_path)
- 方案二:严格的输入校验与规范化
如果必须接收文件名,必须对输入进行严格的校验:- 只允许字母、数字和特定的后缀(如
.pdf)。 - 使用编程语言提供的路径规范化函数(如 Java 的
getCanonicalPath()或 Python 的os.path.realpath()),将路径解析为绝对路径后,检查它是否仍然在允许的根目录内。
- 只允许字母、数字和特定的后缀(如
核心思路:如果业务必须让用户传文件名,那就必须"先查户口,再放人"。
具体操作:
- 白名单校验 :只允许特定的后缀名(如
.pdf,.jpg),禁止包含..、/、\等敏感字符。 - 路径规范化(关键步骤):使用语言自带的函数把路径"拉直",然后检查它是否还在你允许的文件夹里。
Python 安全代码示例:
import os
BASE_DIR = "/var/www/uploads/" # 允许的根目录
user_input = "../../etc/passwd" # 攻击者输入
# 1. 拼接路径
target_path = os.path.join(BASE_DIR, user_input)
# 2. 规范化路径(把 ../ 解析成绝对路径)
real_path = os.path.realpath(target_path)
# 3. 核心防御:检查解析后的路径是否以 BASE_DIR 开头
if not real_path.startswith(os.path.realpath(BASE_DIR)):
raise Exception("非法路径访问!")
# 4. 安全读取
with open(real_path, 'r') as f:
content = f.read()
- 方案三:沙箱机制
将文件服务部署在独立的容器或受限的权限环境中。即使攻击者成功遍历了目录,他也只能访问到该沙箱内的文件,无法触及操作系统的核心文件。
核心思路:把文件服务关进"小黑屋",就算被黑了,损失也有限。
具体操作:
- 容器化部署:使用 Docker 部署文件服务。在 Dockerfile 中,使用非 root 用户运行服务。
- 挂载限制:启动容器时,只把存放文件的目录挂载进去,不要挂载系统根目录。
- 权限最小化 :确保运行服务的系统账户,对操作系统核心文件(如
/etc/shadow)没有读取权限。
Docker 运行示例:
# 只挂载 /data/uploads 目录,且以只读方式挂载(如果只需要读)
docker run -v /data/uploads:/app/uploads:ro --user 1000:1000 my-file-service
总结建议
在实际项目中,建议组合使用:
- 优先使用 方案一(ID映射),这是最安全的。
- 如果必须用方案二,一定要加上
realpath校验。 - 无论用哪种方案,最后都加上 方案三(Docker沙箱) 作为兜底,防止意外发生。
总结
"请求路径异常"本质上是信任了用户的输入 。在修复时,核心原则是:永远不要相信用户传来的文件路径,尽量使用间接引用(ID)来代替直接的文件名。
可遍历下载文件
- 含义:文件下载功能未对文件名或路径做严格校验,允许用户通过修改参数下载任意文件。
- 示例 :下载接口
/download?file=invoice.pdf,攻击者改为file=../../../config/db.yml下载数据库配置。 - 风险等级:高,易导致敏感文件泄露。
"可遍历下载文件"其实是路径遍历漏洞 在文件下载场景下的具体表现。它的核心问题在于:后端代码过度信任了用户传来的文件名,没有做严格的边界限制。
为了让你更直观地理解,我们可以把这个漏洞的完整攻击链路拆解成以下三个步骤:
第一步:正常的业务逻辑(漏洞的温床)
假设你的网站有一个"下载发票"的功能。
- 前端请求 :用户点击下载,浏览器向服务器发送请求:
GET /download?file=invoice.pdf - 后端处理 :服务器收到请求后,直接去指定的文件夹(比如
/var/www/files/)里找invoice.pdf这个文件,找到后发送给浏览器。 - 隐患 :如果后端代码只是简单地把用户传来的
file参数拼接到路径后面,比如open("/var/www/files/" + file),这就埋下了巨大的安全隐患。
第二步:攻击者的"越狱"操作(路径遍历)
攻击者发现这个接口后,开始尝试"搞事情"。他知道 Linux 系统中 .. 代表"返回上一级目录"。
- 篡改参数 :攻击者把请求改成:
GET /download?file=../../../config/db.yml - 后端"上当" :服务器收到请求,依然傻傻地执行拼接逻辑:
/var/www/files/+../../../config/db.yml - 系统解析 :操作系统在读取这个路径时,会一步步"往回走":
/var/www/files/往上一级是/var/www/- 再往上一级是
/var/ - 再往上一级是根目录
/ - 最后进入
/config/目录,找到db.yml文件。
- 结果:攻击者成功下载到了数据库的配置文件,里面可能包含了数据库的账号和密码!
第三步:危害升级(从泄露到控制)
这还只是读取文件。如果这个下载接口同时支持"上传"或者"写入"功能(比如日志上传、头像上传),攻击者就可以利用同样的 ../ 技巧,把恶意的木马脚本(WebShell)直接写入到网站的核心目录甚至系统目录中。一旦写入成功,攻击者就能通过浏览器访问这个木马,彻底接管整台服务器。
为什么它属于 BOLA(对象级授权失效)?
在 BOLA 的视角下,每一个文件都是一个"对象"。
正常的逻辑应该是:用户只能下载属于自己的 发票文件。
但这个漏洞导致用户不仅能下载别人的文件,还能下载系统级别的文件(如配置文件、密码文件)。这相当于权限校验完全失效,攻击者可以随意访问任何层级的"文件对象"。
如何彻底修复?
针对这个漏洞,最稳妥的修复方案是**"白名单 + 路径规范化"**:
- 拒绝直接传文件名 :最好像之前提到的,用数据库 ID 来代替文件名(
/download?id=1001)。 - 强制路径规范化(如果必须传文件名) :
在后端代码中,必须使用语言自带的函数(如 Python 的os.path.realpath或 Java 的getCanonicalPath)将拼接后的路径"拉直",然后严格检查这个最终路径是否仍然在你允许的文件夹内。
Python 安全代码示例:
import os
BASE_DIR = "/var/www/files/" # 允许的根目录
user_input = "../../../config/db.yml" # 攻击者输入
# 1. 拼接路径
target_path = os.path.join(BASE_DIR, user_input)
# 2. 规范化路径(把 ../ 解析成绝对路径)
real_path = os.path.realpath(target_path)
# 3. 核心防御:检查解析后的路径是否以 BASE_DIR 开头
if not real_path.startswith(os.path.realpath(BASE_DIR)):
raise Exception("非法路径访问!")
# 4. 安全读取
with open(real_path, 'r') as f:
content = f.read()
简单来说,永远不要相信用户传来的文件路径,一定要在后端把路径"拉直"后,确认它没有"越狱"才能放行。
鉴权凭证脆弱
- 含义:用于鉴权的令牌、Session或Cookie存在弱点,如过期时间过长、可预测、未绑定设备/IP、或未在服务端有效验证。
- 示例:使用简单自增ID作为Session ID,或Token未签名、未加密,易被伪造或重放。
- 风险等级:中,但一旦利用成功可导致大规模越权。
"鉴权凭证脆弱"这个问题,通俗点说,就是你发给用户的"通行证"(Token、Session或Cookie)质量太差,或者管理太松,导致容易被黑客伪造、猜中或盗用。
这个其实和之前的参数可遍历那个分类差不多,都是因为"通行证/ID"有规律,导致易被伪造
一旦通行证出了问题,系统就会把黑客误认为是合法用户,从而引发大规模的越权访问。我们可以从以下几个具体的"脆弱点"来详细拆解:
1. 凭证"太容易被猜中"(可预测性)
- 含义:系统生成的 Session ID 或 Token 规律性太强。
- 具体例子 :有些老旧系统为了省事,直接用数据库的自增 ID(如
1, 2, 3...)或者时间戳作为 Session ID。黑客只需要写个脚本,从 1 试到 1000,就能轻易猜出其他在线用户的 Session,直接"魂穿"成对方。 - 正确做法:必须使用高强度的随机算法(如 UUID、NanoID 或加密安全的随机数生成器)来生成凭证,确保每个 Token 都是独一无二且无法被预测的。
2. 凭证"没有防伪标识"(未签名/未加密)
- 含义:Token 的内容是明文传输的,或者没有经过数字签名,黑客可以随意篡改里面的内容。
- 具体例子 :假设你的 Token 里存着
{"user_id": 100, "role": "user"}。如果 Token 没有签名保护,黑客把它解码后,把role改成admin,再重新编码发给你。系统一看没报错,就真以为他是管理员了。--就是垂直越权 - 正确做法:使用标准的 JWT(JSON Web Token)等机制,并在服务端配置密钥进行签名。这样黑客一旦修改了 Token 里的任何内容,签名就会失效,系统会直接拒绝。
3. 凭证"保质期太长"(过期时间过长)
- 含义:Token 一旦签发,有效期设置得过长(比如几个月甚至一年),或者根本没有过期时间。
- 具体例子:如果用户的 Token 被黑客在某个公共 Wi-Fi 下截获,由于 Token 长期有效,黑客可以在接下来的几个月里,随时随地用这个 Token 登录用户的账户,而用户毫无察觉。
- 正确做法:设置合理的过期时间(如 Access Token 15分钟,Refresh Token 7天),并配合"刷新机制",让用户在活跃时自动续期,不活跃时自动失效。
4. 凭证"不认人"(未绑定设备/IP)
- 含义:Token 只验证"是不是有效的通行证",不验证"拿通行证的人对不对"。
- 具体例子:你的 Token 在北京的电脑上登录了,下一秒黑客拿着盗来的 Token 在国外的 IP 上登录,系统依然放行。
- 正确做法:在生成 Token 时,绑定用户的设备指纹(如 User-Agent、设备 ID)或 IP 地址段。当请求到来时,校验这些信息是否匹配,如果不匹配则强制下线。
5. 凭证"服务端不查岗"(未在服务端有效验证)
- 含义:系统只检查 Token 的格式对不对,却不检查这个 Token 在服务端是否还"活着"。
- 具体例子:用户点击了"退出登录",前端把 Token 删了。但如果黑客手里还存着这个旧 Token,他继续发给服务器,服务器因为没做黑名单校验,依然认为他是登录状态。
- 正确做法:在服务端维护一个"黑名单"(如 Redis),用户退出或修改密码后,立刻把旧 Token 拉黑;或者使用短效 Token + 服务端状态校验的机制。
总结
"鉴权凭证脆弱"就像是给大楼发了一把塑料做的、没有编号的、永不过期的门禁卡。黑客只要捡到或者复制一把,就能大摇大摆地进出所有房间。
修复的核心原则是 :让通行证随机难猜、带防伪签名、有明确保质期、绑定使用者身份,并且随时能被保安(服务端)查验真伪。
修复建议
- 所有对象访问必须强制校验当前用户是否拥有该对象的访问权限(而非仅验证登录状态)。
- 使用不可预测的UUID替代自增ID作为对象标识符。
- 对文件路径参数进行白名单校验或使用沙箱机制隔离访问。
- 实施严格的输入过滤与路径规范化,防止路径穿越。
- 使用强随机Token、设置合理过期时间、绑定客户端指纹,并在服务端实时验证凭证有效性。