최근 브라우저들은 모두 기본적으로 CORS/XSS를 금지하도록 설정되어 있어

인터넷 여기저기에서 찾아 모은 주옥같은 XMLHttpRequest/Ajax 통신 등의 소스코드들을 조합해서

뭔가 새로운 개발을 하려고 할 때 "액세스가 거부되었습니다"와 같은 곤란을 겪는 경우가 많다.


여기서 잠깐, CORS나 XSS의 정의는 아래 사이트에 상세히 나와 있다.


- CORS(Cross-Origin Resource Sharing): https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

- XSS(Cross-Site Scripting): https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)



그러면, Cross-Site나 Cross-Origin이란 실제로 무엇을 의미하는지부터 살펴봐야겠는데...


>>참조: https://en.wikipedia.org/wiki/Same-origin_policy


위 URL의 도표에 잘 정리되어 있다. 즉, 도메인(FQDN)이 다르거나 프로토콜(http/https)이 다른 경우가 되겠다.

물론 포트 번호가 달라도 Cross-Origin에 해당된다. 즉, 현재 사이트와 뭔가 조금만 달라도 모두 XSS인 것이다.




다음의 실제 예를 통해 XSS(Same-Origin 정책 포함) 상황을 극복하는 방법을 살펴 본다.


1. https://mail.abc.co.kr 사이트의 페이지 A에서 특정 링크를 클릭하면 같은 사이트 내의 팝업 페이지 B(Default.aspx)가 나타난다.

2. 페이지 B는 iframe으로 다른 페이지 C(FileUpload.aspx)를 포함하고 있는데, 특정 버튼을 클릭하면 iframe.contentWindow.fn_call() 과 같은 방식으로 해당 iframe 페이지 C의 Javascript를 호출하여 작업을 실행시킨다.

3. iframe 페이지 C에서는 몇 차례 Ajax 및 HTTP POST 작업을 거친 후 최종적으로 페이지 D(FileUploadOK.aspx)가 로딩된다. 이 때 Ajax 통신으로 저장되는 실제 파일은 다른 서버(http://mass.abc.co.kr)의 공유 폴더에 저장된다.

4. 페이지 D에서는 window.parent.fn_call() 과 같은 방식으로 부모 페이지 B에 있는 Javascript를 호출한다.

5. 마지막으로 페이지 B에서는 window.opener.fn_call() 방식으로 팝업을 실행시킨 부모 페이지 A의 Javascript를 호출하고 창을 닫는다.





실제 OWA 사이트에서 HTML5 파일 업로드를 수행하는 것을 예로 든 상황이라 좀 복잡한데,

위 1~5번 자체로는 나름 훌륭하게 잘 동작한다.


그런데, 여기에서 실제 파일 업로드가 발생하는 3번 부분의 조건이 변경되어

파일 업로드 트래픽이 해당 사이트(https://mail.abc.co.kr)를 거치지 않고

클라이언트에서 곧바로 다른 서버(예: http://mass.abc.co.kr)로 파일이 업로드되도록 해야 한다면?



몇 가지 방법이 떠오를 수 있다.



(1) 가장 먼저, Ajax 업로드 통신을 다른 서버로 바로 보내면 되지 않을까?


xhr.send()를 호출하는 순간 액세스 거부 오류를 만나게 된다. XSS 제한에 걸리기 때문인데 이를 해결하기 위해 헤더에 "Access-Control-Allow-Origin=*"를 넣어주더라도 해결되지 않는다. HTTPS에서 HTTP로 보내는 것 역시 또 다른 XSS 제한에 걸리기 때문으로 이것은 헤더값 설정을 통해 해결되지 않는다.





(2) 그렇다면, 팝업 페이지 B를 다른 서버에 있는 페이지 E로 대체해 버리는 것이 쉽게 생각할 수 있는 방법이다.


그러나 이 경우 5번에서 XSS 제한에 걸려 window.opener.fn_call()이 실행될 수 없기 때문에 불가능하다.

도메인이 달라지게 되니까. 이 때 document.domain = "abc.co.kr" 스크립트를 양쪽 페이지에서 모두 동일하게 넣어 도메인을 일치시켜 주는 XSS 우회 기법을 사용하더라도 여전히 동작하지 않는다. HTTP에서 HTTPS를 호출하는 것 역시 XSS에 해당하니까. 이것은 어떻게 해결할 방법이 없다.





(3) 그 다음으로, 페이지 B에 포함된 iframe 페이지 C를 다른 서버의 페이지 F로 대체하는 방법은?


두 가지 문제가 생긴다. 우선 다른 서버의 페이지 F는 HTTP이므로 보안 경고(혼합 콘텐츠)가 뜨고 사용자가 명시적으로 "모든 콘텐츠 표시"를 클릭해서 허용해야만 페이지 F가 열리는 문제가 있고,

그것을 무시한다 하더라도 iframe.contentWindow.fn_call() 부분이 XSS 제한에 걸려 실행되지 않는다.

도메인이 달라질 뿐만 아니라 이번에는 HTTPS에서 HTTP를 호출하게 되는데 이것 역시 XSS에 해당하니까.





(4) 그러면, 결국 HTTPS가 문제인 것이니까 페이지 B를 HTTPS에서 HTTP로 redirection 되도록 한 이후에 (3)번의 방법대로 iframe 페이지 C를 다른 페이지 F로 대체한다면?


iframe.contentWindow.fn_call() 부분은 document.domain = "abc.co.kr" 코드 삽입으로 처리될 수 있으므로 넘어갈 수 있지만 마지막에 결국 HTTP에서 HTTPS로의 window.opener.fn_call() 부분이 XSS 제한에 걸려 실행되지 않는다.





결국,

ActiveX나 Silverlight와 같은 도구를 사용하여 클라이언트에서 직접 다른 서버로 업로드하는 방법을 사용하지 않고서는 위 시나리오에서 OWA 사이트를 통해 OWA 사이트를 거치치 않고 곧바로 다른 서버로 업로드할 수 있는 방법은 없다...


정말 없을까?






방법이 있다.


위 (4)번의 방법을 응용하되, 페이지를 하나 더 추가하고 마지막 4~5번 부분의 흐름을 바꿔 추가된 페이지를 호출하도록 하면 된다.


정리하자면,


1. https://mail.abc.co.kr 사이트의 페이지 A에서 특정 링크를 클릭하면 같은 사이트 내의 팝업 페이지 B가 나타난다.

2. 페이지 B에서는 HTTP로 같은 페이지 B로 redirection한다. (HTTPS/HTTP 판단 로직 필요)

3. 페이지 B은 iframe으로 다른 서버의 페이지 C1를 포함하고 있는데, 특정 버튼을 클릭하면 iframe.contentWindow.fn_call() 과 같은 방식으로 해당 iframe 페이지 C1의 Javascript를 호출하여 작업을 실행시킨다. (페이지 B, C1에서 각각 document.domain="abc.co.kr"로 맞춰준다.)

4. iframe 페이지 C1에서는 몇 차례 Ajax 및 HTTP POST 작업을 거친 후 최종적으로 페이지 D1가 로딩된다. 이 때 Ajax 통신으로 저장되는 실제 파일은 로컬(다른 서버) 폴더에 저장된다.

5. 페이지 D1에서는 target="_top"을 사용하여 iframe 부모를 https://mail.abc.co.kr 사이트에 있는 페이지 E로 POST 방식으로 전송한다.

6. 페이지 E에서는 POST로 전달받은 값을 이용하여 window.opener.fn_call() 방식으로 팝업을 실행시킨 부모 페이지 A의 Javascript를 호출하고 창을 닫는다.




또는,


1. https://mail.abc.co.kr 사이트의 페이지 A에서 특정 링크를 클릭하면 같은 사이트 내의 팝업 페이지 B가 나타난다.

2. 페이지 B에서는 다른 서버(http://mass.abc.co.kr)의 페이지 B1으로 redirection한다.

3. 페이지 B1은 iframe으로 다른 페이지 C1를 포함하고 있는데, 특정 버튼을 클릭하면 iframe.contentWindow.fn_call() 과 같은 방식으로 해당 iframe 페이지 C1의 Javascript를 호출하여 작업을 실행시킨다.

4. iframe 페이지 C1에서는 몇 차례 Ajax 및 HTTP POST 작업을 거친 후 최종적으로 페이지 D1가 로딩된다. 이 때 Ajax 통신으로 저장되는 실제 파일은 로컬(다른 서버) 폴더에 저장된다.

5. 페이지 D1에서는 target="_top"을 사용하여 iframe 부모를 https://mail.abc.co.kr 사이트에 있는 페이지 E로 POST 방식으로 전송한다.

6. 페이지 E에서는 POST로 전달받은 값을 이용하여 window.opener.fn_call() 방식으로 팝업을 실행시킨 부모 페이지 A의 Javascript를 호출하고 창을 닫는다.




여기서의 핵심은 원본 서버에 있는 웹 페이지들을 다른 서버에 똑같이 두고

해당 서버의 페이지로 redirection하여 로컬 업로드 처리를 한 후

마지막에 원래 서버로 돌아와서(POST) 최종적으로 windows.opener.fn_call() 처리를 해주는 것이다.

즉, 5번에서 페이지 E를 하나 추가하는 것이 결정적인 아이디어가 되겠다.


이렇게 하면 서로 다른 도메인의, HTTPS/HTTP가 다른 경우에도 XSS 제한을 극복하여 Javascript를 호출할 수 있다.


끝.




Posted by 떼르미
,


자바스크립트를 허용해주세요!
Please Enable JavaScript![ Enable JavaScript ]