본문으로 건너뛰기
개발 머꼬
개발 노트JavaScript
hohyeon.dev21

치환 문자열에 $가 들어 있었더니 엉뚱한 내용이 삽입된 이유

  • #Engineering Note
  • #JavaScript
  • #Security

문제 발생

템플릿의 자리표시자를 사용자 입력으로 바꾸는 코드였습니다.

const template = "안녕하세요, {name}님";
const result = template.replace("{name}", userInput);

닉네임이 $&인 사용자에서 결과가 안녕하세요, {name}님이 됐고, $'인 사용자에서는 뒷부분이 통째로 복제됐습니다.

원인 분석

치환 문자열에는 특수 패턴이 있습니다. MDN이 표로 정리해둔 그대로입니다.

패턴삽입되는 것
$$$ 한 글자
$&매칭된 부분 문자열
$`매칭 앞부분 전체
$'매칭 뒷부분 전체
$nn번째 캡처 그룹
$<Name>이름 있는 캡처 그룹

즉 두 번째 인자는 평범한 문자열이 아니라 작은 템플릿 언어입니다. 사용자 입력이 그 자리에 들어가면 입력이 코드처럼 해석됩니다. 눈에 잘 안 띄지만 성격은 인젝션과 같습니다.

문서에는 흔히 놓치는 규칙이 하나 더 있습니다 — 문자열 패턴은 한 번만 치환됩니다. 전역 검색·치환을 하려면 g 플래그가 붙은 정규식을 쓰거나 replaceAll()을 사용하십시오.

해결 방안

  1. 치환값이 데이터라면 함수를 넘깁니다. MDN이 명시합니다 — 위의 특수 치환 패턴은 replacer 함수가 반환한 문자열에는 적용되지 않습니다.
const result = template.replace("{name}", () => userInput);   // 안전
  1. 여러 번 바꿔야 하면 replaceAll을 씁니다. 문자열 패턴 한 번 치환 규칙을 잊고 "왜 첫 개만 바뀌지" 하는 상황을 없앱니다.
template.replaceAll("{name}", () => userInput);
  1. 직접 이스케이프해야 한다면 $만 처리하면 됩니다.
const safe = userInput.replaceAll("$", "$$$$");   // $ → $$

읽기 어려운 코드라서, 가능하면 1번 방식을 씁니다.

  1. 애초에 문자열 조합이 맞는지 봅니다. 화면에 그릴 값이라면 템플릿 치환보다 `안녕하세요, ${name}님`이나 React의 보간이 더 안전하고 명확합니다.

  2. 정규식을 만들 때 사용자 입력을 그대로 넣지 않습니다. 같은 성격의 다른 구멍입니다 — new RegExp(userInput)은 문법 오류나 성능 문제(ReDoS)로 이어질 수 있습니다.

  3. 이런 값으로 테스트합니다. $&, $', $1, $$는 실제 닉네임으로도 충분히 들어올 수 있는 문자열입니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.