前置思路文章

JS 逆向混淆前端对抗

油猴 JS 逆向插件

JS 加解密之 mitmproxy 工具联动 Burp

# JS 挖掘基础

# 伪协议

JavaScript 伪协议是一种在浏览器中模拟网络请求的方法。它使用 window.XMLHttpRequest 对象或 fetch() 方法来模拟发送 HTTP 请求,而不是通过实际的网络请求来获取数据。

例如,以下代码使用 fetch() 方法模拟发送一个 GET 请求到 example.com ,并处理返回的数据

1
2
3
fetch('https://example.com')
.then(response => response.text())
.then(data => console.log(data));

在这个例子中,我们使用 fetch() 方法发送一个 GET 请求到 https://example.com ,然后使用 .text() 方法获取响应的文本内容,最后使用 console.log() 方法将响应的内容输出到控制台。

需要注意的是,虽然使用 JavaScript 伪协议可以模拟网络请求,但它并不能完全取代实际的网络请求,因为它的性能和可靠性都比实际的网络请求要差。此外,使用 JavaScript 伪协议可能会导致一些安全问题,因为它可以被恶意代码利用来获取敏感信息

# XSS 利用 JS 变量提升

XSS 案例

JavaScript 中的变量提升是指在代码执行过程中,JavaScript 引擎会将变量声明提升到其作用域的顶部,但不会提升变量的赋值。这意味着您可以在变量声明之前访问变量,但变量的值将是 undefined


undefinedVar 是个未命名的变量,如果我们直接使用的话会被变量未定义错误。但是如果我们在下面再次去使用这个变量并且去声明它赋值,那么上面的就不会报错了

下方代码前者是未定义就使用的,但下面还是正常的去声明使用了,整体并没有报错出现,这是因为变量提升的原因,把 var undefinedVar 提升到了顶部,最开头 所以往下的 undefinedVar = null 并不会报错

# F12 控制台参数

F12 查看接口出参入参

F12 数据包接口信息

Fkalis 文章接口测试

# 获取接口信息

F12 打开控制台下方就是整体的数据,包括选择的接口,以 Edge 为例在网络选项中打开此页面,如果对响应头或者是请求头不明白直接 ChatGPT 审计就是

Fetch/XHR :

这个功能可以帮助开发人员分析和调试网络请求,包括请求的 URL、请求方法、请求头、请求体、响应状态码、响应头、响应体等信息。通过查看 Fetch/XHR 选项中的请求和响应信息,开发人员可以快速定位网络请求的问题,例如请求失败、响应慢、响应数据异常等,其实也就是一个筛选器,
选项右边我们还可以只查看 CSS JS 这些

测试中接口地址响应后返回了相关的数据,那么我们就可以尝试对这个 /ums/ums/getRole 接口的 getRole 参数进行 Fuzz

观察数据包的请求和响应就可以确定这个接口获取请求是否存在漏洞

数据包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /ums/ums/getRole HTTP/1.1
Host: user.xxxxxxx.sjtu.edu.cn
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Content-Length: 0
Origin: https://user.xxxxxxx.sjtu.edu.cn
Referer: https://user.xxxxxxx.sjtu.edu.cn/ums/user/index.html
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
sec-ch-ua: "Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"

响应包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Credentials: false
Access-Control-Allow-Headers: X-Requested-With,Content-Type
Access-Control-Allow-Methods: PUT,POST,GET,DELETE,OPTIONS
Access-Control-Allow-Origin: https://user.xxxxxxx.sjtu.edu.cn
Connection: keep-alive
Content-Type: application/json; charset=utf-8
Date: Tue, 07 Feb 2023 10:40:42 GMT
Etag: W/"4d4-d17bqK52VNY2JQ4ODTzgbhOimVQ"
Server: nginx
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Nginx-Debug-Variables: host:user.xxxxxxx.sjtu.edu.cn,request_uri:/ums/ums/getRole,
X-Powered-By: Express
X-Xss-Protection: 1; mode=block
Content-Length: 2721

{
"code": 100,
"msg": "请求成功",
"extend": {
"returnMsg": [
{
"roleId": "10",
"name": "用户联络员",
"energyValid": "0",
"imsValid": "0",
"meetingValid": "1",
"visitorValid": "1",
"limsValid": "1",
"param01": null,
"param02": "1",
"param03": "3"
},
{
"roleId": "11",
"name": "运行管理员",
"energyValid": "1",
"imsValid": "1",
"meetingValid": "1",
"visitorValid": "1",
"limsValid": "1",
"param01": null,
"param02": "2",
"param03": "1"
},
{
"roleId": "13",
"name": "系统管理员",
"energyValid": "1",
"imsValid": "1",
"meetingValid": "1",
"visitorValid": "1",
"limsValid": "1",
"param01": null,
"param02": "3",
"param03": "1"
},

请求标头可以获取到 Cookie

# 查看入参

请求就是入参 响应就是出参,这里的入参格式有 key-value 格式也有 json 格式

关于二者区别

它们的区别在于数据的组织方式和传输效率

Key-value 形式的数据是一种简单的数据组织方式,它由一系列键值对组成,每个键值对之间用特定的分隔符分隔。这种数据格式通常用于传输简单的数据,例如表单数据、查询参数等。Key-value 形式的数据传输效率较高,因为数据量较小,传输速度较快

JSON 格式的数据是一种轻量级的数据交换格式,它由一组键值对或数组组成,可以嵌套使用。JSON 格式的数据通常用于传输复杂的数据结构,例如对象、数组等。JSON 格式的数据传输效率较低,因为数据量较大,传输速度较慢。但 JSON 格式的数据具有良好的可读性和可扩展性,可以方便地进行解析和处理

选择何种数据格式取决于具体的应用场景和数据传输需求。如果需要传输简单的数据,可以选择 key-value 形式的数据;如果需要传输复杂的数据结构,可以选择 JSON 格式的数据

在负载这里看到查询字符串参数,[1] 就是入参数据并且可以查看请求源,它是拼接后的数据 下方拼接后为

1
positionId=644&queryWord=&articleId=0&adId=5518

# 查看出参

响应页面看出参数据..

# 查看 Cookie 数据格式

选择请求右侧 Cookie 它的组成其实就是一个个的 key 和 value

# JS 审计

JS 文件可以看是否存在版本或者名称信息这样可以去网上找相关漏洞

# 手动搜索参数

控制台直接搜索这种 api 语句就可以找到不同的接口,获取到特殊的路径,获取其他的敏感字眼

1
2
3
4
5
6
7
8
9
Path
GET
POST
url
ajax
onBoardTab

config.js // 拼接全局配置文件

找到的是隐藏的接口 类似 api/login/name 拼接到主站域名中,普通的路由也就网站的路径,接口指后端中的函数,调用接口等于调用了后端某个函数功能实现增删改查等等

1
网站路径 = url = 目录 = 路由

如果拼接出来是空白的,就按照路径慢慢删掉,回到上级目录有的 .net 网站会有接口管理器,找到一个接口返回上级目录就可以看到所有接口,而且有的 aspx 接口是可以看到参数值的)

1
baidu.com/api/login/name

找到的是 url 在浏览器执行即可 一般会得到 JSON 格式的数据

类似这种路径下存在参数,可以路径带参数构造

http://xx.xx.xx.xx /commonServletfromflag=queryWorkUserBySectionId&sectionId_search=1

控制台查看了一下 RPC 函数的 js 源码,也算是一个审计 js 源码的小 trickjs 函数名 +'' 会自动调用 js 函数对象的 toString 方法,从而输出对应的 js 函数的源码,避免了自己一步步从一堆源码里寻找这个函数

1
console.log(RPC+'')

# 审计源码

在控制台应用程序选项,拉到底部可以在这里看到这个页面所有的 JS脚本 ,一般 JS 文件通常会 报出 CMS版本 ,和一些 验证判断响应值 有了 CMS 可以尝试去网上找通杀 Nday 攻击,并且还有有其他注释没有来的及删除的信息,渗透测试的本质就是信息收集

在挖掘时别人能挖掘出水平越权垂直越权,是因为这个越权漏洞的传参值都是在接口中进行的,所以我们要在挖掘的时候利用 Burp 抓住每一个包
然后再去看 Burp 里面的 http 历史记录 查看接口信息

Webpack 会包含很多接口信息

如果在站点页面是无法看见?id 这个参数的,而你在 burp 历史包中即可以看见此参数,这个参数就是个人身份的参数

接使用浏览器的控制台中的网络即可查找,重发刷新页面就会再进行一次请求,如果再下方找到隐藏的身份参数后,讲 uid=xxx 修改为别人的数值 即是测试越权

# Python 解决 JS 加密

逆向出参数是由什么加密而来的,首尾加入了什么固定的字符串,加密几个参数就写一个方法解密出这些参数

1
2
3
4
5
import execjs
with open("",r) as f // 引入函数
a = execjs.compile(f.read())
result1 =a.call("md5","") // 对应函数 md5是函数名 后面是形参
print(result1)

passwd 是 保存明文密码的文件

# 反编译 js.map [2]

反编译 Webpakc js.map

提取 WebPack 打包站点接口

JSFuzz 接口导致铭感信息泄露

Vue 使用 webpack (静态资源打包器) 的时候,如果未进行正确配置,会产生一个 js.map 文件,而这个 js.map 可以通过工具来反编译还原 Vue 源代码,产生代码泄露,并且前后端分析网站前端使用 Vue 重点找 xxx.app.js 再从中提取新的接口

1
reverse-sourcemap --output-dir . xxx.js.map

# 数据包接口构造

前端 JavaScript 渗透测试步步为营

JSFuzz 接口实现 SSRF

JS 更换请求方式构造

JSFuzz 接口导致铭感信息泄露

JS 拼接杂谈

fkalis 关于 JS 研究

JS 接口系列文章

# Fuzz 前置目录

JS 找到的接口某些情况下不能直接访问,前置或者还有一层目录,这个就需要我们去进行 fuzz 或者信息收集寻找接口间的共性再进行爆破,直接的话只能使用字典,我建议先找个登录点抓个包看看路径是否有一个基路径(一级目录),然后再拼接跑一遍。

1
2
3
4
5
6
7
/data/teach/hr/search?key= // 可疑接口,后面带入参数可疑搜索更多

/data/teach/hr/search?key=1 2 3


roleid // 角色ID

# 构造接口

其实很好理解,如果网站功能点只能允许查看 info 那么在查询接口功能抓包,构造参数为删除 delete , 接口有的是主动构造有的是工具熊猫头或其他审计得到的接口,整理后 GET POS 方法访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
api/info?id=1   // 正常查询接口

api/delete?id=1 // 构造删除接口

/api/v1/api-docs // 尝试/api/v2/api-docs /api/v3/api-docs

------------------------------------------------------

前置接口

/user/saber 无法直接访问


api /user/saber/ fuzz前置接口,递归扫描重要性就出现了

常见业务接口格式如果 js 文件中只有查询的接口,那么自己可以尝试一下构造添加、修改和删除接口

多观察接口,推测其功能,然后根据功能去 FUZZ,毕竟你要实现一个 web 功能,基本都要有对应的增删改查接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 后台的模块的接口格式

OST /PUT /api/模块名


// 添加
POST /api/模块名/add


// 删除接口

DELETE /api/模块名/id

GET /api/模块名/del?id=

POST /api/模块名/


// 修改接口

POST /api/模块名/modify

POST /api/模块名/

// 查询接口

GET /api/模块名/list

/api/模块名/all

1
2
3
4
5
6
7
8
9
10
11
12

查询(获取信息)
search list select query get find

删除(删除某个数据)
del Delete

编辑(更新某个信息)
Update Up edit Change

添加(增加某个信息)
add create new

# 构造参数

JS 寻找关键参数

应该如何寻找参数的蛛丝马迹

无论是熊猫头还是其他工具得到的接口其实是不完整的,直接拼接的话缺少参数是无法访问接口会报错,需要构造 ** 参数,** 如果我们直接去 fuzz 猜参数,那基本上是属于大海捞针,几乎不太可能成功。那这时候就需要去 JS 源码里面找一下它的调用代码,看看它是怎么请求的,都有什么参数

GET 请求参数拼接到链接中, POST 请求构造参数拼接到下方 Body

# 构造 JS 文件

实战某金融 SRC 通过 JS 接口进入后台

当接口参数无法操作,信息收集只要少量 JS 文件既可以使用爆破模块对 JS 进行 fuzz 如果响应了新的文件再从响应包中审计新的接口,案例见标题

实战 FuzzJS 文件

文章展示了针对古老的站点测试手段,野战和 edu 测试功能点无从下手的时候可以回来看看这篇文章看看有没有启发 PHP/ASP.NET fuzzjs 可以利用 BP 配合字典直接开测,观察响应接口 文章推荐了 FFUF 工具操作 对与 403 页面尝试绕过和 dr 工具爆破

# Fuzz 爆破 / 接口路径

api 接口猜测爆破

观察抓取到的数据包的接口,寻找共同函数的特征,爆破后面的数字或许会有未授权访问的接口或者功能可以先猜目录再猜参数网站就一个文件夹存放了很多的文件

1
2
3
/funtion123   
/fun2334
/fun345

响应包出现我们不知道参数,第一先去 JS 中全局找这个参数看看有没有相似的路径拼接构造,有就拼;无则 fuzz

# JS 逆向

逆向加密是为了应对数据包中出现 sign 或者 token 的情况, sigh 和对应的 passwd 字段关联,修改了 passwd 字段 sign 匹配不上就无法测试

所以 sign 的加密肯定会有 passwd 字段参与,找到加密 sign 的位置构造我们想要测试的 passwd 生成新的 sign 字段就是我们所测试生成的字段这样响应包就不会显示签名问题

小程序 F12

1
2
3
4
https://blog.csdn.net/u010062917/article/details/135649668  // 文章

https://github.com/JaveleyQAQ/WeChatOpenDevTools-Python/ // 工具

# 定位加密参数方式

全局搜索 ** password **

根据网络请求已经发现了加密字段 password 全局搜索即可,或是全局搜索 encrypt 函数一般加密前缀都是此名称,利用搜分析其实非常快速

1
2
3
4
5
6
7
8
9
10
(1)复制加密参数的键名

(2)Ctrl + Shift + F 键快捷通过search搜索该键名定位js加密文件,对出现字段位置打入断点

(3)在几个JS函数后打上断点, 然后点击执行或者刷新网页,通过断点停止和参数信息判断如何加密的,如果在里面看到关键加密方式
就说明这很大几率是加密过程

(4)通过一步一步断点,分析加密参数是如何产生的

(5)定位到最初加密字段位置 复现算法

** XHR ** 定位

同样定位到加密字段的请求,请求路径即可,不需要携带参数,针对普通的跟栈找不到 data 数据情况

1
/login.php

输入对应的请求路径后再次点击登录就会定位到我们所选择的堆栈函数

QQ2024616-14257-HD.mp4

跟随堆栈定位

爱拍案例就是此方法,跟进每个函数后还可以在此函数的 JS 里面全局搜索加密字段,因为断点位置可能并无加密方法,也可能是在上下文

# 爱拍案例 MD5

定位出加密的函数有两种方式,1 是定向的找字段,2 是根据请求跟请求调用栈,第一种全局搜索加密参数 但是试用于搜索出来值很少 十个以内就可以利用搜的方法,这里还是选择跟踪加密数据包堆栈函数

# 0X01 堆栈寻找加密函数

进入网站打开网络,任意输入账户密码,在网络中找到关键的请求里面是对应加密过后的账户密码 password 并且要把保留日志和停用缓存勾选,对应关键请求查找就是看哪个存在加密就跟哪个函数

选择启动器查看堆栈函数,从下往上执行结束,我就从结束的点往下选择加密位置

跟入堆栈打入断点并放掉余下的包,余下还有其他的待执行的脚本包,类似于 BP 拦截,所以要全部放掉,之后再次点击登录发出新的包,不然的话包是不一样的

QQ2024616-14055-HD.mp4

断点完成最新的堆栈出现,开始一个个往下跟寻找加密位置,找到未加密前的函数,找到的是加密后的函数就直接放弃进入下一个,直到未加密位置

1
函数名称如果是JQ库或是其他语义化比较明显的js就直接跳过因为这种加密都是现成的并没有算法

一个个移动堆栈直到找到对应的加密算法,为 md5

1
e.md5(q)  q=123456 // 明文

控制台也可以验证此函数打印后的值就是就加密后的值 并和 BP 值一致

控制台控制 q 的值并再使用算法可以生成新的值,试用于手动的进行替换,这样加密参数就为我们所控

# 0X02 跟入函数内部

确定好加密算法位置,然后对此打入断点并放掉所有的脚本重新点击登录数据就会卡在此处,如果先前有断点也要清除只保留这一处断点即可

使用浏览器堆栈工具跟入加密 md5 函数内部并返回,确定了是 md5 加密方式 并返回上一步函数,测试了忽略断点工具使用

QQ2024616-05632-HD.mp4

md5 算法

# 0X03 工具生成算法

跟踪到加密算法后虽然可以在控制台手动的去将加密函数执行并手动替换,但是还是脚本来的快速,打开 WT-JS 选择原生的加解密库 CryptoJS 选择加密方式为 md5

生成加密本地的加密算法

QQ2024616-1335-HD.mp4

加入打印方法调用加密的函数,得出我们所需的加密值

1
2
3
4
5
6
7
8
// MD5加密执行

console.log(MD5_Encrypt("123456'"));

// 执行js脚本

node .\demo.js

# 快乐学堂 DES

前期根据加密请求跟栈找到密码加密位置,在 pass 密码出打入新的断点并清除先前断点,再点击登录,就会在密码卡住,明文被 encryptByDES 函数所加密,从名称上是 DES 对称加密需要寻找函数中的 key

1
pass: encryptByDES(s) 

使用右侧工具跟进此函数内部

发现此加密函数逻辑并出现 Key 打开 WT 工具进行复现写入对应设置生成加密算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义密钥和初始化向量
var _key = 'k1fsa01v'; // DES 加密所使用的密钥
var _iv = 'k1fsa01v'; // DES 加密的初始化向量,ECB 模式下无需使用,但在代码中仍然提供了

// DES 加密函数
function encryptByDES(message) {
// 将密钥转换为 UTF-8 格式的字符数组
var keyHex = CryptoJS.enc.Utf8.parse(_key);

// 使用 CryptoJS 库进行 DES 加密
var encrypted = CryptoJS.DES.encrypt(message, keyHex, {
iv: CryptoJS.enc.Utf8.parse(_iv), // 指定初始化向量,ECB 模式下可以不使用
mode: CryptoJS.mode.ECB, // 指定加密模式为 ECB(电子密码本模式)
padding: CryptoJS.pad.Pkcs7 // 指定填充方式为 PKCS7 填充
});

// 返回加密后的消息字符串
return encrypted.toString();
}

# 翼龙贷 DES

寻找关键的请求花费了一些时间,因为加密的 pass 并未在堆栈中显示,需要自行上下找才行,找到了就打入断点对此加密方法进行跟进测试,注意必须是断点打入到加密函数中才能进入如 encrypt 不然会进错

QQ2024616-233248-HD.mp4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
e = e9284d45-cf2a-4e46-9367-f122413ca6b0

// 使用 CryptoJS 将字符串 e 转换为 UTF-8 格式的字符数组
var a = CryptoJS.enc.Utf8.parse(e);

try {
// 使用 DES 加密方法对字符串 t 进行加密
var s = CryptoJS.DES.encrypt(String(t), a, {
mode: CryptoJS.mode.ECB, // 指定加密模式为 ECB(电子密码本模式)
padding: CryptoJS.pad.Pkcs7 // 指定填充方式为 PKCS7 填充
});
} catch (t) {
// 捕获可能的加密错误并输出到控制台
console.log(t);
}

// 返回加密后的消息字符串
return s.toString();

写入算法完成

# sign 逆向

sign 加密出的字段都是和当前正常的 usernamepasswd 有关,每个数据包参数对应签名,参数被修改 sign 签名失效无法测试

1
2
3
username=admin123&&passwd=000000   == a

sign=signMd5(a) // 加密字段

# Python 生成 sign 原理

追踪到的 sign 生成逻辑,给到 AI 分析然后告诉它利用 Python 编写于 Passwdsign 对应的爆破字典, AI 给出具体的生成代码,并会有关键的字典让你填写进去,除了一些固定的参数如版本号或者其他字段,最重要的就是需要生成 signpasswd 字典

之前一直不明白为什么可以一直反复的替换,原来是生成了对应的 sign 字典再来爆破替换的,每个生成的 sign 对应了我们固定修改的值,爆破多个字段记得 Python 生成的逻辑也不同,需要写入两个文件修改来爆破,但是一般是只修改一个字典的值,如密码或者固定的支付中的单号,最终只会产生一个 sign.txt 供替换,所以原字典需要自己准备也就是想要爆破的值


  1. 不同浏览器看入参的选项不同
    goole : 标头拉到最下方可以看到 Query String Parameters 这个就是入参数据,
    火狐:有效载荷页面和 Edge 类似 ↩︎

  2. VUE 框架,重点找 xxx.app.js 即可 ↩︎

更新于 阅读次数