This lab contains a DOM-clobbering vulnerability. The comment functionality allows "safe" HTML. To solve this lab, construct an HTML injection that clobbers a variable and uses XSS to call the alert() function.
현재 해당 블로그의 댓글 기능은 "안전한" HTML 을 허용한다는 것을 보니, 아마 댓글창에 익스 코드를 삽입하는 문제인 것 같다. 변수를 clobber하여 XSS로 alert() 함수를 호출하는 DOM clobbering 문제이다.
저번 문제와 달리 practitioner가 아니라 expert 문제라서 훨씬 어려웠는데, 구글링 해보니 한국인들의 풀이가 없어서.. 내가 한국어로 적어보려고 함
그 전에 DOM Clobbering 개념을 코드로 잠깐 복습해보자.
<!DOCTYPE html>
<html>
<body>
<h1>DOM Clobbering</h1>
<a id="link" href="first-link">First element</a>
<a id="link" href="second-link">Second element</a>
</body>
</html>
위 코드를 작성한 html 사이트의 콘솔에서 window.link 를 입력하면 위와 같이 HTMLCollection 배열이 출력된다.
HTMLCollection은 본질적으로 HTML 요소로 구성된 배열이므로, 우리가 흔히 사용하는 배열처럼 위와 같이 인덱스로 각 요소에 대해 접근 및 반환이 가능하다.
또한 두 번째 앵커 태그의 이름이 url로 지정되어 있으므로, window.link.url을 입력해도 두 번째 앵커 태그가 출력된다.
반면에 이를 똑같이 크롬이 아닌 firefox와 같은 기타 브라우저에서 window.link.url을 실행했을 때에는 접근이 막혀서 출력되지 않는다. 아마 이것 때문에 해당 문제에서 Chrome을 이용하라고 한 것 같다.
서버에 들어간 뒤 먼저 댓글 기능의 코드부터 확인해보자.
<!DOCTYPE html>
<html>
<head>
<link href=/resources/labheader/css/academyLabHeader.css rel=stylesheet>
<link href=/resources/css/labsBlog.css rel=stylesheet>
<title>Exploiting DOM clobbering to enable XSS</title>
</head>
<body>
<script src="/resources/labheader/js/labHeader.js"></script>
<div id="academyLabHeader">
<section class='academyLabBanner'>
<div class=container>
<div class=logo></div>
<div class=title-container>
<h2>Exploiting DOM clobbering to enable XSS</h2>
<a class=link-back href='https://portswigger.net/web-security/dom-based/dom-clobbering/lab-dom-xss-exploiting-dom-clobbering'>
Back to lab description
<svg version=1.1 id=Layer_1 xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x=0px y=0px viewBox='0 0 28 30' enable-background='new 0 0 28 30' xml:space=preserve title=back-arrow>
<g>
<polygon points='1.4,0 0,1.2 12.6,15 0,28.8 1.4,30 15.1,15'></polygon>
<polygon points='14.3,0 12.9,1.2 25.6,15 12.9,28.8 14.3,30 28,15'></polygon>
</g>
</svg>
</a>
</div>
<div class='widgetcontainer-lab-status is-notsolved'>
<span>LAB</span>
<p>Not solved</p>
<span class=lab-status-icon></span>
</div>
</div>
</div>
</section>
</div>
<div theme="blog">
<section class="maincontainer">
<div class="container is-page">
<header class="navigation-header">
<section class="top-links">
<a href=/>Home</a><p>|</p>
</section>
</header>
<header class="notification-header">
</header>
<div class="blog-post">
<img src="/image/blog/posts/9.jpg">
<h1>I Wanted A Bike</h1>
<p><span id=blog-author>Carrie Atune</span> | 28 June 2024</p>
<hr>
<p>When it comes the holidays more and more people are finding it hard to make ends meet. They turn to Social Media and ask for advice, how they can make Christmas magical for their little ones when they don't have any spare money. So many kind humanitarians reassure them that time is the best gift they can give their kids, a day of fun-filled frolicking.</p>
<p>I didn't want to spend the day with my parents watching movies and eating chocolate. I wanted a bike. Not having money, and not buying me a bike does make you a rubbish parent regardless of what all the nosey parker do-gooders tell you. They say it is all about 'making memories', well that sucks, I'll NEVER forget the Christmas I didn't get that bike, how's that for making memories.</p>
<p>Why do all these people dishing out their meaningless platitudes think that families want to spend the day trapped in the house together, it's not like we even have any old relatives to visit and brighten up the proceedings with a snort and inappropriate passing of wind during dinner. You want to keep me trapped like a prisoner in the house all day and make memories, then you should have bought me that bike.</p>
<div/>
<hr>
<h1>Comments</h1>
<span id='user-comments'>
<script src='/resources/js/domPurify-2.0.15.js'></script>
<script src='/resources/js/loadCommentsWithDomClobbering.js'></script>
<script>loadComments('/post/comment')</script>
</span>
<hr>
<section class="add-comment">
<h2>Leave a comment</h2>
<form action="/post/comment" method="POST" enctype="application/x-www-form-urlencoded">
<input required type="hidden" name="csrf" value="N6xOiYmiK8fc0QVFSBJe947MloJpnKkL">
<input required type="hidden" name="postId" value="7">
<label>Comment:</label>
<div>HTML is allowed</div>
<textarea required rows="12" cols="300" name="comment"></textarea>
<label>Name:</label>
<input required type="text" name="name">
<label>Email:</label>
<input required type="email" name="email">
<label>Website:</label>
<input pattern="(http:|https:).+" type="text" name="website">
<button class="button" type="submit">Post Comment</button>
</form>
</section>
<div class="is-linkback">
<a href="/">Back to Blog</a>
</div>
</div>
</section>
<div class="footer-wrapper">
</div>
</div>
</body>
</html>
댓글 코드를 살펴보면 위와 같이 두 개의 js 파일을 불러오고 있는데, 각 파일의 코드들을 각각 살펴보자.
1. loadCommentWithDomClobbering.js
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'}
위 코드에서 defaultAvatar 변수는 기본 avatar의 URL을 포함하는 객체로 설정된다.
window.defaultAvatar가 이미 정의되어 있으면, 그 값을 defaultAvatar 변수에 할당하고,
window.defaultAvatar가 정의되어 있지 않으면, 후자의 객체를 할당한다.
여기서 DOM clobbering 취약점이 발생할 수 있는 부분은 바로 window.defaultAvatar 속성이다.
예를 들어 HTML에서 id 속성이 defaultAvatar인 요소가 존재한다면, window.defaultAvatar 는 해당 DOM 요소를 우선적으로 참조하게 되어 자바스크립트 객체가 아닌 HTML 요소가 되게 된다.
&&가 아닌 | | 연산자를 사용하는 점도 조건이 참이 되게하여 DOM clobbering에 취약하게 되는 요소 중 하나
해당 코드에 대한 DOM Clobbering 공격 코드 예시를 적어보았음
<!DOCTYPE html>
<html>
<head>
<title>DOM Clobbering Example</title>
</head>
<body>
<!-- 공격자가 삽입한 코드 -->
<div id="defaultAvatar"></div>
<script>
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'};
console.log(defaultAvatar);
// 기대: {avatar: '/resources/images/avatarDefault.svg'}
// 실제: <div id="defaultAvatar"></div>
</script>
</body>
</html>
여기서 현재 id=defaultAvatar를 가진 div 요소가 존재하기 때문에, window.defaultAvatar는 해당 div 요소를 참조하게 된다.
따라서 defaultAvatar 변수는 원래 의도된 객체가 아니라 DOM 요소를 참조하게 되는 것이다!
다시 돌아와서, 이 코드에 대해 DOM Clobbering을 하려면 동일한 id를 가진 두 개의 코드를 만들어 DOM collection으로 묶이게 하면 될 것이다. 두 번째 코드의 name 속성에는 avatar를 포함하여 href 속성의 내용으로 avatar 속성을 clobbering 하면 될 것 같다.
let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';
바로 아래 줄에 있던 위 코드는 자바스크립트로 HTML 문자열을 생성해서 사용자 아바타 이미지를 표시하는 기능을 한다.
- avatarImgHTML이라는 변수를 선언하고, <img> 태그를 포함한 HTML 문자열을 생성하여 할당
- 삼항 연산자 => comment.avatar가 존재하면, escapeHTML 함수로 이를 처리하고 존재하지 않을 경우 defaultAvatar.avatar을 사용한다고 한다.
여기서 defaultAvatar.avatar는 앞서 맨 처음에 DOM Clobbering 개념에서도 설명한 내용과 동일한 것으로, id가 defaultAvatar인 앵커 태그의 이름인 avatar로 접근한 것!
즉, 기본 아바타 이미지 URL href로 해석할 수 있으며 이것이 이후 이미지 태그에 삽입되는 내용이다.
위 코드에서 comment.avatar가 존재하지 않는 경우 defaultAvatar.avatar 를 호출하는데, 이는 바로 위에서 볼 수 있다시피
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'}
이렇게 OR 연산자를 활용함으로써 window.defaultAvatar 로 앵커 태그에 접근하거나, /resources/images/avatarDefault.svg 파일을 불러오게 된다.
따라서 맨 처음에 직접 코드를 작성하여 콘솔에 실행했던 것처럼, defaultAvatar라는 id를 가진 앵커 태그를 두 개 만들어놓고, 두 번째 앵커 태그의 name을 avatar라고 지정하면 defaultAvatar.avatar로 이 앵커 태그가 호출될 것이다.
그리고 이 태그의 href 속성으로 우리가 실행시키고자 하는 자바스크립트문을 삽입하면 해결될 것!!
2. dompurify.js
dom-based 취약점을 방지하기 위해 위와 같은 DOMpurify 필터를 사용하는 것도 볼 수 있다.
그러나 해당 dompurify 파일을 살펴보면 cid: 프로토콜을 사용하는 것을 볼 수 있는데, 이는 큰따옴표(")를 URL에서 인코딩하지 않는 특성이 있다.
즉, 익스플로잇 코드에 런타임에 디코딩되는 인코딩된 큰따옴표를 삽입할 수 있다는 것이다.
여기서 CID의 웹에서의 사용은 다소 생소하기 때문에 개념을 짚고 넘어가보자.
웹에서의 CID는 href 속성이 문자열로 변환되는 방식에 영향을 끼치는 프로토콜이다.
따라서 href 속성의 일반적인 사용법은 cid:가 적용된 href 속성의 사용법과 다소 다른 면이 존재하므로, href 속성이 문자열로 변환되는 방식 또한 서로 다르다.
처음에 소개했던 html 예시 코드를 통해 이해해보자.
기존에 작성했던 href="second-link" 인 상태에서 window.link.url.toSring() 을 해보면 해당 태그와 속한 파일의 기본 경로 정보를 문자열로 출력하는 것을 볼 수 있다.
그러나 이번엔 href 속성에 cid:를 삽입한 뒤 toString() 메소드를 호출해보면, 앞선 href 속성 출력과 차이가 있는 것을 볼 수 있다. 앞선 일반적인 href 속성에서는 파일 경로까지 다 출력이 되었지만, cid href 에서는 우리가 실제로 href에 삽입한 문자열인 'cid:second-link' 만을 단독으로 가져오고 있다.
그렇다면 이 cid: 가 HTML로 인코딩된 큰따옴표가 처리되는 방식에 어떤 영향을 미칠까?
이번에는 위와 같이 각각의 앵커 태그의 href에 인코딩된 큰따옴표를 삽입하고, onerror로는 alert()을 넣어보았다.
CID가 아닌 첫 번째 앵커 태그를 출력해보면, toString()에서 URL로 인코딩된 큰따옴표가 있는 것을 볼 수 있다.
이번엔 CID가 적용된 두 번째 앵커 태그를 출력해보면, 앞선 출력 결과와는 확연히 다른 것을 볼 수 있다.
아까와는 달리 URL로 인코딩된 큰따옴표가 출력되는 것이 아닌 pure한 " 가 그대로 출력되고 있다.
이것으로 추론할 수 있는 점은, avatar의 이미지 태그에 있는 source 속성을 분리할 수 있다는 것이다.
이를 활용하여 작성한 익스 코드는 다음과 같다.
3. 익스플로잇
<a id=defaultAvatar>
<a id=defaultAvatar name=avatar href="cid:"onerror=alert(1)//">
앞서 설명했듯이 id=defaultAvatar 을 가진 앵커 태그를 두 개 작성하여 해당 태그가 자바스크립트의 전역 변수인 window.defaultAvatar와 출돌하게 하여 DOM Clobbering이 발생하도록 한다.
두 번째 <a> 태그의 href 속성은 cid:"onerror=alert(1)// 인데, 여기서 큰따옴표(")가 URL 인코딩된 상태로 삽입하였다.
이는 HTML 문서에서 속성 값을 닫는 역할을 하므로 HTML을 파싱할 때 이를 통해 속성 값이 종료된 것으로 인식할 수 있기 때문이다.
마지막에 주석처리를 한 이유는, 주석 없이 위 코드를 전송한다면 href 속성이 이미지 태그의 src 속성에 들어가게 되는 것인데, 이때 cid: 다음에 큰따옴표를 삽입하고 onerror 속성이 자동으로 추가되도록 한 것이기 때문에 마지막에 기존의 src 속성을 닫는 " 가 하나 추가로 남게 된다. 이를 위해 뒷 부분을 주석처리 해주어야 함!
그렇다면 여기서 또 드는 의문점은, 뒷 부분에 " 가 남는 것 때문에 주석처리를 해줄꺼면 그냥 onerror="alert(1) 이렇게 뒷 부분의 큰따옴표가 없는 채로 전송하면 되는 것이 아닌가? 라고 생각할 수 있다.
그러나 이대로 제출하게 되면 실제로 유효한 HTML 코드가 아니기 때문에 href 속성을 닫지 않아 href 값을 얻을 수가 없게 된다. 즉, HTML을 삽입하려고 시도하면 페이지 창을 다시 로드한 후인 것이기 때문에 이는 불가능하다.
여기서 만약 반대로 cid 프로토콜이 허용되지 않았을 때는, 일반적으로 그냥 브라우저가 해석할 수 있는 방식으로 URL 스키마를 사용하지 않고 바로 다음과 같이 onerror 속성을 사용하면 된다.
<a id=defaultAvatar name=avatar href="javascript:alert(1)"></a>
<a id=defaultAvatar name=avatar href="onerror=alert(1)//"></a>
익스플로잇 코드를 댓글로 전송하고 난 뒤, 포스트로 다시 돌아와서 두 번째 댓글을 남기면 그 다음 페이지가 로드될 때 앞서 코드에 작성했던 alert()가 호출되어 실행된다!
'Web Hacking' 카테고리의 다른 글
[WEB] 네트워크 경유 서버 취약점 및 공격 과정 (0) | 2024.12.02 |
---|---|
[WEB] DreamHack Web Hacking 커리 모두 학습 완료!🎉 (0) | 2024.11.16 |
[WEB] DOM XSS using web messages and a JavaScript URL 풀이 (0) | 2024.07.23 |
[WEB] DOM XSS using web messages 풀이 (0) | 2024.07.21 |
Burp Suite 버프 스위트 설치 방법 및 초기 설정 (0) | 2024.07.20 |