【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 GET
   method = flow.request.method
   if 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":
      #获取请求body
      params = flow.request.text
   else:
      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.parse

java_server = 'http://127.0.0.1:5612/business-demo/invoke'

# RPC
def rpc(action, params, group='rpc-test'):
    url = java_server
    data = {
        'group': group,
        'action': action,
        'param': params
    }
    count = 0  # 计数器
    while 1:
        count += 1
        if count > 10:
            # TODO:处理超时逻辑
            break
        res = requests.post(url, data=data).json()
        if 'data' in res:
            return res
    return ''

# 解密
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.method
    if 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 += 1
   res = 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打包了一下)

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Springboot中创建拦截器

目录 目的 实现过程 1、创建拦截器 2、注册拦截器 完整代码 目的 在Springboot项目中创建拦截器&#xff0c;在进入Controller层之前拦截请求&#xff0c;可对拦截到的请求内容做响应处理&#xff0c;如&#xff1a;校验请求参数、验证证书等操作&#xff1b; 实现过程 1、创…

基于Java+SpringBoot+Vue的数码论坛系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

用于3D MRI和CT扫描的深度学习模型总结

医学成像数据与其他我们日常图像的最大区别之一是它们很多都是3D的&#xff0c;比如在处理DICOM系列数据时尤其如此。DICOM图像由很多的2D切片组成了一个扫描或身体的特定部分。 那么如何为这类数据构建深度学习解决方案呢?本文中将介绍6种神经网络架构&#xff0c;可以使用它…

数据结构:各种结构函数参数辨析

&#xff08;一&#xff09;顺序表 1&#xff09;结构 typedef int SLDateType;typedef struct SeqList {SLDateType* data;int size;int capacity; }SeqList;SeqList ps { 0 }; 2&#xff09;函数参数 // 对数据的管理:增删查改 void SeqListInit(SeqList* ps); void Seq…

webpack基础知识六:说说webpack的热更新是如何做到的?原理是什么?

一、是什么 HMR全称 Hot Module Replacement&#xff0c;可以理解为模块热替换&#xff0c;指在应用程序运行过程中&#xff0c;替换、添加、删除模块&#xff0c;而无需重新刷新整个应用 例如&#xff0c;我们在应用运行过程中修改了某个模块&#xff0c;通过自动刷新会导致…

Android Glide MemorySizeCalculator计算值,Kotlin

Android Glide MemorySizeCalculator计算值,Kotlin for (i in 100..1000 step 50) {val calculator MemorySizeCalculator.Builder(this).setMemoryCacheScreens(i.toFloat()).setBitmapPoolScreens(i.toFloat()).setMaxSizeMultiplier(0.8f).setLowMemoryMaxSizeMultiplier(0…

小程序wx:else提示 Bad attr `wx

问题&#xff1a;以下wx:for里的wx:if &#xff0c; wx:else 会报这个错&#xff1a;Bad attr wx <scroll-view class"scroll1" scroll-x enable-flex"true"><view wx:if"{{playlist.length>0}}" class"item" wx:for"…

C++入门--string类的实现

目录 1.string类常用函数实现&#xff08;1&#xff09;string类成员变量定义&#xff08;2&#xff09; string类默认构造函数实现&#xff08;3&#xff09; string类拷贝构造函数实现&#xff08;4&#xff09;string类析构函数&#xff08;5&#xff09;string类c_str()函数…

redis的配置和使用、redis的数据结构以及缓存遇见的常见问题

目录 1.缓存 2.redis不仅仅可以做缓存&#xff0c;只不过说他的大部分场景&#xff0c;是做缓存。本地缓存重启后缓存里的东西就没有了&#xff0c;但是redis有。 3.redis有几个特性:查询快&#xff0c;但是是放到内存里的〈断电或者重启&#xff0c;数据就丢了)&#xff0c…

小研究 - Mysql快速全同步复制技术的设计和应用(三)

Mysql半同步复制技术在高性能的数据管理中被广泛采用&#xff0c;但它在可靠性方面却存在不足.本文对半同步复制技术进行优化&#xff0c;提出了一种快速全同步复制技术&#xff0c;通过对半同步数据复制过程中的事务流程设置、线程资源合理应用、批量日志应用等技术手段&#…

W5100S-EVB-PICO 做TCP Server进行回环测试(六)

前言 上一章我们用W5100S-EVB-PICO开发板做TCP 客户端连接服务器进行数据回环测试&#xff0c;那么本章将用开发板做TCP服务器来进行数据回环测试。 TCP是什么&#xff1f;什么是TCP Server&#xff1f;能干什么&#xff1f; TCP (Transmission Control Protocol) 是一种面向连…

用C语言构建一个数字识别深度神经网络

接上一篇: 用C语言构建一个数字识别卷积神经网络 1. 深度神经网络 按照深度学习的理论&#xff0c;随着神经网络层数的增加&#xff0c;网络拟合复杂问题的能力也会增强&#xff0c;对事物特征的挖掘也会更加深入&#xff0e;这里尝试构建一个&#xff15;层深度的神经网络&am…

【逗老师的PMP学习笔记】9、项目资源管理

目录 一、规划资源管理1、【关键工具】责任分配矩阵RACI矩阵2、【关键工具】组织理论2.1、马斯洛需求层次理论2.2、麦格雷戈-X-Y理论2.3、赫兹伯格双因素理论 3、【关键输出】资源管理计划4、【关键输出】团队章程 二、估算活动资源1、【关键输入】资源日历 三、获取资源1、【关…

LeetCode_01 精度丢失

1281. 整数的各位积和之差 给你一个整数 n&#xff0c;请你帮忙计算并返回该整数「各位数字之积」与「各位数字之和」的差。 示例 输入&#xff1a;n 234 输出&#xff1a;15 解释&#xff1a; 各位数之积 2 * 3 * 4 24 各位数之和 2 3 4 9 结果 24 - 9 15示例 …

【计算机视觉】干货分享:Segmentation model PyTorch(快速搭建图像分割网络)

一、前言 如何快速搭建图像分割网络&#xff1f; 要手写把backbone &#xff0c;手写decoder 吗&#xff1f; 介绍一个分割神器&#xff0c;分分钟搭建一个分割网络。 仓库的地址&#xff1a; https://github.com/qubvel/segmentation_models.pytorch该库的主要特点是&#…

【2.2】Java微服务:Hystrix的详解与使用

目录 分布式系统面临问题 Hystrix概念 Hystrix作用 降级 什么是降级 order服务导入Hystrix依赖&#xff08;简单判断原则&#xff1a;谁调用远程谁加&#xff09; 启动类添加注解 业务方法添加注解&#xff08;冒号里填回调方法名&#xff0c;回调方法返回兜底数据&…

沁恒ch32V208处理器开发(二)工程配置

概述 MounRiver Studio在进行任何项目的开发时&#xff0c;为了提高效率&#xff0c;往往需要复用芯片厂家或第三方开发的成熟模块&#xff0c;这些模块通过一个.wvproj文件来进行组织&#xff0c;主要包含&#xff1a; 1&#xff09;MCU厂家提供的硬件接口文件&#xff0c;包…

Windows使用docker desktop 安装kafka、zookeeper集群

docker-compose安装zookeeper集群 参考文章&#xff1a;http://t.csdn.cn/TtTYI https://blog.csdn.net/u010416101/article/details/122803105?spm1001.2014.3001.5501 准备工作&#xff1a; ​ 在开始新建集群之前&#xff0c;新建好文件夹&#xff0c;用来挂载kafka、z…

设计师常用的6款UI设计工具

在选择UI设计工具时&#xff0c;设计师需要关注UI设计工具的功能。市场上有很多设计UI的工具。既然UI设计工具这么多&#xff0c;设计师应该如何选择UI设计工具&#xff1f;本文盘点了6种流行的UI设计工具&#xff0c;快来看看。 1.即时设计 即时设计是一款免费的在线 UI 设计…

Kubernetes kubectl管理命令使用方法

陈述式资源管理方法&#xff08;通过命令行&#xff09; 1.kubernetes 集群管理集群资源的唯一入口是通过相应的方法调用 apiserver 的接口 2.kubectl 是官方的CLI命令行工具&#xff0c;用于与 apiserver 进行通信&#xff0c;将用户在命令行输入的命令&#xff0c;组织并转化…