본문 바로가기
Trouble Shooting/업무

[TroubleShooting/업무]🚨 XSS 대응기: Toast UI Editor vs textarea, 서버 대응 방식

by drCode 2025. 9. 4.
728x90
반응형

 

 

xss 공격

 

최근 프로젝트에서 꽤 흥미로운 보안 이슈를 경험했습니다.
바로 크로스 사이트 스크립트(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> → &lt;script&gt;

 


⚔️ 공격 페이로드 모음

테스트 과정에서 사용했던 대표적인 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

&lt;script&gt;alert('xss')&lt;/script&gt;

 

👉 브라우저가 코드로 해석하지 않고, 단순 문자열로 출력합니다.


📊 Toast UI Editor vs textarea 보안 처리 차이

구분  Toast UI Editor  textarea
입력 데이터 HTML 포함 (태그 유지 필요) 순수 텍스트
처리 방법 HTML Sanitizing (화이트리스트 기반) HTML Escape
라이브러리 Jsoup Apache Commons Text
특징 서식 유지 필요 → 정교한 필터링 단순 변환으로 충분

 


💡 실무에서 얻은 교훈

  1. 입력 데이터의 성격에 따라 보안 처리 전략이 달라야 한다.
    • HTML이 필요한 경우 → Sanitizing
    • 텍스트만 필요한 경우 → Escape
  2. 출력 시점에서도 이스케이프 처리는 필수
    • JSP: <c:out>
    • Thymeleaf: th:text
  3. 자동화된 테스트 케이스를 구축하라
    • 여러 XSS 페이로드를 지속적으로 돌려야 안정성이 보장됩니다.

🔚 결론

XSS 방어는 “모두 다 escape”라고 단순화할 수 없는 영역입니다.
에디터인지, textarea인지, 입력 데이터의 목적이 무엇인지에 따라 전략을 달리해야 하죠.

  • Toast UI Editor: 허용된 HTML만 살리는 Sanitizing
  • textarea: HTML escape 처리

이번 경험을 통해, 저는 “보안은 기술이 아니라 습관”이라는 걸 다시 느꼈습니다.
여러분도 비슷한 상황이 있다면, 입력의 성격부터 구분하고 거기에 맞는 보안 처리를 적용해 보시길 추천합니다.

728x90
반응형

댓글