客户端里的“盲人摸象”?用恶意JavaScript搞定未知应用
应用渗透测试人员在编写定制化的恶意JavaScript时,常常目标明确。他们手握测试账号,熟知应用的业务逻辑,知道要窃取何种“核心数据”——无论是批量下载文档、提升自身权限,还是发起转账操作。编写这样的Payload,如同拿着地图寻宝。
然而,对于红队成员而言,情况截然不同。他们面对的往往是完全陌生的应用:功能未知、逻辑未知,甚至连有没有用户登录都是未知数。更关键的是,除了XSS漏洞,红队还可能有其他向应用注入恶意JavaScript代码的途径。这种情况下,预先定制化的Payload基本失效了,怎么办?
这正是JS-Tap诞生的背景。它是一款专门为红队打造的通用JavaScript武器化工具。它不执着于直接攻击应用服务器,而是将注意力集中在客户端,像一个无声的“观察者”,系统性地提取对攻击者有用的情报。即便你对目标应用一无所知,甚至目标用户都未登录,它也能运作。
JS-Tap能窃取哪些“战利品”?
一旦JS-Tap的有效载荷在目标浏览器中运行,它就会开始悄悄收集以下信息:
- 客户端指纹:IP地址、操作系统、浏览器版本。
- 用户输入:文本框中的按键记录(最典型的应用就是窃取登录凭据)。
- 浏览轨迹:用户访问过的所有URL。
- 存储数据:未设置
HttpOnly标志的Cookie、LocalStorage和SessionStorage。
- 页面快照:当前页面的HTML源码(可选)和屏幕截图。
- 网络请求:XHR和Fetch API调用的完整副本(可选,需开启Monkeypatch)。其中包含:
- 请求端点
- 请求方法(GET、POST等)
- 设置的请求头(包括关键的
Authorization头)
- 请求体和响应体
收集到的所有数据,都会实时汇总到一个基于Web的JS-Tap门户中,供攻击者监控和分析。
两种模式,两种玩法:trap vs implant
JS-Tap的Payload提供了两种操作模式,以应对不同的攻击场景。
陷阱模式 (trap)
这是用作XSS Payload时的标准模式。XSS攻击有个痛点:Payload的生命周期很短暂。一旦用户关掉弹窗,或者从当前页面跳转,注入的恶意脚本就会消失。
陷阱模式利用iframe陷阱技术解决了这个问题。Payload一旦执行,会创建一个覆盖全屏的iframe,并将用户重定向到应用的某个合法起始页(比如后台登录页)。用户看似在正常操作,但实际上,他一直被困在我们构建的恶意页面之上,Payload得以持续运行。
在这个过程中,JS-Tap还会巧妙地伪造浏览器地址栏,使其与iframe内的URL保持一致,进一步打消用户的疑虑。
注意:如果目标应用设置了严格的CSP(内容安全策略)或X-Frame-Options标头,这种技术可能会失效。一些基于JavaScript的“框架破坏者”也能阻止iframe陷阱。
植入模式 (implant)
当你已经拿到一台应用服务器或托管JavaScript文件的服务器权限时,植入模式就派上用场了。你可以直接将JS-Tap的Payload注入到一个被广泛引用的JS文件里(比如jQuery.js或main.js),实现“守株待兔”。
植入模式无需iframe陷阱,任何访问了被感染页面的用户,其浏览器都会加载并运行JS-Tap,悄无声息地成为我们的“目标”。
实战演示:给WordPress下个套
光说不练假把式。我们用JS-Tap来对付一个存在反射型XSS漏洞的WordPress站点,看看具体如何操作。
目标:诱骗管理员登录WP后台、检查插件并为新员工创建一个管理员账户。我们的目标是窃取其登录凭据和新账户的密码。
图中展示了WordPress中可被利用的XSS漏洞点。

接下来,启动JS-Tap服务端,获取Web管理后台的登录凭据,并将Payload配置为陷阱模式,起始页设定为WordPress管理员登录页。

我们将漏洞利用的PoC替换为指向jsTapServer的Payload (telemlib.js),用户一旦访问以下URL就会中招:
http://targetapp.localdemo/wp-content/plugins/sketchyPlugin/unauthXSS.php?param=
当目标用户打开此链接时,看到的并非杂乱代码,而是一个“正常”的登录页面。

实际上,这个页面正是我们设置的iframe陷阱。用户能看到地址栏是正常的,便不会起疑。假设我们的钓鱼话术成功,管理员开始登录。

登录成功,进入仪表盘。注意右上角用户名和看似正常的URL,受害者毫无察觉。

管理员按指示查看了插件页面,未发现异常。

随后,管理员转到用户列表,开始为“新员工”创建账户。

管理员进入“添加新用户”页面。他创建了newAdmin账户,但出于安全考虑,并没有手动输入密码,而是使用了WordPress自动生成的强密码(我们假设他会把密码记录下来发给新员工)。这正是我们的机会。

至此,用户的操作告一段落。让我们回到JS-Tap门户,看看都捞到了什么。左侧客户端列表已出现一个新会话。

点击该客户端,右侧会按时间线列出所有捕获到的事件,如URL访问、Cookie信息、本地存储数据等。

在持续滚动的事件流中,我们看到了“HTML Scraped”、“URL Visited”和一个至关重要的“Screenshot Captured”事件。

点开“View Code”就能查看抓取到的页面HTML源码。

继续往下看,激动人心的“User Input”事件出现了!我们成功捕获了管理员输入的账户名admin和密码Password123!。

第一波战利品到手!赶紧把它们存到客户备注里。

接着,我们看到了WordPress仪表盘的截图,可以确认账户确实是管理员。

在接下来的事件中,我们看到了新用户注册表单的页面截图。

紧随其后的就是一系列新的“User Input”事件,记录了为newAdmin用户填入的信息。

咦?密码呢?管理员确实没有手动输入密码,而是用了自动生成的功能。我们把那张表单截图放大看看。

果然,密码区域显示的是“Generate password”(生成密码)按钮,说明密码是自动生成的。但既然页面显示了密码,它就肯定藏在HTML源码里!我们立刻回头去下载这个页面的HTML代码。

在源码中搜索“Generate password”,我们迅速定位到了关键代码段。一个input标签的data-pw属性,赫然暴露了自动生成的强密码!

大获全胜!所有收获都记录到了客户备注里。

进阶功能:拦截API调用,看穿单页应用
WordPress只是开胃菜。对于现代动态Web应用(尤其是单页应用),页面不刷新,数据都通过API请求异步加载。这时,JS-Tap的另一项绝活——Monkeypatch API——就派上用场了。
JavaScript一个奇妙的特性是“猴子补丁”(Monkeypatching),即在运行时动态修改代码的行为。JS-Tap正是利用这一点,在你不知情的情况下,悄悄修改了浏览器底层的XMLHttpRequest (XHR) 和Fetch API,从而拦截所有网络请求。
我们有一个用于测试的演示应用,它通过三种方式(XHR、Fetch、jQuery)发起网络请求以获取“生命奥秘”的答案。

在注入Payload前,我们要在telemlib.js的配置中,将起始页指向这个应用,并启用API拦截和API调用后截图的功能。

启用 monkeypatch API 原型:

设置API调用后截图及其延迟:

配置修改完毕,注入Payload。用户点击完三次按钮后,JS-Tap门户里出现了新的客户端。

查看新客户端的事件,我们首先捕获到了一个藏在LocalStorage中的SECRET_API_KEY_FALL_2023!,它是一个鉴权令牌。

把它也保存到备注里。

继续往下滚动事件列表,API调用的细节一览无余。这里展示了一个XHR API调用的完整过程:请求端点/api/xhrAnswer、方法POST、设置的Content-Type和Authorization头,以及一个“View API Call”按钮。

点击“View API Call”,请求体和响应体并排展示,请求发送{"request": "answer"},响应返回{"answer": "42 of course!"}。

紧接着,延迟的截图也送到了,页面上果然显示着“Answer: 42 of course!”。

Fetch API的调用也被以同样方式拦截。可以看到,响应中的答案是“Definitely vegemite.”。

最后,连通过jQuery发出的请求,在底层同样被XHR API拦截并捕获,暴露了其请求端点为/api/jqueryAnswer。

任务结束,我们可以把针对所有客户记下的笔记一键导出。

总结
JS-Tap为红队提供了一套极具价值的通用客户端攻击武器库。它不依赖对应用的后验知识,能将大量看似无关的客户端行为,转换成可供攻击者利用的情报。虽然对于渗透测试而言,定制化Payload往往能造成更深层的影响,但JS-Tap在红队行动中“广撒网、盲打击”的场景下,无疑是一个不可或缺的强大工具。
对JS-Tap感兴趣?你可以在其GitHub仓库找到完整的代码库:https://github.com/hoodoer/JS-Tap。如果你有任何问题或改进建议,也欢迎与作者 @hoodoer 直接交流。