안녕하세요. App-Biz에서 Atlassian Add-on 개발을 하고있는 Frontend Developer 노은지입니다.
현대 웹 애플리케이션은 점점 더 복잡해지고 있으며, 보안 위협도 그에 따라 증가하고 있습니다. 사용자 데이터 보호 및 애플리케이션의 무결성을 유지하기 위해서는 프론트엔드 보안이 필수적입니다.

이 포스트에서는 제가 HTML을 DOM에 직접 삽입하는 dangerouslySetInnerHTML을 사용하면서 생길 수 있는 보안 문제인 XSS(교차 스크립트 공격)에 대하여 알아보고, 이에 맞서 프론트엔드 보안 강화를 위해 사용할 수 있는 라이브러리인 DOMPurify를 사용한 내용을 다루고 있습니다.

XSS (교차 스크립트 공격)

먼저 XSS (교차 스크립트 공격)이란 뭔지 한 번 짚고 넘어가 볼까요?

XSS(크로스 사이트 스크립팅, Cross-Site Scripting)은 웹 보안 취약점 중 하나로, 공격자가 악성 스크립트를 웹 페이지에 삽입하여 사용자의 브라우저에서 실행되도록 만드는 공격 기법입니다. 이 공격은 주로 웹 애플리케이션에서 사용자 입력을 제대로 검증하지 않거나 출력 시 적절한 처리를 하지 않을 때 발생합니다.

XSS의 유형

XSS는 크게 Stored XSS, Reflected XSS, DOM-based XSS 세 가지로 나뉩니다.

  1. Stored XSS (저장형 XSS):
    • 공격자가 악성 스크립트를 서버에 저장하고, 해당 스크립트가 다른 사용자의 브라우저에서 실행되도록 하는 방식입니다.
    • 예를 들어, 공격자가 웹사이트의 댓글, 게시글 등의 입력 필드에 악성 스크립트를 삽입하면, 다른 사용자가 해당 페이지를 방문할 때 이 스크립트가 실행됩니다.
    • 피해 범위가 클 수 있습니다.
  2. Reflected XSS (반사형 XSS):
    • 서버가 공격자의 악성 스크립트를 사용자에게 즉시 반환하여 사용자의 브라우저에서 실행되도록 하는 방식입니다.
    • 주로 URL에 악성 코드를 포함하여 전송한 후, 사용자가 그 URL을 클릭하면 브라우저에서 스크립트가 실행됩니다.
    • 주로 이메일 피싱 링크나 악성 URL을 통해 이루어집니다.
  3. DOM-based XSS (DOM 기반 XSS):
    • 서버가 아닌 클라이언트 측에서, 즉 브라우저에서 직접적으로 DOM을 조작하여 악성 스크립트가 실행되는 방식입니다.
    • 주로 자바스크립트 코드가 DOM을 동적으로 처리할 때 발생하며, 서버에 영향을 미치지 않고 클라이언트 쪽에서만 발생하는 것이 특징입니다.

XSS 공격의 위험성

XSS 공격을 통해 공격자는 다음과 같은 행위를 할 수 있습니다:

  • 쿠키 탈취: 공격자가 사용자의 세션 쿠키를 탈취하여 세션 가로채기(Session Hijacking)를 시도할 수 있습니다.
  • 사용자 인증 정보 도용: 사용자의 로그인 정보나 개인 정보를 훔칠 수 있습니다.
  • 피싱: 악성 스크립트를 통해 사용자가 악의적인 사이트에 정보를 입력하도록 유도할 수 있습니다.
  • 키로깅: 사용자의 키 입력을 기록하여 민감한 정보를 수집할 수 있습니다.
  • 웹사이트 변조: 웹 페이지의 내용이 악성 스크립트에 의해 변경되어 사용자가 혼란스러워지거나 신뢰성을 잃을 수 있습니다.

dangerouslySetInnerHTML

저는 프로젝트에 어플리케이션의 다국어 처리를 위한 메시지에 줄바꿈을 처리하기 위해 <br/> 태그를 사용했습니다. 하지만 HTML을 직접 사용하면서 여러 가지 문제가 발생할 수 있다는 점을 깨달았습니다.

가장 큰 문제는 보안입니다. 사용자로부터 입력받은 데이터에 HTML 태그가 포함되어 있을 경우, 위에서 설명하였듯 DOM기반의 교차 스크립트 공격(XSS)의 위험이 커질 수 있습니다. 악의적인 사용자가 <script> 태그를 삽입하여 웹 페이지에서 임의의 스크립트를 실행할 수 있는 가능성이 존재합니다. 이러한 공격은 사용자 정보를 탈취하거나, 웹사이트의 기능을 방해하는 등 심각한 결과를 초래할 수 있습니다.

React에서는 DOM에 HTML을 직접 삽입하기 위하여 dangerouslySetInnerHTML을 사용할 수 있지만, 이 경우 항상 HTML을 정제하여 보안을 강화해야 합니다. 저는 많은 방법 중 사용자 입력을 안전하게 처리할 수 있는 라이브러리인 DOMPurify를 활용하는 방법을 고려하였습니다.

dangerouslySetInnerHTML가 위험한 이유?

dangerouslySetInnerHTML 메서드는 HTML 문자열을 그대로 DOM에 삽입하기 때문에, 만약 악성 스크립트가 포함된 HTML이 전달되면, 해당 스크립트가 실행될 수 있습니다.

예를 들어, 다음과 같은 코드가 있을 때:

<div dangerouslySetInnerHTML={{ __html: userInput }} />

이 코드에서 userInput<script>alert('XSS!')</script>와 같은 스크립트가 포함되어 있다면, 이 스크립트가 그대로 실행됩니다. 이로 인해 악성 스크립트가 실행되며, 이는 웹 페이지에서 XSS 공격을 유발할 수 있습니다.

즉, dangerouslySetInnerHTML을 사용하면 사용자 입력이나 외부 데이터를 검증하지 않은 상태로 DOM에 삽입하게 되며, 이로 인해 공격자가 악성 스크립트를 실행할 수 있는 길을 열어줍니다.

하드코딩한 다국어 메시지에도 XSS 공격이 가능할까?

다국어 처리를 위해 하드코딩 된 HTML을 사용하는 경우, 예를 들어 저와 같이 <br/> 태그를 직접 포함하는 경우에도 XSS 공격 위험이 있을 수 있습니다. 그 이유는 다음과 같습니다.

  1. 동적 데이터로부터의 취약점:
    • 하드코딩된 메시지 자체는 문제가 없을 수 있습니다. 하지만, 메시지에 동적으로 삽입되는 데이터가 포함될 경우(예: 사용자 이름, 입력 값 등), 이 데이터가 안전하지 않다면 그 데이터를 통해 악성 스크립트가 삽입될 수 있습니다.
    • 예를 들어, 메시지 내에 {username}과 같은 동적 데이터를 포함하는 경우를 생각해볼 수 있습니다. 만약 이 데이터가 검증되지 않고 <script> 태그 같은 것이 포함되어 있다면, 결국 이 HTML은 실행되어 XSS 공격이 가능합니다.
    • const message = `Welcome, ${username}! <br/> Enjoy your stay!`;
      <div dangerouslySetInnerHTML={{ __html: message }} />
    • 여기서 username에 악성 스크립트가 들어오면 그대로 실행될 수 있습니다. 예를 들어, username<script>alert('XSS!')</script>라면 이 스크립트가 실행됩니다.
  2. 외부 라이브러리나 API로부터 데이터 주입:
    • 다국어 처리를 위해 사용하는 번역 파일 또는 외부 API에서 번역 데이터를 가져오는 경우, 해당 데이터가 안전하지 않으면 하드코딩된 메시지라도 공격에 노출될 수 있습니다.
    • 예를 들어, 번역 파일에 문제가 있거나 API에서 불안전한 데이터가 전달될 경우, 이 데이터가 DOM에 삽입되면서 XSS 공격의 원인이 될 수 있습니다.
  3. 실수로 삽입된 악성 데이터:
    • 개발자가 하드코딩하는 과정에서 실수로 악성 데이터를 포함하거나, 협업 중 누군가가 악성 스크립트를 코드에 추가할 수 있습니다. 보통 하드코딩된 HTML이 안전하다고 생각하지만, 실수나 관리 부주의로 인해 잠재적인 XSS 취약점이 생길 수 있습니다.

3. XSS 공격 방지 방법

  1. 사용자 입력 검증:
    • 사용자로부터 입력받은 데이터는 반드시 검증하고, 악성 코드가 포함되어 있지 않도록 필터링해야 합니다. HTML 태그나 자바스크립트 코드를 문자열 그대로 삽입하는 것을 피하고, 적절하게 인코딩 또는 이스케이프 처리를 해야 합니다.
  2. DOMPurify 등의 라이브러리 사용:
    • HTML을 렌더링할 때, DOMPurify와 같은 검증된 라이브러리를 사용하여 악성 스크립트를 정화할 수 있습니다. 이 라이브러리는 HTML을 안전하게 처리하고, 스크립트나 이벤트 핸들러를 제거하여 XSS 공격을 방지합니다.

DOMPurify

xss 방지 DOMPurify
https://www.npmjs.com/package/dompurify#what-does-it-do

DOMPurify는 웹 애플리케이션에서 사용자 입력을 안전하게 처리하기 위한 라이브러리로, 주로 XSS(교차 스크립트 공격)로부터 보호하기 위해 사용됩니다.

이 라이브러리는 HTML, SVG 및 MathML을 정제하여 악성 스크립트가 포함되지 않도록 도와줍니다. DOMPurify의 주요 기능과 특징은 다음과 같습니다:

주요 기능

  1. XSS 방어: DOMPurify는 사용자가 입력한 HTML 코드에서 악성 스크립트, 이벤트 핸들러, 또는 기타 위험한 요소를 제거합니다. 이를 통해 웹 페이지에서 스크립트가 실행되는 것을 방지할 수 있습니다.
  2. 브라우저 호환성: DOMPurify는 대부분의 현대 브라우저에서 잘 작동하며, 다양한 플랫폼과 환경에서 사용할 수 있습니다.
  3. 간편한 사용법: 라이브러리를 사용하기 위해 복잡한 설정이 필요하지 않으며, 간단한 API로 손쉽게 통합할 수 있습니다. HTML 문자열을 정제하기 위한 단 한 줄의 코드로 사용할 수 있습니다.
  4. 신뢰성 있는 정제: DOMPurify는 검증된 보안 알고리즘을 사용하여 정제 과정을 수행하며, 보안 커뮤니티에서 널리 사용되고 있습니다. GitHub에서도 활발하게 유지 관리되고 있습니다.
  5. 옵션 설정: DOMPurify는 다양한 설정 옵션을 제공하여 사용자가 필요에 따라 정제 프로세스를 세부 조정할 수 있습니다. 예를 들어, 특정 HTML 태그나 속성을 허용하거나 차단할 수 있습니다.

사용 예시

DOMPurify를 사용하는 기본적인 예시는 다음과 같습니다

// DOMPurify, i18next 라이브러리 로드
import DOMPurify from 'dompurify';
import { t } from 'i18next';

// 다국어 처리를 위한 메시지
"I18N_MESSAGE_KEY01": "To manage your profile settings, go to the 'Profile' section in the main menu.<br/>Here, you can update your personal information, change your password, and configure your privacy settings.<br/>Make sure to save your changes before exiting."

/**
 * 다국어 메시지를 안전하게 렌더링할 수 있는 함수
 * @param {string} html - 안전하지 않은 HTML 메시지
 * @returns {Object} - 안전한 HTML 메시지
 */
const sanitizeHtml = (html: string) => {
    return { __html: DOMPurify.sanitize(html) };
};


// 정제된 HTML이 사용부에서 삽입 된 모습

return (
  <ContentDescription dangerouslySetInnerHTML={sanitizeHtml(t('I18N_MESSAGE_KEY01'))} />
);

위의 예시에서 DOMPurify의 sanitizeHtml 메서드를 통해 악성 스크립트가 제거되어 최종적으로 정제된 HTML이 웹 페이지에 삽입됩니다.

사용 예시의 메시지 키를 만약 dangerouslySetInnerHTML를 사용하지 않고, 그대로 렌더링 하면 다음과 같이 보입니다.

반면 dangerouslySetInnerHTML를 사용하고, sanitizeHtml 함수를 통하여 악성 스크립트를 정제 한 뒤 화면에서의 모습은 다음과 같습니다.


결론

dangerouslySetInnerHTML은 HTML을 직접 렌더링할 수 있는 강력한 도구이지만, 이를 사용할 때 사용자 입력을 포함한 데이터가 악성 스크립트에 의해 XSS 공격의 경로로 활용될 수 있습니다.


사용자의 입력을 받지 않는 하드코딩 된 메시지 같은 경우에도, 동적 데이터가 포함되거나 외부 번역 데이터가 삽입되는 경우 검증되지 않은 데이터가 있을 수 있으므로, 이를 처리할 때도 보안에 주의해야 합니다.

<프론트엔드 관련 포스트 더 보기>

다양한 경험을 하고 그 가치를 함께 나누는 것을 좋아합니다. 오픈소스컨설팅 프론트엔드 개발자 노은지입니다.

Leave a Reply

Your email address will not be published. Required fields are marked *