이 글에선 DOM Clobbering이 무엇인지에 대해 소개하고, DOM 취약점을 Clobbering 기술을 이용하여 어떻게 exploit 해야하는지, 방어는 어떠한 방식으로 이루어지는지에 대해 설명할 것이다.
0. Basic
1) DOM Clobbering XSS vs DOM Based XSS
처음 DOM Clobbering XSS를 접했을 때, DOM Based XSS랑 다른건가? 라는 생각이 들었다.
XSS의 종류에 대해 간단하게 짚고 넘어가보자.
위 그림을 보면 알 수 있듯이, XSS에는 2가지 종류 Stored(Persistent) XSS, Reflected XSS로 구분이 가능하다.
DOM Based XSS는 Stored 형태로 나타날 수도 있고, Reflected 형태로 나타날 수도 있다. 따라서 2가지 영역에 걸쳐있다.
DOM Based XSS 중, DOM Clobbering XSS 라는 세부 공격 기법이 존재한다.
다시 말해, DOM Based XSS의 하위 개념이 DOM Clobbering XSS이다.
2) DOM Clobbering 이란..?
- DOM(Document Object Model): 웹 페이지에 대한 인터페이스로, 여러 프로그램들이 페이지의 콘텐츠 및 구조, 그리고 스타일을 읽고 조작할 수 있도록 한다.
DOM은 다믕과 같은HTML문서의 객체 기반 표현 방식으로 표현된다.
<!doctype html> <html lang="en"> <head> <title>Helloworld</title> </head> <body> <h1>Hello, World!</h1> <p>TEST, 1, 2, 3, ...</p> </body> </html>
- Clobbering: 파일 또는 데이터의 삭제 또는 겹쳐 씀(Overwrite)을 의미한다.
DOM clobbering 은 사용자(Client)가 HTML을 수정할 수 있는 경우, 임의의 DOM 객체를 삽입하여 다른 DOM 객체를 삭제하거나 덮어쓰는 방식으로 변조하는 공격이다.
DOM Clobbering의 궁극적인 목표는 DOM을 조작하고 웹페이지에서 동작하는 행위를 컨트롤하는 것이다.
일반적인 Stored XSS, Reflected XSS 공격의 경우 HTTP Response 에 악성 스크립트 구문이 포함되어 브라우저로 전달되지만, DOM based XSS의 경우 서버 응답에서 악성 스크립트 존재 여부를 감지할 수 없다.
다시 말해, 서버로부터 받는 HTTP Response 에는 악성 스크립트가 존재하지 않지만 클라이언트측 코드(View)는 DOM Clobbering 으로 인해 악성 스크립트가 실행될 수 있다는 것이다.
3) 기본적인 공격 원리
아래와 같은 태그가 존재한다고 가정해보자.
<div id="divTag"></div>
해당 태그에 javascript를 이용하여 접근하기 위해서 아래와 같은 2 가지 방법이 존재한다.
// First Method
document.getElementById("divTag")
// Second Method
window.divTag
첫 번째 방법은 일반적으로 자바스크립트에서 쓰이는 방식이다.
두 번째 방법을 보면 알 수 있듯이 브라우저는 상위 DOM 객체에서 자식 DOM 객체에 .(점)을 이용한 직접 접근을 허용한다.
하지만 이 기능이 제3자에게 제공된다면 전역변수나 미리 정의되 어있는 객체 속성과 충돌할 수 있으며, DOM Clobbering 공격이 이루어질 수 있다.
해당 내용은 아래에서 조금 더 자세히 다뤄보겠다.
1. DOM Clobbering (Feat. HTML Collection)
1) 공격 원리
DOM 환경에서 id가 같은 객체가 2개 이상이 존재하면 HTML Collection 객체가 되어 아래 예제와 같이 2개의 객체가 모두 인식된다.
window.aTag 로 접근했을 때, HTMLCollection 이라는 객체가 반환된다.
객체의 내용물은 다음과 같다.
먼저 선언된 a태그와 나중에 선언된 a태그가 모두 존재하는 것을 확인할 수 있다.
여기서 중요한 것은 HTMLCollection 객체에서 접근할 수 있는 하위객체에 aTag 와 hello 가 존재한다.
// aTag (id 속성 값)
window.aTag.aTag
=> <a id="aTag"></a>
// hello (name 속성 값)
window.aTag.hello
=> <a id="aTag" name="hello" href="abcd" class="world">asdf</a>
만약 id가 같은 2개 이상의 태그를 정확하게 구분하고 싶다면?
<a id="aTag" name="a1">dohunny.tistory.com</a>
<a id="aTag" name="a2">dohunny.tistory.com</a>
동일한 id를 가진 태그는 아래와 같이 name을 통해 구분할 수 있다.
따라서 name 속성도 DOM Clobbering 공격에 사용할 수 있다.
( window 의 child 객체의 child 객체를 변조하는 경우에 사용됨 e.g. window.CONFIG.debug )
이 개념을 활용하기 위해 예제를 통해 알아보자.
2) DOM Clobbering 예제 1 - id attribute
아래와 같은 코드가 있고, 코드 삽입 영역에 태그를 삽입하여 "Congratulation"을 출력시키는 것이 목표이다.
<html>
<!--
Code Injection Zone
-->
<script>
if(window.CONFIG){
document.write('Congratulation!!!');
}
</script>
</html>
window.CONFIG 라는 객체가 정의되어있지 않아, if문 내부에 있는 코드가 기본적으로 실행되지 않는다.
위에서 설명했던 id 속성을 이용하면 쉽게 해결할 수 있다.
코드 삽입 영역에 a 태그를 삽입하고, CONFIG 라는 id를 할당해주면 window.CONFIG 라는 객체가 만들어진다.
<html>
<a id="CONFIG"></a>
<script>
if(window.CONFIG){
document.write('Congratulation!!!');
}
</script>
</html>
그러면 아래 사진과 같이 document.write("Congratulation!!!")이 실행되어 HTML 상에서 출력된 것을 확인할 수 있다.
3) DOM Clobbering 예제 2 - value attribute
그렇다면, window.CONFIG.value 값을 변조해야 한다면 어떻게 접근할까? 예제 코드는 다음과 같다.
<html>
<!--
Code Injection Zone
-->
<script>
window.CONFIG = window.CONFIG || {
version:"2021.08.26",
toLocation:"http://example.com",
value:"FLAG{fake_flag}"
}
if (window.CONFIG.value === "FLAG{helloworld}") {
document.write('Congratulation!!! You know DOM Clobbering!!!');
}else{
document.write('Nope..!');
}
</script>
</html>
window.CONFIG.value는 "FLAG{fake_flag}"가 기본값으로 설정되어있다.
목표는 window.CONFIG.value의 값을 "FLAG{helloworld}"로 변조해야한다.
a 태그의 id 속성과 name 속성을 사용한다면 window.CONFIG.value를 a 태그로 바꿀 수는 있지만, 원하는 문자열로 변경하긴 힘들다.
이 때, value라는 속성을 기본적으로 내제하고 있는 태그를 이용한다면 변조가 가능하다.
바로, input 태그이다.
input 태그의 value 속성을 이용한다면
input 태그에 CONFIG 라는 id를 할당해주고, value 속성에 "FLAG{helloworld}"를 할당해주면 된다.
이로써 HTMLCollection을 이용한 DOM Clobbering 개념 설명은 끝이다.
2. DOM Clobbering (Feat. Form Tag)
브라우저는 form 태그 내에 존재하는 객체를 id나 name 속성을 통해 .(점)으로 직접 접근을 허용한다.
이는 window 객체와 form 객체가 비슷하게 접근할 수 있다.
아래의 페이지에서 자세히 확인해보자.
<html>
<form>
<input type="text" />
<input type="submit" />
</form>
</html>
window 객체에서 했던 방법과 비슷하게 form 태그에서도 활용할 수 있다.
이 때, input 태그의 name 속성 또는 id 속성을 이용하여 submit 함수를 객체로 DOM Clobbering 시킬 수 있다.
<html>
<form>
<input type="text" name="submit" /> <!-- name 속성 변경 -->
<input type="submit" />
</form>
</html>
해당 페이지에서 위에서 submit 함수에 접근했었던 것처럼 접근하게 된다면 아래와 같은 결과를 얻을 수 있다.
submit 이라는 함수가 input 태그로 덮여버린 것을 확인할 수 있다.
3. DOM Clobbering XSS (Feat. API for a tag & area tag)
1) 공격 원리
<a> 태그와 <area> 태그의 정확한 객체명은 HTMLHyperlinkElementUtils 이다.
이 객체의 특성 상, 해당 객체를 문자열화(toString)할 경우, href(URL)을 반환한다.
다시 말해, href 속성에 URL이 존재할 때, <a> 태그에서 toString() 함수를 호출할 경우, a태그가 문자열(URL)로 반환된다.
<a id="CONFIG" href="https://dohunny.tistory.com/">블로그 주소</a>
위와 같은 태그가 존재할 때, 해당 태그를 문자열화 해보면 아래와 같은 결과를 확인할 수 있다.
javascript 에서 Object(객체)와 String(문자열)이 연산될 때에는 Object.toString() 함수가 실행된다.
이로 인해 <a>(객체)와 ''(빈문자열)을 + 한 경우에도 toString() 함수가 호출되어, 결과적으로 href 가 출력되었다.
2) DOM Clobbering XSS 예제
아래와 같은 페이지가 존재하고, 해당 페이지에서 Redirect를 발생시켜, 다른 페이지로 이동시키는 것이 목표이다.
<html>
<!--
Code Injection Zone
-->
<script>
window.CONFIG = window.CONFIG ||{
version:"2021.11.18",
toLocation: null,
value:"FLAG{fake_flag}"
}
if (window.CONFIG.toLocation != null) {
location.href = window.CONFIG.toLocation;
}else{
document.write("nope...!");
}
</script>
</html>
location.href 의 값을 바꾸면 페이지의 URL이 변경된다.
window.CONFIG.toLocation 은 기본적으로 null 값이기 때문에 if문이 실행되지 않는다.
if문 내에서 location.href 에 window.CONFIG.toLocation 이 대입되므로 해당 객체를 변조해야한다.
location.href 는 기본적으로 문자열 객체이다.
window.CONFIG.toLocation 객체가 a 태그일 경우, location.href 에 대입될 때 toString()을 호출하게 된다.
이 예제의 풀이방법은 다음과 같다.
1. a 태그를 2개 선언한다. (HTMLCollection => name 속성을 사용하기 위함)
2. 2개의 태그 중, 하나에 name 속성을 "toLocation" 으로 지정한다. (toLocation 객체를 변조하기 위함)
3. name="toLocation" 인 a 태그의 href 속성을 URL(리다이렉트 될 목적지)로 지정한다. (toString 하면 URL을 반환하기 위함)
<html>
<!-- Code Injection Zone -->
<a id="CONFIG"></a>
<a id="CONFIG" name="toLocation" href="javascript:alert('XSS')"></a>
<script>
window.CONFIG = window.CONFIG ||{
version:"2021.11.18",
toLocation: null,
value:"FLAG{fake_flag}"
}
if (window.CONFIG.toLocation != null) {
location.href = window.CONFIG.toLocation;
}else{
document.write("nope...!");
}
</script>
</html>
해당 코드를 페이지로 띄워보면 아래와 같은 결과를 얻을 수 있다.
이로써 HTMLHyperlinkElementUtils API을 이용한 DOM Clobbering 개념 설명은 끝이다.
4. Protection
1) 간접적 메소드 호출 및 접근자를 사용
이러한 방식을 활용하면 실제 객체에 어떤 속성에 정의되었느냐에 상관없이 본래 속성을 접근할 수 있게 된다.
// Example 1
HTMLFormElement.prototype.reset.call(elm)
// Example 2
Object.getOwnPropertyDescriptor(Node.prot otype,'textContent').set.call(elm,'')
하지만 사용이 번거로워 잘 사용되지는 않는다.
2) id, name 등 식별자 attribute를 제거할 수 있는 DOMPurify 와 같은 라이브러리 이용
실제로 많이 사용되는 방법이라고 한다.
다음 사이트를 참고하면 좋을 듯 하다.
3) Object.freeze()로 객체 프리징
해당 방법은 객체 Overwrite 또는 삭제 등 객체의 상태가 변경되는 것을 막는다.
하지만 이 방법은 객체가 프리징 되기 때문에 자바스크립트를 이용한 동적인 페이지를 구성하지 못한다는 한계점이 존재한다.
4) OR 연산자 로 전역변수를 사용하는 등의 좋지 않은 코드패턴은 피하기
습관으로 들어있으면 좋을 듯 하다.