주니어 백엔드 개발자가 반드시 알아야 할 실무 지식 - 8장 실무에서 꼭 필요한 보안 지식
보안은 별도 팀의 일이 아니라 백엔드 개발자의 기본 책임이다.
특히 인증, 인가, 입력 검증, 비밀 관리, 로그 처리처럼 애플리케이션 레벨 보안은 개발자가 가장 먼저 맞닥뜨린다.
방화벽이나 WAF 같은 인프라 장치는 네트워크 경계에서 알려진 공격 패턴을 막아 줄 뿐, “이 사용자가 이 주문을 볼 수 있는가” 같은 판단은 할 수 없다. 그런 판단은 애플리케이션 코드 안에만 있고, 그 코드를 쓰는 사람이 백엔드 개발자다.
최소한 알아야 할 것
1. 인증과 인가 구분
- 인증: 누구인가
- 인가: 무엇을 할 수 있는가
둘을 섞으면 권한 버그가 생긴다.
가장 흔한 권한 버그는 인증만 확인하고 인가를 빼먹는 경우다. 로그인한 사용자라면 URL의 주문 번호만 바꿔서 남의 주문을 볼 수 있는 상태다. HTTP 응답 코드도 둘을 구분한다. RFC 9110에서 401은 유효한 인증 자격 증명이 없다는 뜻이고, 403은 서버가 요청을 이해했지만 거부한다는 뜻이다(RFC 9110 15.5.2, 15.5.4). 두 개념의 차이와 서버에서 지킬 원칙은 Authentication, Authorization 노트에 나눠 정리했다.
2. 비밀번호/토큰/세션
- 비밀번호는 해시 저장
- 토큰은 수명과 폐기 전략이 중요
- 세션은 저장 위치와 확장성이 중요
비밀번호를 해시로 저장하는 이유는 해시가 단방향 함수이기 때문이다. DB가 유출되더라도 공격자가 원래 비밀번호를 바로 얻지 못한다. 다만 SHA-256 같은 빠른 해시는 공격자도 빠르게 대입해 볼 수 있으므로, 일부러 느리고 메모리를 많이 쓰도록 설계한 비밀번호 전용 해시를 쓴다. OWASP는 Argon2id를 최소 19MiB 메모리, 반복 2회, 병렬도 1로 쓰라고 권하고, Argon2id를 쓸 수 없으면 scrypt를, 레거시 시스템이라면 bcrypt를 적절한 work factor로 쓰라고 안내한다(OWASP Password Storage Cheat Sheet).
토큰의 수명과 폐기는 같이 설계해야 한다. 서버가 상태를 갖지 않는 JWT는 발급한 뒤에 서버가 무효화하기 어렵다. 그래서 Access Token의 수명을 짧게 두고 Refresh Token으로 재발급하거나, 폐기 목록을 따로 둔다. 서명 검증과 만료, 폐기의 한계는 JWT의 구조와 검증에서 다뤘다.
세션은 서버가 상태를 갖는 방식이다. 서버 메모리에 세션을 두면 서버가 여러 대일 때 다른 서버로 간 요청은 로그인 상태를 모른다. Redis 같은 공유 저장소에 두거나 같은 사용자를 같은 서버로 보내는 방식이 필요하다. 세션과 JWT의 비교는 Session and JWT에 있다.
3. 입력 검증
- SQL Injection
- XSS
- Command Injection
같은 고전적 취약점은 여전히 유효하다.
세 취약점의 공통점은 사용자 입력이 데이터로 남지 않고 코드(SQL, HTML/JavaScript, 셸 명령)의 일부로 해석된다는 것이다. 그래서 방어도 “입력을 코드와 분리한다”는 같은 원리를 따른다.
- SQL Injection: OWASP의 첫 번째 방어책은 prepared statement(파라미터화된 쿼리)다. SQL 구조를 먼저 정의하고 값은 나중에 바인딩하므로, 값에 따옴표나 SQL 키워드가 들어 있어도 SQL 구문으로 해석되지 않는다(OWASP SQL Injection Prevention Cheat Sheet).
1
2
3
4
5
6
// 위험: 입력이 SQL 문장의 일부가 된다
String sql = "SELECT * FROM users WHERE email = '" + email + "'";
// 안전: 입력은 값으로만 바인딩된다
PreparedStatement ps = connection.prepareStatement("SELECT * FROM users WHERE email = ?");
ps.setString(1, email);
- XSS: 사용자 입력을 HTML에 그대로 출력하면 그 안의 스크립트가 다른 사용자 브라우저에서 실행된다. OWASP는 사용자가 입력한 그대로 화면에 보여 줘야 할 때 출력 인코딩(output encoding)을 권한다. 출력하는 위치(HTML 본문, 속성, JavaScript)에 맞는 인코딩을 써야 한다(OWASP XSS Prevention Cheat Sheet).
- Command Injection: OWASP가 첫 번째로 꼽는 방어는 OS 명령을 직접 호출하지 않는 것이다. 같은 기능을 하는 라이브러리 API가 있다면 그것을 쓴다(OWASP OS Command Injection Defense Cheat Sheet).
4. 비밀 관리
.env에만 의존하지 않기- 운영 비밀은 전용 저장소/시크릿 매니저 사용
- 로그에 비밀번호/토큰 출력 금지
OWASP Secrets Management 치트 시트는 환경 변수가 대개 모든 프로세스에서 접근 가능하고 로그나 시스템 덤프에 포함될 수 있으므로, 다른 방법이 불가능한 경우가 아니라면 비밀 전달에 권장하지 않는다고 적는다(OWASP Secrets Management Cheat Sheet). .env 파일은 결국 환경 변수로 읽히고, 실수로 저장소에 커밋되기도 쉽다. 시크릿 매니저를 쓰면 접근 권한을 사람과 서비스별로 나누고, 누가 언제 읽었는지 기록하고, 값을 교체하기 쉬워진다.
로그도 비밀이 새는 흔한 통로다. OWASP Logging 치트 시트는 세션 식별자, 액세스 토큰, 비밀번호, DB 연결 문자열, 암호화 키 같은 값을 로그에 그대로 남기지 말고 제거하거나 마스킹, 해시, 암호화하라고 권한다(OWASP Logging Cheat Sheet). 요청 전체를 디버그 로그로 찍는 코드가 Authorization 헤더까지 남기고 있지 않은지 확인한다.
실무에서 더 중요한 포인트
보안은 기능 추가가 아니라 실패 시 피해 범위를 줄이는 설계다.
- 최소 권한
- 만료
- 추적 가능성
- 감사 로그
같은 개념이 같이 가야 한다.
네 가지는 “언젠가 뚫린다”는 전제에서 출발한다.
- 최소 권한: 애플리케이션의 DB 계정이 테이블 삭제 권한까지 가지고 있으면, SQL Injection 하나가 데이터 전체 삭제로 이어진다. 필요한 권한만 주면 같은 취약점의 피해가 줄어든다.
- 만료: 유출된 토큰이나 키가 영원히 유효하면 피해 기간도 끝이 없다. 수명을 정해 두면 피해 시간에 상한이 생긴다.
- 추적 가능성: 사고가 났을 때 어떤 요청이 어떤 데이터를 건드렸는지 따라갈 수 있어야 범위를 확정할 수 있다.
- 감사 로그: 권한 변경, 개인정보 조회, 관리자 작업처럼 민감한 행동은 누가, 언제, 무엇을 했는지 별도로 남긴다.
이 장에서 가져갈 점
- 인증 다음에 인가가 빠지지 않았는지 API마다 확인한다. 다른 사용자의 토큰으로 호출해 보는 테스트가 가장 간단한 점검이다.
- 사용자 입력은 항상 데이터로만 다룬다. 쿼리는 파라미터 바인딩으로, 화면 출력은 출력 인코딩으로 처리한다.
- 비밀 값은 코드, 저장소, 로그에 남기지 않는다.
- 보안 설계의 목표는 사고를 0으로 만드는 것이 아니라 사고가 났을 때 피해 범위를 작게 만드는 것이다.
정리
보안은 “특정 취약점 몇 개를 안다”보다,
민감한 데이터를 어떻게 다루고, 권한을 어떻게 제한하고, 실패 시 피해를 어디까지 막을지 설계하는 능력이다.
댓글
아직 댓글이 없습니다