웹 사이트 보안 공격 (XSS, CSRF)

Minjae Kwon·2021년 3월 2일
14

 🍉   Learning Journal

목록 보기
33/36
post-thumbnail

웹 어플리케이션 보안 이슈에서 자주 언급되는 2가지 공격에 대해 알아본다.

💡 Cross-Site Scripting (XSS) 공격

공격자가 클라이언트 코드에 악의적인 스크립트를 주입하는 공격이다. 웹 어플리케이션의 유효성 검사나 인코딩이 충분하지 않을 경우, 브라우저는 스크립트의 악의성을 감별할 수 없다. 공격자는 사용자를 가장하여, 쿠키와 세션 토큰, 사이트에 민감한 정보들에 접근이 가능해지며 클라이언트 코드를 재작성하여 해킹하기도 한다. 이러한 문제는 주로 1) 확인되지 않은 경로를 통해 데이터가 입력되거나 2) 동적인 데이터의 유효성 검사가 적절히 이루어지지 않고 사용자에게 보내지는 경우에 발생한다.

악의적인 콘텐츠는 대개 자바스크립트를 포함하나, 때에 따라 HTML, Flash 등 브라우저가 실행하는 어떤 종류의 코드든 가능하다. XSS 공격은 1) 쿠키나 세션 정보와 같은 기밀 정보를 빼가거나 2) 접속자를 자신들이 의도한 페이지로 리디렉트 하거나 3) 정상적인 사이트 흉내를 내며 사용자의 컴퓨터에 악의적인 공격 (악성코드를 다운로드하거나 개인정보를 입력하도록 유도) 을 하여 해를 입힌다. 대표적인 유형으로 다음 두 가지를 알아본다.

stored / persistent
XSS 취약점이 있는 타겟 서버에 악성 스크립트를 저장시킨 후, 브라우저가 서버에 데이터를 요청하면 희생자는 이 조작된 스크립트를 받게 된다. 만약 회사 등 조직의 개인 컴퓨터가 해킹될 경우, 조직 내부로 악성 코드가 이동하여 내부의 중요 정보가 탈취될 수 있다.

reflected / non-persistent
사용자가 악의적으로 조작된 링크를 누르거나, 폼을 제출하거나, 사이트에 방문을 하는 경우에 주입된 코드가 희생 대상이 되는 웹사이트로 퍼진다. 웹 서버는 주입받은 코드를 정상적인 요청으로 인식하여, 브라우저에 그에 대한 응답을 한다. 에러 메세지를 띄우거나, 특정 검색결과를 보여줄 수도 있고, 어떤 데이터를 포함할 수도 있다. 브라우저는 서버가 악의적으로 영향받았다는 사실을 인지하지 못하므로, 정상 응답으로 파악하여 코드를 실행한다. 만약 input 창에 댓글을 입력하면 화면에 보여주는 기능이 있다고 할때, 악의적으로 <script>alert('this website is hacked!')</script> 과 같이 입력하여 개발자의 의도를 무시하고 새로운 동작을 입힐 수 있다.

🧼 XSS 공격 예방

  • User input value 제한
    유저 입력값이 한정적인 범주안에서 예측 가능하다면, 드롭다운 등을 사용하여 미리 입력될 데이터값을 통제할 수 있다.

  • Sanitize value
    악성 HTML을 필터링해주는 라이브러리 사용도 가능하다.

  • 스크립트 문자 필터링
    DOM 상의 텍스트를 읽을 때 html 태그를 읽는 innerHTML 사용을 지양하고, textContent 등으로 메소드를 대체한다. 메소드 내용은 이전 블로그에 정리한 바 있다.


💡 Cross-Site Request Forgery (CSRF 또는 XSRF) 공격

공격자가 '사용자의 동의없이 / 사용자가 인지하지 못하는 상황에서' 브라우저로 하여금 서버에 어떤 요청을 보내도록 한다. XSS 공격으로 탈취한 정보를 이 때 사용할 수 있다.

MDN 이 인용한 위키피디아의 예시를 살펴보자. 공격자는 이미지 태그를 가장하여, 실제로는 은행 서버로부터 돈을 인출하는 코드를 작성하였다.

<img src="https://bank.example.com/withdraw?account=you&amount=1000000&for=me">

만약 희생자의 컴퓨터가 은행 사이트에 접속한 이후에 쿠키가 유효한 상태라면 (그리고 돈을 인출할 때 필요한 다른 유효성 검사가 없다고 가정하면) 위의 이미지가 포함된 HTML 이 로딩되는 순간, 돈이 인출되도록 할 수 있다. POST 요청을 하는 form 태그는 페이지 로딩이 되는 순간 submit 이 되도록 조작할 수 있기 때문이다.

<form action="https://bank.example.com/withdraw" method="POST">
  <input type="hidden" name="account" value="you">
  <input type="hidden" name="amount" value="1000000">
  <input type="hidden" name="for" value="me">
</form>

<script>
  window.addEventListener('DOMContentLoaded', 
    (e) => { document.querySelector('form').submit(); }
</script>

🧼 CSRF 공격 예방

일반적으로 CSRF 공격 방어는 GET 메소드와 같은 조회성 요청은 제외하고, 데이터 조작이 가능한 POST, PATCH, DELETE 메소드에 적용한다. 때에 따라 기밀 데이터이거나, GET 메소드를 통해 쓰기, 변경 등의 동작을 한다면 GET 메소드도 물론 방어해야 한다.

  • Referrer 검증 (SameSite 쿠키 설정)
    Back-end 단에서 request의 referrer를 확인하여 domain (e.g. *.instagram.com) 이 일치하는 지 검증하는 방법이다. referrer 검증만으로 대부분의 CSRF 공격을 방어할 수 있다. 민감한 정보를 담고 있는 쿠키 (세션 쿠키 등) 는 유효 시간을 짧게 설정하고, 쿠키의 SameSite 속성을 Strict 또는 Lax 로 설정한다. 이러한 세팅으로, 이를 지원하는 브라우저에서는 cross-site 요청에 세션 쿠키를 보내지 않는다. 하지만 같은 domain 내의 페이지에 XSS 취약점이 있는 경우 CSRF 공격에 취약해질 수 있다. domain 단위 검증에서 좀 더 세밀하게 페이지 단위까지 일치하는지 검증을 하면 도메인 내의 타 페이지에서의 XSS 취약점에 의한 CSRF 공격을 방어할 수 있다.

  • Security Token 사용 (A.K.A CSRF Token)
    Referrer 검증이 불가한 환경이라면, Security Token를 활용할 수 있다. 우선 사용자의 세션에 임의의 난수 값을 저장하고 사용자의 요청 마다 해당 난수 값을 포함 시켜 전송한다. 이후 Back-end 단에서 요청을 받을 때마다 세션에 저장된 토큰 값과 요청 파라미터에 전달되는 토큰 값이 일치하는 지 검증하는 방법이다. 이 방법도 결국 같은 도메인 내에 XSS 취약점이 있다면 CSRF 공격에 취약해진다. 아래는 간략한 샘플 코드이다.

// 로그인시, 또는 작업화면 요청시 CSRF 토큰을 생성하여 세션에 저장한다.
session.setAttribute("CSRF_TOKEN",UUID.randomUUID().toString());

// 요청 페이지에 CSRF 토큰을 셋팅하여 전송한다
<input type="hidden" name="_csrf" value="${CSRF_TOKEN}" />
  
// 파라미터로 전달된 CSRF 토큰 값
String param = request.getParameter("_csrf");

// 세션에 저장된 토큰 값과 일치 여부 검증
if (request.getSession().getAttribute("CSRF_TOKEN").equals(param)) {
    return true;
} else {
    response.sendRedirect("/");
    return false;
}

출처: https://itstory.tk/entry/CSRF-공격이란-그리고-CSRF-방어-방법 [덕's IT Story]
  • Double Submit Cookie 검증
    Security Token 검증의 한 종류로 세션을 사용할 수 없는 환경에서 사용할 수 있는 방법이다. 웹 브라우저의 Same Origin 정책으로 인해 자바스크립트에서 타 도메인의 쿠키 값을 확인/수정하지 못한다는 것을 이용한 방어 기법이다. 스크립트 단에서 요청 시 난수 값을 생성하여 쿠키에 저장하고 동일한 난수 값을 요청 파라미터(혹은 헤더)에도 저장하여 서버로 전송한다. 서버 단에서는 쿠키의 토큰 값와 파라미터(혹은 헤더)의 토큰 값이 일치하는 지만 검사하면 된다. 서버에 따로 토큰 값을 저장할 필요가 없어 위에서 살펴본 세션을 이용한 검증보다 개발 공수가 적은 편이다. 피싱 사이트에서는 도메인이 달라 instagram.com 쿠키에 값을 저장하지 못하므로 (Same Origin 정책) 가능한 방어 기법이다.

[참고 자료]

profile
Front-end Developer. 자바스크립트 파헤치기에 주력하고 있습니다 🌴

4개의 댓글

comment-user-thumbnail
2021년 3월 5일

지피지기라서 자기한테 불을 지피는거군요 ~

1개의 답글
comment-user-thumbnail
2023년 11월 4일

네트워크 중간에서 통신을 낚아챈다음 쿠키도 복사하고 파라미터(혹은 헤더)도 복사해서 전송하면 뚫리는거 아닌가요?
RSA 보안에 대한 믿음이 없는 편이어서... 뚫으려고 맘먹으면 어떻게든 뚫릴거 같은데... 국가단위의 최고위층 팀한텐 못뚫는 사이트는 없을거란 생각이라.

답글 달기