RamenHire 채용 게시판의 저장형 HTML 인젝션(stored HTML injection) 버그로 인해, 누구나 공개 양식을 작성하여 팀의 알림 이메일을 피싱 플랫폼으로 악용할 수 있었습니다.
버그가 발생한 경위
RamenHire는 Next.js와 Supabase를 기반으로 운영되며, 스타트업이 로그인 없이 채용 공고를 게시할 수 있도록 지원합니다. 양식이 제출될 때마다 내부 채용 팀으로 이메일이 발송됩니다. 이메일 본문은 회사명, 직무 설명, 담당자 이름과 같은 가공되지 않은 양식 필드들을 HTML 문자열에 직접 결합하여 생성됩니다. 문자열이 메일 서비스에 도달하기 전까지 이스케이프(escaping)나 새니타이제이션(sanitisation) 처리가 전혀 이루어지지 않았습니다.
템플릿이 사용자 입력을 안전한 HTML로 취급하기 때문에, 악의적인 지원자는 "여기를 클릭하여 인증하세요"와 같은 가짜 링크를 삽입할 수 있습니다. 이 페이로드는 채용 공고의 일부로서 서버에 저장되므로, 이후에 발송되는 모든 알림 이메일에 악성 코드가 포함됩니다. 공격자는 해당 링크를 실제 "대시보드 보기" 버튼 옆에 숨겨, 팀원이 자격 증명을 노출하거나 피싱 사이트를 방문하도록 유도할 수 있습니다.
이것이 중요한 이유
이 이메일들은 후보자를 채용 프로세스에 따라 관리하기 위해 끊임없이 링크를 클릭하는 핵심 채용 담당자들에게 전달됩니다.
기술적 허점
HTML 이스케이프는 단일 단계로 끝나지 않습니다. 다음 두 가지 컨텍스트(context)에 대한 보호가 필요합니다.
- 텍스트 노드(Text nodes) – 태그 사이의 콘텐츠.
<를<로,>를>로 변환하면 태그 삽입은 막을 수 있지만, 공격자가 속성 값(attribute value)에서 벗어나는 것은 막지 못합니다. - 속성 값(Attribute values) –
href="…"와 같이 태그 내부의 문자열. 따옴표("또는')를 삽입하면 공격자가 속성을 조기에 닫고 자신만의 마크업을 삽입할 수 있습니다.
기존 코드는 일반 텍스트만 처리했기 때문에 속성 인젝션(attribute injection)에 무방비로 노출되어 있었습니다.
문제 해결
개발자는 작은 escapeHtml 헬퍼 함수를 추가하고, 값이 텍스트 노드에 들어가든 속성에 들어가든 상관없이 보간(interpolation) 전 모든 사용자 제공 값에 이를 적용했습니다.
검증 단계
- 로컬 테스트 – 일련의 인젝션 문자열을 템플릿 함수에 입력했습니다. 각 테스트 결과, 실행 가능한 태그 없이 이스케이프된 문자만 나타났습니다.
- 운영 환경 테스트 – 패치를 배포한 후, 실제 사이트를 통해 페이로드를 제출하여 Cloudflare Turnstile 봇 체크를 통과했습니다. 결과 이메일이 개발자의 받은 편지함에 도착했을 때, 악성 링크와 스크립트 태그는 클릭 가능한 요소가 아닌 깨진 텍스트로 나타났습니다.
Gmail 관련 참고 사항: 서비스에서 URL을 여전히 파란색으로 표시하고 클릭 가능하게 만들 수 있지만, 이는 클라이언트 측의 편의 기능일 뿐 HTML 인젝션이 성공했음을 의미하지는 않습니다. 수정 사항을 통해 인젝션이 실제 버튼을 생성하거나 스크립트를 실행하는 것을 방지했습니다.
개발자가 주의해야 할 사항
- 공개 양식의 데이터를 절대 신뢰하지 마세요 – 겉보기에 무해해 보이는 양식이라도 다른 곳에서 사용되는 저장된 출력물을 생성할 수 있습니다.
- 사용 시점에 이스케이프하세요 – 파이프라인의 앞 단계가 아닌, 렌더링 직전에 컨텍스트를 고려한 이스케이프(텍스트 vs 속성)를 적용하세요.
- 클라이언트와 서버 양쪽 모두 테스트하세요 – 유닛 테스트는 명백한 실패를 잡아내며, 실제 UI를 통한 수동 엔드투엔드(end-to-end) 테스트는 실제 트래픽 환경에서도 방어 체계가 유지되는지 확인해 줍니다.
- 이메일 클라이언트의 특이 사항을 인지하세요 – 일부 클라이언트는 URL을 자동으로 링크로 만드는데, 이는 잘못된 보안 인식을 심어줄 수 있습니다. 기본 HTML에 활성 요소(active elements)가 포함되어 있지 않은지 확인하십시오.
결론
사용자 입력 처리에서의 단 한 번의 실수로 인해 일상적인 알림 이메일이 피싱 경로로 변질되었습니다. 포괄적인 HTML 이스케이프 루틴을 도입하고 로컬 및 운영 환경에서 수정을 검증함으로써 받은 편지함의 무결성을 회복했습니다. 이번 사례는 신뢰할 수 없는 데이터로부터 HTML을 생성하는 모든 웹 앱에 시대를 초월한 교훈을 줍니다. 적절한 새니타이제이션은 타협할 수 없는 필수 사항이며, 이를 무시하는 것은 사용자들의 받은 편지함으로 직접 연결되는 통로를 열어주는 것과 같습니다.
