如何从开发和对抗的角度去思考web网页中的接口逆向。
文章目录
前言
今天突发奇想,做了一些web接口的逆向(用于信息聚合,带个人token,不做爬虫),于是在这里简单分享下,如何从开发和对抗的角度去思考web网页中的接口逆向。
- 本文只做经验分享,不公开测试逆向的网站和逆向后的结果。
- 本文只讲一下思路,并做一些简单测试。
1.从开发和对抗的角度思考接口逆向
开始本文前,先进行一个简单的科普,什么叫接口逆向?(针对B/S架构)
1.1 什么是接口逆向
现如今B/S架构的开发方式普遍是前后端分离,开发流程为:
- 服务端提供能通过互联网访问的接口。
- 前端使用Vue/React等框架进行开发,需要真实数据的时候从服务端提供的接口请求。
- 开发完后使用webpack等工具编译JS代码。
对于一个前后端分离的网站来说,当我们随便打开一个网站,并使用F12去查看时,会发现:
- 网络中会有真实数据的HTTP请求。
- 前端源码一般看不懂。
这个时候,当我们去查看具体的请求,就能看到这个请求的构造,例如,我们可以查看下leetcode首页的一个接口:
你通过这种方式就能看到接口的url、请求体、返回体,一但获得了这三个信息,你就可以自己写一个程序去调用这些接口,获取他们的数据了,当然前提是你得搞定登录和授权。
你获得了这些接口,可以干很多事情:
- 做爬虫爬取大网站的数据。
- 写自动化脚本去进行某些获利的操作,比如抢购、自动签到等。
- 从接口的返回体中发现没有在页面上显示出来的信息。
- 发现接口的水平/垂直越权漏洞。
这些事情都是对网站所有者不利的,所以它自然不想你能这么简单的拿到接口的使用方式。
它就会有一些策略来对抗你:
- 请求接口的时候,对参数进行某种方式的签名,一旦你得签名不对,就直接请求失败。
- 接口返回的时候,对返回的结果加密,怎么解密只有前端知道。
例如:
这些策略其实就是隐藏接口的调用信息,防止你干坏事。
但是,所有这些加密、签名操作都会在前端运行一遍,也就是跑在你的电脑里面,理论上你是一定能分析出它的代码逻辑的,不过在真实场景往往会有各种各样的反制手段组织你去干这件事情,干这件事情的过程,就是逆向,在这个场景里叫接口逆向。接下来,我们可以从开发者的角度来思考下,怎么做这件事情以及如何防护。
1.2 开发的角度思考如何开发策略
假设我是一个全栈开发者,现在我想将我网站的接口的关键信息隐藏起来,让别人不能这么轻易的获得,我应该怎么办?
- 制定一套加密/签名策略,对请求的参数、返回值,甚至是url中的关键内容,前端后端同时执行这套策略。
- 这样只有服务端接受到指定前端发送的请求才知道怎么处理,而只有指定前端才能理解服务端接口返回的内容是什么含义。
这个时候,就会涉及到一些细节问题了,我使用什么样的加密策略,什么样的签名策略?
- 自己去实现一个吗?这些密码学相关的算法非常的严谨,自己实现的很难做到相应的安全保障,且效率方面也会比成熟的库低不少。
- 使用开源的一些密码算法库进行组合,多次套娃。密码算法库一般涉及的运算多,如果套娃过多,特别是使用到了公钥体系中的一些算法,可能会影响接口的性能。而且如果多个算法使用到的密钥不一样还难维护。
做为一个对安全知识有限的开发者来说,最简单的办法是:
- 制定一套简单的规则将所有参数和url拼接起来签一个名,签名的私钥写死在前端,服务端收到请求后,先使用公钥验证签名是否正确再进行后续的处理。
- 服务端对要返回的返回值使用AES进行加密,使用到的初始向量和私钥同样写死在前端。
简单总结就是:
- 使用成熟的密码算法库。
- 前端写死密码算法中会使用到的密钥。
- 不做其他的安全防护。
假设这个版本是v1。
1.3 对抗的角度思考遇到的问题
现在我是一个攻击者,攻击的目标是上述的v1,想要得到这个的具体信息,我该怎么做?
- 对前端请求的部分打上断点,调试参数生成签名的逻辑,返回数据的处理逻辑。
- 搜索成熟的密码算法库的特征。
- 搜索加解密、签名、密钥等关键字。
- 分析请求和这些特征的交互逻辑。
对于v1版本来说,上这几种手段基本就能分析出前端具体的处理逻辑了,而且大概率能看到调用某种成熟算法库进行加解密、签名的操作,这个时候,如果想使用接口,直接使用这种算法其他语言的替代库即可。
这个时候,作为开发者的我有点不开心了,因为你如此简单的就获得了我的接口,导致我的防护措施没起到作用。我也该做出反制了:
- 调试是吧?我直接无限debugger,让你无法正常的调试。
- 搜索密码算法库的特征是吧?我自己实现一个高效率的库,让你没办法找特征。
- 直接搜索密钥是吧?我自己写一个生成密钥的算法,让你根本看不到明文的密钥。
- 分析代码逻辑是吧?我直接加混淆加vm,你面对一堆乱七八糟的js代码分析去吧。
我花费了大量的成本,此时做出了v2版本,这个版本对于99%的正常开发者来说,已经很难去逆向了。对于能逆向的人来说,需要花费大量的时间去分析,就算能逆出来,也不敢大规模的使用或公开,因为一旦被发现,我再把这些策略改一改,又是需要花费许多的时间在上面了。
如果不是拿到这个接口真的能获得巨大的利益,开发者也不会花费这么多成本去防护,攻击者也不会花费这么多成本去攻击。二者的对抗中,本质上就是资源的博弈。
1.4 正常情况下开发者如何防护
- 不必对所有的接口都采用防护策略,有些接口可以放给有需要的人调用。
- 可以在客户端侧的关键业务做一验证码防护,例如登录、签到等业务。
- 可以在服务侧做一些防护的策略,比如限流、特定ip高频访问重点观察。
- 做一些动态的防护策略,例如在做某种大抢购业务时,更换前端和服务端共享的密钥等。
1.5 正常情况攻击者如何做?
二者的对抗本质是资源的博弈,如果ROI够高,也是可以考虑如下策略的。
- 多个逆向人员分工逆向特定的部分。
- 使用不同账户,不同的代理节点进行访问。
- 使用机器学习算法对抗人类验证。
1.6 对抗中的胜者
既然是资源的博弈,谁花的资源少,但是收获大,才是真正的胜者。
比如:
- 开发者在客户端随意引用很多无用的加解密流程,导致攻击者花费了大量的时间去踩坑。
- 攻击者很简单的就发现了防护策略的弱点。
2.某个平台接口逆向案例
接上面那个对返回结果加密的例子,尝试做一次简单的逆向。
先看接口:
纯get请求,没有请求参数,看返回体:
返回体一堆字符,看其特征像base64编码,尝试解码:
发现是乱码,说明是字节编码而来,考虑是明文进行某种加密后再使用base64编码,调试接口的请求部分:
单步调试:
发现接口中有加密相关的布尔值,说明这个接口的内容是加密的,同时指定了parseResponse中的解密算法为i.Decrypt。全局搜索Decrypt,找到Decrypt函数的定义。
仔细查看代码,其实解密函数就是一个AES的CBC模式,并且IV和密钥都是明文在源码中。只需要写个简单的python脚本就能解密返回体。
from Crypto.Cipher import AES
import base64
# 将 JS 代码中 l 和 i 分别转换为 Python 对应的 key 和 iv
key = "63ca0d3f90f844928d236e132a1fee45".encode('utf-8')
iv = bytes.fromhex("00") * 16
# 假设密文为 Base64 编码的字符串,请替换为实际的加密数据
encrypted_base64 = "oUVOya5NG43FFSaV8gojbexUwU/RWNL7s/Ow***"
ciphertext = base64.b64decode(encrypted_base64)
# 创建 AES 解密器,注意这里使用 CBC 模式
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext_bytes = cipher.decrypt(ciphertext)
# 去除 PKCS7 填充
padding_length = plaintext_bytes[-1]
plaintext_bytes = plaintext_bytes[:-padding_length]
plaintext = plaintext_bytes.decode('utf-8')
print("解密后的明文:", plaintext)
结果为:
可以看到是一个标准的json格式,到底这个接口的防护策略就被我们突破了。
ATFWUS 2025-03-05