2. 핵심 원리
체크리스트 13항목입니다. 항목마다 공격 지점과 방어 코드, Spring·MyBatis 적용 지점을 붙였습니다.
2.1 XSS (Cross-Site Scripting)
- 무엇이 뚫리나: 사용자 입력을 그대로 HTML 에 출력하면
<script> 가 그대로 실행됩니다.
- 어떻게 막나: 출력 시
&, <, >, ", ' 를 이스케이프하고 Content-Security-Policy 헤더를 붙입니다.
- Spring·MyBatis 에서는: Vue 는
{{ }} 보간이 기본 이스케이프지만, v-html 로 서버 응답을 그대로 넣으면 이스케이프가 풀립니다.
2.2 CSRF (Cross-Site Request Forgery)
- 무엇이 뚫리나: 로그인한 브라우저가 공격자 페이지의 요청을 자동으로 쿠키와 함께 보냅니다.
- 어떻게 막나: 쿠키에
SameSite=Strict 또는 Lax 를 붙이고, 상태 변경 요청에 CSRF 토큰을 검증합니다.
- Spring·MyBatis 에서는: Bearer JWT 는 쿠키 자동 전송이 없어 CSRF 에 강하지만,
localStorage 저장 시 XSS 로 토큰이 탈취되는 트레이드오프가 있습니다.
2.3 SQL 인젝션
- 무엇이 뚫리나: 사용자 입력이 SQL 문자열에 그대로 이어붙으면 조건절이 조작됩니다.
- 어떻게 막나: 값은 바인드 파라미터로, 컬럼명·테이블명처럼 바인드가 안 되는 자리는 화이트리스트로 검증합니다.
- Spring·MyBatis 에서는:
#{} 는 PreparedStatement 바인딩이라 안전하지만, ${} 는 문자열 치환이라 ORDER BY 컬럼·테이블명에만 화이트리스트와 함께 씁니다.
2.4 경로 탐색 (Path Traversal)
- 무엇이 뚫리나: 다운로드 파일명에
../../etc/passwd 처럼 넣으면 기준 폴더 밖 파일을 읽어갑니다.
- 어떻게 막나: 기준 폴더와 요청 경로를 합쳐
normalize() 한 뒤, 결과가 기준 폴더로 startsWith 하는지 검사합니다.
- Spring·MyBatis 에서는: 첨부파일 서비스에서 DB 에는 원본 파일명만 저장하고, 실제 저장 경로는 서버가 만든 UUID 로 관리합니다.
2.5 업로드 검증
- 무엇이 뚫리나: 확장자만 검사하면
photo.png 로 위장한 실행 스크립트가 업로드됩니다.
- 어떻게 막나: 확장자 화이트리스트와 매직 바이트를 함께 검사하고, 크기 제한을 두고, 저장 파일명은 UUID 로 바꿉니다.
- Spring·MyBatis 에서는: 업로드 폴더는 실행 권한이 없는 별도 디스크로 두고, 다운로드 시
Content-Disposition: attachment 로 강제 다운로드합니다.
2.6 인가 누락 (IDOR)
- 무엇이 뚫리나: 로그인은 확인하지만 "이 데이터가 내 것인지"는 확인하지 않아, id 값만 바꾸면 남의 데이터가 보입니다.
- 어떻게 막나: 컨트롤러가 아니라 서비스 계층에서 조회 대상의 소유자와 로그인 사용자를 비교합니다.
- Spring·MyBatis 에서는: MyBatis 쿼리 자체에
WHERE owner_id = #{loginId} 조건을 넣어 DB 단에서도 한 번 더 걸러냅니다.
2.7 대량 할당 (Mass Assignment)
- 무엇이 뚫리나: 요청 JSON 을 엔티티에 그대로 바인딩하면,
isAdmin 같은 필드를 요청에 몰래 끼워 넣어 값을 바꿀 수 있습니다.
- 어떻게 막나: 요청 전용 DTO 를 따로 두고, DTO 에 없는 필드는 애초에 바인딩되지 않게 합니다.
- Spring·MyBatis 에서는:
@RequestBody 는 DTO 로만 받고, 엔티티 변환은 서비스 계층에서 필드를 하나씩 매핑합니다.
2.8 보안 헤더 누락
- 무엇이 뚫리나: 헤더가 없으면 브라우저가 MIME 스니핑, 클릭재킹, 평문 통신을 허용합니다.
- 어떻게 막나:
X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Strict-Transport-Security 를 공통으로 붙입니다.
- Spring·MyBatis 에서는: 개인정보가 담긴 응답에는
Cache-Control: no-store 를 추가해 브라우저·프록시 캐시를 막습니다.
2.9 에러 노출
- 무엇이 뚫리나: 예외를 그대로 응답에 담으면 스택트레이스, SQL 문, 내부 경로가 노출됩니다.
- 어떻게 막나: 예외는 서버 로그에만 남기고, 클라이언트에는 07 레슨의 REST 에러 규격처럼 코드와 메시지만 내려줍니다.
- Spring·MyBatis 에서는:
@RestControllerAdvice 로 전역 예외 처리를 하나로 모아 실수로 원본 예외가 새는 지점을 없앱니다.
2.10 로그 인젝션
- 무엇이 뚫리나: 사용자 입력을 로그에 그대로 남기면 CRLF 로 가짜 로그 줄을 끼워 넣어 로그를 위조합니다.
- 어떻게 막나: 로그에 남기기 전
\r, \n 을 제거하고, 10 레슨 로깅 기준대로 개인정보는 마스킹합니다.
- Spring·MyBatis 에서는: Logback 패턴에 사용자 입력을 그대로 넣지 말고, 항상 정리 함수를 거친 값만 넣습니다.
2.11 로그인 시도 제한·레이트 리밋
- 무엇이 뚫리나: 시도 횟수 제한이 없으면 비밀번호를 무한정 대입해 볼 수 있습니다.
- 어떻게 막나: 계정·IP 별로 실패 횟수를 세어 일정 횟수 이상이면 일정 시간 잠급니다.
- Spring·MyBatis 에서는: 로그인 실패 횟수는 캐시(18 레슨)나 DB 컬럼에 두고, 성공 시 초기화합니다.
2.12 의존성 취약점
- 무엇이 뚫리나: 오래된 라이브러리에 이미 공개된 CVE 가 있으면 공격자가 그대로 익스플로잇을 씁니다.
- 어떻게 막나: 배포 전 사용 중인 jar 버전과 알려진 CVE 목록을 대조합니다.
- Spring·MyBatis 에서는: 폐쇄망은 OWASP dependency-check 오프라인 DB 를 내려받아 쓰고,
rpm -qa 결과와 jar 목록을 함께 점검합니다.
2.13 설정 노출
- 무엇이 뚫리나: actuator 의
env, heapdump 엔드포인트나 .git, 백업 파일이 그대로 서빙되면 설정값과 소스가 유출됩니다.
- 어떻게 막나: actuator 는 인증된 관리자만 접근하게 하고, 정적 파일 서버에서
.git, *.bak, *.sql 확장자를 차단합니다.
- Spring·MyBatis 에서는:
management.endpoints.web.exposure.include 는 필요한 것만 열고, env·heapdump 는 기본 차단합니다.
2.14 Spring Security 대응표
| 체크 항목 |
Spring Security 설정 |
| CSRF |
csrf().csrfTokenRepository(...) 또는 세션 미사용 시 비활성화 |
| 세션 고정 |
sessionManagement().sessionFixation().migrateSession() |
| 인가 |
authorizeHttpRequests() 로 경로별 권한 선언 |
| 비밀번호 저장 |
BCryptPasswordEncoder |
| 로그인 실패 제한 |
AuthenticationFailureHandler 에서 실패 카운트 |
| 보안 헤더 |
headers().contentSecurityPolicy(...), frameOptions() |
| CORS |
cors().configurationSource(...) 로 허용 출처 명시 |
2.15 배포 전 점검표
- 모든 출력에 이스케이프가 적용됐는지 확인합니다.
- 상태 변경 API 에 CSRF 방어(토큰 또는 SameSite)가 있는지 확인합니다.
- MyBatis 매퍼에서
${} 사용처를 모두 찾아 화이트리스트 여부를 확인합니다.
- 파일 다운로드·업로드 경로에
safePath 검증이 있는지 확인합니다.
- 업로드 확장자·매직 바이트·크기 제한이 모두 걸려 있는지 확인합니다.
- 상세 조회·수정 API 마다 소유자 검증이 있는지 확인합니다.
- 요청 DTO 와 엔티티가 분리돼 있는지 확인합니다.
- 공통 보안 헤더가 필터·인터셉터에 등록됐는지 확인합니다.
- 전역 예외 처리가 스택트레이스를 감추는지 확인합니다.
- 로그에 CRLF 제거와 개인정보 마스킹이 적용됐는지 확인합니다.
- 로그인 실패 제한이 실제로 동작하는지 테스트합니다.
- 사용 중인 jar 버전과 CVE 목록을 대조했는지 확인합니다.
- actuator 민감 엔드포인트가 차단됐는지 확인합니다.
- 정적 파일 서버가
.git, 백업 파일을 서빙하지 않는지 확인합니다.
- 개인정보 응답에
Cache-Control: no-store 가 있는지 확인합니다.