资讯动态

API安全实战:防越权、防刷接口、防信息泄露

发布时间:2026/10/21 17:29:43 来源:尧图企业网站定制
1. 先搞清楚API是怎么一步步变成“后门”的做技术这么多年我见过太多系统在“功能上线”和“安全上线”之间差了十万八千里。很多团队花了几周时间把接口调通、联调、上线却忘了问一句除了自己人还有谁会来调这个接口接口安全这个事说白了就是自家大门的事。门锁不装、钥匙乱发、窗户不关晚上来了贼你还觉得很奇怪明明服务器防火墙、数据库密码都设好了啊。但攻击者根本不用打你那台堡垒他就是拿着你的API文档像逛商场一样把数据一件件拿走了。这里说的API指的是应用程序对外暴露的接口包括Web后端接口、移动端服务端接口、第三方开放接口哪怕内部服务之间的调用也算。所谓“API成为后门”并不是说接口本身设计得有多差而是说它在某些环节被绕过了、被滥用了、被利用了变成了一个比系统漏洞更隐蔽的入口。我在做安全评估和代码审计的时候几乎每一次都能在看似正常的业务系统里找到几处能让普通用户看到别人数据的接口。这不是偶然是结构性的忽略。这篇内容我会用三个比较典型的真实场景来拆解接口为什么会变成后门以及每一类问题应该怎么修、怎么防。文章里不会堆一堆晦涩的安全术语我会尽量把每个问题的来龙去脉讲明白把可复用的排查思路和落地手段给到你。适合后端开发、架构师、运维、测试还有那些正在搭建开放平台的技术负责人。就算你目前只负责一个小项目也可以对照着自查一遍。1.1 攻击面为什么越来越大这几年API攻击面变大不是某个人的错觉是架构演进的必然结果。过去是一个单体应用后端、前端、数据库都在一个进程里用户只通过浏览器访问攻击入口就那几个页面。现在不一样了前端有Web有H5有App有小程序还有一堆后台管理端、运营后台、开放平台、IoT设备、内部服务间调用。每多一个入口就多一套接口要维护多一份鉴权逻辑要写。而人的精力是有限的业务需求又催得紧安全意识稍微跟不上接口就会漏风。更麻烦的是很多接口在设计时是按照“客户端可信任”的逻辑来的。比如前端调接口时传一个user_id后端拿着这个user_id去查数据完全没有判断这个user_id是不是当前登录用户。这就像酒店前台只看房卡号码不问你是谁捡到一张别人的房卡就能随便进出房间。移动端和Web端的大量业务逻辑是靠接口串起来的如果接口层不校验身份归属业务逻辑层的防线就是一张纸。还有一个变化是微服务和前后端分离的普及。拆分以后服务与服务的通信、服务与数据库的交互都变得多样化接口不只是给浏览器用了。某些服务的内部接口可能没有严格的鉴权觉得“反正外网访问不到”但实际网络隔离并没有想象中那么可靠。一旦某个边缘服务被打穿或者SSRF绕一圈内部接口就直接暴露了。这也是API安全越来越受重视的背景接口已经不是那个只属于后端开发的小事了。1.2 三个最常见的“失守原因”我盘点了这些年看过的接口安全问题发现表面上五花八门本质上可以归成三类。第一类是“身份与权限检查缺失”。具体表现是接口能识别你是谁但不检查你有没有权限做这件事。最典型的是水平越权也就是普通用户能访问其他同级用户的数据还有垂直越权普通用户能调管理员接口。这属于最致命的一类因为攻击者不需要多高深的技术只要正常登录改个参数就行。第二类是“资源滥用和频率失控”。具体表现是接口的调用成本低、次数不受限被脚本恶意循环调用。典型场景包括短信验证码被刷、注册接口被批量薅羊毛、AI接口被无限白嫖、搜索接口被爬虫拖库。这类问题不会直接泄露数据但会造成资金损失、服务瘫痪甚至间接拖垮整个后端集群。第三类是“信息过度暴露”。接口的错误信息太详细、接口文档没有权限控制、调试模式遗留、响应里带了太多多余的字段。攻击者根据这些信息能精准判断技术栈、数据库类型、表结构甚至拿到内网路径。这些东西单看都是“小问题”组合起来就是攻击手册。这三类问题我会在后面三个案例里一个个展开。每个案例都是我在不同项目中看到过的真实场景的抽象不是某些大公司的独家新闻而是任何一个团队都可能踩进去的坑。你看完之后最好别觉得“我也写过这种代码”因为大概率你写过。2. 三个真实案例每一处都是血泪教训2.1 案例一订单查询接口暴露了所有用户的收货信息先说一个我印象最深的案例发生在某电商平台。这个平台的App端有一个“查看订单详情”的接口路径大概是/api/order/detail传参是一个orderId。正常情况下用户查询自己的订单传入自己订单的ID返回对应的订单信息。问题出在后端拿到orderId后直接根据这个ID去数据库查询然后把订单数据返回了。你可能会说这不是很正常的逻辑吗问题就在这里。后端完全没有校验“这个订单是不是当前登录用户的订单”。我用普通账号登录后把orderId改成附近的一个数字比如自己的订单是10001我改成10002结果直接返回了另一个用户的订单信息里面包含了收货人姓名、手机号、完整地址。我继续循环枚举了一个范围拿到了大量真实订单数据。这就是典型的话:水平越权也叫IDOR。这个问题的根子在于开发者把“能查到这个订单”等同于“有权查看这个订单”。查询这个动作本身只依赖了一个业务主键而这个主键是用户可以控制、可以枚举的。有人可能会觉得自增ID换成UUID不就安全了吗UUID确实提高了枚举难度攻击者没法简单地猜下一个ID但如果接口逻辑不校验归属只要ID通过其他渠道泄露一次照样可以越权访问。硬要打比方的话换远程锁和把钥匙保管好是两码事。修复方案其实不复杂关键是要在服务端拿到当前登录用户上下文再去比较订单归属。# 修复前直接按ID查询不校验归属 def get_order_detail(order_id): return db.query_order_by_id(order_id) # 修复后先获取当前登录用户再校验归属 def get_order_detail(order_id, current_user): order db.query_order_by_id(order_id) if order is None: raise NotFound(订单不存在) if order.user_id ! current_user.id: raise Forbidden(无权访问该订单) return order这段伪代码看起来简单但实际项目里会衍生出很多问题订单可能包含子订单、售后单、发票、物流等关联数据但凡有一个关联接口忘了传user_id进去就会产生新的越权点。所以真正稳妥的做法是在数据查询的入口统一加上“数据归属过滤器”而不是在每个业务方法里手写判断。比如使用查询过滤器在查询订单时自动追加where user_id 当前用户id这样就算开发忘了写权限判断漏数据也漏不出去。这个案例里有几个细节值得注意。一是攻击者不一定逐个改参数还会用批量工具自动遍历二是某些接口可能返回“当前用户不存在”“订单不存在”和“无权访问”三种不同的错误信息攻击者可以根据这些响应头判断ID是否有效所以修复时最好统一错误文案不要让外部区分出资源是否存在。三是越权问题既然出现在订单接口那商品、购物车、优惠券、收货地址、支付流水这些模块也得一并排查否则修了一处攻击者换一个模块继续测。2.2 案例二一个“忘了加频率限制”的验证码接口被脚本刷爆第二个案例是短信验证码接口。某平台的用户注册和找回密码都依赖短信验证码接口大概是/api/sms/send传一个手机号后端就调短信服务商的接口发送验证码。这个接口上线时没有做任何频率限制也没有图形验证码。结果上线当天晚上运维发现短信账户余额在以每分钟几百条的速度下降。一开始还以为是短信服务商出了故障查日志以后发现同一个手机号在几秒钟内被提交了十几次发送请求紧接着换了一批手机号继续刷。简单说攻击者拿到这个接口地址后写了一个循环脚本不断调用接口把短信通道变成了“垃圾短信发送器”。这一步不仅让公司损失了短信费用还导致正常用户收到大量骚扰短信平台被投诉服务商甚至差点停止服务。我们来拆解一下这个问题。接口发送短信有两个硬成本一是调用短信通道有费用二是对用户来说每个手机号每天接收的短信数量有极限。如果接口不做限制攻击者就能用极小的成本调用这个接口把平台的钱变成攻击者的“弹药”。更麻烦的是这种刷量行为可以伪装成正常用户操作单看某一个IP时看不出异常因为它会换IP、换手机号、模拟不同的指纹。修复这类接口的核心思路是“让一次合法请求的成本足够低让一次非法请求的成本足够高”。具体手段可以分三层。第一层是请求限制同一个手机号每分钟最多一条每天最多五条同一个IP每小时最多十条同一个客户端指纹每天最多二十条。落到具体实现上建议用分布式限流组件而不是单机限流因为现在服务基本都是多实例部署单机的计数器在负载均衡下会失效。第二层是增加人机验证发送验证码前先要求用户完成图形验证码或者滑块验证这一步可以拦住绝大多数脚本攻击。第三层是签名防重放在请求参数中加入时间戳和签名签名由服务端和客户端协商生成服务端校验时间窗口超出窗口的请求直接拒绝。当初我们修复的时候采用的方案是“Redis计数 图形验证码 签名校验”三管齐下。下面是伪代码示例def send_sms(mobile, captcha, timestamp, sign): # 校验签名防止重放 if not verify_sign(mobile, timestamp, sign): return error(非法请求) # 校验图形验证码 if not check_captcha(mobile, captcha): return error(验证码错误) # Redis 限流同一手机号 60 秒内只能发一次 if redis.exists(fsms:limit:{mobile}): return error(发送过于频繁) redis.set(fsms:limit:{mobile}, 1, ex60) # 日限次校验 day_count redis.incr(fsms:day:{mobile}) if day_count 5: return error(今日发送次数已用完) redis.expire(fsms:day:{mobile}, 86400) # 真正调短信通道 send_code(mobile)这里有个容易漏掉的小细节**限流计数要放在业务处理之前而且一旦判定超限就直接返回不要再走后面的逻辑。**有时候开发图省事把限流放在发送短信之后才执行结果接口已经被刷完了才反应过来。另外限流key的有效期要设置合理别把分钟的key设成永久也别把天的key设成几分钟否则要么误伤用户要么形同虚设。2.3 案例三调试接口没关数据库结构被看了个精光第三个案例看起来不那么“惊悚”但造成的后果一点也不轻。某团队在开发阶段为了方便联调做了一个/api/debug/db接口能够查看某张表的前一百条数据还挂了一个swagger页面把所有的接口定义、参数说明、返回结构都暴露在外面。上线的时候运维把服务跑了起来却忘了这两个东西没有关闭。后来做安全巡检时我用了一个常规操作直接访问项目的/swagger-ui.html发现接口文档完全开放。从文档里能看到每个接口的请求参数、返回字段、鉴权方式还能看到内部接口的定义。顺着文档往下翻有一个/api/debug/db接口入参是表名返回结果是表数据。试着传了一张用户表的表名结果真的返回了明文数据。这类问题在行业里有个名字叫“信息泄露”但它比一般的信息泄露更烦人因为接口文档和调试接口等于把系统的地图手工画好了递给攻击者。攻击者不用再去猜接口路径、参数格式、返回结构直接照着文档写脚本就行。如果调试接口还能操作数据那攻击者就可以任意增删改危害直接拉满。修复方案看起来很简单关掉调试接口、关闭swagger。但实际中有几个常见的坑。一是开发环境和生产环境的配置经常混在一起开发时把开关打开上线时忘了改回去二是很多人不知道swagger可以通过配置只在内网开放而不是必须彻底关掉三是调试接口可能不止一个还有定时任务入口、缓存清理接口、配置热更新接口这些都可能在生产环境残留。从代码层面看比较稳妥的做法是单独抽出一个“环境开关”配置所有调试相关接口都通过这个开关控制并且开启时强制要求IP白名单。配置示例# application-prod.yml app: debug: enabled: false docs: enabled: false同时在代码里做一层拦截如果检测到当前环境是生产环境请求任何内部调试接口都直接返回404。这里的思路不是把接口“隐藏”起来而是从路由层面直接剥离掉。因为隐藏接口这种行为只能防君子防不了扫描器扫描器会遍历常见路径比如/debug、/actuator、/console这些你隐藏了路径它扫不到但如果它扫到你遗留的其他接口呢干脆让这些路径在生产环境根本不存在才是彻底的做法。我遇到过有人问swagger关了那运维和测试怎么看接口文档答案是可以用独立的接口文档平台把文档发布到内部平台上加权限控制跟生产环境完全隔离。要明确一点接口文档是给开发、测试、联调协作用的不是给最终用户用的它在生产环境开放对自己没有任何好处全是风险。3. 守住接口安全这四层防线必须层层落实看完案例可能有人觉得这些问题要么是“忘了”要么是“粗心”。但从系统化角度看接口安全不能靠某几个人的细心来保障要建立机制让不安全的接口在交付之前就被拦住。我习惯把接口安全拆成四层防线认证授权、网关收口、参数校验、审计监控。下面每一层我都会讲清楚为什么要有这一层以及落到具体项目里怎么做。3.1 第一层认证授权不能只做“表面功夫”认证和授权是两个东西。认证是确认“你是谁”授权是确认“你能做什么”。很多项目只做了认证登录后拿到一个token后端验证token有效就放行但完全没有验证这个用户有没有调用当前接口的权限。这个状态比没登录更危险因为攻击者已经拿到了一个合法身份接下来测试的就是哪些接口能越权。常用的认证方案是JWT。JWT本身是一种无状态token服务端不保存会话靠签名保证token没有被篡改。使用JWT时要注意几个细节签名密钥要足够复杂并且定期轮换过期时间不能太长一般建议access token控制在两小时以内不要在token里存放敏感数据手机号、身份证号、订单金额都不该塞进去服务端解析token后要把它缓存到请求上下文里后续的业务逻辑只从上下文取用户信息不要相信前端传过来的任何身份字段。授权方面推荐用RBAC模型也就是用户绑角色、角色绑权限。当某个接口被调用时后端根据用户角色判断是否可以访问。如果系统里的数据属于不同用户比如订单、消息、文件那么单靠RBAC还不够还要在数据层面再做一次归属校验这就是前面案例一里说到的对象级授权。我见过不少项目只做了“能访问订单模块”的角色校验却没有做“只能访问自己的订单”的数据级校验结果角色权限再严格普通用户照样能水平越权。3.2 第二层网关统一收口别让业务裸奔如果你的系统有多个服务网关不是一个可选项而是必需品。网关的意义在于所有外部请求都先经过它由它完成认证、限流、路由、审计等通用逻辑业务服务只需要专注业务本身。没有网关的话每个服务都要自己写一遍限流和鉴权总有一个会漏漏了就成了入口。具体落地时网关一般负责这几件事一是校验请求的API Key或JWT无效请求直接拒之门外二是限流可以针对IP、用户、接口路径做多维度的流量控制三是黑白名单把恶意IP和内部管理接口的访问范围在网关层控制住四是协议转换屏蔽内部服务的真实地址。举个例子外部只能访问/api/xxx内部某个服务实际的调用路径是/internal/order/xxx网关把这两者映射好外部就永远接触不到内部路径。另外要提醒一点**内部服务之间的调用也要过一层轻量级认证。**很多团队觉得“内网调用不需要鉴权”结果一台边缘被攻陷攻击者直接请求内网服务。内部调用可以用mTLS或者内部token成本不高但能把横向移动的路径堵上。3.3 第三层参数校验与数据最小化正常输入参数校验大家都懂但接口安全角度有几个容易忽视的地方。第一个是请求体大小限制。某些接口接收JSON解析时如果不限制大小攻击者可以提交几十兆的JSON把后端内存打爆。第二是字段类型严格校验。字符串、整数、枚举都要明确指出预期类型传了不该传的类型直接拒绝不要做隐式转换。第三是数组长度限制分页参数里的pageSize最大值要限制避免一次查出全表数据。第四是SQL注入和命令注入防护使用参数化查询不要把用户输入直接拼进SQL或系统命令。数据最小化这个原则很多团队是不执行的。接口返回对象直接把数据库实体序列化后发出去里面包含了密码哈希、手机号、内部备注字段用户本来只需要一个名字和头像结果额外拿到了大量和自己无关的数据。一旦接口被越权调用泄露的数据面就被放大了。正确做法是给每个接口定义独立的DTO只包含必要字段禁止直接返回ORM实体。安全领域的经验是接口返回的数据越少泄露后的损害越小这是非常划算的一层防护。3.4 第四层审计与监控出了问题要能及时看见这层防线最容易被忽视因为它不直接影响功能但对安全来说极其重要。如果没有日志没有监控一次越权攻击可能发生了几周之后才在某个偶然的数据泄露通报里被发现。有了详细的调用日志你才能定位攻击路径才能判断攻击者拿到了哪些数据。接口日志至少要记录请求时间、访问路径、调用方标识用户ID或AppKey、来源IP、请求参数摘要、处理结果状态码、响应耗时。注意日志里不要记录明文密码、短信验证码、支付卡号这类敏感信息必要时要脱敏。建议加上一个traceId贯穿整个请求链路方便排查问题时把网关日志、服务日志、数据库慢查询日志串起来。监控告警方面可以设置几类规则某接口的调用量突然暴涨、某用户的调用频次异常、某IP在短时间内访问了大量不同用户的资源、接口错误率连续上升。这些规则不需要一开始就做得很重哪怕是每天把异常调用清单汇总成邮件也能起到很大作用。我坚持的原则是别等攻击者告诉你系统被攻破了要让监控数据先告诉你。4. 实操中常踩的坑与排查技巧实录4.1 越权修复的两个典型误区第一个误区是“前端控制等于安全”。有些团队接到越权问题后第一反应是把前端页面里的操作按钮隐藏掉或者把输入框改成只读。比如普通用户的页面上不显示“删除用户”按钮就觉得安全了。实际上接口还是那个接口请求还是能构造。攻击者根本不看你的前端页面他用抓包工具直接构造HTTP请求就完了。任何安全控制只要没有发生在服务端就等于没做。第二个误区是“用前端传的userId作为权限判断依据”。有些接口需要区分用户但后端没有从登录态里拿用户身份反而让前端把userId作为一个参数传进来。后端查询时拿这个userId去数据库取数逻辑看似走了“校验当前用户”实际只是校验了一个攻击者可以随便改的参数。正确的做法是始终从token或session里解析当前用户前端传的userId最多只能作为“要操作的目标对象ID”并且还要和当前用户做关系校验。4.2 限流方案怎么选、怎么落地限流方案最常见的两种是令牌桶算法和滑动窗口算法。单机场景下纯内存实现就能搞定分布式场景下要用Redis或组件的分布式限流否则你限的是单台机器请求负载均衡转一圈就绕过了限制。接口级别区分登录接口、短信接口、验证码接口这类“攻击高发区”要严格限流普通查询接口可以放宽一些内部管理接口单独设更严格的访问控制。如果系统里同时有Web端和App端建议按端来分开统计便于识别某一端的异常行为。有几个细节值得提一下。限流键要选对单纯按IP限流容易被共享IP误伤单纯按用户限流容易被攻击者换账号绕过通常要用“IP用户接口”组合维度。限流触发后的反馈也很重要一般返回429状态码提示“请求过于频繁”不要返回5xx5xx会让前端认为是系统故障同时也会触发告警。限流规则不是配一次就完事的需要定期根据访问量调整阈值太严格会误伤正常用户太宽松则形同虚设。4.3 日志脱敏的细节坑说到日志脱敏很多团队会写一个正则表达式把手机号中间四位替换成星号。实际上这个办法有很多坑。第一正则只对固定格式有效邮箱、身份证号、银行卡号有各自不同的格式漏掉一个就是泄露。第二日志里除了数据本身还有一些拼接字符串可能隐含着敏感信息比如查询条件、错误详情、堆栈信息。第三脱敏的时机也很讲究应该在日志输出之前统一处理而不是在业务代码里到处调用脱敏方法否则总有漏网之鱼。建议的做法是使用日志框架自带的转换器定义一批常用敏感字段的脱敏规则集中处理。输出日志时只记录业务必要字段不多打一行。这里有一个老生常谈但我必须再强调的点**日志和监控系统本身也要做访问控制别让日志平台成为新的数据泄露源。**我见过不止一次一个完全没有权限控制的日志查询页面挂在公网上任何人打开就能搜到全量生产日志里面的敏感数据比数据库泄露还直接。4.4 一份快速自查清单如果你现在接手一个老系统或者正在开发新系统的接口建议拿下面这份清单过一遍。这份清单不是全量安全测试方案而是最快帮你找到高危漏洞的步骤。检查所有接口是否默认要求登录认证。有没有未登录就能访问的接口用爬虫扫一遍常见路径看返回什么。检查每个需要用户身份的接口是否校验了资源归属。重点测订单、消息、文件、收货地址这些模块改ID访问试试。检查敏感接口是否有频率限制。短信、登录、注册、导出、支付这类接口是否能被一个脚本连续调用一百次以上。检查生产环境是否开放了调试接口、接口文档、监控端点。访问一下常见路径看是否存在。检查接口返回内容是否包含敏感字段。观察响应JSON里有没有手机号、身份证号、密码哈希、token。检查管理后台接口是否和普通用户接口混在一起是否有独立的更严格的权限体系。检查错误信息是否对用户暴露了堆栈、SQL语句、服务器版本信息。这份清单如果能在一小时内全部检查一遍并且没有发现问题那至少说明这个系统在常见攻击面前不会是“裸奔”状态。如果某一项发现了问题那恭喜你少了一次被攻击者免费教学的机会。5. 安全接口的日常维护这些事顺手做掉接口安全不是上线时做一次就完了它需要的是持续的迭代和维护。比如每次业务迭代新增接口时评审里必须带上安全评审第三方接口依赖更新时要检查登录态校验、限流逻辑是否受影响团队成员变动时要轮换内部服务间的密钥和token。这些东西不需要专门的安全团队开发负责人顺手就能盯住。这里有一个我自己的习惯每隔一段时间把自己当成攻击者对着自己负责的系统做一轮接口排查。不求找出所有问题能发现一两个就算赚到。具体的做法是打开抓包工具抓一次App或Web端的正常请求然后尝试修改请求里的ID、手机号、金额、数量、状态这些关键字段观察后端返回和数据库变化。尤其是金额和状态这种字段很多系统只在客户端校验服务端完全不校验这要是被抓到了问题就大了。另一个习惯是换一个低权限账号去访问高权限操作比如普通用户访问管理接口这也是成本极低但效果最好的测试方式。说到底接口安全没有一劳永逸的银弹它靠的是每一层的机制和每一次迭代时的警觉。很多问题在事后看都非常简单为什么当时没发现因为它的触发条件太隐蔽了。你不可能保证每一行代码都写得没有漏洞但你可以保证每个接口都有认证、有授权、有限流、有监控、有日志。这些机制单独拿出来都不难难的是让它们成为所有人默认的开发规范而不是某一次安全演练时的临时补丁。最后再分享一个小技巧在应用启动时打印一行当前环境的说明写清楚现在是开发、测试还是生产上线时看一眼别嫌烦。很多调试接口残留和swagger泄露本质上都是“开发环境配置和生产环境配置混用”造成的。把环境隔离这件事做好等于在源头上消灭了一整类接口安全问题。我做过的项目里凡是配置规范严格的安全事故率都会低一个量级这个经验你可以直接复制到自己的团队流程里。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑