특수문자와 엔터키가 데이터를 깨뜨릴 때
개행과 특수문자가 SQL, HTML, JSON, CSV 각 계층에서 깨지는 지점과, 데이터를 코드로 만들지 않는 계층별 escaping 원칙을 정리한다.
텍스트 입력 칸 하나에 사용자는 무엇이든 넣는다. 따옴표, 백슬래시, 엔터(개행), 탭, 이모지, 제어문자. 이 값이 SQL 쿼리, HTML 페이지, JSON 응답, CSV 다운로드를 거치며 각 계층의 문법 문자와 충돌하면 데이터가 깨지거나, 더 나쁘게는 인젝션으로 이어진다. 핵심 원칙은 하나다. 데이터를 코드 문법에 넣을 때는 그 계층에서 escape 한다.
핵심 개념: 같은 문자, 다른 계층, 다른 의미
'(작은따옴표)는 SQL에서 문자열 경계, HTML 속성에서 인용 부호, JSON에선 보통 무해하다. \n(개행)은 텍스트에선 줄바꿈일 뿐이지만 CSV에선 행 경계로 오해되고, HTTP 헤더에 들어가면 헤더 인젝션이 된다. 즉 한 계층에서 안전한 문자가 다른 계층에선 문법 문자다. 그래서 “입력을 한 번 청소하면 끝”이 아니라, 각 출력 계층마다 그 계층의 규칙으로 escape 해야 한다.
graph TD
I["사용자 입력: O'Brien\n2줄"] --> SQL
I --> HTML
I --> JSON
I --> CSV
SQL["SQL: ' 는 문자열 경계 → 바인딩 변수로"]
HTML["HTML: < > & 는 마크업 → 엔티티 인코딩"]
JSON["JSON: \" \\ 제어문자 → JSON 이스케이프"]
CSV["CSV: 콤마/개행/따옴표 → 필드 인용 + \"\" "]
계층별 안전 처리
SQL. 따옴표를 직접 escape 하려 들지 말고 PreparedStatement 바인딩(? 또는 MyBatis #{})을 쓴다. 값이 SQL 문법 바깥에서 바인딩되므로 O'Brien의 따옴표가 경계를 깨지 못한다.
1
2
3
// 안전: 값은 바인딩 변수로
String sql = "SELECT * FROM users WHERE name = ?";
ps.setString(1, "O'Brien"); // 따옴표가 SQL을 깨지 않는다
JSON. 직렬화는 직접 문자열 조립하지 말고 검증된 라이브러리(Jackson 등)에 맡긴다. 라이브러리가 ", \, 제어문자(\n, \t 등)를 규칙대로 escape 한다. 손으로 "key":"value"를 이어붙이면 값 안의 따옴표·개행에서 깨진다.
CSV. 가장 자주 터진다. 값에 콤마·개행·따옴표가 있으면 전체 필드를 큰따옴표로 감싸고, 값 안의 큰따옴표는 두 개("")로 표기한다(RFC 4180).
1
2
3
4
5
6
static String csvField(String v) {
if (v == null) return "";
boolean needsQuote = v.contains(",") || v.contains("\n") || v.contains("\"");
String escaped = v.replace("\"", "\"\"");
return needsQuote ? "\"" + escaped + "\"" : escaped;
}
HTML. 앞서 다룬 대로 < > & " '를 엔티티로 인코딩한다.
운영 함정
인코딩과 정규화의 경계 문제. 입력 단계에서 양끝 공백 trim, 개행 통일(\r\n → \n) 같은 정규화는 해도 좋다. 하지만 따옴표를 제거하거나 백슬래시를 지우는 식의 “입력 청소”는 데이터를 손상시키고(이름에 진짜 따옴표가 있는 사람), 우회당하기도 쉽다. 정규화는 입력에서, escaping은 출력 계층에서 — 이 둘을 분리해야 한다.
또 하나, DB 컬럼의 문자셋과 길이. 이모지는 한 글자가 여러 바이트(utf8mb4에서 4바이트)다. VARCHAR(n)이 바이트 기준 환경이면 글자 수보다 일찍 잘리거나 저장 오류가 난다. 멀티바이트·이모지를 받는 컬럼은 utf8mb4 + 충분한 길이로 잡는다. 제어문자(NUL 등)는 저장 전 거르는 것이 안전하다.
핵심 요약
- 한 계층에서 무해한 문자가 다른 계층에선 문법 문자다. 출력 계층마다 그 계층 규칙으로 escape.
- SQL은 바인딩, JSON은 직렬화 라이브러리, CSV는 RFC 4180 인용, HTML은 엔티티 인코딩.
- 입력은 정규화(trim/개행 통일)까지만. 문자 제거식 청소는 데이터를 손상시킨다.