浅谈安全测试中绕过签名校验和参数加密的几种思路
日常的安全测试中常会遇到 Web 系统或者 APP 发出的 HTTP 请求进行签名校验或参数二次加密的限制,这意味着我们无法直接通过中间人代理工具篡改请求数据的内容来达到重放的效果。
在遇到以上情况时,笔者常采用的一些解决思路:
- 逆向签名/加密算法,对 HTTP 请求写前置脚本在发送前进行重签名和重加密。
- 使用自动化测试框架 Selenium、Appium 等模拟输入,输入数据由代理中转。
- 使用 Xposed、Frida 挂钩框架,寻找加签、加密方法后主动调用
这里暂且将用户使用的 APP 或者 Web 系统都归为客户端,二者的常用开发语言分别 JavaScript/Java/Objective-c/cpp等,其中对于安全意识薄弱的开发者,在没有使用任何安全策略的情况下,寻找签名及加密的算法还是非常简单的。
JavaScript 客户端
具有签名校验和参数加密的常见请求方式如下:
Case 1: 请求参数处签名校验
GET /info?user_id=001?signature=12c3f597d3e873fc7b46cb754e51ed9766d8a769ae0699f35738eee1a69e0399 HTTP/1.1
Case 2: 请求头处签名校验
GET /info?user_id=001 HTTP/1.1
Sign: DkzMTBjMTE4NgTY4NTllMjU4OTM0ZGNmN2IzYjdmYTQyMDY2NThjMjJlNDJiMWMwN2IyMDVlODkzMTBjMTE4Ng==
Case 3: 请求参数密码加密
如何快速找到加签和加密方法呢?大概分为以下三步:
- 环境准备:将前端代码下载到本地调试,或者直接使用浏览器的开发人员工具在线调试
- 静态分析:对于常规无混淆的 JS 文件可以通读(PS:注意是否存在 SourceMap),或者只分析关键加密及签名,使用全局搜索定位关键参数如:sign、password,定位常见加密方法:encrypt、aes 等等
- 动态调试:下断点后单步调试查看调用栈、局部变量等,其中下断点的位置可以通过代码处、xhr、fetch、DOM、Event Listener、Exception
第一个小栗子:
通过搜索关键字定位到加密方法,其代码如下:
function login() {
var pwd = $("#password").val();
if (pwd != '') {
try {
var key = CryptoJS.enc.Utf8.parse(g_pwdKey);
var iv = CryptoJS.enc.Utf8.parse(g_pwdKey);
pwd = CryptoJS.enc.Utf8.parse(pwd);
pwd = CryptoJS.AES.encrypt(pwd, key, {
iv: iv, mode:CryptoJS.mode.C
//填充模式
BC, padding: CryptoJS.pad.Pkcs7
}
);
} catch (e) {
alert("加密失败");
}
//其中 key iv 通过接口获取且值固定不变的。
function initPwdKey() {
$.ajax( {
type: 'GET', url: '/sv/fds/getpwdkey', success: function (pwdKey) {
g_pwdKey = pwdKey;
}
}
);
}
第二个小栗子:
通过 network 面板查看请求的调用栈,进去下断点,签名算法一览无余:
Android 客户端
level 0 的级别莫过于没有加壳、代码无混淆、无调试检测、无 Hook 框架检测的 APK
第一个小栗子:
HTTP 请求的响应包加密
将 APK 直接拉入 JADX 中,搜索敏感字符即可定位到加密方法和密钥位置
根据加密逻辑编写解密脚本
level 1 的级别 apk 已加壳、代码混淆,但是没有进行 Hook 框架和反调试检测
关于 Hook 框架可以选择 Xposed、Frida 等,在此衍生出更多基于 Hook 框架的工具(脚本),大大提高了安全测试的效率。
- 基于 Xposed 的 Inspeckage、Xserver
- 基于 Frida 的 Objection、Brida
如果 APP 开发者使用标准库提供的加解密方法,Inspeckage 可直接打印出详细信息(明文、密文、密钥、加密类型
利用 Frida 加载 Hook 常见加解密算法的脚本
利用 Objection 在无需反编译应用包下获取签名密钥
PS: 无需反编译是指我们可以 trace 敏感关键字的类如:util、crypt,输入明文后搜索对应的函数调用,查看堆栈信息获取上游函数。这部分会比较侧重于 Android 应用安全,之后会整理一篇新的文章来介绍。
iOS 客户端
使用 Objection Hook 获取签名密钥的方法,打印参数和返回值即可
使用 Objection Hook 参数加密的算法和密钥,打印明文和密文信息
在初步解决上述问题后,接下来面临的一个问题就是我们如何尽量使其自动化。对密文手动解密后修改参数值后再加密,签名同理,这样一套下来无疑浪费了不少时间,且效率低下。
考虑这样一个登录场景,无验证码校验和请求次数限制,但存在对参数和时间戳的签名校验。
首先,我们可能先尝试下暴力破解这种测试方法,通过前端代码分析和调试后梳理出签名算法流程.
其次,编写简易的测试脚本。(仅起到验证该问题效果
最后,分析下这样的测试方法有哪些局限性,抛开脚本中没有引入外部账号密码字典,测试的漏洞类型也非常单一,若是登录处存在注入,岂不是也得引入一份测试 SQLi 注入的字典?那万一还会出现 XSS、RCE 等情况呢
到这里大家应该都想到比较通用的办法了,针对这种情况直接在中间人代理工具那一层做重签名的操作不就好了吗?
比如给常用的 BurpSuite 写一个插件,关于编写 BurpSuite 的插件的文章挺多的,若有不熟悉的同学自行谷歌。下面分享的是另一个中间人代理工具在安全测试中的应用
mitmproxy 是一款基于 Python 对 HTTP(s)、Websocket(s) 抓包的中间人代理工具,支持自定义脚本、透明代理模式等
BurpSuite 配置上游代理界面
其余 Sqlmap、Xray 等配置代理方式自行查看工具的帮助文件
举例一个使用 mitmproxy 联合 Xray 自动化绕过签名校验并检测漏洞的小栗子
以上思路的核心思想是保证扫描器发出的请求包均利用中间代理完成了重签名操作,且返回到扫描器的响应包也是可以识别的。
实现这种方法的前提是攻击者分析出了加密加签的算法逻辑,然后去重写实现或主动调用。
在移动安全领域中,厂商为了防止攻击者去逆向得到核心代码逻辑,往往会采用加壳、代码混淆、反调试检测等手段来给攻击者带来困难。
Web 应用领域中也会采用同种类型的手段,比如对 JavaScript 脚本进行混淆,比如注入无意义的变量、各种编码无用的逻辑代码,加密、插入反调试代码检测等等
面对下面这种短时间内无法还原签名算法的情况,如何处理呢?
用作演示的一个 Demo 站点 :P
-
https://docs.mitmproxy.org/stable/
-
https://frida.re/docs/home/
-
https://github.com/ac-pm/Inspeckage
-
https://github.com/sensepost/objection/wiki