找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1567

积分

0

好友

203

主题
发表于 2026-2-15 19:20:59 | 查看: 33| 回复: 0

在Web应用的漏洞挖掘领域,前端JavaScript代码是一个蕴含大量信息的宝库。通过对JS文件进行系统性的审计与分析,安全研究人员可以发现从信息泄露到逻辑漏洞在内的诸多安全问题。本文将聚焦于利用JavaScript进行漏洞挖掘的实战技术,涵盖静态信息收集、动态代码调试与加密参数逆向等多个维度。

一、 JavaScript中的静态信息泄露

在未压缩或混淆不充分的JS文件中,常常可以发现以下敏感信息:

  1. 硬编码的凭据:如默认的管理员用户名、密码、API密钥等。
  2. 敏感配置:包括数据库连接字符串、第三方服务密钥、加密盐值等。
  3. 指纹信息:框架类型(如Vue、React)、开发商信息、内部版本号等,这些信息有助于攻击者进行精准的漏洞利用。

二、 接口与业务逻辑泄露

JS文件是前后端交互的桥梁,其中必然包含大量的API接口调用。通过系统性地爬取和分析JS源码,可以提取出前端未显示的、隐藏的接口路径。一个常见的方法是使用正则表达式匹配urlpath/api/等关键词,从而集中获取待测试的接口列表,这常常能发现未授权访问等漏洞。

在某些开发环境中,我们可以在源码中看到清晰的模块定义和接口映射。例如,在一个代码编辑器的界面中,左侧文件树显示了imgjs等目录,右侧的代码区域定义了一个返回对象,其中包含了path(如 ../index.html)、nametitle 等字段,这本身就是一种可供分析的元数据信息。

三、 利用异步加载挖掘隐藏功能

现代Web应用大量使用异步加载技术来提升性能。当访问一个系统(如登录页面)时,并非所有功能的代码都会立即加载。这时,可以手动触发JS的异步加载,以揭示隐藏的业务模块。

技术原理

同步加载使用 <script> 标签,会阻塞HTML解析。异步加载则通过 <script> 标签的 asyncdefer 属性实现,允许浏览器并行加载JS文件。

实战案例

假设目标系统登录页无注册入口,常规测试无法绕过。此时,可以打开浏览器开发者工具的“网络”(Network)面板,筛选类型为“XHR”或“Script”的请求,找出页面异步加载的JS文件。

为了更全面地分析,我们可以编写一段代码,将关键JS文件动态加载到当前页面上下文:

var arr=[
  “https://xxx.xxx.com/xxxxxxx/xxxx/0.1.0/js/xxxxxxx.js”, // 引入完整的JS路径
  “https://xxx.xxx.com/xxxxxxx/xxxx/0.1.0/js/xxxxxxx.js”
];
for(var i=0;i<arr.length;i++){
  var script = document.createElement(‘script’);
  script.src = arr[i];
  document.getElementsByTagName(‘head’)[0].appendChild(script);
}

在控制台运行上述代码后,开发者工具的网络面板会显示这些JS文件被成功加载。随后,我们便可以在“源代码”(Sources)面板中查看并搜索完整的JS代码。

在一次审计中,通过此方法发现了一个包含/svr____files____路径的接口函数,代码如下所示:

c = async(e,t)=>{
  await i.z.requestAsync().POST({
    url: “/svr____files____”,
    data: e,
    config: Object.assign(t || {}, {
      params: {},
      headers: {
        authorization: “a”,
        attachInfo: “a”
      }
    })
  })
}

通过跟踪调用该函数的位置,可以构造请求参数。发起请求后,响应中可能包含一个云存储服务的临时访问地址(如 https://xxx.aliyuncs.com/?sign=...)。直接访问该链接,可能直接列出存储桶(Bucket)内的文件列表,从而构成一个对象存储信息泄露漏洞。其响应体格式通常为XML,示例如下:

<ListBucketResult>
  <Name>private</Name>
  <Prefix/>
  <MaxKeys>100</MaxKeys>
  <IsTruncated>true</IsTruncated>
  <Contents>
    <Key>2020-01_1...3f2a99c.xlsx</Key>
    <LastModified>2020-12-21T04:10:25.000Z</LastModified>
    <Size>12367</Size>
  </Contents>
</ListBucketResult>

四、 JavaScript逆向与加密参数破解

许多应用会对关键请求参数进行加密,给漏洞测试带来阻碍。此时需要进行JS逆向分析来还原加密逻辑。

基本思路:定位加密函数 -> 静态分析结合动态调试 -> 理解算法 -> 转化为本地脚本 -> 实现参数构造。

案例分析:某系统越权漏洞挖掘

  1. 抓包分析:对目标功能点进行抓包,发现请求体被加密。一个典型的加密请求可能如下所示:

    {
      “timestamp”: 1725669291503,
      “nonce”: “aKy3fhaJ3C3”,
      “signature”: “QW001iGzEBkN4”,
      ...
    }

    其中nonce是防重放的随机数,signature是签名。

  2. 定位加密函数:在开发者工具的“源代码”面板中,全局搜索关键接口路径(如/redstack/queryTask)或加密参数名(如sign)。若搜索无果,则需在可能的网络请求发起处下断点进行动态调试。通过搜索,我们可能定位到一个包含大量模块定义的Webpack打包文件。进一步搜索,可以找到一个名为getKeyParams的关键函数。

  3. 动态调试与逻辑分析:在getKeyParams函数处设置断点,重新触发请求。观察传入的参数,通常第二个参数e是一个RSA公钥字符串。该函数的核心逻辑是生成一个包含所有加密参数的请求对象。

    核心加密函数getKeyParams分析如下:

    getKeyParams: function(t, e) {
        var n = {
            timestamp: “”,
            nonce: “”,
            skey: “”,
            body: “”,
            sign: “”,
            aesSecretKey: “”
        };
        ut = e;
        n.timestamp = (new Date).getTime();
        n.nonce = this.getNonce(32); // 生成32位随机字符串
        n.skey = this.getAesSecretKey(); // 生成AES密钥并用RSA加密
        n.aesSecretKey = rt;
        n.body = this.encryptByAES(r()(t), rt, “12xxxxxxxxxxxef”).encryptContent; // AES加密请求体
        var i = this.encryptByMD5(n.timestamp + n.nonce + n.skey + n.body); // 生成MD5签名
        return n.sign = this.encryptByRSA(i, ut), n; // RSA加密MD5签名
    }

    算法流程梳理

    • timestamp:当前时间戳。
    • nonce:由getNonce函数生成的随机字符串。
    • skey:随机生成的AES密钥,但经过RSA公钥加密。
    • body:原始的请求数据(JSON格式),使用上述AES密钥和固定IV进行CBC模式加密。
    • sign:将timestampnonceskeybody拼接后计算MD5,再将MD5值用RSA公钥加密。
  4. 算法复现(Python):理解算法后,使用Python还原加密过程。

    import base64
    import hashlib
    import random
    import time
    from Crypto.Cipher import AES, PKCS1_v1_5
    from Crypto.PublicKey import RSA
    from Crypto.Util.Padding import pad
    
    rsa_public_key = ‘‘‘-----BEGIN PUBLIC KEY-----
    MxxxxxxxxxMBUD
    -----END PUBLIC KEY-----‘‘‘.strip()
    
    class EncryptHandler:
        def __init__(self, rsa_public_key):
            self.aes_key = self.get_nonce(16)
            self.iv = ‘12xxxxxxxxxef‘.encode(‘utf-8‘)
            self.rsa_public_key = rsa_public_key
    
        @staticmethod
        def get_nonce(length):
            characters = “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789”
            return ‘‘.join(random.choice(characters) for _ in range(length))
    
        def aes_encrypt(self, data):
            cipher = AES.new(self.aes_key.encode(‘utf-8‘), AES.MODE_CBC, self.iv)
            encrypted = cipher.encrypt(pad(data.encode(‘utf-8‘), AES.block_size))
            return base64.b64encode(encrypted).decode(‘utf-8‘)
    
        def md5_sign(self, data):
            return hashlib.md5(data.encode(‘utf-8‘)).hexdigest().upper()
    
        def rsa_encrypt(self, data):
            key = RSA.import_key(self.rsa_public_key)
            cipher = PKCS1_v1_5.new(key)
            encrypted_data = cipher.encrypt(data.encode(‘utf-8‘))
            return base64.b64encode(encrypted_data).decode(‘utf-8‘)
    
        def prepare_request(self, body):
            timestamp = str(int(time.time() * 1000))
            nonce = self.get_nonce(32)
            aes_encrypted_body = self.aes_encrypt(body)
            skey = self.rsa_encrypt(self.aes_key)
            sign_str = timestamp + nonce + skey + aes_encrypted_body
            md5_signature = self.md5_sign(sign_str)
            rsa_signature = self.rsa_encrypt(md5_signature)
            request_data = {
                “timestamp”: timestamp,
                “nonce”: nonce,
                “skey”: skey,
                “body”: aes_encrypted_body,
                “sign”: rsa_signature
            }
            return request_data
    
    handler = EncryptHandler(rsa_public_key)
    encrypted_request = handler.prepare_request(“{待测试的参数}”)
    print(“Encrypted Request:”, encrypted_request)

    运行脚本即可生成有效的加密参数,格式如:{‘timestamp‘: ‘1729067307872‘, ‘nonce‘: ‘eYB7HWBe‘, ‘body‘: ‘KRna8t1M68...‘, ‘sign‘: ‘...‘}。将其替换到原始请求包中,即可进行越权、注入等后续漏洞测试。

五、 使用JS RPC绕过复杂加密环境

对于使用了复杂前端混淆、环境检测或流程的加密,直接扣取代码还原算法成本极高。此时,可以使用JsRpc方案,其核心思想是远程调用浏览器上下文中的原生函数,避免“扣代码”和“补环境”。

工作原理

  1. 在浏览器控制台中注入一个JS环境,该环境通过WebSocket与本地的一个服务端连接。
  2. 将目标网页中的加密函数注册为RPC可调用的方法。
  3. 测试脚本直接通过HTTP请求调用本地服务端,服务端通过WebSocket转发给浏览器执行真正的加密函数,并将结果返回。

实战步骤

  1. 启动服务端:下载JsRpc项目并在本地运行。启动后,服务端会监听指定端口(如12080),并提示“当前监听地址::12080 ssl启用状态:false”。
  2. 注入客户端环境:将项目中的客户端JS(如/resources/JsEnv_De.js)全部内容复制到目标网页的浏览器控制台中执行。这会创建一个名为Hlclient的连接对象。
  3. 建立连接:在控制台继续执行连接代码,建立WebSocket连接。
    var demo = new Hlclient(“ws://127.0.0.1:12080/ws?group=zzz”);
    // 连接成功后会打印“rpc连接成功”
  4. 注册加密函数:假设我们已经通过分析,将目标的getKeyParams函数暴露到了全局(如window.getKeyParams)。我们在控制台将其注册为一个RPC动作。
    var rsa = “MIxxxxxxxxDAQAB”; // 固定的RSA公钥
    demo.regAction(“getEncryptedData”, function(resolve, param) {
        var userParam = param[“param”];
        var res = getKeyParams(userParam, rsa); // 调用浏览器中原生的加密函数
        resolve(res);
    });
  5. 远程调用:现在,任何能访问本机12080端口的脚本,都可以远程触发浏览器执行加密。调用方式为HTTP GET请求:
    http://127.0.0.1:12080/go?group=zzz&action=getEncryptedData¶m=需要加密的数据

    该请求会返回由浏览器真实环境计算出的完整加密参数对象。在服务端日志中,可以看到相应的执行记录,如 [GIN] - 18:35:25 [get message: 执行成功]

这种方法将复杂的加密逻辑执行留在浏览器环境中,测试者只需关心如何构造输入参数和解析输出结果,极大地提升了针对强加密场景的安全测试效率。

参考资料

[1] 漏洞挖掘之利用js挖掘漏洞, 微信公众号:mp.weixin.qq.com/s/Tqc7yQno7_CDD8pIwLwDGw

版权声明:本文由 云栈社区 整理发布,版权归原作者所有。




上一篇:前端开发新范式:基于AI Skills实现React/Vue高效与规范编码
下一篇:基于Unidbg的ARM64间接跳转消除实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-23 11:44 , Processed in 0.604621 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表