Spring Security - 웹 애플리케이션의 보안


이 포스트에서는 애플리케이션에 존재하는 보안 취약의 종류에 대해 알아본다.

취약성을 예방하는 것이 공격받은 후 대처하는 것보다 적은 비용이 든다!


목차


1. 웹 애플리케이션의 일반적인 보안 취약성

OWASP

취약성을 알아보기에 좋은 오픈 웹 애플리케이션 보안 프로젝트로, 애플리케이션 개발 시 피해야 하는 일반적인 취약성에 대한 자료가 있음


1.1. 인증과 권한 부여의 취약성

인증은 유저 식별 프로세스이고, 권한 부여는 인증된 유저가 특정 기능과 데이터에 대한 이용 권리가 있는지 확인하는 프로세스이다.

인증 취약성이 있다는 것은 사용자가 악의를 가지고 다른 사람의 기능, 데이터에 접근할 수 있다는 의미이다.

특정 역할이 있는 인증된 사용자만 특정 엔드포인트에 접근할 수 있도록 정의해도, 데이터 수준의 제한이 없으면 다른 사용자의 데이터를 이용할 수 있는 허점이 생길 수 있다.

예) 인증된 사용자가 /products/{name} 엔드포인트에 접근할 수 있을 경우 데이터를 반환하면서 누구의 데이터인지 확인하지 않으면 결국 다른 사람의 데이터를 조회할 수 있다.
엔드포인트에만 인증 식별을 적용할 경우 인증된 유저는 /products/lee, /products/kim 등 본인이 아닌 사람의 데이터까지 조회가 가능하다.
엔드포인트 뿐 아니라 사용자의 데이터 검색까지 인증을 해야 한다.


1.2. 세션 고정 (Session fixation)

세션 고정 취약성은 웹 애플리케이션이 인증 프로세스 중에 고유한 세션 ID 를 할당하지 않아 기존 세션 ID 가 재사용될 가능성이 있을 때 발생하며, 이 취약성이 존재하면 공격자는 이미 생성된 세션 ID 를 재이용하여 유효한 사용자를 가장할 수 있다.

애플리케이션이 세션 ID 를 URL 에 넣는 경우 공격자는 피해자가 악성 링크를 클릭하도록 유인할 수 있다.
애플리케이션이 hidden form 을 이용하는 경우 공격자는 다른 양식을 서버에 제출하도록 할 수 있다.
애플리케이션이 세션값을 쿠키에 저장하는 경우 공격자는 스크립트를 주입하여 피해자의 브라우저가 스크립트를 실행하도록 할 수 있다.


1.3. XSS (Cross-Site Scripting, 교차 사이트 스크립팅)

XSS 는 서버에 노출된 웹 서비스로 클라이언트(사용자) 쪽 스크립트를 주입해 다른 사용자가 이를 실행하도록 하는 공격이다.

이 취약성이 악용되면 계정 탈취나 DDos 와 같은 분산 공격, 악성코드 다운로드 등의 결과가 발생할 수 있다.

XSS 는 script 문자 필터링과 html 엔티티를 사용하는 방법이 있다.


1.4. CSRF (Cross-Site Request Forgery, 사이트 간 요청 위조)

CSRF 공격은 특정 서버에서 작업을 호출하는 URL 을 추출하여 애플리케이션 외부에서 재사용할 수 있다고 가정한다. 서버가 요청의 출처를 확인하지 않고 실행하면 다른 모든 곳에서 요청이 실행될 수 있다.

예를 들어 아래와 같은 경우를 보자.

  • 피해자가 aaa.com 에 로그인을 시도함
  • aaa.com 백엔드 서버에서는 브라우저에 저장할 쿠키를 만들어서 주고, 클라이언트는 이 쿠키를 aaa.com 이라는 이름으로 저장
  • 이 때 피해자가 같은 브라우저의 다른 탭으로 hacker.com 웹사이트에 접속함
  • hacker.com 은 aaa.com 의 계정을 해킹하기 위해 악의적인 링크가 삽입되어 있는 웹 페이지 반환
  • 피해자가 악의적인 링크를 누르면 aaa.com 에 악의적인 요청이 감
  • aaa.com 백엔드 서버 입장에서는 로그인 쿠키가 동일한 브라우저에 존재하고, 악의적인 요청이 aaa.com 이라는 도메인에서 이루어지고 있으므로 이것이 사용자의 요청인지 악의적인 요청인지 구별 불가

이 취약성은 해당 HTTP 요청이 사용자의 인터페이스를 통해 이루어졌는지 확인함으로써 해결할 수 있다.
이 때 CSRF 토큰을 사용하는데, 이 토큰은 사용자 세션마다 고유해야 하며, 쉽게 추측할 수 없는 임의의 값이어야 한다.

  • 피해자가 aaa.com 에 로그인을 시도함
  • aaa.com 백엔드 서버에서는 쿠키와 임의로 생성된 CSRF 토큰을 생성함
  • aaa.com 백엔드 서버에서는 브라우저에 저장할 쿠키 (이 쿠키에는 특정 유저 세션을 위해 임의로 생성한 고유 CSRF 토큰이 저장됨) 를 만들어서 주고, 클라이언트는 이 쿠키를 aaa.com 이라는 이름으로 저장
  • 이 때 피해자가 같은 브라우저의 다른 탭으로 hacker.com 웹사이트에 접속함
  • hacker.com 은 aaa.com 의 계정을 해킹하기 위해 악의적인 링크가 삽입되어 있는 웹 페이지 반환
  • 피해자가 악의적인 링크를 누르면 aaa.com 에 악의적인 요청이 감
  • 백엔드 서버에서는 Authentication 쿠키 외 CSRF 토큰이 담긴 쿠키를 확인
  • CSRF 토큰은 최종 클라이언트의 요청이 동일한 UI 에서 오는지 확인하기 위해 애플리케이션 서버에서 사용되며, 애플리케이션 서버는 CSRF 토큰이 일치하지 않으면 요청을 거부

가해자가 자바스크립트를 실행할 때 hacker.com 도메인에서 실행이 되는데 aaa.com 사용자가 로그인할 때 쿠키에 저장된 CSRF 토큰은 aaa.com 도메인에 저장되었다.
가해자의 자바스크립트가 실행되는 hacker.com 도메인에는 이 쿠키가 저장되지 않았으므로 가해자가 아무리 hacker.com 도메인에서 자바스크립트를 실행해도 aaa.com 백엔드 서버에서는 CSRF 쿠키의 토큰이 있어야 로직을 수행하기 때문에 동작하지 않는다.

CORS (Cross-Origin Resource Sharing)

CORS 는 프로토콜로써, 서로 다른 origin 일 경우 리소스와 상호 작용하기 위해 클라이언트인 브라우저에서 실행되는 스크립트임
기본적으로 다른 도메인에서 API 를 호출하게 되면 차단함
CORS 는 보안이나 공격이 아닌 서로 다른 Origin 간의 데이터 통신을 할 때 브라우저에서 이를 중지하기 위해 제공하는 기본 보호 기능
만일 백엔드에서 프론트엔드의 도메인을 CORS 허용 설정하지 않으면 프론트엔드의 접근이 차단됨


1.5. 웹 애플리케이션의 주입 (Injection) 취약성

주입 공격의 목표는 시스템에 피해를 주고, 원치 않는 방법으로 데이터를 변경 또는 접근 불가인 데이터를 검색하는 것이다.
주입 공격은 클라이언트 쪽 스크립트는 주입하는 방식으로 진행된다.

주입 공격은 여러 유형이 있으며, XSS 도 주입 취약성의 하나이다. 이 외에도 SQL 주입, XPath 주입, LDAP 주입 등이 있다.


1.6. 민감한 데이터의 노출

자격 증명, 개인키 등은 Vault 에 넣어야 한다.

이러한 값을 application.yaml 파일 등의 구성 파일에 설정하면 소스 코드를 볼 수 있는 모든 사람이 개인 값에 접근할 수 있다.

민감한 데이터를 로그로 남기는 것도 위험하며, 특히 예외 발생 시 서버가 클라이언트에 반환하는 정보에도 유의해야 한다.

예를 들어 클라이언트로 반환하는 정보에 IP 가 노출되면 공격자는 이 주소를 보고 네트워크 구성을 파악한 후 최종적으로 인프라를 통제하는 방법을 찾아낼 수 있다.

애플리케이션의 내부 구조나 애플리케이션이 이용하는 종속성의 버전을 노출하는 것도 굉장히 위험하다. (예를 들어 톰캣의 버전 노출)
이러한 정보가 노출되면 공격자는 해당 특정 버전의 취약성을 찾아 공격할 수 있다.

컨텍스트의 정보를 공개하는 메시지도 위험하다.

// 응답 1: 
{
  "status": 401,
  "message": "이름이 올바르지 않음"
}

// 응답 2:
{
  "status": 401,
  "message": "암호가 올바르지 않음"
}

엔드포인트에 제공된 다양한 입력에 대해 다른 메시지를 제공하면 이 메시지를 이용하여 실행 컨텍스트를 분석할 수 있다.
예를 들어 사용자 이름은 맞고 암호는 틀린 상황을 식별할 수 있게 된다. 이렇게 클라이언트로 반환되는 응답이 특정 입력이 무엇인지 추측 가능하도록 하면 안된다.


1.7. 메서드 접근 제어 부족

애플리케이션 수준에도 한 계층에만 권한 부여를 적용하면 안된다.
아래와 같은 예시를 보자.

컨트롤러 계층에 권한 부여

위와 같은 상황에서 동일한 레파지토리를 이용하는 다른 기능을 추가하게 된다면 아래와 같은 상황이 벌어질 수 있으므로 레파지토리 뿐 아니라 애플리케이션의 모든 계층에 권한 부여를 적용하는 것이 좋다.

같은 레파지토리를 사용하는 다른 기능 추가


2. 다양한 아키텍처에 적용되는 보안


2.1. 일체형 웹 애플리케이션 설계

일체형 웹 애플리케이션는 백엔드와 프론트엔드 간의 직접적인 분리가 없다.

세션이 있는 한 세션 고정 취약성과 CSRF 가능성을 고려해야 하고, HTTP 세션에 저장하는 정보로 고려해야 한다.

서버 쪽 세션은 준영구적이다.
메모리에 유지되는 시간이 길수록 접근 가능성이 커지고, 힙 덤프에 접근할 수 있는 사람은 내부 메모리에 있는 정보를 읽을 수 있다.
참고로 힙 덤프는 어렵지 않게 얻을 수 있다.

스프링 부트로 개발 시 애플리케이션에 Actuator 를 포함하는 경우가 많은데, 구성 방법에 따라 엔드포인트 호출만으로도 힙 덤프를 반환할 수 있다. (= VM 에 대한 루트 접근 권한 없이도 가능)

CSRF 취약성에 대비하기 위해서 CSRF 방지 토큰을 사용하는 것이 좋다.
이 기능은 스트링 시큐리티에 기본적으로 포함되어 있고, CSRF 보호와 CORS 의 검증도 기본적으로 활성화되어 있다. (원하지 않으면 비활성화 가능)


2.2. 백엔드/프론트엔드 분리를 위한 보안 설계

이러한 구조에 대한 보안 설계는 Spring Security - BE, FE 분리된 설계의 애플리케이션 구현 을 참고하세요.

일반적으로 서버 쪽 세션을 줄이고 클라이언트 쪽 세션으로 대체하는 것이 좋다.

엔드포인트 인증에 HTTP Basic 을 이용하는 방법은 간단하지만 좋은 방법은 아니다.
HTTP Basic 을 이용하려면 호출마다 자격 증명을 전송해야 하는데, 이 때 자격 증명은 암호화되지 않는다.
브라우저는 Base64 인코딩을 이용하여 사용자 이름과 암호를 전송하므로 각 엔드포인트 호출의 헤더에 자격 증명이 노출된다.

이러한 이유때문에 OAuth 2 를 이용한 인증과 권한 부여를 이용하는 것이 좋다.

OAuth 2 에 대해서는 Spring Security - OAuth 2(1): Grant 유형 을 참고하세요.


2.3. OAuth 2 흐름

백엔드를 요청할 때마다 자격 증명을 전송하고, 이 자격 증명을 클라이언트 쪽에 저장하는 것은 좋지 않은 방법이다.
OAuth 2 흐름으로 인증과 권한 부여를 구현하는 것이 좋다.

OAuth 2 프레임워크는 권한 부여 서버리소스 서버 라는 2 가지 엔티티를 정의한다.

권한 부여 서버는 사용자에게 권한을 부여하고, 사용자의 이용 권리 집합을 지정하는 토큰을 제공하는 것이다.
리소스 서버는 사용자가 접근해야 하는 리소스를 조회할 때 사용하며, 권한 부여를 수행한 후 획득한 토큰에 따라 리소스에 대한 호출이 허용/거부된다.

암호 그랜트 유형을 이용하는 OAuth 2 권한 부여 흐름

클라이언트는 권한 부여 서버를 호출하여 토큰을 얻는데, 토큰을 얻기 위해 사용자 자격 증명이나 갱신 토큰을 보낸다.
자격 증명이나 갱신 토큰이 유효하면 권한 부여 서버가 새로운 액세스 토큰을 클라이언트로 반환하고, 필요한 리소스를 호출할 때 리소스 서버에 대한 요청 헤더에 액세스 토큰을 넣는다.

토큰의 수명을 고정되어 있으며 일반적으로 오래 유지되지 않는다.

OAuth 2 는 아래와 같은 장점이 있다.

  • 클라이언트는 사용자 자격 증명을 저장할 필요없이 액세스 토큰과 갱신 토큰만 저장하면 됨
  • 애플리케이션은 사용자 자격 증명을 노출하지 않음
  • 토큰이 탈취되면 사용자 자격 증명을 무효로 할 필요없이 토큰을 실격시킴
  • 토큰을 이용하여 제 3자가 리소스에 접근할 수도 있지만, 이 경우 토큰의 수명이 제한되므로 이 취약성을 악용할 수 있는 기간도 제한됨

여기서는 암호 그랜트 유형의 OAuth 2 흐름을 설명했지만, 클라이언트에 반드시 자격 증명이 있는 것은 아니다.
승인 코드 그랜트를 이용하면 애플리케이션은 인증을 브라우저에서 권한 부여 서버가 구현하는 로그인으로 리디렉션한다.

승인 코드 그랜트에 대한 상세한 내용은 3.1. 승인 코드 그랜트 유형 을 참고하세요.

토큰을 관리하는 방법은 앱의 메모리에 토큰 유지, DB 에 토큰 유지, JWT 를 통한 암호화 서명 사용 등을 이용하는 방법이 있다.

위 관리 방법들에 대한 좀 더 상세한 내용은 추후 다룰 예정입니다.


2.4. API 키, 암호화 서명, IP 검증을 이용한 요청 보안

인증 및 권한 부여를 위해 사용자 이름과 암호가 필요없는 상황에서도 통신되는 데이터를 보호해야 한다.

두 백엔드 간 요청이 있을 때가 바로 그러한 경우이다.

이 때는 3 가지 방법으로 요청 검증이 가능하다.

  • API 키 이용 (요청 및 응답 헤더에 정적인 키 사용)
  • 암호화 서명으로 요청 및 응답 서명
  • IP 주소 검증

API 키를 사용하는 것은 가장 약한 접근법이다. 이 접근법을 이용할때는 보통 IP 주소 허용 목록을 함께 사용한다.

통신의 신뢰성을 검증하는 더 좋은 방법은 암호화 서명을 이용하는 것이다.
키로 요청과 응답에 서명하며, 연결을 통해 키를 보낼 필요가 없다는 점에서 API 키 이용보다 안전하다.
상대는 자신의 키로 서명을 검증한다.
구현은 2 개의 비대칭 키 쌍을 이용하며, 개인 키는 교환하지 않는다고 가정한다.

요청이 들어오는 특정 주소나 주소의 범위를 아는 경우 IP 주소 검증을 적용할 수 있다.
대부분 IP 주소 검증은 애플리케이션 수준이 아닌 네트워크 계층에서 수행된다.


참고 사이트 & 함께 보면 좋은 사이트

본 포스트는 로렌티우 스필카 저자의 스프링 시큐리티 인 액션을 기반으로 스터디하며 정리한 내용들입니다.






© 2020.08. by assu10

Powered by assu10