渗透测试高级技巧(二):对抗前端动态密钥与非对称加密防护

在前文的技术分享中,我们描述了验签和静态对称加密(静态密钥 AES)的常见场景,大家我相信遇到类似的加解密清醒,基本都可以通过热加载的基本使用获得破解前端加密解密的方法,达到一个比较好的测试状态。

在本文中,我们在保持同样的通用适配度的同时,将会来接触更加复杂的前端加密与解密场景:

动态密钥:无法通过简单的阅读 JavaScript 直接获取加密密钥

非对称加密技术:使用 RSA / SM2 技术来加密通信数据以避免明文传输

案例:获取密钥和提交表单不在同一个请求中
这个场景可能初步听起来略微抽象,但是实际上却非常常见,我们使用基础的时序图将会很容易理解这个案例:我们首先请求/get-key接口获取一个密钥,然后开始用获取到的 KEY 加密表单,在 POST 中提交加密后的数据(可能并不携带密钥),服务器端可以通过服务器的密钥来解密数据

1698823491_6541fd433b50904ea6b1e.png!small?1698823491746

一般来说,这种加密方式并不是一个非常冷门的方式,反而很多网站会使用这种技术来防护自己的登录信息不会泄漏。为此,我们基于实际网站的前端加密混淆方式构建了一个非常经典的案例,来帮助大家掌握这些测试技术。我们在 Vulinbox 中可以获取到关于这个部分的靶场测试环境:

1698823507_6541fd532fcfde54b8a78.png!small?1698823507862

首先打开网站,抓包看一下通信流程发现网站请求完资源后会去加载一套证书:

1698823517_6541fd5d9e86cb01b30f4.png!small?1698823518152

因此我们需要去寻找 JavaScript 中的加密线索:

1698823527_6541fd678c7d00cbf6de5.png!small?1698823528314

那么基于此,加密的基本脉络就很清楚了,我们需要先发一个请求去服务端获取私钥用来解密签名的数据(来自于服务器),获取公钥来签名需要发送到后端的数据。但是实际上这还不够,我们知道有一些基本资料:

RSA-OAEP的最大加密长度取决于RSA的密钥长度和OAEP填充的长度。OAEP填充需要额外的空间,通常是两倍的哈希长度加上2(对于SHA-1和SHA-256,这将分别是42和66字节)。因此,对于一个n位的RSA密钥,最大的明文长度将是n/8减去OAEP填充的长度。

我们并不能直接使用 OAEP 去加密大段的数据,那这种情况下,我们知道 AES KEY 一般来说并不会太大,所以使用“混合加密(RSA-OAEP + AES)” 实际上是比较好的解决方案,否则我们就需要把大量数据进行切片传输,这实际上非常不符合常理。

那么我们继续从 JavaScript 中寻找线索:

1698823552_6541fd80b0bf694d4f1d9.png!small?1698823553612

捋清思路,准备动手
1697177464_6528df780aa558f22175c.png!small?1697177464022

在这个超复杂情况中,我们发现实际执行的时候,需要两个请求,这个操作恰好符合我们 Web Fuzzer 的请求序列的定义。

请求一:先执行GET /crypto/js/rsa/generator获取签名验证用的 RSA 密钥

使用数据提取功能,提取请求一中的公钥的 PublicKey,用来加密 KEY 和 IV

使用 AES 加密数据,并把加密后的 KEY 和 IV 一起发送到服务器

实际我们发现,这个结果处理起来也并不复杂,随机生成 KEY 和 IV 可以写死,这样用它加密真实数据即可,这个过程和我们在《渗透测试高级技巧(一)》中的操作非常类似,我们可以在热加载中很容易处理。

实际对我们来说,最大的工作量是把 KEY 和 IV 使用 RSA-OAEP 加密好,传输给服务器。这个步骤在 yaklang 中可以轻易使用codec.RSAEncryptWithOAEP(pemPublic, data)这个函数做到,因此我们的 Yaklang 热加载代码可以写成:

aesKey = “aaaaaaaaaaaaaaaa”
iv = “aaaaaaaaaaaa”
aesGCM = data => {
return codec.EncodeBase64(codec.AESGCMEncryptWithNonceSize12(aesKey, data, iv)~)
}
rsaOAEP_key = (pem) => {
return codec.EncodeBase64(codec.RSAEncryptWithOAEP(pem, aesKey)~)
}
rsaOAEP_iv = (pem) => {
return codec.EncodeBase64(codec.RSAEncryptWithOAEP(pem, iv)~)
}
对应的,我们的使用标签函数就可以是{{yak(rsaOAEP_key|{{params(publicKey)}})和{{yak(rsaOAEP_iv|{{params(publicKey)}})}},如果使用加密数据,而且我们的 AES KEY 是硬编码的,可以直接写成{{yak(aesGCM|…)}}`

到现在,基本核心功能有了非常好的实现,我们可以设置热加载代码并且完成我们的 Web Fuzzer 序列了。

首先我们在序列一中设置好数据提取器,数据提取器来提取服务端的 publicKey

请求一:获取签名(加密)用的公钥
1698823664_6541fdf0354007272c471.png!small?1698823664853

我们在第一个请求中,使用“数据提取器”添加一个配置项目,设置JQ(*)提取数据类型,范围设置成“响应体”,把提取字段名设置为publicKey,这样我们在后续的请求中,可以用{{param(publicKey)}}安全引用上一个请求中提取到的变量。

请求二:继承请求一中的密钥,加密请求中参数
1698825004_6542032c2f8d5f2bab3e8.png!small?1698825004662

我们在实际的发送的时候,发现 AES 密钥和 IV 可以硬编码,因此根据原数据包,我们把 JSON 中的关键字符串使用热加载 fuzztag 替换掉,形成如下效果:

{
“data”:“{{yak(aesGCM|abc)}}”,
“iv”:“aaaaaaaaaaaa”,
“encryptedIV”:“{{yak(rsaOAEP_iv|{{param(publicKey)}})}}”,
“encryptedKey”:“{{yak(rsaOAEP_key|{{param(publicKey)}})}}”
}
上述 Payload 中对应的热加载代码为:

aesKey = “aaaaaaaaaaaaaaaa”
iv = “aaaaaaaaaaaa”
aesGCM = data => {
return codec.EncodeBase64(codec.AESGCMEncryptWithNonceSize12(aesKey, data, iv)~)
}
rsaOAEP_key = (pem) => {
return codec.EncodeBase64(codec.RSAEncryptWithOAEP(pem, aesKey)~)
}
rsaOAEP_iv = (pem) => {
return codec.EncodeBase64(codec.RSAEncryptWithOAEP(pem, iv)~)
}

实际上就是把加密内容进行 Base64 写回原来数据包,并且 aes 和 iv 为了方便测试,已经写好了!

Wait a Minute,事情还没结束!
1698825130_654203aa79ec12e93d356.png!small?1698825131232

经过上述的操作,我们请求确实发送出去了,并且得到了正确的回应,但是我们似乎遇到了更大的问题:我们居然没有办法直接根据响应内容判断我们到底登陆成功了没有。

那么如何判断登陆成功了?我们发现服务端返回的信息中,包含了encryptedIV还有encryptedKey还有 data,大致直接可以推断出,返回的信息也被加密了。这也是一个非常棘手的问题

我们想要解决这种问题,那就需要找一个地方可以获取到相应的信息,并且可以加密解密。这里就需要进入我们本地技巧集的第二部分。

难点:解决 HTTP 响应加密问题
思路一:继续利用 Web Fuzzer 序列,添加第三个请求,处理第二个请求中响应的信息。

思路二:通过热加载afterRequest或mirrorHTTPFlow来编写代码解密响应中的信息

我们有了思路之后,可以尝试动手来做

使用 Fuzzer 序列来解密响应
类似 publicKey 的使用,我们可以在“请求一”中添加privateKey的提取配置,使用.privateKeyJQ 来提取初始化中的响应信息

1698825160_654203c8f11df56d85bb9.png!small?1698825161356

在第二个响应中,我们需要获取到响应的加密信息来解密响应中的数据,从而判断请求提交的真实结果,因此我们需要在请求二中设置提取encryptedIV / encryptedKey / data三个变量,通过数据提取器的配置:

1698825440_654204e0842986a0b04e0.png!small?1698825440898

1698825447_654204e7f28ebc7f05c54.png!small?1698825448379

可以很容易配置成功,接下来,通过第三个请求中使用热加载标签,执行这两个函数即可马上解密 RSA-OAEP 的内容。

oaepDec = (pem,data) => {
return string(codec.RSADecryptWithOAEP(pem, codec.DecodeBase64(data)))
}
dec = (key,data,iv) => {
dump(key, data, iv)
return string(codec.AESGCMDecryptWithNonceSize12(key, codec.DecodeBase64(data)~, iv)~)
}
相对来说,oaepDec 直接使用 PEM 和 Data 可以解密 KEY,和 IV,然后再使用 key 和 iv 去解密 data 中的内容最终就可以实现对响应的解密。

1698825482_6542050ad41968c9ac5eb.png!small?1698825483473

可以,非常棒,接下来我们就可以直接修改用户名和密码来进行爆破了对吧?利用这种特性,我们在第二个请求中,需要对用户名和密码进行爆破:

1698825503_6542051f081c3c2d993fb.png!small?1698825503593

在第二个请求的 web fuzzer 配置中,增加设置变量 user 和 pass,然后这两个变量实际上数据被加密,并且能把 encryptedIV 和 encryptedKey 设置正确。

我们配置 user 为admin|root配置 pass 为admin|666666|88888888|admin123|123456,实现了用户名和密码的爆破,总共应该有 2 * 5 个结果,点击序列执行查看效果:

1698825519_6542052f7cf461d93f74a.png!small?1698825519894

通过这种操作,我们实现了简单的“爆破功能”。真实情况是,这个场景已经非常复杂了,基本上属于“最严密防护”的那一类,在这种严密防护下,实际开发的过程也会非常艰辛,当然我们也很难实际真的遇到这种情况。但是如果这种情况我们可以解决,绝大多数弱于这种防护的策略我们都可以轻松突破。

优化流程:更好地处理响应中加密的部分
很多时候,响应的数据其实我们需要提炼一下信息或者认真处理一下加密的信息,不能总是使用序列来解决吧?那我们如何在一个 web fuzzer 流程中,处理好响应中的加密数据呢?其实并不是不可以解决的

Yaklang 的最新版本 1.2.7 及以上的版本,设置了一个热加载中的新 hook 函数帮助大家解决:

mirrorHTTPFlow可以给大家提供一种 “编程提炼 Web Fuzzer 请求和响应数据” 的新路径:

具体定义如下:

mirrorHTTPFlow = (reqBytes, rspBytes, params) => {
return {“key”: “value”}
}

// 使用案例如下
// 如果响应中包含 “Success”,就在提取的变量中输出一个“登陆”:“成功”的变量
mirrorHTTPFlow = (req, rsp, params) => {
data = {}
if string(rsp).Contains(“Success”) {
data[“登陆”] = “成功”
}
return data
}
我们可以在热加载中添加这个函数,来获取上下文,甚至执行对请求响应的解密。

所以我们可以整理一下,在上一个解决方案中,还可以做的更简单,直接使用热加载调制一个函数来实现加密解密。

mirrorHTTPFlow = (req, rsp, params) => {
// 获取私钥以解密响应数据
pem = params.privateKey

// 切割响应中的数据,作为 JSON 加载
_, body = poc.Split(rsp)
body = json.loads(body)

// 获取响应中的加密部分,data 为密文
// IV 和 KEY 分别是 AES-GCM 的加密密钥和 IV
data = body.data
ivEnc = body.encryptedIV
keyEnc = body.encryptedKey

obj = {}
// 使用 RSA-OAEP 解密 IV 和 KEY
iv = codec.RSADecryptWithOAEP(pem, codec.DecodeBase64(ivEnc))
key = codec.RSADecryptWithOAEP(pem, codec.DecodeBase64(keyEnc))

// 使用 AES-GCM 解密
obj[“data”] = codec.AESGCMDecryptWithNonceSize12(key, codec.DecodeBase64(data)~, iv)~
return obj
}
1698828419_654210832046bc895226a.png!small?1698828419824

通过我们新增加的mirrorHTTPFlow函数,我们同时处理请求,响应和序列变量三个部分,实现了对流量的解密,并且很方便的识别出我们的爆破到底成功没有。

总结
在本篇“高级技巧”中,我们明显和《渗透测试高级技巧(一)》中的验签有了非常明显的区分,我们大量使用了 Web Fuzzer 序列来实现有上下文关联的部分。并且在这个案例中,因为响应的加密,导致区分“爆破”结果变得十分困难,我们也给出了一些处理技巧和实际方案。

当然本篇内容的靶场和使用案例也并不是空想,而是来源于真实案例的总结和抽象。在实际使用中,加密算法和接口名称与当前案例略有差别,但是笔者相信,在完整实现过这个流程之后,你一定已经对 Web Fuzzer 序列的使用更加得心应手了。

CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/205558.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

定时器的使用

目录 前言 正文 1.方法 schedule(TimerTask task, Date time) 的测试 (1)执行任务的时间晚于当前时间(在未来执行)的效果 (2)线程TimerThread不销毁的原因 (3)使用 public void cancel() 方法实现 T…

阿里云高效计划学生和老师免费代金券申请认证方法

阿里云高校计划学生和教师均可参与,完成学生认证和教师验证后学生可以免费领取300元无门槛代金券和3折优惠折扣,适用于云服务器等全量公共云产品,订单原价金额封顶5000元/年,阿里云百科aliyunbaike.com分享阿里云高校计划入口及学…

不是说人工智能是风口吗,那为什么工作还那么难找?

最近确实有很多媒体、机构渲染人工智能可以拿高薪,这在行业内也是事实,但前提是你有足够的竞争力,真的懂人工智能。 首先,人工智能岗位技能要求高,人工智能是一个涵盖了多个学科领域的综合性学科,包括数学、…

LiteOS同步实验(实现生产者-消费者问题)

效果如下图: 给大家解释一下上述效果:在左侧(顶格)的是生产者(Producer);在右侧(空格)的是消费者(Consumer)。生产者有1个,代号为“0”…

Scala如何写一个通用的游戏数据爬虫程序

以前想要获取一些网站数据的时候,都是通过人工手动复制粘贴,这样的效率及其低下。数据少无所谓,如果需要采集大量数据,手动就显得乏力了。半夜睡不着,爬起来写一段有关游戏商品数据的爬虫通用模板,希望能帮…

Git——感谢尚硅谷官方文档

Git——尚硅谷学习笔记 第1章 Git 概述1.1 何为版本控制1.2 为什么需要版本控制1.3 版本控制工具1.4 Git 简史1.5 Git 工作机制1.6 Git 和代码托管中心 第2章 Git 安装第 3 章 Git 常用命令3.1 设置用户签名3.2 初始化本地库3.3 查看本地库状态3.4 添加暂存区3.4.1 将工作区的文…

JavaDS —— 初识集合框架 + 时间/空间复杂度

目录 1. 初识集合框架 1.1 集合框架的初识 1.2 什么是数据结构? 2. 时间与空间复杂度 2.1 时间复杂度 2.2 大O的渐进表示法 2.3 常见时间复杂度计算举例 2.4 空间复杂度 1. 初识集合框架 1.1 集合框架的初识 什么叫集合?什么叫框架?什么又叫集…

React函数组件渲染两次

渲染两次是因为react默认开启了严格模式 React.StrictMode标签作用: 1、识别不安全的生命周期 2、关于使用过时字符串 ref API 的警告 3、关于使用废弃的 findDOMNode 方法的警告 4、检测意外的副作用 5、检测过时的 context API 注释掉React.StrictMode即为关闭严…

Vue3实现粒子动态背景

官网: https://particles.js.org/ npm: https://www.npmjs.com/package/particles.vue3 安装 pnpm add particles.vue3 pnpm add tsparticles-slim 注册 main.js import { createApp } from vue import type { App } from vue import globleApp f…

Spark---转换算子、行动算子、持久化算子

一、转换算子和行动算子 1、Transformations转换算子 1)、概念 Transformations类算子是一类算子(函数)叫做转换算子,如map、flatMap、reduceByKey等。Transformations算子是延迟执行,也叫懒加载执行。 2)、Transf…

Java Stream中的API你都用过了吗?

公众号「架构成长指南」,专注于生产实践、云原生、分布式系统、大数据技术分享。 在本教程中,您将通过大量示例来学习 Java 8 Stream API。 Java 在 Java 8 中提供了一个新的附加包,称为 java.util.stream。该包由类、接口和枚举组成&#x…

Maven中常用命令以及idea中使用maven指南

文章目录 Maven 常用命令compiletestcleanpackageinstallMaven 指令的生命周期maven 的概念模型 idea 开发maven 项目idea 的maven 配置idea 中创建一个maven 的web 工程在pom.xml 文件添加坐标坐标的来源方式依赖范围编写servlet maven 工程运行调试 Maven 常用命令 compile …