AngularJS는 웹 상에서 많이 사용되는 개발 프레임워크입니다. 이런 프레임워크에는 당연히 보안 로직, 정책이 들어가게되죠. 그 중에 대표적인 것은 바로 SandBox 입니다. Sandbox 로 인해 우리는 성공한 공격이 영향력이 없어지는 진귀한 광경을 목격하게되죠.
많은 해커들은 버전별로 AngularJS sandbox를 우회하려 하였고 덕분에 각 버전별로 여러 사람이 만든 우회 루틴이 존재합니다. Angular에서 sandbox 는 1.1.5 버전 이후로부터 적용되었고 sandbox, 즉 Angular Expression으로 인해 로컬영역으로 묶인 sandbox 밖에서 함수 호출이 실패하게 됩니다. 자주 사용하는 DOM 기반의 XSS에 영향을 줄 수 있는 부분이죠.
오늘은 AngularJS에 적용된 sandbox를 우회하는 방법과 Constructor에 대해 이야기할까 합니다.
Constructor
Javascript에선 모든 데이터가 Object입니다. 우리가 사용하는 Object 타입의 데이터, String, Array 등 모든 데이터는 Object로 표현됩니다. 아니 정확하겐 가장 중심이 되는 뿌리가 Object입니다.
이러한 Object가 생성될 때 초기화 등을 위해 동작할 함수는 Constructor, 즉 생성자입니다. 타 언어에서의 생성자와 유사한 기능을 수행합니다. 그래서 새로운 Object를 선언하면 Constructor에 의해 생성이 아래 코드로 보면 String
인 alert(45)
가 함수 형태로 생성되어 실행되는 걸 알 수 있죠.
|
|
Escape Sandbox of AngularJS 1.6
이전 버전과는 다르게 오히려 최신버전에서는 아주 간단하게 우회가 가능합니다. Constructor는 생성자이기 떄문에 Sandbox 외부에서 함수 실행이 가능합니다. 그리고 각 Object의 constructor 들은 constructor의 constructor를 가지고 있습니다.
|
|
보통 constructor.constructor
는 Function()
을 가리키기 떄문에 아래와 같이 String으로 원하는 함수를 넘겨서 실행할 수 있습니다. (이건 Eval과 비슷한 특징을 가지죠)
|
|
그래서 위에 보여드렸던 코드로 쉽게 sandbox 우회가 가능합니다. 너무 간단해서.. 더 설명드릴게 없네요. 그럼 이전에는 어떤 방식으로 풀어나갔었는지 한번 볼까요?
Escape Sandbox of AngularJS 1.2
Angular에 적용된 초기버전의 sandbox는 ensureSafeMemberName() 함수를 통해 구현되었고 이 함수는 Javascript 속성에 위에서 말한 생성자(constructor)가 있는지 검사하는 로직을 가집니다.
|
|
초기의 우회루틴은 아래와 같은 형태로 이루어졌습니다.
|
|
임의의 변수에 constructor 라는 문자열 값을 저장한 후 Object를 이용하여 constructor를 불러오고 인자값으로 실제 실행할 함수 이름과 값을 넘겨 실행합니다.
|
|
간단하게 요약하자면 name의 문자열로 필터링을 하니 필터링 다른 변수에 속성값을 저장하고 로드해서 사용하는 식으로 우회된 케이스이지요. 물론 현재 이 방법은 패치가 되어있습니다. 1.2 버전 이하에서만 영향력이 있죠.
다만 우리는 우회했던 방법에 대해선 잘 파악해둬야합니다.
Escape Sandbox of AngularJS 1.4
여러 버전으로 업데이트 되면서 로직에 변화가 생기고 새로운 함수들이 나타났습니다. 1.4 버전에선 __proto__
와 __defineSetter__
를 통해 Sandbox 우회가 가능합니다. __proto__
는 Safari, IE11에서 전역 선언이 가능해집니다. sandbox는 지역선언이 된 object를 해당 지역 이외로 나가지 못하도록(정확히는 나가면 실행이 되지 않게 함) 하지만 기능으로 전역 사용이 가능한 것이 나오고 말았죠.
해커들은 이를 놓치지 않았습니다. __proto__
를 이용해서 전역변수와 같이 해당 area 이외 구간으로 넘어갈 수 있으니 쉽게 Bypass가 가능합니다. 바로 Prototype Pollution이죠.
기존
|
|
__proto__
내 임의의 영역(코드에선 hwul)에 Function 을 집어넣고 x를 호출하여 익명함수로 만듭니다.
|
|
함수 형태로 넘겨주면 실행이 되니깐 정상적으로 alert()
함수가 실행됩니다. 물론 전역으로요.
|
|
Firefox 51에선 __lookupGetter__
를 통해서 함수의 호출자를 얻을 수 있습니다. 이땐 Firefox만 가능했던 기능이죠. 비슷한 맥락으로 __lookupGetter__
를 이용해 함수 호출자를 얻은 후 location 을 javascript 구문으로 바꾸어 상위 영역에서 함수를 호출합니다.
|
|
이런식으로 Javascript단에서의 SandBox 탈출이 가능하죠. (Angular 기준)
AngularJS Sandbox Escape cheatsheet
1.0.1 - 1.1.5 == works
|
|
1.2.0 - 1.2.18 == works
|
|
1.2.19 - 1.2.23 == works
|
|
1.2.24 - 1.2.29 == not working
|
|
1.3.0 == not working (calls $$watchers)
|
|
1.3.1 - 1.5.8 == not working (calls $eval)
|
|
1.6.0 > == works (sandbox gone)
|
|
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
- https://www.hahwul.com/cullinan/prototype-pollution/