在Web应用的漏洞挖掘领域,前端JavaScript代码是一个蕴含大量信息的宝库。通过对JS文件进行系统性的审计与分析,安全研究人员可以发现从信息泄露到逻辑漏洞在内的诸多安全问题。本文将聚焦于利用JavaScript进行漏洞挖掘的实战技术,涵盖静态信息收集、动态代码调试与加密参数逆向等多个维度。
一、 JavaScript中的静态信息泄露
在未压缩或混淆不充分的JS文件中,常常可以发现以下敏感信息:
- 硬编码的凭据:如默认的管理员用户名、密码、API密钥等。
- 敏感配置:包括数据库连接字符串、第三方服务密钥、加密盐值等。
- 指纹信息:框架类型(如Vue、React)、开发商信息、内部版本号等,这些信息有助于攻击者进行精准的漏洞利用。
二、 接口与业务逻辑泄露
JS文件是前后端交互的桥梁,其中必然包含大量的API接口调用。通过系统性地爬取和分析JS源码,可以提取出前端未显示的、隐藏的接口路径。一个常见的方法是使用正则表达式匹配url、path、/api/等关键词,从而集中获取待测试的接口列表,这常常能发现未授权访问等漏洞。
在某些开发环境中,我们可以在源码中看到清晰的模块定义和接口映射。例如,在一个代码编辑器的界面中,左侧文件树显示了img、js等目录,右侧的代码区域定义了一个返回对象,其中包含了path(如 ../index.html)、name、title 等字段,这本身就是一种可供分析的元数据信息。
三、 利用异步加载挖掘隐藏功能
现代Web应用大量使用异步加载技术来提升性能。当访问一个系统(如登录页面)时,并非所有功能的代码都会立即加载。这时,可以手动触发JS的异步加载,以揭示隐藏的业务模块。
技术原理
同步加载使用 <script> 标签,会阻塞HTML解析。异步加载则通过 <script> 标签的 async 或 defer 属性实现,允许浏览器并行加载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逆向分析来还原加密逻辑。
基本思路:定位加密函数 -> 静态分析结合动态调试 -> 理解算法 -> 转化为本地脚本 -> 实现参数构造。
案例分析:某系统越权漏洞挖掘
-
抓包分析:对目标功能点进行抓包,发现请求体被加密。一个典型的加密请求可能如下所示:
{
“timestamp”: 1725669291503,
“nonce”: “aKy3fhaJ3C3”,
“signature”: “QW001iGzEBkN4”,
...
}
其中nonce是防重放的随机数,signature是签名。
-
定位加密函数:在开发者工具的“源代码”面板中,全局搜索关键接口路径(如/redstack/queryTask)或加密参数名(如sign)。若搜索无果,则需在可能的网络请求发起处下断点进行动态调试。通过搜索,我们可能定位到一个包含大量模块定义的Webpack打包文件。进一步搜索,可以找到一个名为getKeyParams的关键函数。
-
动态调试与逻辑分析:在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:将timestamp、nonce、skey、body拼接后计算MD5,再将MD5值用RSA公钥加密。
-
算法复现(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方案,其核心思想是远程调用浏览器上下文中的原生函数,避免“扣代码”和“补环境”。
工作原理
- 在浏览器控制台中注入一个JS环境,该环境通过WebSocket与本地的一个服务端连接。
- 将目标网页中的加密函数注册为RPC可调用的方法。
- 测试脚本直接通过HTTP请求调用本地服务端,服务端通过WebSocket转发给浏览器执行真正的加密函数,并将结果返回。
实战步骤
- 启动服务端:下载
JsRpc项目并在本地运行。启动后,服务端会监听指定端口(如12080),并提示“当前监听地址::12080 ssl启用状态:false”。
- 注入客户端环境:将项目中的客户端JS(如
/resources/JsEnv_De.js)全部内容复制到目标网页的浏览器控制台中执行。这会创建一个名为Hlclient的连接对象。
- 建立连接:在控制台继续执行连接代码,建立WebSocket连接。
var demo = new Hlclient(“ws://127.0.0.1:12080/ws?group=zzz”);
// 连接成功后会打印“rpc连接成功”
- 注册加密函数:假设我们已经通过分析,将目标的
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);
});
- 远程调用:现在,任何能访问本机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
版权声明:本文由 云栈社区 整理发布,版权归原作者所有。