본문 바로가기
IT/개발지식

XSS와 CSRF

by InfopediaBK 2023. 2. 10.
728x90

XSS(Cross-Site Scripting)및 CSRF(Cross-Site Request Forgery / XSRF)는 웹 애플리케이션 개발 시 고려되는 일반적인 보안 취약성입니다. 이러한 공격을 통해 공격자는 웹 사이트에 악의적인 코드를 주입하거나 웹 사이트의 동작을 조작할 수 있습니다.

 

XSS와 CSRF는 모두 중요한 정보의 도난, 웹 사이트 콘텐츠의 변경 등 보안 문제를 일으킬 수 있습니다. 웹 애플리케이션을 개발하는 개발자라면 이러한 공격이 작동하는 방식과 이를 방지할 수 있는 방법을 이해하고 있는 것이 중요하다고 생각합니다.

XSS(Cross-Site-Scripting)

XSS의 공격 대상은 홈페이지 이용자입니다. 서버에는 직접적인 영향을 주지 않지만 사이트 변조의 위험이 있는 공격입니다. 스크립트 태그 사용이 가능한 게시판에 스크립트를 불러오거나 직접 작성하여 클라이언트에서 오작동이 발생하도록 합니다.

저장 XSS

XSS 취약점이 있는 웹 서버에 공격용 스크립트를 입력시켜 놓고, 악성 스크립트가 삽입되어 있는 페이지를 방문하는 순간 방문자의 브라우저를 공격하는 방식입니다. 가장 일반적인 방법은 자극적인 제목을 가진 글을 게시판에 스크립트 태그를 포함하여 게시하는 것입니다.

<script>alert(document.cookie)</script>

예를 들어, 게시글 내용 중에 위와 같은 코드를 삽입한다고 하면 게시글을 열었을 때 사용자의 쿠키 정보가 alertalert 될 것입니다. 물론, 해커의 경우 훨씬 복잡한 코드를 넣을 것입니다.

반사 XSS

악성 스크립트가 포함된 URL을 사용자가 클릭하도록 유도하고 클릭 시 클라이언트를 공격하는 것입니다. 반사형 XSS는 전체적인 XSS 공격 중 가장 빈번한 공격 유형이라고 보시면 됩니다.

test.com/news.php?title=1<script>alert('111')</script>
test.com/news.php?title=1<script>href.location("해커사이트")</script>
test.com/news.php?title=1<iframe src="해커사이트" width=400 height=400></iframe>

이런 식의 URL을 배포하여 클라이언트가 클릭하면 취약사이트에 반사되어 XSS공격이 클라이언트에게 일어나게 됩니다.

DOM 기반 XSS

DOM 환경에서 공격 URL을 통해 사용자의 브라우저를 공격하는 것입니다. DOM 기반 XSS 공격은 페이지 자체는 변하지 않으나, 페이지에 포함되어 있는 브라우저 측 코드가 DOM 환경에서 악성코드로 실행되게 됩니다. 앞의 유형들과 차이점은 DOM 기반 XSS는 서버와 관계없이 브라우저에서 발생하는 것이 차이점이라고 볼 수 있습니다.

XSS 방어 방법

인코딩

사용자의 입력값을 HTML로 인코딩하여 반환합니다. 이런 식으로 처리하게 되면 악의적인 사용자가 삽입한 스크립트가 다른 사용자에게 실행 가능한 형태로 전달되지 않습니다. <c:out>${}의 차이를 보면 알 수 있습니다. <c:out><c:out>으로 출력할 경우 <, >가 &lt; &gt; 로 자동변환되지만 ${}를 이용한 출력은 그렇게 되지 않습니다.

시큐어 코딩

클라이언트에서 서버에서 내려준 변수에 대해서 스크립트가 포함된 태그인지 아닌지 판단한 뒤 렌더링하는 것입니다. 요즘엔 많이 쓰이지 않지만, jstl을 예로 들어보겠습니다.

<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

<%
 String securecoding = "hello, boanit!";
 request.setAttribute("securecoding", securecoding);
%>

<!-- With escapeXml() Function: -->
${fn:escapeXml(securecoding)}

CSRF(Cross-Site Request Forgery / XSRF)

XSS 공격과 CSRF(Cross-site request forgery)는 피해자의 브라우저를 목표로 하는 비슷한 공격입니다. 가장 큰 차이점은 CSRF는 사용자의 인증된 세션을 악용하며(예를 들어, 은행 계좌 로그인), XSS는 인증된 세션이 없어도 공격 효과를 거둘 수 있다는 점입니다.

<form action="http://www.bank.com/sendmoney.do?from=you&to=hacker&amount=5000" method="POST">
  <button type="submit">여기를 누르면 공짜 돈을 드립니다!</button>
</form>

만약, 은행사이트에 로그인이 된 상태에서 어떤 게시판의 있는 위의 링크를 클릭했다고 가정해 보면, 은행의 보안 방식에 따라 다르겠지만 5000달러가 날아갈 수 있는 위험이 있습니다.

CSRF 방어 방법

Security 토큰

토큰 값을 이용해 사용자를 검증하는 방식입니다.

 

1. 사용자가 로그인 시 서버에서는 임의의 토큰 값을 세션에 저장하고 이를 클라이언트로 같이 보냅니다.

session.setAttribute("CSRF_TOKEN",UUID.randomUUID().toString());

2. 클라이언트 측에서 Request를 보낼 때 토큰 값을 같이 보냅니다

<input type="hidden" name="_csrf" value="${CSRF_TOKEN}" />

3. 세션에 저장된 토큰 값과 파라미터로 넘어온 토큰 값을 서버에서 검증합니다.

4. 일치하지 않으면 reject처리합니다.

String param = request.getParameter("_csrf");

if (request.getSession().getAttribute("CSRF_TOKEN").equals(param)) {
    return true;
} else {
    response.sendRedirect("/");
    return false; 
}

Double Submit Cookie

Security Token 검증의 한 종류로 세션을 사용할 수 없는 환경에서 사용할 수 있는 방법입니다. 웹브라우저의 Same Origin 정책으로 인해 자바스크립트에서 타 도메인의 쿠키 값을 확인/수정하지 못한다는 것을 이용한 방어 기법입니다.

 

1. 스크립트 단에서 요청 시 토큰 값을 생성하여 쿠키에 저장합니다.

var generateCsrfToken = function() { 
    function generateRandomString(length) { 
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 
        for(var i = 0; i < length; i++) { 
            text += possible.charAt(Math.floor(Math.random() * possible.length)); 
        } 
        return text; 
    }; 
    return btoa(generateRandomString(32)); 
} 

var setCookie = function (cname, cvalue) { 
    document.cookie = cname + "=" + cvalue + ";path=/"; 
}

2. 동일한 토큰 값을 요청 파라미터(혹은 헤더)에도 저장하여 서버로 전송합니다.

jQuery.ajaxSetup({ 
    beforeSend: function(xhr, settings) { 
        if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { 
            var csrfToken = generateCsrfToken(); 
            setCookie('CSRF_TOKEN', encodeURIComponent(csrfToken)); 
            xhr.setRequestHeader("_csrf", csrfToken); 
        } 
    } 
});

3. 서버에서 쿠키의 토큰 값과 파라미터의 토큰 값이 일치하는지 검증합니다.

String paramToken = request.getHeader("_csrf");
String cookieToken = null; 

for (Cookie cookie : request.getCookies()) { 
    if ("CSRF_TOKEN".equals(cookie.getName())) {
        cookieToken = URLDecoder.decode(cookie.getValue(), "UTF-8");
        cookie.setPath("/"); 
        cookie.setValue(""); 
        cookie.setMaxAge(0); 
        response.addCookie(cookie); 
        break; 
    } 
}

if (cookieToke.equals(paramToken)) { 
    return true; 
} else {
    return false;
}

결론

사실, 오늘 소개한 보안 취약점들은 현대 개발에서 사용하는 프레임워크나 라이브러리에서 대부분 쉽게 해결할 수 있도록 설계가 되어있습니다. 그러다 보니 예시도 조금은 과거의 코드들을 사용하게 된 부분도 없지 않아 있는 것 같습니다.

 

다만, 어떤 식으로 보안 취약점들이 발생하는지 이해했다면 요즘 주로 사용되는 React나 Vue 등과 같은 프레임워크 혹은 라이브러리에서 어떤 식으로 이를 해결하고 있는지 한 번쯤은 공부해 보시는 것을 추천드립니다.

728x90

댓글0