본문 바로가기
📖 책 찢기/모던 리액트 Deep Dive

[모던 리액트 Deep Dive] 14장. 웹사이트 보안을 위한 리액트와 웹페이지 보안 이슈

by 짱돌보리 2024. 7. 30.
728x90

https://github.com/Study-FE-Techbook/Modern-React-Deep-Dive

 

GitHub - Study-FE-Techbook/Modern-React-Deep-Dive: 모던 리액트 딥다이브 스터디

모던 리액트 딥다이브 스터디. Contribute to Study-FE-Techbook/Modern-React-Deep-Dive development by creating an account on GitHub.

github.com

[14장] 웹사이트 보안을 위한 리액트와 웹페이지 보안 이슈

코드의 규모 증가 = 보안 취약점 노출 확률 증가

14.1 리액트에서 발생하는 크로스 사이트 스크립팅(XSS)

  • 웹사이트 개발자가 아닌 제3자가 웹사이트에 악성 스크립트를 삽입해 실행할 수 있는 취약점

✨dangerouslySetInnerHTML prop

  • 특정 브라우저 DOM의 innerHTML을 특정한 내용으로 교체할 수 있는 방법
const htmlContent = `<p>악</p>`
const MyComponent = ({ htmlContent }) => {
  return (
    <div
      dangerouslySetInnerHTML={{ __html: htmlContent }}
    />
  );
};

export default MyComponent;
  • 오직 __html 을 키로 가지고 있는 개체만 인수로 받을 수 있고, 이 인수로 넘겨받은 문자열을 DOM에 그대로 표시하는 역할을 한다.
  • 인수로 받는 문자열에는 제한이 없어 위험하다.
  • 문자열 값은 한 번 더 검증이 필요하다.

✨useRef를 활용한 직접 삽입

  • DOM에 직접 내용을 삽입할 수 있는 방법
  • innerHTML에 보안 취약점이 있는 스크립트를 삽입하면 위와 동일한 문제가 발생한다.
import React, { useRef, useEffect } from 'react';

const MyComponent = ({ htmlContent }) => {
  const divRef = useRef(null);

  useEffect(() => {
    if (divRef.current) {
      divRef.current.innerHTML = htmlContent;
    }
  }, [htmlContent]);

  return <div ref={divRef}></div>;
};

export default MyComponent;

✨리액트에서 XSS 문제를 피하는 방법

  • 제3자가 삽입할 수 있는 HTML을 안전한 HTML 코드로 한 번 치환하는 것 (=새니타이즈 or 이스케이프)
const MyComponent = ({ htmlContent }) => {
  const divRef = useRef(null);

  useEffect(() => {
    if (divRef.current) {
      const cleanHtml = sanitizeHtml(htmlContent, {
        allowedTags: ['b', 'i', 'em', 'strong', 'a'], // 허용할 태그를 명시
        allowedAttributes: {
          'a': ['href']
        },
        // 스타일 속성을 제거
        allowedStyles: {},
        // 스크립트 태그를 제거
        disallowedTagsMode: 'discard'
      });
      divRef.current.innerHTML = cleanHtml;
    }
  }, [htmlContent]);

  return <div ref={divRef}></div>;
};

export default MyComponent;

14.2 getServerSideProps와 서버 컴포넌트를 주의하자

  • 서버에는 일반 사용자에게 노출되면 안 되는 정보들이 담겨 있기 때문에 클라이언트, 즉 브라우저에 정보를 내려줄 때는 조심해야 한다.
  • getServerSideProps가 반환하는 props값은 모두 사용자의 HTML에 기록되고, 전역 변수로 등록되어 스크립트로 충분히 접근할 수 있는 보안 위협에 노출되는 값이 된다. (API 키, 사용자 세션 정보, 민감한 설정 값 등)
  • 충분히 getServerSideProps에서 처리할 수 있는 리다이렉트가 클라이언트에서 실행되어 성능 측면에서도 손해를 본다. → getServerSideProps가 반환하는 값 또는 서버 컴포넌트가 클라이언트 컴포넌트에 반환하는 props는 반드시 필요한 값으로만 철저하게 제한되어야 한다.
export async function getServerSideProps(context) {
  // 민감한 데이터는 서버에서만 사용하고 클라이언트로 전달하지 않는다.
  const data = await fetchDataFromServer();
  const safeData = {
    id: data.id,
    name: data.name,
    // 클라이언트에 필요한 최소한의 데이터만 포함한다.
  };

  return {
    props: { safeData },
  };
}

function Page({ safeData }) {
  return (
    <div>
      <h1>{safeData.name}</h1>
      {/* 민감한 정보가 포함되지 않은 안전한 데이터를 렌더링 */}
    </div>
  );
}

export default Page;

14.3 <a> 태그의 값에 적절한 제한을 둬야 한다

  • <a> 태그는 반드시 페이지 이동이 있을 때만 사용하자.
  • 페이지 이동이 없어 어떠한 핸들러만 작동시키고 싶다면 button을 사용하자.
  • href에 사용자가 입력한 주소를 넣을 수 있다면 이 또한 보안 이슈로 이어질 수 있다. → href 값 제한하기
  • 피싱 사이트로 이동하는 것을 막기 위해 origin도 확인해 처리하자.

14.4 HTTP 보안 헤더 설정하기

✨Strict-Transport-Security

  • HTTP의 Strict-Transport-Security 응답 헤더는 모든 사이트가 HTTPS를 통해 접근해야 하며, 만약 HTTP로 접근하는 경우 모든 시도는 HTTPS로 변경되게 한다.

✨X-XSS-Protection

  • 비표준 기술, 현재 사파리와 구형 브라우저에 제공되는 기능
    • 0: XSS 필터 비활성화
    • 1: XSS 필터 활성화하고, 공격이 탐지되면 페이지 로드를 차단
    • 1; mode=block: XSS 필터를 활성화하고, 공격이 탐지되면 페이지 로드를 차단
    • 1; report=<reporting-URI>: XSS 필터를 활성화하고, 공격이 탐지되면 페이지 로드를 차단하며, 지정된 URI로 보고함.

✨X-Frame-Options

  • 페이지를 frame, iframe, embed, object 내부에서 렌더링을 허용할지를 나타낼 수 있다.
  • DENY: 모든 도메인에서 해당 페이지를 프레임 내에 렌더링하는 것을 금지
  • X-Frame-Options: DENY
  • SAMEORIGIN: 동일한 출처에서만 해당 페이지를 프레임 내에 렌더링할 수 있도록 허용
  • X-Frame-Options: SAMEORIGIN
  • ALLOW-FROM uri: 특정 출처(uri)에서만 해당 페이지를 프레임 내에 렌더링할 수 있도록 허용한다. 이 옵션은 최신 브라우저에서 지원되지 않는 경우가 있으므로, 사용 시 주의가 필요하다.
  • X-Frame-Options: ALLOW-FROM <https://example.com/>

✨Permissions-Policy

  • 웹사이트에서 사용할 수 있는 기능과 사용할 수 없는 기능을 명시적으로 선언하는 헤더

[XSS를 사용하는 예제 헤더]

server {
    ...
    # 모든 위치 정보 접근 막기
    # geolocation 기능을 비활성화하여, 페이지가 위치 정보에 접근하지 못하게 함
    add_header Permissions-Policy "geolocation=()";
    
    # 위치 정보는 페이지 자신과 특정 페이지에 대해서만 허용
    # 카메라는 모든 출처에서 허용
    # 화면 전체 모드(PIP)를 비활성화
    # - geolocation: 현재 도메인과 example.com에서만 위치 정보 접근 허용
    # - camera: 모든 출처에서 카메라 접근 허용
    # - fullscreen: 화면 전체 모드 비활성화
    add_header Permissions-Policy "geolocation=(self <https://example.com>), camera=(*), fullscreen=()";
    
    ...
}

 

✨X-Content-Type-Options

❓MIME(Multipurpose Internet Mail Extensions)
메시지의 콘텐츠가 어떤 형식인지 브라우저와 서버에게 알려주는 메타데이터 MIME 헤더는 이메일 메시지나 HTTP 응답의 메타데이터를 정의한다. Content-Type, Content-Disposition, Content-Encoding 등

 

✨Referrer-Policy

  • 웹사이트가 다른 웹사이트로 요청을 보낼 때, 리퍼러(즉, 현재 페이지의 URL 정보)를 어떻게 전송할지를 제어하는 보안 기능
  • 이 헤더를 사용하면 사이트가 자신의 URL 정보를 얼마나 공개할지를 설정할 수 있으며, 이를 통해 사용자 개인정보를 보호하고 보안 위험을 줄일 수 있다.
    • scheme: HTTPS 프로토콜
    • hostname: yceffort.kr이라는 호스트명 의미
    • port: 443

 

Origin A Origin B "Same-origin" 또는 "cross-origin"
https://www.example.com:443 https://www.evil.com:443 교차 출처 (Cross-origin): 도메인이 다름
https://example.com:443 https://www.example.com:443 교차 출처 (Cross-origin): 서브도메인이 다름
https://login.example.com:443 https://www.example.com:443 교차 출처 (Cross-origin): 서브도메인이 다름
http://www.example.com:443 https://www.example.com:443 교차 출처 (Cross-origin): 스킴(HTTP와 HTTPS)이 다름
https://www.example.com:443 https://www.example.com:80 교차 출처 (Cross-origin): 포트 번호가 다름
https://www.example.com:443 https://www.example.com:443 동일 출처 (Same-origin): 정확히 일치
https://www.example.com https://www.example.com 동일 출처 (Same-origin): 암시적 포트 번호(443)와 일치

 

✨Content-Security-Policy

  • 콘텐트 보안 정책은 XSS 공격이나 데이터 삽입 공격과 같은 다양한 보안 위협을 막기 위해 설계됐다.

📍-src*

CSP에서 다양한 종류의 리소스 출처를 정의하는 데 사용된다.

Content-Security-Policy: connect-src 'self';
                         font-src 'self';
                         frame-src 'self';
                         img-src 'self';
                         manifest-src 'self';
                         media-src 'self';
                         object-src 'self';
                         script-src <https://example.com>;
                         style-src 'self';
                         worker-src 'self'

📍form-action

폼 양식으로 제출할 수 있는 URL을 제한할 수 있다.

Content-Security-Policy: form-action 'self' <https://forms.example.com>


    
    
    

Submit Form

    // 이 폼은 자기 도메인과 <https://forms.example.com으로> 데이터를 제출할 수 있다.







✨보안 헤더 설정하기

📍Next.js Next.js 애플리케이션에서는 보안 헤더를 설정하는 데 next.config.js 파일을 사용한다. 이 파일에서 headers 함수를 활용하여 HTTP 응답 헤더를 설정할 수 있다.

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        // 적용할 경로
        source: '/(.*)', // 모든 경로에 적용
        headers: [
          {
            key: 'Content-Security-Policy',
            value: "default-src 'self'; script-src 'self' <https://trusted.cdn.com>; style-src 'self' 'unsafe-inline';",
          },
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=31536000; includeSubDomains',
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'X-XSS-Protection',
            value: '1; mode=block',
          },
          {
            key: 'Referrer-Policy',
            value: 'no-referrer',
          },
          {
            key: 'Permissions-Policy',
            value: 'geolocation=(self), camera=(self)',
          },
        ],
      },
    ];
  },
};
  • Content-Security-Policy (CSP): XSS 및 데이터 삽입 공격 방지.
  • Strict-Transport-Security: HTTPS를 강제.
  • X-Content-Type-Options: MIME 타입 스니핑 방지.
  • X-Frame-Options: 클릭재킹 방지.
  • X-XSS-Protection: XSS 공격 방지.
  • Referrer-Policy: 참조자 정보 제어.
  • Permissions-Policy: 웹 API 접근 제어.

📍NGINX 서버 블록(server block) 또는 위치 블록(location block)에서 add_header 지시어를 사용하여 보안 헤더를 설정할 수 있다.

server {
    listen 80;
    server_name example.com;

    # HTTP를 HTTPS로 리디렉션
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;

    # SSL 인증서 설정
    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    # 보안 헤더 설정
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' <https://trusted.cdn.com>; style-src 'self' 'unsafe-inline';";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
    add_header X-Content-Type-Options "nosniff";
    add_header X-Frame-Options "DENY";
    add_header X-XSS-Protection "1; mode=block";
    add_header Referrer-Policy "no-referrer";
    add_header Permissions-Policy "geolocation=(self), camera=(self)";

    location / {
        # 기본 설정
        try_files $uri $uri/ =404;
    }
}

 

✨보안 헤더 확인하기

Analyse your HTTP response headers

 

Analyse your HTTP response headers

Quickly and easily assess the security of your HTTP response headers

securityheaders.com

 

14.5 취약점이 있는 패키지의 사용을 피하자

Snyk Vulnerability Database | Snyk

 

Snyk Vulnerability Database | Snyk

The most comprehensive, accurate, and timely database for open source vulnerabilities.

security.snyk.io

 

14.6 OWASP Top 10

  • Open Worldwide Application Security Project: 오픈소스 웹 애플리케이션 보안 프로젝트를 의미한다.
  • 주로 웹에서 발생할 수 있는 정보 노출, 악성 스크립트, 보안 취약점 등을 연구하며, 주기적으로 10대 웹 애플리케이션 취약점을 공개하는데 이것을 OWASP Top 10이라고 한다.

2022년 OWASP 10대 취약점 총정리 가이드 -

 

2022년 OWASP 10대 취약점 총정리 가이드 -

보안 모범 사례를 기업에 안내하기 위해 상위 10개 항목을 기업에게 제공하는 OWASP의 개정된 10대 취약점을 확인하여 앱을 안전하게 보호하세요

www.appsealing.com

 

참고