티스토리 뷰

1. XSS(Cross‑Site Scripting)란?

XSS(Cross‑Site Scripting) : 웹 애플리케이션에서 자주 등장하는 취약점으로, 공격자가 악성 스크립트를 주입해 사용자의 브라우저에서 실행시키는 기법

  1. 세션·쿠키 탈취 : 악성 스크립트가 실행되면 로그인 세션 쿠키를 탈취해 계정 탈취로 이어질 수 있습니다.
  2. 피싱·악성 행위 : DOM을 조작해 가짜 로그인 창을 띄우거나, 숨은 네트워크 요청으로 토큰·개인정보를 외부로 전송할 수 있습니다.
  3. 연계 공격 : CSRF 등 다른 취약점과 결합해 결제·게시물 작성 등 의도치 않은 동작을 수행시키기도 합니다.

발생 원인

  • 사용자 입력검증·필터링·인코딩 없이 그대로 HTML이나 속성(src 등)에 삽입하는 경우
  • React, Vue 등에서 dangerouslySetInnerHTML이나 v-html을 무분별하게 사용
  • URL 파라미터(location.state?.src, querystring 등)를 <iframe>에 직접 주입하며, javascript: 스킴 등을 막지 않는 경우

2. 문제 발견: iframe src 무분별 삽입

프로젝트에서 Fortify 보안 검사를 진행한 결과, <iframe>의 src 속성에 화이트리스트나 필터링 없이 외부 입력(location.state?.src)을 그대로 주입하는 구간에서 XSS 취약점이 지적되었습니다.

// 취약점이 발생한 예시
<iframe src={location.state?.src} />
공격자가 location.state?.src 에 javascript:alert(document.cookie) 같은 악성 스킴을 넘기면, 최종적으로 사용자의 브라우저에서 임의 코드가 실행될 수 있습니다.
 

3. 개선 목표

  1. 허용된 URL만 <iframe> 에 주입한다.
  2. 외부 입력 값이 화이트리스트에 없으면 로드하지 않도록 차단한다.
  3. 추후 메뉴 구성이 변경 되었어도 확장하기 쉬운 구조로 만든다.

4. 화이트리스트 기반 접근(WhiteList Approach)

  1. 이미 메뉴 정보(menuList)에 어떤 URL이 iframe으로 열려야 되는지 정의되어 있음
    이 중 menuTypeCd === 'L'인 route만 iframe 허용 URL 이라고 간주
  2. menuList를 재귀적으로 순회하여, menuTypeCd === 'L'에 해당하는 모든 route화이트리스트로 모음.
  3. <iframe>에 넣으려는 URL(location.state?.src)이 이 화이트리스트 안에 속해 있지 않으면, iframe을 비워두거나 다른 화면으로 대체한다.

4.1. 기존 메뉴 정보 활용

서비스에는 이미 메뉴 정보(zustand 스토어의 menuList)에 iframe으로 띄울 수 있는 URL이 정의되어 있습니다.

  • menuTypeCd === 'L' 인 메뉴 항목만 정상 iframe URL로 간주
// store/menuStore.js (예시)
menuList: [
  {
    key: 'menu-1',
    menuTypeCd: 'B',            // 일반 페이지
    route: '/board',
    children: [
      {
        key: 'menu-1-1',
        menuTypeCd: 'L',        // iframe 대상
        route: 'https://example.com/iframePage',
      }
    ]
  },
  {
    key: 'menu-2',
    menuTypeCd: 'L',            // iframe 대상
    route: 'https://another.com/reportView',
  }
]

4.2. 재귀함수로 화이트리스트 수집

중첩된 children 까지 모두 순회해, menuTypeCd==='L' 인 route 들을 한곳에 모읍니다.

const collectIframeRoutes = (menuList, result = []) => {
  menuList.forEach(item => {
    if (item.menuTypeCd === 'L') {
      result.push(item.route);
    }
    if (Array.isArray(item.children)) {
      collectIframeRoutes(item.children, result);
    }
  });
  return result;
}

4.3. IFrameLayout 컴포넌트 적용

최종적으로 렌더링 시점에, 사용자가 넘긴 urlSrc 가 화이트리스트에 포함되어야만 <iframe> 에 주입됩니다.

import React from 'react';
import { Box } from '@mui/material';
import { useLocation } from 'react-router-dom';
import { useMenuStore } from '@/stores/menuStore';

const collectIframeRoutes = (menuList, result = []) => {
  menuList.forEach(item => {
    if (item.menuTypeCd === 'L') {
      result.push(item.route);
    }
    if (Array.isArray(item.children)) {
      collectIframeRoutes(item.children, result);
    }
  });
  return result;
}

const IFrameLayout = () => {
  const menuList = useMenuStore(state => state.menuList);
  const location = useLocation();
  const urlSrc = location.state?.src;

  // 화이트리스트 생성
  const iframeWhiteList = collectIframeRoutes(menuList);
  // 허용 URL 여부 판단
  const isValid = iframeWhiteList.includes(urlSrc);

  return (
    <Box sx={{ height: '100%', width: '100%' }}>
      <iframe
        src={isValid ? urlSrc : '/no-permission'}
        style={{ height: '100%', width: '100%' }}
        title="iframeLayout"
      />
    </Box>
  );
};

export default IFrameLayout;

이로써 메뉴 정보에 등록되지 않은 URL은 <iframe> 에 로드되지 않아, XSS 위험을 효과적으로 차단할 수 있습니다.

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함