前言
Javascript (.js) 文件一般存储的是客户端代码,Javascript 文件可帮助网站执行某些功能,例如监视单击某个按钮的时间,或者当用户将鼠标移到图像上,甚至代表用户发出请求(例如检索信息)时。有时开发人员可以混淆他们的 javascript 代码,使人无法正常阅读,但是大多数类型的混淆是可以反混淆的;如果你是一个漏洞赏金猎人,你应该花一些时间从中寻找宝藏。如果运气好, 可能会发现严重程度较高的漏洞。
怎么找 JS
1. 手动查找
右键单击所在的页面并单击“查看源代码”,然后开始在 HTML 中查找 .js。如果是手动,那么这是首选方法,因为您会注意到某些 .js 文件仅包含你所在的特定端点的代码,它可能包含你不知道的存在的新的 api 接口。
2. 使用 Burp Suite 专业版
如果使用的是 Burp Suite 专业版,在 Target > sitemap 下,右键点击感兴趣的站点,选择 Engagement tools > Find scripts。使用此特性,你可以导出该应用程序中的所有脚本内容。
3. 使用 waybackurls
这是由英国著名白帽子 tomnomnom 开发的一个工具:github.com/tomnomnom/w…
这个工具的主要逻辑是让你在 archive.org/web/ (这个网站可以理解为数字图书馆, 你可以查找到相对应网站的所有历史网页) 这个网站里面去查找某个目标网站的所有历史数据。
使用方法:
webbackurls target.com | grep "\.js" | uniq | sort
验证 JS 文件
使用 Wayback Machine 可能会导致误报,所以,在收集了 JavaScript 文件的 url 列表之后,我们需要检查这个 js 文件是否真的还存在。
使用 curl 快速检查:
cat js_files_url_list.txt | parallel -j50 -q curl -w 'Status:%{http_code}\t Size:%{size_download}\t %{url_effective}\n' -o /dev/null -sk
使用 hakcheckurl:
# 安装
go install github.com/hakluke/hakcheckurl@latest
# 使用
cat lyftgalactic-js-urls.txt | hakcheckurl
其它相关工具
当然还有其他的一些工具, 这里简单列一下他们的链接, 有兴趣的读者可以自行去研究研究:
- github.com/003random/g…
- github.com/jobertabma/…
- github.com/0xsha/GoLin…
- gau - github.com/lc/gau
- linkfinder - github.com/GerbenJavad…
- getSrc - github.com/m4ll0k/Bug-…
- SecretFinder - github.com/m4ll0k/Secr…
- antiburl - github.com/tomnomnom/h…
- antiburl.py - github.com/m4ll0k/Bug-…
- ffuf - github.com/ffuf/ffuf
- getJswords.py - github.com/m4ll0k/Bug-…
- availableForPurchase.py - raw.githubusercontent.com/m4ll0k/Bug-…
- jsbeautify.py - github.com/m4ll0k/Bug-…
- collector.py - github.com/m4ll0k/Bug-…
JS 美化
大多数时候,我们收集的 JavaScript 文件都是经过压缩、混淆的。
- 压缩工具 (UglifyJS): 这是一个压缩 JS 代码的工具,它也可以作为 npm 包使用。
- 解压缩/美化工具 (JS Beautifier):
其他的相关工具如下:
JS 文件中需要去找的关键信息
1. JS 危险函数
-
将字符串当做代码去执行的三个函数
eval("alert(1)")setTimeout("alert(1)",1000)// 就是下面这么写 Function("alert(1)")() -
innerHTML 函数 如果你发现了这个函数,这意味着如果没有进行适当的处理,XSS 漏洞可能在向你招手, 即使经过了处理,试着看能不能绕过。
- React 中就有一个和 innerHTML 差不多的函数叫做 dangerouslytSetInnerHTML,这个函数也是咱们的重点关注对象。
- 还有 Angular 中的 bypassSecurityTrustX,还有咱们熟悉的 eval 函数。
-
Postmessage 函数 最好先去看看它的官方文档。一旦了解了与 postMessage 相关的可能的安全问题,就可以在 JavaScript 文件中查找实现。在消息发送方,寻找
window.postMessage并在接收方寻找监听器window.addEventListener。// Postmessage 有如下几种发送与监听的方式 // 1. 从父窗口往子窗口发送消息 // 父窗口 <body> <h1>父窗口</h1> <iframe id="childFrame" src="getMessage.html" frameborder="0"></iframe> // 目标子窗口 <button>发送</button> // 点击即可触发发送 <script> const sendPostmessage = document.querySelector('button'); sendPostmessage.addEventListener('click', function () { const iframe = document.getElementById('childFrame'); iframe.contentWindow.postMessage('<img src=x onerror="alert(1)">', '*'); // 通过iframe往iframe引入的子窗口发送postmessage消息 }); </script> </body> // 子窗口 <body> <h1>子窗口</h1> <script> window.addEventListener('message', function (event) { document.querySelector('h1').innerHTML = event.data; // 通过在window中注册message事件监听postmessage消息,当有postmessage消息往这个子窗口发送消息时,触发事件,执行回调函数,将发送的消息写入到h1标签。如果开发者没有对接收消息做过滤,且用innerHTML等危险的DOM操作方法接收了数据,则有可能造成XSS漏洞 }); </script> </body> // 2. 子窗口向父窗口发送消息 // 父窗口 <body> <h1>父窗口</h1> <iframe id="childFrame" src="getMessage.html" frameborder="0"></iframe> // 子窗口 <script> window.addEventListener('message', function(event) { // 开启message监听 if (event.origin !== window.location.origin) return; // 来源验证,event.origin检查接收到的数据的原始url,window.location.origin获取当前窗口的原始url,event.origin !== window.location.origin的意思便是如果来源不是同源的url,则不接收数据 document.querySelector('h1').innerHTML = event.data; }); </script> </body> // 子窗口 <body> <h1>子窗口</h1> <script> window.parent.postMessage('<img src=x onerror="alert(1)">', "*"); // iframe加载后自动发送数据,如果父窗口没有做好来源校验,则很有可能会造成相关漏洞 </script> </body> // 3. 父窗口向弹窗发送消息与接收消息 // 父窗口 <body> <h1>父窗口</h1> <button id="openPopup">打开弹窗</button> <button id="sendPopup">向弹窗发送消息</button> <script> let popup; function openPopup() { // 打开弹窗 popup = window.open('getMessage.html', 'popup', 'width=300,height=200'); } function sendToPopup() { // 向弹窗发送消息 if (popup) { popup.postMessage('<img src=x onerror="alert(1)">', '*'); } } let openpopup = document.querySelector('#openPopup'); let sendpopup = document.querySelector('#openPopup'); openpopup.addEventListener('click', openPopup); sendpopup.addEventListener('click', sendToPopup); // 接收从弹窗来的消息 window.onmessage = function(event) { if (event.origin !== window.location.origin) return; document.querySelector('h1').innerHTML = event.data; }; </script> </body> // 弹窗 <body> <h1>弹窗</h1> <button>Send to Opener</button> <script> function sendToOpener() { // 通过opener向父窗口发送消息 window.opener.postMessage('<img src=x onerror="alert(1)">', '*'); } const sendopener = document.querySelector('button'); sendopener.addEventListener('click', sendToOpener); // 接收从opener来的消息,即父窗口向弹窗发送的消息 window.addEventListener('message', function(event) { document.querySelector('h1').innerHTML = event.data; }); </script> </body> // 4. 兄弟iframe间通信 // 父窗口 <body> <h1>父窗口</h1> <iframe id="iframe1" src="sibling1.html" frameborder="0"></iframe> <iframe id="iframe2" src="sibling2.html" frameborder="0"></iframe> <script> const iframe1 = document.querySelector('#iframe1'); const iframe2 = document.querySelector('#iframe2'); // 接收来自iframe 的消息,并转发到iframe2 window.addEventListener('message', function(event) { if (event.origin !== window.location.origin) return; if (event.data.type === 'toSibling') { iframe2.contentWindow.postMessage(event.data.message, '*'); } else if (event.data.type === 'toSibling1') { iframe1.contentWindow.postMessage(event.data.message, '*'); } }); </script> </body> // 子窗口1 <body> <h1>子窗口1</h1> <button>发送到子窗口2</button> <script> function sendToSibling() { // 发送到父窗口,中转到兄弟 window.parent.postMessage({ type: 'toSibling', message: '<img src=x onerror="alert(1)">' }, '*'); } const btn = document.querySelector('button'); btn.addEventListener('click', sendToSibling); // 接收回复 window.addEventListener('message', function(event) { console.log('Sibling 1 received:', event.data); }); </script> </body> // 子窗口2 <body> <h1>子窗口2</h1> <script> // 接收从中转来的消息 window.addEventListener('message', function(event) { if (event.origin !== window.location.origin) return; document.querySelector('h1').innerHTML = event.data; }); // 回复回兄弟1 window.parent.postMessage({ type: 'toSibling1', message: 'Hi Hacker!' }, '*'); </script> </body>// postmessage存在的漏洞 1. 第一个漏洞在于“postMessage”函数的第二个参数。此参数指定允许哪个源接收消息。使用通配符“*”表示允许任何源接收消息。由于目标窗口位于不同的源,因此发送方窗口在发送消息时无法知道目标窗口是否在目标源。如果目标窗口是另一个源,则另一个源将接收数据。如果发送的数据是敏感信息,则代表着任何网页都有可能通过iframe引入来接收postmessage发送的数据。 2. 第二个漏洞在于接收端。由于侦听器侦听任何消息,攻击者可以通过从攻击者的源发送消息来欺骗应用程序,这将使接收方认为它从发送方的窗口接收了消息。为避免这种情况,接收方必须使用“message.origin”属性验证消息的来源。如使用正则表达式来验证源。 window.addEventListener("message", function(message){ if(/^http://www.examplesender.com$/.test(message.origin)){ console.log(message.data); } }); 显然,这个正则表达式有缺陷,转义“.”字符很重要,这段代码不仅允许来自“www.examplesender.com”的消息,还允许“wwwaexamplesender.com”、“wwwbexamplesender.com”等消息。 3. 第三个漏洞是DOM XSS,它以应用程序将其视为HTML脚本的方式使用消息,例如: window.addEventListener("message", function(message){ if(/^http://www.examplesender.com$/.test(message.origin)){ document.getElementById("message").innerHTML = message.data; } }); 如果发送方发送的数据是<img src=x onerror="alert(1)">,这将触发XSS // 没有postmessage? 就算应用程序本身不使用postmessage,但许多第三方脚本使用postMessage与第三方服务通信,因此应用程序可能会在开发者不知情的情况下使用postMessage。我们可以使用Chrome Devtools在Sources -> Global Listeners下检查页面是否有已注册的消息监听器(以及哪个脚本注册了它) -
String.prototype.search() 一些开发人员使用它来查找一个字符串在另一个字符串中的出现。然而,”.” 在此函数中被视为通配符。
// String.prototype.search()是javascript中字符串的内置方法,如 // let i = "hello world!".search("hello"); // 从javascript中查找子串是否存在,不存在则返回-1,一些开发人员使用它来查找一个字符串在另一个字符串中的出现,而有的在进行安全验证时使用,从而造成了验证的绕过 if ("https://www.baidu.com".search(target.origin) !== -1) { eval(target.data); } // 根据MDN,search的参数是一个正则表达式对象,如果参数不是RegExp对象,并且不具有Symbol.search方法,则会使用new RegExp(regexp)将其隐式转换为RegExp。在正则表达式中,点(.)被视为通配符。换句话说,任何数字起源的字符都可以替换为点。攻击者可以利用它并使用特殊域而不是官方域来绕过验证,例如 www.bai.u.com -
location 相关的几个函数
location: wooyun.2xss.cc/bug_detail.…location.href: wooyun.2xss.cc/bug_detail.…location.pathname: wooyun.2xss.cc/bug_detail.…
-
document.cookie
-
window.name
window.name是浏览器窗口的一个字符串属性,用于标识或存储窗口的“名称”。它本质上是一个持久化存储机制,类似于sessionStorage,但更简单且跨页面持久。只要窗口不关闭、不刷新,window.name就保持不变。即使导航到新URL,它也不会丢失。如果用window.open打开新窗口,它会继承opener的name。有这几种访问方式:当前窗口window.name,父窗口top.name,iframe中parent.name。 比如有一个页面存在XSS,但利用需要写很多javascript代码,同时该注入点有字符限制。攻击者便可以首先编写钓鱼页: var payload = btoa('完整 exploit 代码'); window.open('https://site.com/users/attacker', payload); // 打开存在XSS的页面 接着存在XSS的页面使用 eval(atob(window.name)) atob对payload进行base64解码,eval执行解码后的JS -
localStorage 以及 sessionStorage
2. 过时的依赖和框架
从 JS 代码中查找过时的依赖项,可以直接使用 retire.js 来扫描漏洞。可以在以下链接中找到该项目:
3. 敏感信息
有时,开发人员会在客户端 JS 代码中留下大量信息,例如密码、API 密钥等硬编码。从 JS 代码中找到这些信息。
例如,AWS 密钥正则表达式可能如下所示:
(?i)aws(.{0,20})?(?-i)['\”][0–9a-zA-Z\/+]{40}['\”]
更多参考工具:
相关的漏洞报告:
4. 隐蔽的接口
有时,不是对接口进行模糊测试,而是从 Javascript 文件中查找隐蔽接口会更有效。这些接口可能是一些废弃的服务(开发人员有时忘记删除它)或用户不应该访问的服务(如隐蔽的 API)。如果你找到了,这些隐蔽接口通常比主要的 Web 应用程序更容易受到攻击。
有一些很方便的工具帮助我们从 js 文件中提取这些接口:
5. 开发人员的注释
开发人员注释(例如 // 这是一个开发评论 或 /* 这是一个多行开发评论 */)有时可以包含诸如代码何时发布或发生的任何更新之类的信息(有时候还会注释关于 XSS 过滤,这有助于我了解他们如何修复它并去绕过)。如果代码是旧版本的,那么你发现问题的机会就更大。
6. js.map 文件
还有一个比较特殊的文件, 是以 js.map 为后缀的文件, 非常多 Webpack 打包的站点都会存在 js.map 文件。通过还原前端代码找到 API, 间接性获取未授权访问漏洞。
简单说,Source map 就是一个信息文件,里面储存着位置信息。转换后的代码的每一个位置,所对应的转换前的位置。有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码, 这无疑给开发者带来了很大方便。
相关的处理工具有 (可以使用它们将代码还原):
- www.npmjs.com/package/res…
- github.com/paazmaya/sh…
- www.npmjs.com/package/rev…
- github.com/rarecoil/un…
在 React.js 上读取 .js 文件的示例
在查看一个 React.js 的网站的时候, 发现一个 settings.js 文件: app.js 是我们的重点。我们要找什么?新的端点、参数,也许还有 api key。打开它时,我们会看到似乎是“乱七八糟”的东西。
第一步:美化。 美化会将我们的代码变成可读的代码。复制代码内容并使用 Javascript 美化器,例如:beautifier.io/
第二步:搜索关键字。
现在我们的代码可读了,让我们开始寻找新的端点、参数,也许还有 api key。通过搜索关键字 pathname,我能够找到网站上使用的所有端点,这些端点扩展了攻击面以查找错误,并开始查找有关其 API 的信息。
要查找的其他常见关键字:
url:POSTapiGETsetRequestHeadersend((注意: 只有一个 (,因为它在发出 Ajax 请求时使用!)headersonreadystatechangevar {xyz} =getParameter()parameter.theirdomain.comapiKeypostMessagemessageListenger.innerHTMLdocument.write(document.cookielocation.hrefredirectUrlwindow.hash
自动化读取工具
如果是自动读取的话,可以利用上面的三个工具:
JS 混淆处理
如果 js 被混淆了,可以使用下面的工具:
大多数类型的混淆都可以解决。