Lucy.SpaceLucy.Space.

프론트엔드 개발자가 알아야할 웹 보안 팁

프론트엔드 개발자가 알아야할 웹 보안 팁

10년 전 웹 애플리케이션과 현재 웹 애플리케이션을 비교하면, 현재의 웹 애플리케이션은 더 많은 도구에 의존하고 있습니다. 그만큼 보안에 더 취약해졌다고 볼 수 있지요. 공격은 라이브러리, 패키지 매니저, 네트워크 통신, 데이터베이스 연결, 웹 서버 등 각각의 파트에서 일어날 수 있습니다.

제가 다닌 전 회사는 SaaS를 서비스로 한 회사였습니다. 그렇기 때문에 사용자 간의 데이터 전달이 빈번했는데, 그 당시 보안팀을 채용하고, 버그 바운티 프로그램를 채택하여 웹 보안을 지키고자 하였습니다. 이 과정을 통해 이슈티켓이 만들어지면, 주로 제가 할당이 되어 이슈를 해결하곤 했습니다.

그 덕분에 어떤 웹 보안이 있는지, 어떤 방식으로 테스트해야하는 지에 대해 많이 알게되었고, 프론트엔드 개발자가 안전한 웹 애플리케이션을 만들기 위해서는 어떤 부분을 유념해서 코드를 짜야되는지, 웹보안 관련 코드리뷰는 어떻게 해야하는지에 대해 공유하고자 합니다.

#1. XSS

먼저 살펴볼 공격은 가장 보편적으로 알려진 사이트 간 스크립팅입니다. XSS의 종류는 저장(stored), 반사(reflected), DOM 기반(DOM-based), 3가지로 나눠볼 수 있습니다.

#1.1. 저장(stored)

저장 XSS는 코드를 실행하기 전에 데이터베이스에 저장하는 방식입니다. 가장 일반적인 유형의 XSS 공격으로 가장 많은 사용자에게 영향을 끼치는 경우가 많습니다. 그러나 탐지하기가 쉽기 때문에 방어가 가장 수월합니다.

어떤 쇼핑몰의 Q&A 게시판이 있다고 가정해봅니다. 공격자가 Q&A 게시판에 문의글과 함께 회원 정보를 자신의 서버로 보내는 코드를 추가했습니다. 그 코드는 데이터베이스에 그대로 들어가게되고, 관리자가 그 게시물을 클릭하게 되면, 쇼핑몰의 회원 정보가 모두 공격자에게 보내지게됩니다.

#1.2. 반사(reflected)

반사 XSS는 코드를 데이터베이스에 저장하지 않고 서버에 의해 반사하는 방식입니다. 저장 XSS과 다른 점은 데이터베이스에 저장되지 않는 점에서 차이점이 있습니다. 그렇기 때문에 저장 XSS보다 탐지하기가 더 어렵습니다.

먼저, 공격자는 공격을 위해서 정찰을 합니다. 공격할 페이지의 검색 결과 페이지를 살펴봅니다. 키워드에 스크립트 태그(<script>alert(’hi’)</script>)를 추가하여 알럿이 노출되는 지 확인합니다. 알럿이 노출된다면 이 URL은 반사 XSS를 적용할 수 있는 URL입니다.

공격자는 스크립트 태그가 포함된 링크를 채팅이나 댓글 등에 공유합니다. 사용자는 그 링크를 클릭하여 사이트에 진입하는 순간, 스크립트가 실행됩니다.

#1.3. DOM 기반(DOM-based)

DOM 기반 XSS는 코드를 브라우저에 저장해 실행하는 방식입니다. DOM 기반 XSS 공격은 서버와 전혀 상호작용하지 않고, 브라우저 DOM 싱크와 소스를 사용하는 것이 특징입니다. 서버를 전혀 건드리지 않으므로 정적 분석 도구나 그 외 스캐너를 사용해서 탐지하는 것이 거의 불가능합니다.

또한 각 브라우저의 종류, 버전에 따라 일어나지 않을 수도 있고, 일어날 수도 있습니다.

클라이언트에서 검색 기능이 되는 SPA 페이지가 있다고 가정합니다. 키워드로 사과를 입력하면, 전체의 코드를 돌며, 사과에 매칭되는 모든 것을 찾아서 document.write(매칭되는 갯수 + hash)를 보여주는 로직이 있습니다.

공격자는 키워드로 #<script>alert(document.cookie)</script>를 작성합니다. 웹페이지가 document.write 함수를 통해 페이지에 로딩되는 순간 스크립트가 실행됩니다.

#1.4. mXSS

뮤테이션 XSS는 최근에 발견된 XSS로 DOMPurify, OWASP 등 정제 라이브러리에서는 안전하다고 통과한 뒤 안전하지 않은 페이로드로 변하는 것이 핵심적인 작동 원리입니다.

아래의 코드는 태그가 올바로 구성되지 않았기 때문에 스크립트가 실행되지 않습니다. 즉, DOMPurity는 이 페이로드가 XSS 위험이 없는 것으로 평가해 통과시킵니다.

1<noscript><p title="</noscript><img src=x onerror=alert(1)>">

그러나 DOMPurity는 루트 엘리먼트 <template>을 이용하여 정제하는데, <template> 태그 내에서는 엘리먼트 스크립팅이 비활성화됩니다. 즉, 정제 후에는 이미지 태그만 남게되어 스크립팅이 실행되게 됩니다.

1<img src=x onerror=alert(1)>">

#1.5. 방어 방법

사실, innerHTML 대신 innerText를 사용하여 문자열을 제외한 데이터를 DOM에 전달하지 않는 것이 가장 좋은 방어 방법이긴 합니다. 그러나 innerHTML을 사용해야하는 경우가 종종 있습니다. 스타일은 유지가 되어야한다거나 하이퍼링크를 채팅창에 추가시키는 등의 로직이 있을 수도 있습니다.

이를 방어하기 위한 방법으로는 5가지로 나누어볼 수 있습니다.

  1. sanitize 라이브러리를 사용하기: innerHTML을 사용하여 화면에 코드를 보여주는 경우, sanitize 라이브러리를 통해 한번 감싼 뒤 innerHTML으로 보여줍니다. 그렇게 되면 라이브러리에서 설정한 태그에 대해서만 DOM 으로 생성되고, 그렇지 않은 태그는 텍스트로 노출됩니다.

  2. html 앤티티 사용하기: 백엔드로 보내는 데이터 중 앤티티로 변환이 가능한지 아닌지를 확인합니다. 그리고 앤티티로 변환이 가능하다면, 앤티티 태그로 변경합니다. 이 부분은 기획서를 읽고, 어떤 부분에 특수문자가 허용이 가능한지 등을 면밀히 살펴야합니다.

  3. 쿼리파람, 파람 인코딩하기: 외부로부터 들어오는 쿼리파람과 파람이 화면에 그대로 보여져야하는 경우, 그대로 인코딩을 시켜서 보여줄 수 있도록 합니다.

  4. CSP 정책 사용하기: 서버가 요청할 때마다 Content-Security-Policy 헤더를 보내는 방법과 메타 태그를 추가하는 방법이 있습니다. CSP는 특정 출처의 스크립트만 실행되도록 제한하여 XSS 공격을 방지합니다.

    1// HTTP 헤더 (추천)
    2Content-Security-Policy: script-src 'self' https://test.com;
    3
    4// meta 태그
    5<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://test.com;">
  5. HttpOnly 쿠키 플래그 사용하기: HttpOnly 플래그는 JavaScript로 쿠키 접근을 차단해 쿠키 탈취를 방지하지만, XSS 자체를 막지는 못하므로 CSP와 함께 사용하는 것이 권장됩니다.

#2. CSRF

API 앤드포인트는 알고있으나 권한이 필요하여 액세스할 수 없는 경우에 사용되는 공격방법입니다. CSRF는 사용자의 브라우저에 저장되어 있는 쿠키를 이용하여 API를 호출시키기 때문에 공격자가 직접적으로 쿠키를 탈취하는 방법은 아닙니다. 그렇기 때문에 CSRF 공격은 눈에 띄지 않기 때문에 눈치채지 못하는 경우가 많습니다.

#2.1. 질의 매개변수 변조

하이퍼링크를 통한 매개변수 변조로 가장 일반적인 CSRF의 공격 유형입니다. 공격자는 미리 API 앤드포인트와 쿼리파람의 정보를 수집합니다.

은행 사이트가 있다고 가정해봅니다. 공격자는 미리 정보를 수집하여 API 앤드포인트와 자신의 유저 아이디를 알고있습니다. 공격자는 댓글, 게시물 등 웹페이지에 아래의 하이퍼링크를 공유합니다.

1<a href="https://testback.com/transfer?to_user={123}&amount=10000000">경품받기</a>

이 링크를 클릭한 사용자 중 은행 사이트에 로그인이 되어있는 사용자가 있다면, 자신도 모르는 사이에 공격자에게 돈을 송금하게 됩니다. 이같은 공격이 가능한 이유는, 우리의 브라우저에 각 사이트의 쿠키가 저장되어 있기 때문에 가능하게됩니다.

#2.2. GET 페이로드 바꿔치기

브라우저가 웹페이지를 렌더링하는 과정을 보면, DOM이 만들어지는 시점에 웹페이지에서 사용하고 있는 CSS, 이미지 파일 또한 HTTP GET방식으로 받아오는 것을 볼 수 있습니다. 이 파일들의 경로에 공격자가 미리 수집한 API 앤드포인트를 추가하는 작업을 통해 CSRF 공격이 이루어집니다.

사용자가 비디오를 링크를 이용하여 업로드할 수 있는 플랫폼이 있고, 그 비디오를 또 다른 사용자가 볼 수 있는 기능이 있는 페이지가 있다고 가정해봅니다. 공격자는 앤드포인트 API를 경로로 하여 비디오를 업로드합니다.

1<video>
2  <source src="https://testback.com/transfer?to_user={123}&amount=10000000" />
3</video>

이 비디오를 확인한 사용자 중 브라우저에 쿠키가 저장된 사용자는 공격자에게 돈을 송금하게 됩니다.

#2.3. POST 앤드포인트에 대한 CSRF

POST 앤드포인트에 대한 공격은 주로 폼에서 일어납니다.

공격자는 정상적인 은행 폼을 미리 수집합니다. 그리고, 그 폼을 이메일이나 게시물에 배포합니다. 히든 타입을 이용하여 어디로 송금되는지, 누구에게 송금되는 지에 대한 정보는 가려놓습니다.

1<form action="https://testback.com/transfer" method="POST">
2  <input type="hidden" value="hacker" name="to_user" />
3  <input type="hidden" value="1000" name="amount" />
4  <input type="text" value="username" name="username" />
5  <input type="text" value="username" name="username" />
6  <input type="password" value="password" name="password" />
7  <input type="submit" value="submit" />
8</form>

사용자는 이 폼을 작성하여 클릭하게되면, 서버에 HTTP POST 요청이 이루어지게 되고, 쿠키가 저장된 사용자는 공격자에게 돈을 송금하게 됩니다.

#2.4. 방어 방법

쿠키가 동일 출처 쿠키 플래그를 사용하는 지 확인하는 방법과 안티 CSRF 토큰을 사용하여 방지할 수 있습니다.

  • 쿠키 플래그: strict 플래그나 lax 플래그를 사용합니다.

  • 안티 CSRF: 사용자로부터 시작되지 않은 요청은 CSRF 토큰이 없기 때문에 서버가 요청을 차단됩니다.

    1. 클라이언트가 웹 페이지에 요청을 합니다.
    2. 서버는 웹 페이지를 CSRF 토큰과 함께 전송합니다. 이 토큰은 요청 시마다 생성할 수도 있지만, 일반적으로 세션마다 생성합니다.
    3. 클라이언트는 서버의 HTTP 리소스를 요청하며 CSRF 토큰을 첨부합니다.
    4. 서버는 CSRF 토큰을 검증하고 클라이언트에게 응답을 보냅니다. 요청이 서버에 도착하면 토큰이 살아있고, 진본이며, 조작되지 않았음을 검증합니다.

#3. 웹 보안 테스트 방법

물론, 자동화가 되어 있거나 요금을 지불하여 웹 보안 관련 테스트 로직이 있으면 정말 좋지만, 그러지 못한 경우에는 수동으로 테스트를 해야합니다.

웹보안 리뷰어로 할당이 되었다면, 코드나 기획서를 확인하면서 프론트엔드에서 일어날만한 보안 이슈를 체크해야합니다. 서버에서 데이터가 들어오는 시점부터 화면에 보여지기까지를 전반적으로 확인해야합니다. 먼저, 제가 개발하면서 경험했던 보안 이슈는 동적 DOM을 만드는 경우, 쿼리파람을 통해 인증을 스위칭할 수 있는 경우 이 2가지로 나누어볼 수 있었습니다.

#3.1 동적 DOM을 만드는 경우를 테스트하는 방법

  1. 서버에서 오는 데이터 확인하기: 서버에서 응답값으로 오는 데이터를 확인하고, 그 데이터 중, 유저가 직접 작성할 수 있는 데이터가 존재하는 지 확인해야합니다. 예를 들면, projectName 데이터가 포함되어 있다고 하면, projectName의 경우에는 유저가 수정할 수 있는 항목이기 때문에, 이 항목들을 모두 체크해놓습니다.
  2. 데이터를 화면에 보여주는 작업 확인하기: 데이터들이 화면에 보여지는 부분을 확인합니다. innerHTML을 사용하여 화면에 보여지는 경우인 지 아닌 지 확인합니다. 저는 이 때, 1번에서 확인한 유저가 수정할 수 있는 항목에 전부 악성코드를 주입하여 공격이 이루어지는 지에 대해 판단합니다.
  3. 데이터를 변환하는 작업 확인하기: 공격이 주입되는 곳을 찾으면, 이 데이터를 어디에서 XSS 공격을 막는 것이 효율적인지에 대해서 살펴봐야합니다. 물론, innerHTML을 사용하기 직전에 막으면 좋을 수 있으나 라이브러리를 사용하는 부분이거나, 여러 컴포넌트가 사용하고 있는 데이터라면, 그 데이터를 변환하는 부분에서 막아주는 것도 좋은 방법입니다.

#3.2 쿼리파람을 통해 인증을 스위칭하는 경우를 테스트하는 방법

프론트엔드에서 개발을 하면서 다른 페이지로 이동하면서 데이터를 보낼 때, 쿼리파람을 이용하여 함께 데이터를 보내는 부분이 있습니다. 여기서 중요하지 않은 데이터라고 하면, 그냥 보내도 상관 없습니다. 그러나 is_admin 등과 같은 민감한 데이터를 쿼리파람으로 보내는 경우가 있습니다.

물론, 이 코드를 작성했다고 하더라도 내부에서 어드민인지에 대한 체크로직이 있을 수 있으나 사용자가 쿼리파람을 변경함으로써 데이터가 달라진다면 세션이나 토큰을 활용하거나 내부 데이터로 가지고 있어서 ContextRedux를 활용하여 그 데이터를 보내주는 방식으로 변경하는 게 좋습니다.

#4. Reference

웹 애플리케이션 보안