애플리케이션에 보안을 적용하는 방법을 배우기 전에 무엇으로부터 애플리케이션을 보호하는지 알아야 한다. 공격자는 공격을 시작하기 전에 애플리케이션의 취약성(Vulnerability)을 파악하고 공략해야한다.
다음은 알고 있어야 할 일반적인 취약성의 목록이다.
•
인증 취약성
•
세션 고정
•
XSS(교차 사이트 스크립팅)
•
CSRF(사이트 간 요청 위조)
•
주입
•
기밀 데이터 노출
•
메서드 접근 제어 부족
•
알려진 취약성이 있는 종속성 이용
1. 인증과 권한 부여의 취약성
인증은 애플리케이션을 이용하려는 사람을 식별하는 프로세스를 의미한다. 익명 액세스를 지원하기도 하지만 대부분 식별된 사용자만 특정 작업을 이용할 수 있게 한다. 식별된 사용자에게 권한을 부여한다. 권한 부여란 인증된 이용자가 특정 기능과 데이터에 대한 이용 권리가 있는지 확인하는 프로세스이다.
인증 취약성이 있으면 다른 사람의 데이터에 접근 및 이용할 수 있는 상황이 나올 수 있다. 또한 수준에 따른 제한이 없는 경우도 마찬가지이다.
2. 세션 고정(Session fixation)
세션 고정 취약성은 웹 애플리케이션이 인증 프로세스 중에 고유한 세션 ID를 할당하지 않아, 기존 세션 ID가 재사용될 가능성이 있는 경우에 발생한다. 즉, 이미 생성된 세션ID를 재이용해 유효한 사용자를 가장할 수 있다.
공격자는 유효한 세션ID를 획득한 후 사용자의 브라우저가 이를 이용하게 한다. 웹 애플리케이션이 구현된 방식에 따라 다양하게 이용 가능하며 악성 링크 유도 및 외래 양식 이용 유도를 할 수 있다. 또한 애플리케이션이 세션 값을 쿠키에 저장하는 경우 스크립트를 주입하여 브라우저가 스크립트를 실행하게 할 수 있다.
3. XSS(Cross-Site Scripting, 교차 사이트 스크립팅)
클라이언트 쪽 스크립트를 주입하여 다른 이용자가 이를 실행하도록 하는 공격이다. 이러한 외래 스크립트를 방지하기 위해 이용 전에 요청을 적절하게 확인하는 과정이 필요하다. 해당 공격에 방어 체계가 없는 경우 XSS에 노출된 모든 이용자들이 공격자가 의도한대로 정보를 가져오거나 보낼 수 있다.
4. CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조)
특정 서버에서 작업을 호출하는 URL를 추출하여 애플리케이션 외부에서 재사용할 수 있다는 가정하에 서버가 요청의 출처를 확인하지 않고 실행하면 다른 모든 곳에서 요청이 발생할 수 있다. 공격자는 사용자가 원치 않는 동작을 실행하도록 할 수 있어 시스템의 데이터를 변경하는 동작을 실행할 수 있다.
사용자는 자신의 계정에 로그인한 후 위조된 코드가 포함된 페이지에 접근한다. 위조된 페이지에는 사용자를 대신해 실행할 스크립트가 들어 있다. 사용자가 접근 시, 브라우저는 이 스크립트를 실행하고 그러면 악성코드가 사용자를 대신해 동작을 실행한다.
5. 웹 애플리케이션의 주입 취약성
위와 같은 공격들은 취약점을 파악하여 데이터를 주입(Injection) 공격을 이용한다. 원치 않는 요청 및 접근이 불가능한 데이터에 접근 및 변경하는 것이 목적이다.
주입 공격에는 여러 유형이 있으며, XSS, SQL 주입, XPath 주입, OS 명령 주입, LDAP 주입 등 여러 가지가 있다.
6. 민감한 데이터의 노출 처리
노출되지 말아야할 정보를 application.yml, application.properties와 같은 파일에 설정하면 소스 코드를 볼 수 있는 모든 사람이 이러한 개인값에 접근이 가능하다. 또한 값의 변경 기록이 저장되는 것도 확인할 수도 있다. 공개 정보가 아닌 것은 절대 로그에 기록하지 말아야 한다.
특히 git을 통한 협업 진행 시 이러한 주요 데이터의 노출이 발생할 경우가 높은데, gitignore 처리를 반드시 해서 이를 방지하는 것이 중요하다.
다음은 로그에 기록해서는 안 될 정보를 포함하는 메시지의 예시이다.
"User logged in with password: mySuperSecretPassword"
Bash
복사
비밀번호는 반드시 해싱처리해야하며, 절대 로그에 기록해서는 안된다.
응답 A:
{
"상태": 401,
"오류": "권한 없음",
"메시지": "사용자 이름이 올바르지 않음",
"경로" : "/login "
}
응답 B:
{
"상태": 401,
"오류": "권한 없음",
"메시지": "암호가 올바르지 않음",
"경로" : "/login "
}
Bash
복사
위의 응답은 같은 인증 엔드포인트를 호출한 다른 결과를 보여준다. 이러한 경우, 로그 메시지를 이용해 실행 컨텍스트를 분석할 수 있고 시스템이 무차별 대입 공격에 더 취약해질 수 있다.
{
"상태": 401,
"오류": "권한 없음",
"메시지": "사용자 이름 또는 암호가 올바르지 않음",
"경로" : "/login "
}
Bash
복사
이렇게 개선해야 한다.
7. 메서드 접근 제어 부족
에를 들어 controller → service → repository 애플리케이션이 있고 controller 계층에만 권한 부여를 적용한다고 가정하였을 때 service에서 인증된 사용자에게 속하지 않은 계정을 요청해도 repository에서 데이터 검색을 제한하지 않는다. 당장은 문제가 생기지 않더라도 기능을 추가할 때 문제가 발생할 수 있다. 각각의 컨트롤러에 권한 부여 규칙을 다 적용한다면 괜찮을 수 있지만 각각의 계층에서 인증된, 권한이 있는 사용자에게 데이터를 노출하지 않는 것이 가장 바람직하다. 따라서 위의 경우에는 권한 부여에 대한 구성을 controller에서 repository로 옮기는 것이 더 좋은 방법이다.
다양한 아키텍처에 적용된 보안
어떠한 보안 시스템이 무조건 정답일수는 없다. 가장 알맞은 아키텍처를 사용해야 하며 이에 따라서 스프링 시큐리티 구성에도 큰 영향을 미친다.
다음과 같이 다양한 아키텍처에 적용된 보안이 있다.
•
일체형 웹 애플리케이션 설계
•
백엔드/프론트엔드 분리를 위한 보안 설계
•
OAuth2 흐름
•
API Key, 암호화 서명, IP 검증을 이용한 보안 요청
일체형 웹 애플리케이션
일체형 웹 애플리케이션에서는 백엔드와 프론트엔드간 직접적인 분리가 없다. 애플리케이션이 HTTP 요청수신하고 응답을 클라이언트에 보내는 일반적인 서블릿 흐름을 통한다.
예를 들어 스프링 부트로 개발을 진행할 때 액추에이터를 포함하는 경우 루트 접근 권한 없이도 다른 데이터에 접근 및 수정이 가능하다. 이러한 접근 방식은 메모리에 유지되는 시간이 길수록 통계적으로 접근 가능성이 커진다.
이러한 CSRF의 취약성을 완화하는 가장 쉬운 방법은 CSRF 방지 토큰을 사용하는 것이다. 스프링 시큐리티에 기본적으로 포함되어있으며 CORS의 검증도 기본적으로 활성화 되어있다. 인증과 권한 부여를 통하여 사용자의 자격증명을 애플리케이션과 다른 시스템에서 관리할 수 있다.
백엔드/프론트엔드 분리
현재는 웹 애플리케이션을 개발할 때, 프론트와 백을 분리하도록 선택하는 경우가 많다. 이러한 아키텍처에서 개발자는 앵귤러, React, Vue.js와 같은 프레임워크를 이용해 프론트엔드를 개발하며, REST 엔드포인트를 통해 백엔드와 통신한다.
일반적으로 서버 쪽 세션을 줄이고 클라이언트 쪽 세션으로 대체하는 것이 좋다. 이러한 유형의 시스템 설계는 모바일 애플리케이션에 이용되는 것과 비슷하다.
이런 경우 보안의 관점에서 고려할 몇 가지 다른 측면이 있다. CSRF 및 CORS 구성이 더 복잡하며, 엔드포인트 인증에 HTTP Basic을 이용하는 방법은 실용적이지만 바람직하지 않다.
HTTP Basic을 이용하려면 호출마다 자격 증명을 전송해야 한다. 자격 증명은 암호화 되지 않고, 브라우저는 Base64 인코딩을 이용해 사용자 이름과 암호를 전송하기에, 각 엔드포인트 호출의 헤더에 자격 증명이 노출된다. 또한 인증 정보가 로그인한 사용자를 나타낸다면, 사용자는 모든 요청에 대해 자격 증명을 입력하는 것은 원치 않을 것이고, 그렇다고 자격 증명을 클라이언트 쪽에 저장하는 것도 원치 않을 것이다. 따라서 이 방식은 바람직하지 않다. 이러한 이유로 OAuth 2 흐름이라는 더 나은 접근법을 활용한다.
OAuth 2
OAuth2는 권한 부여 서버와 리소스 서버라는 두 가지 별도의 엔티티를 정의한다. 권한 부여 서버의 목적은 사용자에게 권한을 부여하고 사용자의 이용 권리 집합을 지정하는 토큰을 제공하는 것이다. 해당 기능을 구현하는 백엔드 부분을 리소스 서버라고 하며 호출할 수 있는 리소스에 대한 호출이 허용되거나 거부된다.
OAuth2의 흐름은 다음과 같다.
1.
사용자가 애플리케이션에 접근하면 백엔드의 리소스를 호출하게 한다.
2.
리소스를 호출하려면 액세스 토큰이 필요하므로 권한 부여 서버에서 토큰을 얻게한다.
3.
액세스 토큰, 리프레시 토큰이 올바른 경우 권한 부여 서버가 새로운 액세스 토큰을 클라이언트로 반환한다.
4.
필요한 리소스를 호출할 때 헤더의 액세스 토큰을 활용한다.
토큰의 수명은 고정되어 있으며 오래 유지되지 않는다. 만료된 경우 새 토큰을 발급받아야 하며 필요한 경우 서버에서 토큰의 만료 시간보다 일찍 토큰을 만료시킬 수 있다. 이러한 방법을 사용하면 다음과 같은 장점이 있다.
1.
클라이언트는 자격 증명을 저장할 필요 없이 엑세스 토큰과 리프레시 토큰만 저장하면 된다.
2.
애플리케이션은 사용자 자격 증명을 노출하지 않는다.
3.
누군가 토큰을 가로채도 만료시키면 된다.
4.
토큰을 이용하면 공격자가 사용자 대신 리소스에 접근할 수도 있지만, 토큰 수명에 제한을 받아 악용할 수 있는 기간도 제한된다.
이러한 토큰을 관리하는 방법은 앱 메모리에 토큰 유지, 데이터베이스에 토큰 유지, JWT를 통한 암호화 서명 사용 등이 있다.