이효석
Study

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

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

  • 제 3자가 웹사이트에 악성 스크립트를 삽입하여 실행하는 보안 이슈

14.1.1 DangerouslySetInnerHTML prop

  • 특정 브라우저의 DOM 을 특정한 내용으로 교체하는 방법
const html = `<span><svg/onload=alert(origin)</span>`;
 
function App() {
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
  • 이러한 방식으로 특정 DOM 에 보안을 위협하는 코드 삽입이 가능하므로 주의하여 사용 필요

14.1.2 useRef 를 활용한 직접 삽입

  • useRef 를 활용하면 DOM 에 직접 내용을 삽입할 수 있으므로 innerHTML 과 동일한 보안 취약점이 발생한다
const html = `<span><svg/onload=alert(origin)></span>`;
 
function App() {
  const divRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    if (divRef.current) {
      divRef.current.innerHTML = html;
    }
  });
  return <div ref={divRef} />;
}

14.1.3 리액트에서 XXS 문제를 피하는 방법

  • 제 3자가 삽입할 수 있는 HTML 을 안전한 HTML 로 한번 치환하는 것으로 새니타이즈(sanitize) 또는 이스케이프(espace)라 불린다
  • html 태그에서 허용할 목록을 하나하나 확인하여 보안 이슈가 발생할 부분을 제거하는 형태
  • 직접 구현하기 보다는 npm 라이브러리 사용 추천
  • 또한 Query, GET 메소드의 파라마터, 서버 데이터 등 모든 코드에 대한 주의 및 방비가 필요하다

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

  • 서버에서 사용되는 정보가 클라이언트에 바로 노출될 수 있으므로 주의가 필요하다

  • 쿠키 유효성을 체크하는 아래의 코드

export default function App({ cookie }: { cookie: string }) {
  if (!validateCookie(cookie)) {
    Router.replace(/*...*/);
    return null;
  }
 
  /* do something... */
}
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
  const cookie = ctx.req.headers.cookie || "";
 
  return { props: { cookie } };
};
  • getServerSideProps 가 처리하는 모든 props 값은 사용자의 HTML 에 기록 되므로 주의있는 사용이 필요하다

  • 위의 코드를 수정한 코드, 쿠키 전체 값이 아닌 필요한 token 만 잘라서 사용하였으며 예외 처리 부분도 서버에서 처리하여 보안 문제와 효율성을 전부 해결

export default function App({ token }: { token: string }) {
  const user = JSON.parse(window.atob(token.split(".")[1]));
  const user_id = user.id;
  /* do something... */
}
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
  const cookie = ctx.req.headers.cookie || "";
  const token = validateCookie(cookie);
 
  if (!token) {
    return { redirect: { destination: "/404", permanent: false } };
  }
 
  return { props: { token } };
};

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

  • a 태그의 href 에 javascript 를 넣어 a 태그의 기본 기능을 막고 onClick 을 실행시키는 경우가 있다
  • 이러한 경우도 XSS 의 위험성에 노출 되므로 처리 필요
function App() {
  return (
    <>
      <a href="javascript:alert('XSS');">링크</a>
    </>
  );
}

14.4 HTTP 보안 헤더 설정하기

14.4.1 Strict-Tranport-Security

  • Strict-Tranport-Security 응답 헤더는 모든 사이트가 HTTPS 를 통해 접근해야 하며, 만약 HTTP 로 접근 시 모든 시도를 HTTPS 로 변경
Strict-Tranport-Security: max-age=<expire-time>; includeSubDomains
  • exprie-time : 해당 설정을 브라우저가 기억해야할 타임, 해당 기간 동안에는 Strict-Tranport-Security 특성 발휘
  • includeSubDomains 을 통해 모든 하위 도메인에 적용 가능

14.4.2 X-XSS-Protection

  • X-XSS-Protection 은 비표준 기술로 사파리와 구형 브라우저에서만 제공되는 기능
  • XSS 취약점이 발경되면 즉시 페이지 로딩을 중단하는 헤더

14.4.3 X-Frame-Options

  • 페이지를 frame, iframe, embed, object 내부에서 렌더링을 허용할지 여부 설정
  • 사용자가 iframe 위의 페이지를 실제 페이지로 착각하여 개인 정보 노출 가능성이 있음
X-Frame-Options : DENY          # 무조건 불가능
X-Frame-Options : SAMEORIGIN    # 같은 origin 의 페이지만 가능

14.4.4 Permissions-Policy

  • 웹사이트에서 사용할 수 있는 기능과 없는 기능을 명시적으로 선언하는 헤더
  • 브라우저 기능, API, GPS, 카메라 등에 대한 접근을 미리 선언 가능
# 모든 geolocation 사용을 막는다.
Permissions-Policy: geolocation=()
 
# geolocation을 페이지 자신과 몇 가지 페이지에 대해서만 허용한다.
Permissions-Policy: geolocation=(self "https://a.yceffort.kr" "https://b.yceffort.kr")
 
# 카메라는 모든 곳에서 허용한다.
Permissions-Policy: camera=*;
 
# pip 기능을 막고, geolocation은 자신과 특정 페이지만 허용하며, # 카메라는 모든 곳에서 허용한다.
Permissions-Policy: picture-in-picture=(), geolocation=(self https://yceffort.kr), camera=*;

14.4.5 X-Content-Type-Options

  • MIME(Multipurpose Internet Mail Extensions) : 메일을 전송할 때 사용하던 인코딩 옵션
  • 해당 옵션을 통해 브라우저가 특정 파일을 읽어서 HTML 로 파싱하는 부분에 대한 제한 설정이 가능
  • 따라서, 웹 서버가 브라우저에게 강제로 특정 파일을 읽는 것을 막아준다(ex. jpg 에 스크립트를 심어 실행하도록 유도)

14.4.6 Referrer-Policy

** [p. 892] 오타 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 우리나라로 따지면 참죠 규칙 정도 되겠군요 ㅎㅎㅎ

  • 현재 요청을 보낸 페이지의 주소가 나타나는 헤더, 사용자가 어디에서 방문했는지 인식이 가능한 헤더
  • 다만, 사용자 입장에서는 원치 않는 정보를 노출 될 수 있으므로 주의가 필요하다
  • 해당 정보를 기반으로 cross-origin 을 판단

14.4.7 Content-Security-Policy

*-src

  • 다양한 src 를 제어할 수 있는 지시문
Content-Security-Policy: font-src <source>;
Content-Security-Policy: font-src <source> <source>;
  • 위와 같이 선언하여 font 를 가져오는 출처의 제한이 가능
  • script / style / font / img / connect(스크립트로 접근하는 URL) / worker / object / media / manifest / frame / prefetch / child(frame 또는 iframe) 에 대한 출처 제한이 가능하다
  • 아래 처럼 default 를 사용하여 한 번에 처리 가능
Content-Security-Policy: default-src <source>;
Content-Security-Policy: default-src <source> <source>;

form-action

<meta http-equiv="Content-Security-Policy" content="form-action 'none'" />
  • 위와 같이 form-action 자체를 막을 수 있음

14.4.8 보안 헤더 설정하기

Next.js

  • next.config.js 에서 HTTP 경로별로 보안 헤더 적용 가능
  • 가능한 설정 값들
    • X-DNS-Prefetch-Control
    • Strict-Transport-Security
    • X-XSS-Protection
    • X-Frame-Options
    • Permissions-Policy
    • X-Content-Type-Options
    • Referrer-Policy
    • Content-Security-Policy
const securityHeaders = [
  {
    key: "key",
    value: "value",
  },
];
 
module.exports = {
  async headers() {
    return [
      {
        // 모든 주소에 설정한다.
        source: "/:path*",
        headers: securityHeaders,
      },
    ];
  },
};

NGINX

  • add_header 지시자를 사용하여 헤더 추가 가능
location / {
  # ...
  add_header X-XSS-Protection "1; mode=block";
}

14.4.9 보안 헤더 확인하기

** [p. 898] 인프런은 F, 패캠은 B 네요 ㅎㅎㅎㅎ

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

  • Dependabot 같은 기능을 사용하여 취약점 발견 & 해결 필요

14.6 OWASP Top 10

  • Open Worldwide Application Security Project 의 약자로 주기적으로 10대 웹 어플리케이션 취약점을 공개
  • 해당 내용을 주기적으로 확인하여 보안 문제에 대한 대비가 필요

15. 마치며

15.1 리액트 프로젝트를 시작할 때 고려해야 할 사항

15.1.1 유지보수 중인 서비스라면 리액트 버전을 최소 16.8.6 에서 최대 17.0.2 로 올려두자

  • 리액트 16.8 에서 리액트 훅 도입 및 많은 변화가 있었으므로 업그레이드 필요
  • 다만 기존 클래스 컴포넌트를 함수형으로 리팩토링 할 필요는 없다

15.1.2 인터넷 익스플로러 11 지원을 목표로 한다면 각별히 더 주의를 기한다

  • 리액트 18 지원 X
  • Next 13 지원 X
  • Query-string 6 지원 X

** [p. 904] 걍 버립시다 🤣

15.1.3 서버 사이드 렌더링 어플리케이션을 우선적으로 고려한다

  • 모바일 기기기 확대되고 있는 만큼 클라이언트 성능의 차이가 심하므로 SSR 을 우선 고려 하자

15.1.4 상태 관리 라이브러리는 꼭 필요할 때만 사용한다

15.1.5 리액트 의존성 라이브러리 설치를 조심한다

  • peerDependencies 가 설치하고자하는 프로젝트의 리액트 버전과 맞는지 확인 필요

15.2 언젠가 사라질 수도 있는 리액트

15.2.1 리액트는 그래서 정말 완벽한 라이브러리인가?

클래스 컴포넌트에서 함수 컴포넌트로 넘어오면서 느껴지는 혼란

  • 리액트가 고도화 되면서 리액트 만의 문법으로 인하여 JS 와 동떨어지고 있다

너무 방대한 자유가 주는 혼란

  • 다양한 방법을 전부 지원하다보니 오히려 혼안을 준다

15.2.2 오픈소스 생태계의 명과 암

페이스북 라이선스 이슈

  • 리액트는 페이스북에 의해 자유로운 MIT 라이선스가 아닌 BSD+Patents 라이선스로 제공 되므로 페이스북의 선택에 의해 언제든 라이선스 종료가 가능했다
  • 2017년에 아파치 재단에서 BSD+Patents 를 금지시켜 페이스북도 한동안 고집을 유지하다 해당 라이선스를 포기하고 MIT 라이선스로 넘어갔다

오픈소스는 무료로 걔속 제공될 수 있는가? colors.js, faker.js, 그리고 바벨

  • 돈 문제는 항상 발생한다

** [p. 913] No more free work from Marak - Pay Me or Fork This ** 어찌 생각하시나요?

15.2.3 JQuery, AngularJS, React 그리고 다음은 무엇인가?

  • 스프링, ASP.NET, Rails, PHP 등 웹 프레임워크에서 프론트엔드 영역이 분리 되어 각광 받은 것은 얼마 안되었다
  • 그리고 시장고 상황은 항상 변화한다

15.2.4 웹 개발자로서 가져야 할 유연한 자세

** [p. 917] 뭐 일단, 리액트가 대세니 이거 잘하다가 또 바뀌면 넘어가야져 ㅋㅋ