【web逆向】全报文加密流量的去加密测试方案

aHR0cHM6Ly90ZGx6LmNjYi5jb20vIy9sb2dpbg 国密混合

WEB JS逆向篇

先看报文:请求和响应都是全加密,这种情况就不像参数加密可以方便全文搜索定位加密代码,但因为前端必须解密响应的密文,因此万能的方法就是搜索拦截器,从第一行下断点分析,以找到加密的位置。

在这里插入图片描述

通常vue前端会使用axios配置拦截器,如下图,在搜索到的api.js的134、187行下断点,然后任意请求即可。

在这里插入图片描述

实际操作的时候断点建议打在第一个箭头函数和第二个箭头函数的第一行,避免因参数差异越过需要分析的逻辑。

在这里插入图片描述

做一下简单审计和判断,由于参数保存在config里,所以重点关注对config操作的代码,步入173行的函数进一步分析。

在这里插入图片描述

84、93行和96行看到加密方法了,分别是在post和get情况下对参数加密处理的逻辑。调试时由于是post方法,故步入84行函数调用。
在这里插入图片描述

步入该方法才实际调用加密函数本身,对应的猜测29行是解密函数,顺便下个断点。
在这里插入图片描述

该金融公司用的名为microAppSafety的js文件是个加解密库,且做了如上形式的混淆(本例涉及国密,该公司其它站点下使用类似文件名的js加解密库或均采用了国密)
混淆严重影响了源码的分析和阅读,但由于目的不是了解其加密逻辑的具体实现,到此该节就结束了,刚才解密的断点待服务器响应后也会停住,同理分析即可。
后记:该站点实际于测试环境进行,与生产环境的代码略有不同(源码形式不同,非代码实现不同),起初分析的时候由于种种原因都没有找到加密的函数,但在生产环境下找到,反推测试环境:找到的加密函数的关键字"encryptData"在测试环境中同文件里搜索并下断点,才得以成功完成测试环境的分析。

加密/验签策略定位技巧总结

前端加解密通常涉及两个开源库:CryptoJS和JSEncrypt,两个库的代码分别形如:

//CryptoJS
var wordArray = CryptoJS.enc.Utf8.parse('𤭢');
var utf8  = CryptoJS.enc.Utf8.stringify(wordArray);//JSEncrypt
var crypt = new JSEncrypt();
crypt.setKey(__YOUR_OPENSSL_PRIVATE_OR_PUBLIC_KEY__); 
var text = 'test';
var enc = crypt.encrypt(text);

单个参数加密

全局搜索加密的参数名,并跟踪从取值到请求过程值的变化(处理)
加密位置常见于:

  1. 赋值处(从组件取值时)
var pwd = $('#pwd').val();
var pwd = encrypt(pwd);
  1. 拦截器处理当中和ajax之前;
    在这里插入图片描述

全报文加密

  1. 全局搜索interceptors.request.use并对其下断点单步分析
  2. 调用栈分析
    无法确认哪一帧调用的加密时,从较早的帧开始分析传入的参数是明文还是密文,并在数据为密文的第一帧的上一帧下断点,基本就可以分析处加密的位置了。
    2.1. 从“网络“(浏览器开发者工具栏)中定位某个全报文加密的XHR请求(图示使用firefox,chrome中在启动器一栏中查看调用栈)
    在这里插入图片描述

2.2. 对全报文加密的请求设置XHR断点,查看调用栈

加密形式判断

  1. 数字信封:随机数生成对称密钥加密数据(一次一密)-》公钥加密对称密钥-》传输:
    两个值:jsonData,key或一个值:一个公钥加密的值包含对称密钥和数据密文
  2. 单一形式的加密
    仅使用对称或非对称密码进行加密,复杂加密机制的通常会有密钥协商(交换)的过程或其他一些密钥安全的机制,如:每个请求前先请求getPubkey获取公钥(非对称)、使用两对密钥,前端硬编码用于加密的公钥A和用于解密的私钥B,后端使用私钥A解密,使用公钥B加密,(非对称,因此前端如果发现PRIVATE_KEY和PUBLIC_KEY实际是两对密钥)等;简单加密机制通常可以直接在js中找到硬编码的key(对称,利用burp插件就可以直接完成自动化加解密)
    2.1. 对称加密:aes/des/3des/国密(如常见sm4)-》传输
    全部参数名:仅对某个值加密或全报文
    2.2. 非对称密钥:RSA/国密(常见sm2)-》传输
    全部参数名:仅对某个值加密或全报文

签名机制分析定位

全局搜索保存签名的字段(通常包含sign)

  1. url参数签名+签名参数如:
parm=1&sign=md5(parm=1)
  1. 参数值签名+签名参数如:
pwd=a&name=b&sign=md5(a|b)
  1. 请求头如
    x-passwd:123
    x-timestamp:169
    x-sign:md5(123+169)

请求改造篇

基于RPC技术的自动化去加密测试。
本地替换js修改加密调用

在这里插入图片描述

在这里插入图片描述

前端拦截器里最后通过方法g()实现对参数处理的逻辑,但不是加密方法本身,其中实现了request method的判断和超时参数的添加(方法p)。
所以改造p方法来去掉加密的调用,使burp接收明文。
代码如下:

function p(e) {return JSON.stringify({data: Object(u["a"])({}, e),dataExpireTime: 1689999999999 

//使用一个较大的固定时间戳而非实时生成,以绕过服务器的时间戳超时校验机制
})
}

验证码获取请求(GET)
在这里插入图片描述

请求(POST)

在这里插入图片描述

GET方法和POST方法经过处理后的流量在burp里已经是明文。

编写RPC客户端通讯脚本

首先定义RPC调用的加解密对象:
在这里插入图片描述

调试过程将加密对象设置为全局对象,第二个打印的变量是加密密钥(源码硬编码值)。
Q&A:
为什么是d?
因为调试的当前页面里指向加密对象的变量是d;
为什么不定义p方法或是d.encryptData方法?
因为如果全局变量指向p方法会导致其运行加密方法时找不到某些定义在加解密库js文件中的方法而报undefined异常(破坏了作用域链的顺序);直接指向这个加密对象便于加解密时RPC使用同一个全局对象。

将RPC客户端加载到浏览器环境里,注入方法如油猴插件hook,本地覆盖到页面js和运行代码段等,建议在代码段里运行(实测注入到页面js中ws的通讯不稳定)
使用sekiro框架提供的服务端与客户端demo即可,并按实际情况修改通讯代码

//省略未变动的代码,将在参考链接中给出
var client = new SekiroClient("ws://127.0.0.1:5612/business-demo/register?group=rpc-test&clientId="+guid());client.registerAction("enc",function(request, resolve, reject){resolve(secApp.encryptData(request["params"],'04337449135FE6BD62D0683CE30AEA1BD178B879A392162D9F87A2FF0EC819A…'));
})

编写中间处理脚本

编写用于处理加密调用和密文流量转发的脚本mitm.py:

#rpc加密
def encrypt(params):# print("request params in enc func:::{}".format(params))api = "http://127.0.0.1:5612/business-demo/invoke?group=rpc-test&action=enc&jsonData={}".format(params)res = requests.get(api).json()# print("res:::{}:::{}".format(str(res),str(res['data'])) ) json()转换响应为json对象便于访问data字段return json.dumps({'data':res['data'],'responseCode':res['responseCode'],'responseDesc':res['responseDesc']})#修改请求
def request(flow):#获取请求方法,返回字符串:POST GETmethod = flow.request.methodif method == "GET":#获取查询参数# :MultiDictView[('{"data":{},"dataExpireTime":1689999999999}', '')]:::type is::: <class 'mitmproxy.coretypes.multidict.MultiDictView'># params = flow.request.query 目标对整个查询字串加密,query.get("param_name")针对参数加密的情况使用params = flow.request.url.split("?", 1)[1]elif method == "POST":#获取请求bodyparams = flow.request.textelse:params = None# print("request params:::{}:::type is:::{}".format(params,type(params)))encryptedData = encrypt(params)print("encData:::{}".format(encryptedData) )if method == "GET":flow.request.url = flow.request.url.split("?",1)[0]+ "?" + encryptedData# print("request url:::{}".format(flow.request.url))elif method == "POST":flow.request.text = encryptedData

Tips:这里可以利用burp的repeater模块进行脚本的测试,不必在浏览器里操作站点功能。

响应改造

同理对系统解密的流程分析并编写相关的脚本。
在这里插入图片描述

如果请求未通过,服务器将响应200以外的代码,此时拦截器进入reject部分,该部分的处理是没有解密过程的,响应包也可看到是明文形式的,因此编写中间脚本时需要判断返回码以决定是否进入RPC解密的调用。
一般情况使用GET方法便于浏览器API调用,但受限于URL长度,当对响应解密时,响应过长会导致GET方法无法正常请求,因此API调用改换POST方式。

#RPC解密
def decrypt(params):api = "http://127.0.0.1:5612/business-demo/invoke"jsonObj = {"group": "rpc-test","action": "dec","param": str(params)}res = requests.post(api, json=jsonObj).json()print("res:::{}:::".format(str(res['data'])) )return json.dumps(res['data'])#修改响应
def response(flow):body = flow.response.content.decode('utf-8')print("response body:::{}".format(body))#判断响应是否为密文if flow.response.status_code == 200:decryptedBody = decrypt(body)flow.response.text = decryptedBody

重写本地JS时对方法f改造,即“直接调用解密方法“的方法。
在这里插入图片描述

function f(e) {return e
}

RPC client加上解密的action:

client.registerAction("dec", function(request, resolve, reject){resolve(secApp.decryptDataOneWay(request["param"], 'DA7668FAx7'));
});

开启RPC(先服务端、再客户端)和mitmdump
在这里插入图片描述

(图示上窗运行RPC server,下窗运行mitmdump)
在这里插入图片描述
(运行RPC client)

burp进行测试中的流量:
在这里插入图片描述

原本加密的响应数据已呈明文。

通讯图示

红色箭头发起第一个请求,RPC Server和RPC Clinet之间通过websocket通讯,mitmdump和RPC Server间通过http通讯(API),其余箭头都是代理流量转发的过程。
在这里插入图片描述

注: 可以联动自动化工具,把流量用脚本加密代理出去。

参考文档

mitmproxy
https://docs.mitmproxy.org/stable/api/mitmproxy/http.html#Request
sekiroAPI:
https://sekiro.iinti.cn/sekiro-doc/01_user_manual/3.restful_api.html#get%E5%92%8Cpost
sekiro客户端:
https://sekiro.virjar.com/sekiro-doc/assets/sekiro_web_client.js
完整通讯代码:

function guid() {function S4() {return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);}return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
}var client = new SekiroClient("ws://127.0.0.1:5612/business-demo/register?group=rpc-test&clientId=" + guid());// var secApp = new microAppSafety;client.registerAction("enc", function (request, resolve, reject) {resolve(secApp.encryptData(request["param"],enckey));})client.registerAction("dec", function (request, resolve, reject) {resolve(secApp.decryptDataOneWay(request["param"],deckey));})//站点请求一次后在控制台中执行window.secApp = new microAppSafety;注册全局对象

续:脚本优化

import json
import requests
import urllib.parsejava_server = 'http://127.0.0.1:5612/business-demo/invoke'# RPC
def rpc(action, params, group='rpc-test'):url = java_serverdata = {'group': group,'action': action,'param': params}count = 0  # 计数器while 1:count += 1if count > 10:# TODO:处理超时逻辑breakres = requests.post(url, data=data).json()if 'data' in res:return resreturn ''# 解密
def decrypt(params: str):res = rpc('dec', params)print("dec:::res:::{}:::".format(str(res)))return json.dumps({'data': res['data'], 'responseCode': res['responseCode'], 'responseDesc': res['responseDesc']})# 加密
def encrypt(params: str):res = rpc('enc', params)return res['data']# 修改请求
def request(flow):method = flow.request.methodif method == "GET":flow.request.url = flow.request.url.split("?", 1)[0] + "?" + encrypt(urllib.parse.unquote(flow.request.url.split("?", 1)[1]))print('GET')elif method == "POST":flow.request.text = encrypt(flow.request.text)# 修改响应
def response(flow):body = flow.response.content.decode('utf-8')if flow.response.status_code == 200:flow.response.text = decrypt(body)

埋坑(框架bug):

  1. 实际操作时会出现RPC handler undefined异常,暂未探究具体原因,个人解决方法是在中间脚本里轮询RPC,判断是否正常调用:
count = 0
while 1:count += 1res = requests.post(api, json=jsonObj).json()print(count)if 'data' in res:break
  1. 解密RPC的响应长这样:
    {‘clientId’: ‘5af6b925-aa77-7184-dae5-1bfe289b7b21’, ‘data’: {‘verifyCode’: ‘/9w/UUAf/9k=’, ‘verifyId’: ‘654054282479276032’}, ‘responseCode’: ‘success’, ‘responseDesc’: ‘success’, ‘status’: 0}
    这就导致起初直接拿data无法正常进入前端逻辑(序列化结果与原字串存在差异,嵌套json被提出来了),因此中间脚本返回时需要加上后面两个字段:‘responseCode’和’responseDesc’。(进分析是由于客户端处理时的偏差,没有把响应的数据正确的全部作为data属性的值,可以修改客户端代码,这里处理方法是把加解密的结果b64打包了一下)

古早文档,代码部分已全量改进,先不放了,没时间改了,马上要去看电影了

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

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

相关文章

Golang之路---04 并发编程——WaitGroup

WaitGroup 为了保证 main goroutine 在所有的 goroutine 都执行完毕后再退出&#xff0c;前面使用了 time.Sleep 这种简单的方式。 由于写的 demo 都是比较简单的&#xff0c; sleep 个 1 秒&#xff0c;我们主观上认为是够用的。 但在实际开发中&#xff0c;开发人员是无法…

Swish - Mac 触控板手势窗口管理工具[macOS]

Swish for Mac是一款Mac触控板增强工具&#xff0c;借助直观的两指轻扫&#xff0c;捏合&#xff0c;轻击和按住手势&#xff0c;就可以从触控板上控制窗口和应用程序。 Swish for Mac又不仅仅只是一个窗口管理器&#xff0c;Swish具有28个易于使用的标题栏&#xff0c;停靠栏…

设计模式之策略模式(Strategy)

一、概述 定义一系列的算法&#xff0c;把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的类而变化。 二、适用性 1.许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。 2.需要使用一个算法的不同变体。…

【DFS】17. 电话号码的字母组合

【DFS】17. 电话号码的字母组合 Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法…感兴趣就关注我bua&#xff01; TOC 题目: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rikjhVFD-1691333891079)(C:\Us…

高成本帖子——RDK X3模组快速体验载板辅助选型

转眼&#xff0c;又是一年了&#xff0c;还记得22年6月自己第一次体验X3开发板 [首发] 多方位玩转“地平线新发布AIoT开发板——旭日X3派(Sunrise x3 Pi)” 。至今还记得拿到手的新奇与兴奋&#xff0c;还记得第一次玩BPU就遇到了大坑&#xff0c;第一次发帖就把论坛搞崩。如今…

34.利用matlab解 多变量多目标规划问题(matlab程序)

1.简述 学习目标&#xff1a;适合解 多变量多目标规划问题&#xff0c;例如 收益最大&#xff0c;风险最小 主要目标法&#xff0c;线性加权法&#xff0c;权值我们可以自己设定。 收益函数是 70*x(1)66*x(2) &#xff1b; 风险函数是 0.02*x(1)^20.01*x(2)^20.04*(x…

Tcp的粘包和半包问题及解决方案

目录 粘包&#xff1a; 半包&#xff1a; 应用进程如何解读字节流&#xff1f;如何解决粘包和半包问题&#xff1f; ①&#xff1a;固定长度 ②&#xff1a;分隔符 ③&#xff1a;固定长度字段存储内容的长度信息 粘包&#xff1a; 一次接收到多个消息&#xff0c;粘包 应…

第4章 变量、作用域与内存

引言 由于js是一门只有在声明变量后才能明确类型的语言&#xff0c;并且在任意时刻都可以改变数据类型。这也引起了一些问题 原始值与引用值 原始值就是基本数据类型&#xff0c;引言值就是复杂数据类型 变量在赋值的时候。js会判断如果是原始值&#xff0c;访问时就是按值访问…

1、sparkStreaming概述

1、sparkStreaming概述 1.1 SparkStreaming是什么 它是一个可扩展&#xff0c;高吞吐具有容错性的流式计算框架 吞吐量&#xff1a;单位时间内成功传输数据的数量 之前我们接触的spark-core和spark-sql都是处理属于离线批处理任务&#xff0c;数据一般都是在固定位置上&…

python简单知识点大全

python简单知识点大全 一、变量二、字符串三、比较运算符四、随机数4.1、随机整数4.2、随机浮点数4.3、随机数重现 五、数字类型5.1、整数5.2、浮点数5.3、复数 六、数字运算七、内置函数八、布尔类型八、逻辑运算符九、短路逻辑和运算符优先级十、分支和循环10.1、if语句10.2、…

相机传感器格式与镜头光圈参数

相机靶面大小 CCD/CMOS图像传感器尺寸&#xff08;sensor format&#xff09;1/2’‘、1/3’‘、1/4’实际是多大 1英寸——靶面尺寸为宽12.7mm*高9.6mm&#xff0c;对角线16mm。 2/3英寸——靶面尺寸为宽8.8mm*高6.6mm&#xff0c;对角线11mm。 1/2英寸——靶面尺寸为宽6.…

maven安装(windows)

环境 maven&#xff1a;Apache Maven 3.5.2 jdk环境&#xff1a;jdk 1.8.0_192 系统版本&#xff1a;win10 一、安装 apache官网下载需要的版本&#xff0c;然后解压缩&#xff0c;解压路径尽量不要有空格和中文 官网下载地址 https://maven.apache.org/download.cgihttps:…

微信小程序animation动画,微信小程序animation动画无限循环播放

需求是酱紫的&#xff1a; 页面顶部的喇叭通知&#xff0c;内容不固定&#xff0c;宽度不固定&#xff0c;就是做走马灯&#xff08;轮播&#xff09;效果&#xff0c;从左到右的走马灯&#xff08;轮播&#xff09;&#xff0c;每播放一遍暂停 1500ms &#xff5e; 2000ms 刚…

ARCGIS地理配准出现的问题

第一种。已有省级行政区矢量数据&#xff0c;在网上随便找一个相同省级行政区图片&#xff0c;利用地理配准工具给图片添加坐标信息。 依次添加省级行政区选择矢量数据、浙江省图片。 此时&#xff0c;图层默认的坐标系与第一个加载进来的省级行政区选择矢量数据的坐标系一致…

【TypeScript】TS接口interface类型(三)

【TypeScript】TS接口interface类型&#xff08;三&#xff09; 【TypeScript】TS接口interface类型&#xff08;三&#xff09;一、接口类型二、实践使用2.1 常规类型2.2 设置属性只读 readonly2.3 设置索引签名2.4 设置可选属性2.5 函数类型接口 一、接口类型 TypeScript中的…

【Linux操作系统】Vim:提升你的编辑效率

Vim是一款功能强大的文本编辑器&#xff0c;它具有高度可定制性和灵活性&#xff0c;可以帮助程序员和文本编辑者提高编辑效率。本文将介绍Vim的基本使用方法、常用功能和一些实用技巧。 文章目录 1. Vim的基本使用方法&#xff1a;2. 常用功能&#xff1a;2.1 文件操作&#…

linux的目录结构和文件类型

p&#xff1a;同一台主机之间不同程序/进程间通信要用的文件 s&#xff1a;不同主机之间不同程序/进程间通信要用的文件

Vue-组件二次封装

本次对el-input进行简单封装进行演示 封装很简单&#xff0c;就给激活样式的边框(主要是功能) 本次封装主要使用到vue自带的几个对象 $attrs&#xff1a;获取绑定在组件上的所有属性$listeners: 获取绑定在组件上的所有函数方法$slots&#xff1a; 获取应用在组件内的所有插槽 …

微服务——DSL查询文档+搜索结果处理

DSL Query分类 DSL Query的基本语法 全文检索查询 常用场景 match查询 要填一个存在的字段&#xff0c;已经要检索的内容 匹配度越高排名越前&#xff0c;这里all字段包含三个字段在里面。 multi_match查询 精确查询 不分词的查询 查询语法 term查询 range查询 gte是大于等…

Python web实战之 Django 的 MVC 设计模式详解

技术栈&#xff1a;Python、Django、HTML、CSS、JavaScript。 概要 在 Web 开发中&#xff0c;MVC&#xff08;Model-View-Controller&#xff09;模式是一种非常常见的设计模式&#xff0c;它可以帮助我们更好地管理代码&#xff0c;提高代码的可维护性。今天就介绍如何使用 …