최근 프로젝트에서 꽤 흥미로운 보안 이슈를 경험했습니다.
바로 크로스 사이트 스크립트(XSS) 대응이었습니다.
재직 중인 회사의 시스템이 특성상 보안에 민감하다 보니,
사용자가 입력한 데이터가 조금이라도 브라우저에서 실행될 여지가 있으면 큰 사고로 이어질 수 있습니다.
이번에 제가 다뤘던 케이스는
Toast UI Editor(WYSIWYG 에디터)와
기본 textarea 입력을 처리할 때, 서버에서 보안 처리 전략이 달랐던 점이었습니다.
🔎 XSS란 무엇인가?
XSS는 사용자가 입력한 악성 스크립트가 브라우저에서 그대로 실행되는 공격입니다.
대표적인 예시는 다음과 같습니다:
<script>alert('해킹!')</script>
<img src=x onerror=alert('해킹!')>
<a href="javascript:alert('해킹!')">Click me</a>
이런 코드가 그대로 서비스 화면에 뿌려진다면?
👉 세션 탈취, 피싱, 화면 변조 등 보안사고로 이어질 수 있습니다.
📝 Toast UI Editor의 특성
Toast UI Editor는 단순 텍스트 입력이 아니라 HTML 서식을 허용합니다.
굵게(<b>), 기울임(<i>), 표(<table>), 이미지(<img>) 같은 태그를 유지해야 하죠.
따라서 서버에서 무조건 HTML escape를 해버리면 사용자가 입력한 서식 자체가 다 깨져버립니다.
그래서 화이트리스트 기반 Sanitizing이 필요했습니다.
🖼️ textarea의 특성
반대로 textarea는 본질적으로 순수 텍스트 입력입니다.
태그 유지가 필요하지 않으므로, <script> 같은 태그를 굳이 살릴 이유가 없죠.
따라서 HTML escape 처리만 해주면 충분합니다.
예: <script> → <script>
⚔️ 공격 페이로드 모음
테스트 과정에서 사용했던 대표적인 XSS 페이로드는 다음과 같습니다:
<script>alert('xss-1')</script>
<img src=x onerror=alert('xss-2')>
<a href="javascript:alert('xss-3')">click</a>
<svg onload=alert('xss-4')></svg>
<div style="background-image:url(javascript:alert('xss-5'))">test</div>
🛡️ Toast UI Editor 대응: Jsoup Sanitizing
Toast UI Editor에서 넘어오는 HTML은 Jsoup의 Safelist를 이용해 허용된 태그만 남기고, 나머지는 제거했습니다.
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
public class HtmlSanitizer {
private static Safelist toastUiSafelist() {
return new Safelist()
.addTags("p","br","b","i","u","strong","em",
"ul","ol","li","table","thead","tbody","tr","th","td",
"a","img","pre","code","blockquote")
.addAttributes("a", "href", "title")
.addAttributes("img", "src", "alt", "title")
.addProtocols("a", "href", "http", "https")
.addProtocols("img", "src", "http", "https");
}
public static String sanitizeToastUiHtml(String unsafeHtml) {
return Jsoup.clean(unsafeHtml, toastUiSafelist());
}
}
✅ 전/후 결과 비교
Before
<img src=x onerror=alert('xss')>
<a href="javascript:alert(1)">click</a>
<script>alert('xss')</script>
After
<img src="x">
<a>click</a>
👉 위험한 속성과 스킴(javascript:)은 제거되고, 정상적인 태그만 남았습니다.
🛡️ textarea 대응: HTML Escape
textarea 입력은 단순 텍스트이므로, HTML escape만 적용하면 안전합니다.
import org.apache.commons.text.StringEscapeUtils;
public class TextEscaper {
public static String escapeForHtml(String raw) {
return StringEscapeUtils.escapeHtml4(raw);
}
}
✅ 전/후 결과 비교
Before
<script>alert('xss')</script>
After
<script>alert('xss')</script>
👉 브라우저가 코드로 해석하지 않고, 단순 문자열로 출력합니다.
📊 Toast UI Editor vs textarea 보안 처리 차이
구분 | Toast UI Editor | textarea |
입력 데이터 | HTML 포함 (태그 유지 필요) | 순수 텍스트 |
처리 방법 | HTML Sanitizing (화이트리스트 기반) | HTML Escape |
라이브러리 | Jsoup | Apache Commons Text |
특징 | 서식 유지 필요 → 정교한 필터링 | 단순 변환으로 충분 |
💡 실무에서 얻은 교훈
- 입력 데이터의 성격에 따라 보안 처리 전략이 달라야 한다.
- HTML이 필요한 경우 → Sanitizing
- 텍스트만 필요한 경우 → Escape
- 출력 시점에서도 이스케이프 처리는 필수
- JSP: <c:out>
- Thymeleaf: th:text
- 자동화된 테스트 케이스를 구축하라
- 여러 XSS 페이로드를 지속적으로 돌려야 안정성이 보장됩니다.
🔚 결론
XSS 방어는 “모두 다 escape”라고 단순화할 수 없는 영역입니다.
에디터인지, textarea인지, 입력 데이터의 목적이 무엇인지에 따라 전략을 달리해야 하죠.
- Toast UI Editor: 허용된 HTML만 살리는 Sanitizing
- textarea: HTML escape 처리
이번 경험을 통해, 저는 “보안은 기술이 아니라 습관”이라는 걸 다시 느꼈습니다.
여러분도 비슷한 상황이 있다면, 입력의 성격부터 구분하고 거기에 맞는 보안 처리를 적용해 보시길 추천합니다.
'Trouble Shooting > 업무' 카테고리의 다른 글
[Trouble Shooting/업무] 대외 스위치 장비 교체로 인한 회선 통신 불안정 현상 개선 경험 - traceroute (5) | 2025.06.05 |
---|---|
[Trouble Shooting/업무] jdk 오라클버전에 따른 ojdbc 버전의 중요성 - Protocol violation (0) | 2025.06.05 |
댓글