[Spring Security in Action] 1장. 오늘 날의 보안
스프링 시큐리티는 인증과 접근 제어를 위해 세부적인 맞춤구성이 가능한 강력한 프레임워크라고 소개한다.
스프링 애플리케이션에 스프링 시큐리티를 어떻게 이용하면 좋을까?
일반적으로 애플리케이션 수준에서 가장 흔한 보안의 활용 사례는 누가 작업을 수행할 수 있는지, 특정 데이터를 이용할 수 있는지를 결정하는 것이다. 구성을 기반으로 요청을 가로채고 권한을 가진 사용자만 보호된 리소스에 접근할 수 있도록 스프링 시큐리티 구성 요소를 작성한다.
스프링 시큐리티 구성 요소의 다른 책임은 시스템의 다른 부분 간의 데이터 전송 및 저장과 관련이 있다.
구성 요소는 이 다른 부분에 대한 호출을 가로채서 데이터에 작업을 한다. 예를 들어가 데이터가 저장될때 암호화나 해싱 알고리즘을 적용할 수 있고 데이터 인코딩으로 이용 권리가 있는 주체만 데이터에 접근할 수 있게한다
소프트웨어 보안
GDPR (General Data Protection Regulatons; 일반 데이터 보호 규정) 요구 사항을 고려할 때 상당 부분이 민감한 정보일 수 있는 대량의 데이터를 관리한다. 애플리케이션은 이러한 정보에 접근, 변경 또는 가로챌 기회가 없게 해야 한다.
보안은 각 계층에 적용된다. 스프링 시큐리티는 애플리케이션 수준 보안을 구현한 프레임워크
마이크로서비스 아키텍처에서는 다양한 취약성이 생길 수 있으니 주의 해야한다. 악성 사용자가 애플리케이션 보안이 적용되지 않는 VM에 침입하는데 성공하면 시스템에 속한 다른 애플리케이션도 통제할 수 있다. VM은 인프라 계층으로 따진다.
한 계층의 보안 문제를 해결할 때는 되도록 위 계층이 존재하지 않는다고 가정하는 것이 바람직하다.
- 모놀리식과 마이크로서비스
모놀리식 아키텍처란 실행 가능 한 하나의 아티팩트로 모든 책임을 구현하는 애플리케이션을 말한다. 한 애플리케이션이 모든 활용 사례를 충족한다고 볼 수 있다. 이 아키텍처에서는 애플리케이션을 유지관리하기 쉽게 만들기 위해 책음을 다른 모듈 내에서 구현할 수 잇지만 런타임에 한 모듈의 논리를 다른 모듈의 논리와 분리할 수 없다. 일반적으로 모놀리식 아키텍처는 확장과 배포 관리를 위한 유연성이 없다.
마이크로 시스템에선 여러 실행 가능한 아티팩트에서 책임을 구현한다. 동시에 실행되고 필요할 때 네트워크를 통해 서로 통신하는 여러 애플리케이션으로 구성된 시스템이라고 할 수 있다. 이렇게 하면 확장 유연성은 좋지만 지연 시간, 보안 문제, 네트워크 안정성, 분산 지속성, 배포 관리 등의 다른 어려움이 있다.
웹 애플리케이션의 일반적인 보안 취약성
1) 인증과 권한 부여의 치약성
인증을 통해 권한을 부여 받고 이에 해당되는 서비스에 접근한다.
2) 세션 고정
이미 생성된 세션 ID를 재 이용해 유효한 사용자를 가장한다. 애플리케이션이 세션 값을 쿠키에 저장하는 경우 공격자는 스크립트를 주입해 피해자의 브라우저가 스크립트를 실행하도록 할 수 있다.
3) XSS(교차 사이트 스크립팅)
서버에 노출된 웹 서비스로 클라이언트 쪽 스크립트를 주입해 다른 사용자가 이를 실행하도록 하는 공격. 따라서 원치 않는 외래 스크립트의 실행을 막기 위해 이용하기 전이나 저장하기 전에도 요청을 적절하게 '소독'하는 과정이 필요하다. 계정 가장이나 DDoS 같은 분산 공격 참여 등의 결과가 일어날 수 있다.
4) CSRF(사이트 간 요청 위조)
특정 서버에서 작업을 호출하는 URL을 추출해 애플리케이션 외부에서 재사용할 수 있다 가정한다. 서버가 요청의 출처를 확인하지 않고 무턱대고 실행하면 다른 모든 곳에서 요청이 실행될 수 있다. csrf를 통해 동작을 숨겨 사용자가 서버에서 원치 않는 동작을 실행하도록 할 수 있다. 일반적으로 시스템의 데이터를 변경하는 동작을 실행한다.
5) 웹 애플리케이션 주입 취약성
시스템에 특정 데이터를 유입하는 취약성을 이용한다. 공격의 목표는 시스템에 피해를 주고, 원치 않는 방법으로 데이터를 변경하거나 원래는 접근할 수 없는 데이터를 검색하는 것이다. XSS도 주입 취약성의 하나로 시스템에 피해를 가할 목적의 클라이언트 쪽 스크립트를 주입한다. 다른 예로는 SQL 주입, XPath 주입, OS 명령 주입, LDAP 주입 등 여러 가지가 있다.
6) 민간함 데이터의 노출
민감한 키 값 같은걸 application.properties 또는 yml 파일등의 구성 파일에서 설정하면 다른 사람이 볼 수 있다. 민감한 데이터의 노출과 관련햏서는 애플리케이션에서 콘솔에 기록하거나 스플렁크, 일래스틱서치 같이 데이터베이스에 저장하는 로그 정보도 있다. 공개정보가 아닌 것은 절대 로그에 기록하지 말자.
7) 메서드 접근 제어 부족
애플리케이션 수준에도 한ㄱ ㅖ층에만 권한부여를 하면 안된다. COntroller에만 권한부여 하면 잘 동작하긴 하지만 향후 구현을 추가할 때 모든 권한 부여 요구 사항을 테스트 하지 않고 해당 기능을 노출하는 문제의 소지가 있다.
8) 알려진 취약성이 있는 종속성 이용
기능을 만들기 위해 이용하는 라이브러리나 프레임워크 같은 조옥성에 취약성이 있을 수 있다. 이용하는 종속성은 항상 주의 깊게 살펴보자.
다양한 아키텍처에 적용된 보안
일체형 웹 애플리케이션 설계
백엔드, 프론트엔드 개발 간 직접적 분리가 없다. 애플리케이션이 HTTP 요청을 수신하고 HTTP 응답을 클라이언트에 보내는 일반 서블릿 흐름을 통하는 것이다.
세션이 있는 한 세션 고정 취약성과 앞서 언급한 CSRF 가능성을 고려해야하고 하고 HTTP 세션에 저장하는 정보도 고려해야한다.
서버 쪽 세션은 준 영구적이며 데이터의 상태를 저장하므로 수명이 더 길다. 메모리에 유지되는 시간이 길수록 통계적으로 접근 가능성이 커진다. 힙 덤프에 접근할 수 있는 사람은 내부 메모리의 정보를 읽을 수 있다. 액추에이터를 포함하는 경우가 많은데 힙 덤프를 반환하기 쉽다.
CSRF의 취약성에 관한 관점으로 돌아가 이 취약성을 완화하는 방법으로는 CSRF 방지 토큰을 이용하는 것이다. 스프링 시큐리ㅣ에 기본적으로 들어있고 CSRF 보호와 원점 CORS의 검증도 기본적으로 활성화되어 있다.
백엔드 프론트엔드 분리를 위한 보안 설계
현재는 웹 애플리케이션을 개발할 때 둘을 분리하는 경우가 많다. 따로 만들어진 프론트 코드는 Rest 엔드포인트를 통해 백엔드와 통신한다.
일반적으로 서버 쪽 세션을 줄이고 클라이언트 쪽 세션으로 대체하는 것이 좋다. 이러한 유형의 시스템 설계는 모바일 애플리케이션에 이용되는 것과 비슷하다. 안드로이드나 IOS 운영체제에서실행되는 애플리케이션은 REST 엔드포인트로 백엔드를 호출한다.
보안의 관점에서 고려할 때 몇 가지 다른 측면 존재.
1) CSRF 및 CORS 구성은 일반적으로 더 복잡하다. 시스템을 수평적으로 확장하기를 원할 수 있는데, 반드시 백엔드와 같은 출처의 프론트엔드를 이용해야 하는 것은 아니다. 모바일 애플리케이션의 경우 출처를 확인할 수 조차 없다.
엔드포인트 인증에 HTTP Basic을 이용하는 방법은 실용적이고 가장 간단하지만 바람직하지는 않다. HTTP Basic을 이용하려면 호출마다 자격 증명을 전송해야한다. 자격 증명은 암호화되지 않는다. 브라우저는 Base64 인코딩을 이용해 사용자 이름과 암호를 전송하므로 각 엔드포인트 호출의 헤더에 자격 증명이 노출된다. 인증 정보가 로그인한 사용자를 나타낸다고 하면 사용자가 모든 요청에 대해 자격증명을 입력해야하는 것을 원하지 않을 것이다. 자격 증명을 클라이언트 쪽에 저장하는 것도 원하지 않을 것이다.
OAuth 2 흐름이라는 더 나은 접근법을 이용하는 인증과 권한 부여의 대안이 나온다.
- 애플리케이션 확장성에 대한 간단한 복습
확장성이란 애플리케이션 자체 또는 그 아키텍처를 변경할 필요 없이 이용되는 리소스에 맞게 처리할 수 있는 요청을 늘리거나 줄일 수 있는 소프트웨어 애플리케이션의 특성이다. 확장성은 주로 수직 확장성과 수평 확장성의 두 가지 유형으로 분류.
수직으로 확장되면 시스템의 리소스가 애플리케이션의 필요에 맞게 조정된다. 예를 들어 요청이 증가하면 시스템에 더 많은 메모리와 처리 성능이 추가된다.
수평으로 확장되면 실행 중인 애플리케이션의 인스턴스 수를 변경하는 방법으로 수행된다. 예를 들어 요청이 증가하면 이를 처리하기 위해 다른 인스턴스를 더 시작한다. 물론, 새로 시작된 애플리케이션 인스턴스는 떄떄로 여러 데이터 센터의 추가 하드웨어에서 제공하는 리소스를 소비한다. 수요가 감소하면 인스턴스를 줄일 수 있다.
OAuth 2 흐름 이해
백엔드에 요청할 때마다 자격 증명을 다시 전송하고 자격 증명을 클라이어느 쪽에 저장하는 것은 좋지 않다. 이의 대안책이다.
OAuth 2 프레임워크는 권한 부여 서버와 리소스 서버라는 두 가지 별도의 엔티티를 정의한다. 권한 부여 서버의 목적은 사용자에게 궈한을 부여하고 사용자의 이용 권리 집합을 지정하는 토큰을 제공하는 것이다. 이 기능을 구현하는 백엔드 부분이 리소스 서버라고 하며 호출할 수 있는 엔드포인트는 보호된 리소스라고 볼 수 있다. 권한 부여를 수행한 후 획득한 토큰에 따라 리소스에 대한 호출이 허용되거나 거부된다. 표준 OAuth 2 권한 부여 흐름의 일반적인 모습을 보여준다.
- 사용자가 애플리케이션(클라이언트)의 기능에 접근한다. 애플리케이션은 백엔드의 리소스를 호출해야 한다.
- 애플리케이션이 리소스를 호출하려면 먼저 액세스 토큰을 얻어야 하므로 권한 부여 서버를 호출해서 토큰을 얻는다. 이 요청을 위해 사용자 자격 즈명이나 때에 따라 갱신 토큰을 보낸다.
- 자격 증명이나 갱신 토큰이 올바르면 권한 부여 서버가 새로운 액세스 토큰을 클라이언트로 반환한다.
- 필요한 리소스를 호출할 때 리소스 서버에 대한 요청의 헤더는 액세스 토큰을 이용한다.
토큰의 수명은 고정되고 일반적으로 오래 유지되지 않으며 토큰이 만료되면 앱이 새 토큰을 받아야 한다. 서버는 필요한 경우 토큰의 만료 시간보다 일찍 토큰을 실격 시킬 수 있다. 다음 목록은 이 흐름의 몇 가지 이점이다.
- 클라이언트는 사용자 자격 증명을 저장할 필요 없이 액세스 토큰과 (최종적으로) 갱신 토큰만 저장하면 된다.
- 애플리케이션은 사용자 자격 증명을 (종종 네트워크에서) 노출하지 않는다.
- 누군가가 토큰을 가로채면 사용자 자격 증명을 무효로 할 필요 없이 토큰을 실격시킬 수 있다.
- 토큰을 이용하면 제삼자가 사용자를 가장하지 않고도 사용자 대신 리소스에 접근할 수 있다. 물론 이 경우 공격자가 토큰을 훔칠 수 있지만 토큰은 일반적으로 수명이 제한되므로 이 취약성을 악용할 수 있는 기간도 제한된다.
- 참고.
간단한 개요 위해 암호 그랜트 유형이라는 OAuth 2 흐름을 설명했다. OAuth 2는 여러 그랜트 유형 정의한다. 클라이언트 애플리케이션에 반드시 자격 증명이 있는 것은 아니다. 승인 코드 그랜트를 이용하면 애플리케이션은 인증을 브라우저에서 권한 부여 서버가 구현하는 로그인으로 리디렉션한다. OAuth 2 흐름으로 모든 상황을 완벽하게 해결할 수 있는 것은 아니다.
토큰을 관리하는 가장 좋은 방법은?
- 앱의 메모리에 토큰 유지
- 데이터베이스에 토큰 유지
- JWT(JSON 웹 토큰)를 통한 암호화 서명 사용
API 키, 암호화 서명, IP 검증을 이용해 요청 보안
호출자를 인증하고 권한을 부여하기 위해 사용자 이름과 암호가 필요 없는 경우도 가끔 있지만, 이때도 교환되는 메시지를 아무도 변경할 수 없게 하고 싶을 것이다. 두 백엔드 구성 요소 간에 요청이 있을 때, 이 접근법이 필요할 수 있다. 예를 들어 백엔드를 서비스의 그룹으로 배포하거나 시스템 외부의 다른 백엔드를 이용하는 구성 요소 간ㄴ의 메시지를 어떤 방식으로든 검증하고 싶을 수 있다.
- 요청 및 응답헤더에 정적 키 이용
- 암호화 서명으로 요청 및 응답 서명
- IP 주소에 검증 적용
정적 키를 이용하는 것은 가장 약한 접근법이다. 요청과 응답의 헤더에 키를 이용하며 헤더 값이 잘못되면 요청과 응답이 수락되지 않는다. 이는 네트워크에서 키 값을 교환한다고 가정한 것이며 트래픽이 데이터 센터 외부로 이동하면 가로채기 쉽다. 키 값을 얻으면 엔드포인트에 대한 호출을 재현할 수 있다. 이 접근법을 이용할 때는 일반적으로 IP 주소 허용 목록을 함께 결합한다.
통신의 신뢰성을 테스트하는 더 좋은 방법은 암호화의 서명을 이용하는 것이다. 키로 요청과 응답에 서명하며 연결을 통해 키를 보낼 필요가 없다는 점에서 정적 권한 부여 값보다 이점이 있다. 상대는 자신의 키로 서명을 검증할 수 있다. 구현은 두 개의 비대칭 키 쌍을 이용할 수 있으며 개인 키를 교환하지 않는다고 가정한다. 더 간단한 버전은 구성을 위해 처음에 교환이 필요한 대칭키를 이용하며 이때는 서명을 계산하는데 더 많은리소스가 소비된다.
요청이 들어오는 특정 주소 또는 주소의 범위를 아는 경우 앞서 언급한 솔루션 중 하나를 이용해 IP 주소 검증을 할 수 있다. 이 방법은 앱이 수락하도록 구성한 IP 주소 외의 주소에서 오는 요청을 거부한다고 가정한다. 그러나 대부분의 경우 IP 검증은 애플리케이션 수준에서 수행되지 않고 훨씬 이전의 네트워킹 계층에서 수행된다.