🔥 8장 실무에서 꼭 필요한 보안 지식
🚀 중요한 보안
https://주소/...?cd=고객 코드로그인한 유저는 특별한 검증없이 고객 코드를 조회할 수 있어서 고객 정보가 유출되는 보안 사고 사례가 있다.
무작위로 만든 고객 코드가 실제 고객 코드와 일치하면 해당 고객의 정보를 취득할 수 있었다.
또 다른 사례로는 API 를 호출할 때 회원 식별자를 전달했는데 이 API 는 암호 변경 API 로 회원 식별자와 변경할 암호의 두 파라미터를 전달받았다.
- 현재 요청이 로그인한 회원의 요청인지 확인하지 않음
- 회원 식별자가 로그인한 회원의 식별자인지 검증하지 않음
- 변경하지 전 암호를 검증하지 않음
위 문제로 인해 API 의 구조만 알면 누구나 다른 회원의 암호를 변경할 수 있었다.
보안 사고가 발생할 가능성을 낮추려면 서버 개발자는 기본적인 보안에 신경 써야 한다.
🚀 인증과 인가
- 인증: 사용자가 누구인지 확인
- 인가: 사용자에게 자원에 접근할 수 있는 권한을 확인
🎯 인증과 토큰
아이디와 암호를 입력하는 로그인은 인증의 한 형태이다. 보안을 강화하기 위해 2단계 인증을 사용하기도 한다.
인증이 필요한 기능에 대해 매번 아이디와 암호를 입력받지 않고 토큰을 사용해서 사용자를 식별한다.
토큰과 사용자 간의 매핑 정보를 저장할 위치로는 크게 다음 2가지를 사용한다.
- 서버의 별도 저장소: 별도 저장소에 토큰과 사용자 식별 정보를 저장한다.
- 토큰: 토큰 자체에 사용자 식별자 정보를 저장한다.
✅ 별도 저장소에 토큰과 사용자 식별자 정보 저장하기
서버는 토큰과 사용자 식별 정보를 DB나 레디스와 같은 별도 저장소에 보관할 수 있다.
외부 저장소에 보관되는 정보는 토큰, 사용자 식별자, 생성 시간, 최근 사용 시간, 그 외 유효시간, 클라이언트 버전 등의 데이터를 갖는다.
서버는 클라이언트가 전송한 토큰을 이용해서 저장소에서 사용자 식별자를 구한다.
토큰 데이터는 크기가 크지 않기 때문에 수백만 개의 토큰을 저장해도 DB 용량에 큰 부담은 없다.
외부 저장소가 아닌 서버 메모리에 토큰 데이터를 저장할 수도 있다. 서블릿 세션이 이에 해당한다.
톰캣과 같은 컨테이너는 메모리에 세션 객체를 저장한다. 서블릴 세션은 고유의 세션 ID 를 생성하는데 이 세션 ID 가 토큰에 해당한다.
메모리에 토큰 데이터를 저장하는 방식을 사용할 때는 고정 세션이 필요하다.
분산 환경에서는 로드밸런서를 이용해 고정 세션 방식으로 풀어낸다.
메모리는 서버 재시작과 크기에 대한 제약이 있어 별도 저장소에 보관하기도 한다.
✅ 토큰 자체에 사용자 식별자 정보 저장하기
대표적인 방식이 JWT가 있다.
사용자가 로그인에 성공하면 사용자 식별자를 값으로 갖는 JWT 를 생성해서 클라이언트에 토큰으로 응답한다.
장점
- 토큰만 있으면 사용자가 누구인지 확인할 수 있다.
- 별도의 외부 저장소나 메모리에 토큰 데이터를 저장할 필요가 없다.
단점
- 네트워크 트래픽이 증가한다. (서버와 클라이언트가 주고받는 데이터의 크기가 증가하므로)
- 토큰은 서버에서 제어할 수 없다.
✅ 토큰 송수신
클라이언트는 서버에 토큰을 전송할 때 주로 쿠키나 헤더로 전송한다.
웹 사이트는 주로 쿠키 방식을 사용한다.
서버 세션도 쿠키를 사용해서 세션 ID를 주고 받는다.
서버는 토큰 문자열을 값으로 갖는 쿠키를 웹 브라우저에 응답한다.
웹 브라우저는 서버가 전송한 쿠키를 모든 요청에 함께 전송하므로 토큰을 서버에 전송하기 위해 별도 코드를 작성할 필요가 없다.
헤더를 사용할 수도 있다.
쿠키도 헤더를 통해 전송되지만 쿠키를 제외한 다른 헤더를 의미하며, 헤더를 통해 토큰을 전송한다.
클라이언트는 토큰을 로컬에 저장했다가 서버 API 요청을 호출할 때 헤더를 이용해서 토큰을 전송한다.
✅ 토큰 보안
보안을 위해서 토큰을 사용하는 만큼 토큰 자체의 보안에도 신경 써야 한다.
서버 보안을 철저히 해도 클라이언트가 보안에 취약하면 토큰이 탈취될 수 있기 때문이다.
토큰을 탈취한 클라이언트는 원래 토큰 소유자처럼 행세할 수 있다.
- 토큰 유효 시간 제한
- 토큰 생성 시점을 기준으로 제한 시간 두기
- 마지막 접근 시간을 기준으로 토큰 유효 시간 설정하기 (서블릿 세션이 이 방식을 사용함)
토큰 유효 시간은 너무 짧으면 불편하고 너무 길면 잠시 자리를 비운 사이 위험할 수 있다.
유효 시간과 함께 클라이언트 IP 를 비교하면 토큰 보안이 향상된다.
토큰을 생성할 때 접근한 클라이언트 IP 와 실제 토큰을 전송한 클라이언트 IP 가 같은지 비교한다.
보안 사교 영향을 줄이고 싶다면 토큰을 무효화해서 강제로 로그아웃시키는 기능도 필요하다.
✅ 토큰 재발급
인증과 인가에서 사용하는 토큰으로 액세스 토큰과 리프레시 토큰이 있다.
만료 시간이 짧은 액세스 토큰과 함께 만료 시간이 상대적으로 긴 리프레시 토큰을 함께 발급한 후,
액세스 토큰이 만료되면 리프레시 토큰을 이용해서 새로운 액세스 토큰을 발급해 준다.
🎯 인가와 접근 제어 모델
인가는 사용자가 요청한 기능을 실행할 권한이 있는지 확인하는 역할을 한다.
접근 제어의 기본은 접근한 사용자를 토큰이나 세션으로 식별하는 것이다.
서비스에 따라 사용자마다 실행할 수 있는 기능에 차이를 두기도 한다.
사용자가 접근할 수 있는 기능(또는 자원)을 관리하기 위한 모델을 접근 제어 모델이라고 한다.
대표적인 접근 제어 모델로는 역할 기반 접근 제어 모델이 있다.
RBAC 는 역할별로 실행 가능한 기능 집합을 할당하고, 사용자에게는 역할을 부여한다.
- 역할은 허용된 기능 집합을 갖는다.
- 사용자에게는 역할을 부여해 역할에 허용된 기능을 실행할 수 있는 권한을 가진다.
- 사용자마다 개별적으로 권한을 부여할 수도 있다.
역할별 권한 부여 방식과 사용자별 권한 부여 방식은 각각 장단점이 있기 때문에 함께 사용하는 경우가 많다.
RBAC 를 사용할 때는 역할의 설계와 관리에 신경 써야 한다.
역할을 무분별하게 정의하면 중복된 기능을 가진 유사한 역할이 계속 생기기 쉽다.
사용자별 권한 부여 방식은 시스템 규모가 작거나 역할을 나누기 애매할 때 적합하다.
또한 역할별 권한 부여보다 구현이 단순하기 때문에 개발 시간이나 우선 순위 등을 고려해 사용자별 권한 방식을 선택하기도 한다.
사용자의 속성을 이용해서 접근을 제어하는 속성 기반 접근 제어 모델도 있다.(ex. 사용자별 IP)
정교한 접근 제어가 가능하지만, 구현이 복잡해지고 사용할 속성과 규칙을 정의하는데도 많은 시간이 소요된다.
🚀 데이터 암호화
로그인 아이디와 비밀번호는 유출되면 가장 큰 피해로 이어질 수 있는 데이터 중 하나다.
비밀번호는 여러 서비스에서 동일하게 사용하는 경향이 있어 다른 서비스마저 위험해지고
외부 유출뿐 아니라 내부에서도 문제가 될 수 있다.
데이터베이스에 접근할 수 있는 엔지니어가 평문으로된 데이터를 확인한다면 그 자체로 보안에 위협이 될 수 있다.
엔지니어가 악용하지 않더라도 PC 가 해킹을 당해 유출될 가능성이 있다.
🎯 단방향 암호화
암호화한 데이터를 다시 복호화할 수 없는 암호화 방식이다.
단방향 암호화는 해시 함수를 사용해서 데이터를 해시 값으로 변환한다.
해시 알고리즘에는 SHA-256, MD5, BCrypt 등이 있다.
로그인 비밀번호 같은 문자열을 암호화하는 데 주로 사용되지만, 실제 암호화는 바이트 데이터를 기준으로 동작한다.
입력 파라미터와 리턴 값은 바이트 배열이므로 문자열에 알맞는 캐릭터셋을 이용한다.
암호환 결과를 영속화하려면 바이트 배열을 문자열로 표현해야 한다. 16진수 표기법이나 Base64 표기법을 사용해서 문자열로 표현한다.
📝 충돌 저항성(collision resistance)
해시 함수는 원본 데이터에 상관없이 일정한 길이의 해시 값을 생성한다.
길이가 제한되기 때문에 서로 다른 데이터가 동일한 해시 값을 가질 수 있다.
서로 다른 데이터에 대해 최대한 다른 해시 값을 생성하는 해시 알고리즘이 좋다고 할 수 있다.
동일한 해시 값을 갖는 서로 다른 데이터를 찾기 어려울 때 해시 함수는 충돌 저항성을 갖는다.
✅ 값의 비교
단방향 암호화는 해시 함수로 생성한 해시 값이 같다면 두 데이터가 같다고 간주한다.
로그인할 때 비밀번호가 일치하는지 여부도 해시 값을 이용해서 비교한다.
단방향 암호화는 원본 데이터로 복호화할 수 없기 때문에, 사용자가 비밀번호를 잊었을 때 기존 비밀번호를 알려주는 기능은 구현할 수 없다.
✅ Salt 로 보안 강화하기
같은 해시 알고리즘을 사용하면 동일한 원본 데이터에 대해 항상 동일한 해시 값이 생성된다.
같은 원본 데이터에 대해 항상 동일한 해시 값을 생성하는 것은 보안에 취약하다.
해시 알고리즘은 이 취약점을 보완하기 위해 솔트를 사용한다.
솔트는 임의의 값이며, 암호활 때 솔트를 함께 사용하면 솔트 값에 따라 결과 해시 값이 달라진다.
솔트를 사용해서 암호화한 해시 값은 유출되더라도 미리 계산해둔 해시 표에서 일치하는 값을 찾기 어렵다.
사용자마다 서로 다른 솔트를 사용하면 보안 강도를 더욱 높일 수 있다.
🎯 양방향 암호화
양방향 암호화는 암호화와 복호화가 모두 가능한 방식이다.
양방향 암호화는 암호화/복호화할 때 키 를 사용한다.
대칭 키 방식과 비대칭 키 방식으로 나뉜다.
대칭 키는 암/복호화할 때 동일한 키를 사용한다.
비대칭 키는 암/복호화할 때 서로 다른 키를 사용한다.
비대칭 키 암호화에서는 공개 키와 개인 키를 생성한다.
개인 키는 키 소유자만 접근할 수 있어야 한다.
공개 키는 암호화할 때, 비공개 키는 복호화할 때 사용된다.
키 소유자는 공개 키와 개인 키 쌍을 생성한 뒤, 데이터 송신자에게 공개 키를 제공한다.
공개 키로 암호화한 데이터는 개인 키로만 복호화할 수 있기 때문에, 공개 키가 유출되더라도 암호화한 데이터를 복호화할 수 없다.
개인 키로 암호화하고 공개 키로 복호화할 수도 있다.
보통 개인 키로 데이터를 암호화하는 것은 신원 확인이나 서명과 같은 인증 목적으로 사용된다.
📝 SSH 의 키 쌍을 이용한 사용자 인증 과정
- 클라이언트는 인증에 사용할 키 쌍의 ID 를 서버에 전송한다.
- 서버는 키 ID 에 해당하는 공개 키를 authorized_keys 파일에서 찾는다.
- 임의의 숫자를 공개 키로 암호화해서 클라이언트에게 전송한다.
- 클라이언트는 개인 키로 해당 값을 복호화하고 공유 세션 키를 결합한 값의 해시를 구한다.
- 클라리언트는 해시 값을 서버에 전송한다.
- 서버는 해시 값과 임의 숫자와 공유 세션 키로 생성한 해시 값이 같은지 확인한다.
✅ AES 대칭 키 암호화 예
대표적인 대칭 키 암호화 알고리즘에는 AES 가 있다.
AES 알고리즘을 사용할 때는 다음의 두 값을 생성해서 공유한다.
- 키 (Key)
- IV (Initialization Vector, 초기화 벡터)
AES 는 키 값으로 128비트, 192비트, 256비트 중 하나를 사용한다.
바이트로는 16바이트, 24바이트, 32바이트가 된다.
키는 무작위로 생성해서 유추가 어려워야 한다. 예를 들어 256비트 키를 생성할 때는 무작위로 32바이트 배열을 생성하고 이를 보관한다.
같은 키를 사용해서 같은 데이터를 암호화하면 항상 같은 결과가 생성된다.
이를 방지하기 위해 IV(Initialization Vector, 초기화 벡터) 를 사용한다.
IV 는 임의의 바이트 배열로서 같은 키를 쓰더라도 매번 암호화의 결과값이 달라져 패턴이 드러나는 것을 방지할 수 있다.
복호화할 때는 IV 값도 필요하기 때문에 안전하게 전달하거나 저장해야 한다.
단방향 암호화와 마찬가지로 암복호화도 바이트 데이터를 대상으로 한다.
필요에 따라 Base64 인코딩, 16진수 표기법(문자열) 등으로 표현할 수 있다.
예를 들어 암복호화할 때 "AES/CBC/PKCS5Padding" 을 사용해서 Cipher 객체를 구한다면,
각각 "암호화 알고리즘/암호화 모드/패딩" 방식을 뜻한다.
AES 는 정해진 길이의 블록 단위로 암호화를 수행하기 때문에 마지막 블록은 길이가 맞지 않을 수 있다.
이때 길이가 부족한 값을 규칙에 따라 채우는데 이 규칙을 패딩 방식이라 한다.
패딩이 없는 경우도 있다. 그럴 땐 "NoPadding" 을 사용한다.
✅ 비대칭 키 암호화 예
비대칭 키 암호화는 공개 키/개인 키 쌍을 생성한 뒤에 공개 키를 공유한다.
공개 키는 Base64 로 바이트 배열을 인코딩하거나 바이트 배열 자체를 파일로 저장해서 공유한다.
문자열이나 바이너리 형태로 저장한 공개 키와 개인 키는 다시 코드에서 사용할 수 있는 형태로 변환하여 사용한다.
🚀 HMAC을 이용한 데이터 검증
API 통신을 할 때 클라이언트는 데이터를 알맞게 생성해서 서버에 전송한다.
위변조 공격에 대응하기 위해 HMAC 을 주로 사용한다.
무결성과 인증을 보장하기 위해 HMAC 해시 함수와 비밀 키는 다음 2가지를 보장한다.
- 메시지 무결성: 메시지가 중간에 위변조되지 않았음
- 인증: 메시지 발신자를 인증할 수 있음(발신자만 비밀 키에 접근)
메시지의 발신자와 수신자만 알고 있는 비밀 키를 공유한다.
발신자는 메시지를 비밀 키로 해성해서 생성한 MAC을 원본 메시지와 함께 수신자에게 전송한다.수신자는 메시지와 비밀 키를 이용해서 MAC을 다시 생성한 뒤 발신자가 보낸 MAC과 비교한다.HMAC 의 장점은 단순함과 효율성에 있다. 단점은 비밀 키의 관리이다.
HMAC 을 이용해서 MAC 을 생성하는 코드는 비교적 간단하게 생성할 수 있다.
🚀 방화벽으로 필요한 트래픽만 허용하기
서버가 외부에 노출되면 포트 스캔부터 다양한 공격이 들어온다.
가장 기본적인 네트워크 접근 차단은 방화벽부터 시작된다.
방화벽은 물리적인 장비로 존재하기도 하고 가상 방화벽(ex. AWS 보안 그룹)으로 존재하기도 한다.
- 인바운드 트래픽: 외부에서 내부로
- 아웃바운드 트래픽: 내부에서 외부로
기본적으로 인바운드는 필수 트래픽만 허용하고 나머지는 차단할 것을 권장한다.
EX
- 서비스 API: 외부의 모든 IP에서 서버 IP에 443 포트로 접근 가능
- 관리자 API: 사내 IP만 서버B IP의 443 포트로 접근 가능
아웃바운드 트래픽 역시 필수만 허용하고 나머지는 모두 차단하는 것이 좋다.
모두 허용하면 서버가 해킹당했을 때 해커의 중간 경유지로 악용될 수 있으므로 정해진 목적지로만 트래픽을 허용해야 한다.
방화벽은 단순히 트래픽을 제어하는 것 외에도, DDos 나 포트 스캔 같은 네트워크 공격을 차단하는 기능도 제공한다.
🎯 DDos
DDos는 Distributed Denial of Service 의 약자로 분산 서비스 거부 공격이라고도 한다.
여러 위치에서 동시에 다량의 트래픽을 서버에 보내 서버를 느리게 만들거나 아예 서비스를 중단시키는 공격이다.
웹 방화벽을 사용하면 HTTP/HTTPS 수준에서 발생하는 공격도 방어할 수 있다.
SQL 인젝션, XSS 같은 웹 기반 위협을 감지하고 차단한다.
🚀 감사 로그(audit log) 남기기
감사 로그는 감사 추적이라고도 하며 활동의 순서를 입증하는 보안 관련 기록이다.
- 사용자의 로그인/로그아웃 내역
- 암호 초기화 등 설정 변경 내역
- 환자 기록을 조회한 의료진 정보
- 계약서의 수정 이력
감사 로그가 컴플라이언스나 정책을 지키기 위해 활동 내역을 기록한다면, 일반 로그는 개발자가 버그나 오류 같은 문제를 해결할 때 도움을 받기 위한 목적으로 기록된다.
감사 로그는 필수로 기록해야 하고 일정 기간 동안 보관해야 한다.
🚀 데이터 노출 줄이기
고객을 특정할 수 있는 민감 정보를 노출하면 안 된다.
마스킹을 하거나 소수 인원에게만 고객을 조회할 수 있는 권한을 부여한다.
로그 메시지에도 신경써야 한다.
🚀 비정상 접근 처리
사용자가 평소와 다른 행동 패턴을 보이면 비정상 접근으로 판단한다.
- 평소와 다른 장소에서 로그인함
- 평소와 다른 기기에서 로그인함
- 로그인에 여러 차례 실패함
사용자에게 알리거나 계정 사용 중지와 같은 정책을 적용할 수도 있다.
동일한 URL 이나 API 를 반복해서 접근하거나, 권한이 없는 URL 이나 API 에 대한 접근 시도도 부정 사용으로 간주할 수 있다.
🚀 시큐어 코딩
import java.sql.ResultSet; public static void main(String[] args) { String id = request.getParameter("id"); String query = "SELECT id, name FROM member WHERE id = '" + id + "'"; ResultSet rs = stmt.executeQuery(query); }
' or 1=1 or id = ' 이 값을 입력하게 되면 항상 참인 1=1 조건으로 인해 SQL 인젝션 공격이 가능하다.SQL 인젝션 외에도 서버 프로그램을 개발할 때는 보안을 신경써야 한다.
- 입력 값 검증: 클라이언트가 전송 값에 대하여.
- 개인 정보/민감 정보 암호화: 로그인 비밀번호 외에 고유 식별 정보도 암호화
- 에러 메시지에서 시스템 정보 미노출: 내부 IP 나 DB IP 등 시스템 정보 미노출
- 보안 통신: HTTPS 처럼 데이터를 암호화해서 데이터 유출 방지
- CORS 설정: 허용된 도메인만 서버 자원에 접근할 수 있도록 제한
- CSRF 대응: 타 사이트에서 위조 공격 방어. CSRF 토큰, SameSite 쿠키, 캡차 등
🚀 개인 보안
개발자는 다양한 서버에 접근할 수 있다. 권한에 따라 DB에 접속해서 다양한 쿼리도 실행할 수 있다.
개발자가 접근할 수 있는 시스템이 많은 만큼 개발자 PC가 해킹 당하면 큰 사고로 이어진다.
그래서 개발자는 보안 위험에 민감해야 한다.
출처가 불분명한 파일을 다운받거나, 의심스러운 이메일의 첨부 파일을 실행하는 식이다.
물리적인 보안도 중요하다. 민감 정보가 담긴 문서를 잘 파기하거나 유출되지 않아야 한다.
![[주니어 백엔드 개발자가 반드시 알아야 할 실무 지식] 8장 실무에서 꼭 필요한 보안 지식](/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fattachment%253A69c6b5a2-a34c-4b63-a8e6-ff3aad6605ec%253A2025-5.jpeg%3Ftable%3Dblock%26id%3D298a12d0-8f8e-8008-8b91-db2571e502d7%26cache%3Dv2&w=1920&q=75)