从抓包到攻防:解锁API安全设计的秘密

系列文章回顾

在开发 Home Assistant(HASS)插件的过程中,我遇到并解决了各种技术挑战,从验证码识别到设备追踪、前端调试再到协议分析,分享了一系列实战经验。这些主题虽然发散,但都源于HASS插件开发的真实场景。以下是前四篇,按发布时间顺序排列:


本篇作为系列收尾,将聚焦抓包调试视角下的API安全设计,层层递进探讨如何保护敏感信息,构建更安全的API体系。如何确保 API 数据在传输中的安全?本文聚焦 API 传输安全设计,提出从 HTTPS 到 mTLS 的递进防护策略,结合攻防视角(如 Frida 绕过证书固定),分享调试与逆向中的经验教训,为开发者提供实用的安全思路。

注意,重放攻击 或者 暴力破解 不在本文讨论范畴,因为它们属于应用层服务端需要考虑的,而非传输层。尽管如此,这个安全仍然十分重要,不可忽视。比如很多网站/APP使用的短信验证码仅4位数字,若不对验证码校验接口增加频率限制,最差情况仅需暴力一万种情况即可认证成功。

抓包调试下的API安全挑战

优秀的开发者常通过抓包工具(如 Wireshark、tcpdump、Proxyman)分析 API 请求,理解其设计逻辑,提升技术能力。然而,这种"白帽"行为也揭示了一个严峻现实:任何未加密或弱加密的通信都可能被恶意利用

攻击者可借助中间人(MITM, Man-in-the-Middle Attack)手段,拦截敏感信息(如认证 token、用户数据),甚至篡改请求以实施欺诈。因此,API 安全设计必须遵循 最小暴露原则 (又称迪米特法则,Law of Demeter),从基础加密逐步升级到高级认证,构建纵深防御体系。

基础防护:启用HTTPS,防范MITM攻击

在明文 HTTP 下,抓包工具可直接读取敏感信息,极易遭到中间人攻击,数据被篡改或窃取。自 2017 年 1 月起,Chrome 和 Firefox 浏览器便开始对访问 HTTP 的网站默认显示不安全警告,但一些内置浏览器(如微信内置网页)可能仍然允许 HTTP 直接访问且不作任何警告。

如下图所示,某网站若使用 HTTP 部署,整个传输过程的请求/响应信息将一览无遗。

然而,一旦采用 HTTPS ,抓包工具默认只能看到请求的域名和一个 CONNECT 请求方法,无法获取其他有效信息。这是因为代理服务器的工作原理:当检测到代理后,客户端会先向代理服务器发起 CONNECT 请求,告知目标域名。但由于后续所有消息都已被加密,代理服务器无法获取具体的请求地址、方法或响应体。

HTTPS 强制所有流量通过加密通道传输,使用服务器证书验证身份,从而防止流量被明文截获。

早期,开发者不愿迁移到 HTTPS 的原因主要有:

  • 成本:需要采购额外的证书。
  • 认知偏差:认为 API 不传输敏感信息,所以无所谓。
  • 嫌弃麻烦:需要额外的配置和维护。
  • 性能开销:HTTPS 握手过程比 HTTP 多了额外步骤,增加了开销。

下图来自Cloudflare的官网,黄色部分就是握手过程多出来的步骤:

但如今,随着 Google 等大厂的强力推动、Let's Encrypt 的普及以及开发者安全意识的提高,这些理由已不再成立。

许多开发者误以为将 HTTP 重定向到 HTTPS 即可高枕无忧。实则不然------攻击者仍可通过 SSL Stripping(降级攻击)拦截重定向,迫使客户端使用明文通信。

设置正确的HSTS(HTTP Strict Transport Security)请求头可强制浏览器始终使用 HTTPS,防止降级攻击。

对于 HSTS 来说,首次访问时仍然存在风险。因此,还有一个 HSTS Preloading 概念,它允许网站将自己的域名预先提交到主流浏览器的 HSTS 列表中,从而在首次访问时就强制使用 HTTPS。感兴趣的读者可以访问 hstspreload.org/ 了解更多

部署完成后,建议使用 SSL Labs 测试工具 验证网站安全等级。

这层防护是起点,但面对证书伪造攻击,仍需升级。

证书固定:引入SSL Pinning,强化证书验证

即使启用了 HTTPS,攻击者仍可安装自定义的根证书(例如在抓包工具中),伪造服务器证书进行 MITM 解密,导致加密失效。这正是所有抓包工具在抓取 HTTPS 流量时,都要求你安装并信任其证书的原因。

为了应对这种情况,安全性较高的 App 或软件会使用 SSL Pinning(证书固定) 技术:客户端预置了服务器证书的公钥指纹或哈希值,只信任特定的证书,而不依赖于操作系统或浏览器的 CA 链。

然而,这项技术也存在一些弊端:

  • 浏览器不支持:Web 端难以实现 Pinning,因为浏览器依赖系统 CA,无法预置固定证书。
  • 维护问题:证书过期时,必须更新客户端,可能导致 App 强制升级。若证书被泄露,整个系统易受影响,需要复杂的过渡期策略。
  • 绕过风险 :攻击者可使用 Frida等逆向工具 Hook Pinning 逻辑,注入自定义证书来绕过防护。例如,笔者就曾利用 NVISOsecurity/disable-flutter-tls-verification 等工具成功抓包某个使用了 Pinning 的 PC 软件。

尽管 Pinning 增强了证书信任,但如果攻击者绕过了证书层,传输的数据仍然会暴露。因此,我们需要在数据传输之上再增加一层额外的加密。

消息层加密:结合对称与非对称加密,提升数据保密

即使 TLS 证书层安全无虞,数据在服务端或客户端解密后仍然可能暴露。因此,需要对消息体进行额外的加密。就像给重要文件套上一个"数字保险箱"。为了实现高效且安全的加密,我们通常会结合使用对称加密非对称加密

对称加密:高效但面临密钥分发难题

AES、SM4、DES 等算法属于对称加密,其特点是加密与解密使用相同的密钥,如同用同一把钥匙完成上锁与开锁。

这类算法的优势在于加解密速度快,资源消耗低,适合处理大量数据。

然而,其核心缺陷在于密钥分发:通信双方必须事先共享同一密钥。若密钥在传输过程中被截获,整个加密体系将失效。

非对称加密:解决密钥分发,但性能较低

RSA、SM2、ECC 等算法属于非对称加密,使用一对密钥:公钥可公开分发,用于加密;私钥由接收方保密,用于解密。

可将其类比为一个"公开的收件箱":任何人都能将信息投入其中,但只有私钥持有者才能取出内容。

该机制天然解决了密钥分发问题,但计算开销大、加解密速度慢,且对加密数据长度有限制,不适合直接用于大量数据的加密。

混合加密:发挥各自优势

因此,实际应用中通常采用混合模式:

  • 使用对称加密(如 AES 或 SM4)加密业务数据,确保高效处理;
  • 使用非对称加密(如 RSA 或 SM2)加密对称密钥,确保密钥安全传递;
  • 接收方先用私钥解密获取对称密钥,再用该密钥解密业务数据。

这一机制实现了用非对称加密传递密钥,用对称加密保护数据的设计目标,在保障安全性的同时兼顾了性能效率。

一个更通俗的例子:

  • 先用 AES 保险箱 装好所有数据
  • 再用 RSA 快递服务 把 AES 的钥匙安全送出去
  • 对方收到后,先用私钥打开小盒子取出钥匙,再用钥匙打开保险箱
  • 这就是 AES + RSA 混合加密一个负责装东西(AES),一个负责送钥匙(RSA) ,既安全又高效

实际上,该方案不仅适用于高安全要求的 API 通信,也是 TLS、PGP 等主流安全协议的核心设计思想。通过在应用层叠加消息加密,即使传输层被突破,攻击者仍无法获取明文内容。

流程图大概如下:

graph LR A[客户端] -- 请求获取公钥 --> B[服务器] B -- 返回 RSA 公钥 --> A A -- 生成随机 AES 密钥 --> C[用服务器公钥加密该 AES 密钥] C -- 发送加密后的 AES 密钥 + AES 加密的数据 --> B B -- 用自己的 RSA 私钥解密出 AES 密钥 --> D[用 AES 密钥解密数据] D --> E[处理业务]

我们再用时序图画一下大概是这样一个思路, 这也是目前笔者见过的最多的API设计方案:

sequenceDiagram participant C as 客户端 participant S as 服务端 Note over C,S: 阶段一:获取服务端公钥(可选,若已预置则跳过) C->>S: 请求服务端公钥 S-->>C: 返回公钥(public_key) Note right of C: 本地缓存公钥 Note over C,S: 阶段二:发起安全API请求 C->>C: 生成随机对称密钥(session_key)
使用安全的随机数生成器 C->>C: 使用 session_key 对请求数据进行对称加密(AES)
Encrypted_Request = 加密(request_data, session_key) C->>C: 使用 public_key 对 session_key 进行非对称加密(RSA)
Encrypted_Key = 加密(session_key, public_key) C->>S: 发送加密请求
{ payload: Encrypted_Request, key: Encrypted_Key, nonce: Nonce } alt 解密成功 S->>S: 使用私钥解密 Encrypted_Key,得到 session_key S->>S: 使用 session_key 解密 Encrypted_Request,得到 request_data S->>S: 验证 Nonce,防止重放攻击 S->>S: 处理业务逻辑 else 解密失败 S-->>C: 返回错误响应(401 Unauthorized) end Note over S,C: 阶段三:返回安全响应 S->>S: 使用 session_key 对响应数据进行对称加密(AES)
Encrypted_Response = 加密(response_data, session_key) S-->>C: 返回加密响应
{ payload: Encrypted_Response, nonce: Nonce } C->>C: 使用 session_key 解密 Encrypted_Response,得到 response_data C->>C: 验证 Nonce,确认响应有效性

当我们引入上述这套加密传输之后,却还有一个问题没有得到解决: 防篡改 。这时候的 hash算法就派上了用场,比如 SM3、MD5、SHA256等。(MD5现在来看基本过时,尽量不要再使用)

一个比较著名的攻击方式叫做 Bit-flipping_attack, 读者可以在下面的两个链接里面查看到演示和具体的攻击原理。

还有一种自带校验的加密算法, 比如 AES-GCM,他可以有效避免上述攻击方式。

事实上,无论是支付宝(RSA2 = RSA + SHA256)、微信(AES-GCM)、还是银联,他们的支付API设计原理基本如此。以支付宝为例:

支付宝要求对接的的商户都要生成一个 密钥对 自己保存好私钥,公钥则上传至支付宝,与此同时,支付宝也会提供自己的支付宝公钥给商家(所有商家都一样)。

这样可以保证某个商家的私钥即使泄露,也可以定位是哪个商家的同时保证其他商家的数据安全,而签名用的sha256算法则是保证整个消息不会被恶意篡改。

前向安全:为什么每次通信都要"临时约个暗号"?

在前面的消息层加密方案中,我们使用服务器的 非对称加密算法(如RSA)的 公钥来加密会话密钥 session_key,听起来很安全。但如果你仔细想想,这里其实藏着一个隐患:

一旦长期密钥(比如服务器私钥)暴露,所有依赖它的历史会话都会被回溯破解------哪怕当时通信是加密的。

这就像你用同一把钥匙锁了 100 扇门,有一天钥匙丢了,那 100 扇门都得重装。

具体来说:如果攻击者长期录制你的 APP 所有 HTTPS 流量,并在未来某一天获取了服务器的私钥,那么他就可以用这把私钥,逐个解密过去所有请求中的 Encrypted_Key,还原出每一个 session_key,进而解密全部历史数据。

这种"未来泄露私钥 → 回溯破解历史通信"的风险,就是缺乏 前向安全性(Forward Secrecy)

为了解决这个问题,我们需要改变密钥的生成方式:让每一次通信的加密密钥,都由一次性的临时密钥"现场共同计算"出来,而不是靠服务器那个长期不变的私钥去解密得到。

在这个过程中,服务器的长期私钥只做一件小事:对它这次使用的临时公钥签个名,证明"这个临时公钥确实是我发的"。但它并不参与"计算会话密钥"这件事本身。

就像公司财务流程中,审批人只签字确认身份,但从不亲手碰钱一样。这种"分工明确"的设计,确保了即使未来这个长期私钥被泄露,也无法用来还原过去任何一次会话的密钥------因为那些密钥是由早已销毁的临时密钥算出来的,无迹可寻

这就是著名的 迪菲-赫尔曼密钥交换(Diffie-Hellman, DH) 的核心思想:

两个人可以在不安全的信道上,通过交换公开信息,共同计算出一个只有他们知道的秘密数,而第三方无法推导。

需要注意的是,这只是完成前向安全 的前提条件(解耦完成),更重要的我们需要把服务器的那个密钥也变成一次性的,否则攻击者仍有可能拿到泄漏的密钥重新演算曾经的数据,在2014年的一个特别出名的 heartbleed 就可能会导致服务器长期私钥泄露,有兴趣的读者可以看看。

所以我们需要把这个长期换成"一次性" 就保证了!这就是 DHE/ECDHE 之中的E(Ephemeral,临时)。事实上,这就是TLS v1.3和 TLS v1.2最重要的区别:

TLS v1.2 虽支持DHE和ECDHE等临时密钥交换方式,但这些是可选的。而TLS v1.3 则变成了强制。

彩蛋:深入高可用系统原理与设计一文有这么一个比较数据,可以看看性能:

思考题: 为什么我们不在自己的应用层去做这种DH协商重复造一次轮子呢?

高级认证:部署mTLS,双向验证身份

HTTPS 和消息层加密确保了数据在传输中的保密性,但它们主要验证服务器的身份 。如果服务器也需要确认 客户端的身份 ,该怎么办?这就引出了mTLS(双向TLS认证) ,一种双向身份验证机制,确保客户端和服务器在通信前都互相信任。类比现实场景,这就像双方在进入高安全区域前,都要出示身份证,确认对方是"可信之人"。

不过若抓包工具可以获取您的客户端证书,仍然可以做到抓包,比如 Proxyman 允许自定义证书(Custom Certificates)

对于这一刻,笔者经验甚少,不再赘述。

结语

我们今天聊的每一层防护------HTTPS、Pinning、消息加密、前向安全、mTLS------都不是孤立的银弹。它们像一道道防线,共同构成纵深防御体系:

  • 攻击者可以绕过 Pinning,但绕不过 mTLS
  • 可以录制流量,但解不开 ECDHE 的临时密钥
  • 可以爆破接口,但挡不住服务端的限流与签名验证

真正的安全,不是"加个 HTTPS 就高枕无忧",而是让攻击者每前进一步,都要付出巨大的时间、技术和经济成本。

正如我在前文《前端攻防:揭秘Chrome DevTools与反调试的博弈》中提到的 JS 混淆、反调试等手段,其本质也是在增加攻击者的逆向成本。安全不是功能,而是一种设计哲学:在每一个环节设置障碍,让非法访问变得"不值得"。

作为开发者,不妨定期用自己的抓包工具 "攻击" 一下自己的系统,不断提升和改进这些你的系统。

最后,感谢你耐心的读完这个系列。

相关推荐
言之。2 小时前
Django REST Framework响应类Response详解
后端·python·django
Abadbeginning2 小时前
FastSoyAdmin centos7云服务器+宝塔部署
vue.js·后端·python
xuejianxinokok3 小时前
PostgreSQL 18 新功能:虚拟生成列
数据库·后端
未来影子3 小时前
SpringAI(GA):Neo4j向量数据库存储快速上手
后端
武子康3 小时前
大数据-95 Spark 集群 SparkSQL Action与Transformation操作 详细解释与测试案例
大数据·后端·spark
知其然亦知其所以然3 小时前
MySQL8.x 面试高频题:为什么一定要有主键?99%的人答不全
后端·mysql·面试
FE_C_P小麦3 小时前
Git 常用指令
前端·后端·github
某某祺3 小时前
向量存储、检索及 Qdrant 浅析
后端