Skip to content

02. 입력 검증과 컨텍스트 출력 인코딩

많은 웹 취약점은 "사용자 입력을 어떻게 받을 것인가"와 "받은 값을 어디에 어떻게 다시 보여줄 것인가"를 구분하지 못할 때 생긴다.

학습 목표

  1. 입력 검증, 정규화, 출력 인코딩, Sanitization의 차이를 설명할 수 있다.
  2. Allow-list 검증과 타입 강제의 중요성을 이해한다.
  3. HTML, 속성, JavaScript, URL 컨텍스트마다 인코딩 방식이 다르다는 점을 설명할 수 있다.
  4. 저장 시점 치환과 같은 오래된 대응 방식의 한계를 이해한다.

1. 먼저 용어를 분리해야 한다

개념목적예시
입력 검증허용할 값인지 판정숫자만, 이메일 형식, 길이 제한
정규화비교 전에 형식을 통일공백 제거, Unicode 정규화
출력 인코딩브라우저가 코드를 실행하지 못하게 함HTML 엔티티 인코딩
Sanitization제한된 HTML을 안전하게 정리에디터 본문 허용 시 위험 태그 제거

이 네 가지는 비슷해 보이지만 역할이 다르다.

  • 입력 검증은 들어올 수 있는 값의 범위를 제한한다
  • 출력 인코딩은 브라우저 해석 방식을 제어한다
  • Sanitization은 HTML을 일부 허용해야 할 때만 사용한다

2. 안전한 입력 처리 흐름

핵심은 두 가지다.

  • 저장하기 전에 검증한다
  • 화면에 보여주기 직전에 컨텍스트에 맞게 인코딩한다

이 두 단계가 합쳐져야 한다.


3. 입력 검증의 기본 원칙

3.1 Allow-list가 기본

허용하지 않을 값을 끝없이 나열하는 블랙리스트보다, 허용할 형식을 명확히 정하는 편이 안전하다.

예를 들어 다음과 같은 기준이 좋다.

  • 게시글 ID: 정수만 허용
  • 국가 코드: 미리 정한 코드 집합만 허용
  • 정렬 방향: asc, desc 둘 중 하나만 허용
  • 이미지 확장자: jpg, png, webp 같은 고정 목록만 허용

3.2 타입을 빨리 강제한다

문자열을 오래 끌고 가지 말고 가능한 빨리 의미 있는 타입으로 바꾼다.

  • 숫자는 정수나 소수 타입으로 변환
  • 날짜는 날짜 객체로 변환
  • 열거형은 enum 또는 상수 집합으로 변환

이렇게 해야 뒤 단계에서 문자열 연결 실수를 줄일 수 있다.

3.3 길이와 범위를 같이 본다

형식만 맞는다고 안전한 것이 아니다.

  • 사용자 이름: 최소/최대 길이
  • 검색어: 최대 길이
  • 업로드 파일명: 최대 길이
  • 페이지 번호: 1 이상

길이 제한은 보안과 성능을 동시에 지켜 준다.


4. 컨텍스트별 출력 인코딩

같은 문자열이라도 어디에 넣느냐에 따라 필요한 방어가 달라진다.

출력 위치필요한 대응설명
HTML 본문HTML 인코딩태그 시작 문자 해석 방지
HTML 속성속성 컨텍스트 인코딩따옴표 탈출 방지
JavaScript 문자열JS 컨텍스트 인코딩문자열 종료 방지
URL 파라미터URL 인코딩쿼리 분리 문자 처리
CSS 값가능하면 직접 삽입 금지CSS 컨텍스트는 실수가 많음

가장 흔한 실수는 화면에 출력하기 전에 한 번 치환했으니 끝이라고 생각하는 것이다.
출력 인코딩은 저장 시점이 아니라 출력 시점에, 현재 컨텍스트에 맞게 해야 한다.


5. HTML을 허용해야 하는 경우

리치 텍스트 에디터처럼 HTML을 일부 허용해야 하는 기능이 있다.
이때는 단순 치환이 아니라 Sanitizer가 필요하다.

권장 방향은 다음과 같다.

  • HTML이 필요 없으면 태그를 허용하지 않는다
  • HTML이 필요하면 허용 태그와 허용 속성을 매우 좁게 정한다
  • 신뢰할 수 있는 Sanitizer를 사용한다

실무에서 많이 쓰는 선택지는 다음과 같다.

환경대표 도구
브라우저DOMPurify
PHPHTML Purifier
JavaOWASP Java HTML Sanitizer

반대로 오래된 보안 강의에서 자주 보이는 "저장할 때 위험 문자를 한꺼번에 바꿔서 넣자"는 방식은 현재 기준으로 권장하기 어렵다.


6. 정규식은 만능이 아니다

정규식은 유용하지만, 그것만으로 보안을 끝내면 안 된다.

좋은 사용처

  • 숫자 형식 확인
  • 슬러그 형식 확인
  • 국가 코드, 정렬 키 같은 작은 문자열 집합 확인

위험한 사용처

  • 복잡한 HTML 정리
  • 모든 이메일 형식을 완벽히 판정
  • 파일 형식 전체 판정

정규식은 입력 검증의 일부일 뿐이고, 파일 형식이나 HTML 구조 같은 문제는 전용 파서나 전용 라이브러리가 더 적합하다.


7. 흔한 잘못된 대응

잘못된 대응왜 문제인가더 나은 방식
저장 시점에 전체 문자열 치환출력 맥락이 바뀌면 이중 인코딩이나 누락 발생출력 시점 인코딩
블랙리스트로 위험 문자만 막기우회 문자가 계속 생김Allow-list와 타입 강제
프론트엔드 검증만 믿기브라우저 우회가 가능함서버 측 검증 필수
trim() 한 번으로 충분하다고 생각Unicode, 경로, URL 문제는 더 복잡함정규화 + 타입 변환 + 정책 검증

8. 실무 예시

안전한 방향

  • 정렬 키는 createdAt, name 같은 허용 목록에서만 선택
  • 페이지 번호는 정수로 파싱하고 범위를 제한
  • 게시글 제목은 길이와 문자 집합을 검증
  • 댓글 본문은 저장하되, 화면에 넣을 때 HTML 본문 컨텍스트로 인코딩

위험한 방향

  • 사용자가 보낸 문자열을 그대로 innerHTML에 넣기
  • sortBy 값을 그대로 SQL ORDER BY나 ORM Raw query에 넣기
  • 파일명이나 URL을 문자열 덧붙이기로 조립하기

9. PR 리뷰 체크리스트

  • 서버 측에서 입력 검증을 다시 하고 있는가
  • 허용 값 집합을 코드나 enum으로 고정했는가
  • 숫자, 날짜, 불리언을 문자열로 오래 끌고 가지 않는가
  • HTML을 허용하는 기능에 Sanitizer가 있는가
  • 출력 시점에 현재 컨텍스트에 맞는 인코딩을 적용하는가
  • innerHTML, dangerouslySetInnerHTML, v-html 같은 위험 API를 정말 필요한 곳에서만 쓰는가

핵심 정리

  • 입력 검증과 출력 인코딩은 다른 문제다
  • 저장 전에 검증하고, 출력할 때 컨텍스트별 인코딩을 적용해야 한다
  • Allow-list, 타입 강제, 길이 제한은 가장 기본적이면서 효과적인 방어다
  • HTML을 일부 허용해야 한다면 치환이 아니라 Sanitizer를 사용해야 한다