본문 바로가기

FrontEnd

리액트 엘리먼트에 $$typeof 속성이 존재하는 이유

반응형

리액트 엘리먼트에 $$typeof 속성이 존재하는 이유를 알아봅니다.

TLDR : XSS 공격 방지

원문입니다 : https://overreacted.io/why-do-react-elements-have-typeof-property/

 

Why Do React Elements Have a $$typeof Property?

It has something to do with security.

overreacted.io

JSX는 사실 리액트 엘리먼트로 컴파일되고

리액트 엘리먼트는 JSON으로 컴파일됩니다.

<marquee bgcolor="#ffa7c4">hi</marquee>

JSX를 작성한다 생각했지만, 실제로, 당신은 함수를 호출하고 있습니다:

React.createElement(
  /* type */ 'marquee',
  /* props */ { bgcolor: '#ffa7c4' },
  /* children */ 'hi'
)

그리고 그 함수는 객체를 리턴합니다. 우리는 이 객체를 React 엘리먼트라고 부릅니다. 
다음에 무엇을 렌더링할지 React에게 알려줍니다. 컴포넌트는 엘리먼트 트리를 반환합니다.

{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'), // 🧐 Who dis}

React를 사용했다면 type, props, key 및 ref 필드에 익숙할 것입니다.
하지만 $$typeof는 뭐죠? 왜 Symbol()이 값으로 있을까요?

이것은 React를 사용하기 위해 알아야 할 필요는 없지만
알면 기분이 좋아지는 것 중 하나입니다.
이 게시물에는 당신이 알고 싶어할 수 있는 보안에 대한 몇 가지 팁도 있습니다.
언젠가 당신이 자신만의 UI 라이브러리를 작성할 일이 있다면 이 내용이 유용할 것입니다.
나는 확실히 그렇길 바랍니다.

클라이언트 측 UI 라이브러리가 일반화되고 기본 보안 기능이 추가되기 전에는
앱 코드에서 HTML을 구성하고 DOM에 삽입하는 것이 일반적이었습니다.
const messageEl = document.getElementById('message');
messageEl.innerHTML = '<p>' + message.text + '</p>';

 

message.text가 '<img src onerror="stealYourPassword()">'와 같은 경우를 제외하고는 잘 작동합니다.

낯선 사람이 작성한 내용이 앱의 렌더링된 HTML에 그대로 표시되는 것을 원하지 않기 때문입니다.

 

(재미있는 사실: 클라이언트 측 렌더링만 수행하는 경우 여기에서 <script> 태그를 사용해도 JavaScript를 실행하지 않습니다.
하지만 이를 허용하여(don’t let this) 잘못된 보안 감각에 빠지진 마세요)
이러한 공격으로부터 보호하려면
텍스트만 처리하는 document.createTextNode() 또는 textContent와 같은 안전한 API를 사용할 수 있습니다.
또한 사용자가 제공한 텍스트에서 <, > 및 기타와 같은 잠재적으로 위험한 문자를 대체하여
입력을 선제적으로 "이스케이프"할 수 있습니다.
여전히 실수의 비용은 높으며 사용자가 작성한 문자열을 출력에 삽입할 때마다 이를 기억하는 것이 번거롭습니다.
이것이 React와 같은 최신 라이브러리가 기본적으로 문자열의 텍스트 콘텐츠를 이스케이프 처리하는 이유입니다.
<p>
  {message.text}
</p>

message.text가 <img> 또는 다른 태그가 있는 악성 문자열인 경우 실제 <img> 태그로 바뀌지 않습니다.
React는 콘텐츠를 이스케이프한 다음 DOM에 삽입합니다. 따라서 <img> 태그를 보는 대신 마크업(문자열 구조)만 볼 수 있습니다.

 

React 엘리먼트 내에서 임의의 HTML을 렌더링하려면
dangerouslySetInnerHTML={{ __html: message.text }}를 작성해야 합니다.
이건 기능이라고 하기도 뭐합니다.
코드 리뷰 및 코드베이스 감사에서 포착할 수 있도록 눈에 잘 띄도록 설계되었습니다.

React가 주입 공격으로부터 완전히 안전하다는 의미입니까?
아니요. HTML과 DOM은 React나 다른 UI 라이브러리가 완화하기에는 너무 어렵거나 공격을 늦추기 어려운 표면을 많이 제공합니다.
나머지 공격 벡터의 대부분은 속성을 포함합니다.
예를 들어 <a href={user.website}>를 렌더링하는 경우
user의 웹사이트가 'javascript:stealYourPassword()'인 경우를 조심해야 합니다.
<div {...userData}> 와 같은 사용자 입력을 스프레드 하는 것은 드물지만 위험합니다.

React는 시간이 지남에 따라 더 많은 보호를 제공할 수 있지만(could)
대부분의 경우 이러한 결과는 서버에서 해결되어야 합니다(should). 

 

그러나 텍스트 콘텐츠를 이스케이프하는 것은 많은 잠재적 공격을 포착하는 합리적인 첫 번째 방어선입니다. 
이런 코드가 안전하다는 것을 아는 것이 좋지 않습니까?

// Escaped automatically
<p>
  {message.text}
</p>
글쎄, 그것은 항상 사실인 것은 아니었습니다. 그리고 $$typeof가 등장합니다.

React 엘리먼트는 기본적으로 일반적인 객체입니다.
{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'),
}
일반적으로 React.createElement()로 생성하지만 필수는 아닙니다.
일반 엘리먼트 객체를 지원하는 React를 위한 유효한 사용 사례가 있습니다.
물론 이런 식으로 작성하고 싶지는 않을 것입니다.
하지만 이것은
컴파일러를 최적화하거나
worker 간에 UI 요소를 전달하거나
React 패키지에서 JSX를 분리하는 데 유용할 수 있습니다.

그러나 클라이언트는 문자열을 예상하하지만
서버에 사용자가 임의의 JSON 개체를 저장할 수 있는 구멍이 있는 경우 문제가 될 수 있습니다.

서버에서 짝퉁 리액트 엘리먼트 json을 보내면 막을 방법이 없었음
// Server could have a hole that lets user store JSON
let expectedTextButGotJSON = {
  type: 'div',
  props: {
    dangerouslySetInnerHTML: {
      __html: '/* put your exploit here */'
    },
  },
  // ...
};
let message = { text: expectedTextButGotJSON };

// Dangerous in React 0.13
<p>
  {message.text}
</p>

이 경우 React 0.13은 XSS 공격에 취약(vulnerable)합니다.
다시 한 번 명확히 하자면 이 공격은 기존 서버의 구멍에 의존합니다.
그래도 React는 사람들을 더 잘 보호할 수 있습니다. 그리고 React 0.14부터 가능합니다.

 

React 0.14의 수정 사항은 모든 React 요소에 Symbol을 태그로 지정하는 것이었습니다.
(tag every React element with a Symbol)

{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'),
}
이것은 JSON에 Symbol을 넣을 수 없기 때문에 동작합니다.
따라서 서버에 보안 허점이 있고 텍스트 대신 JSON을 반환하더라도
해당 JSON은 Symbol.for('react.element')를 포함할 수 없습니다.
React는 element.$$typeof를 확인하고 엘리먼트에 해당 속성이 없거나 해당 속성이 유효하지 않은 경우 처리를 거부합니다.
 
특히 Symbol.for()를 사용할 때 좋은 점은 Symbol이 iframe과 worker와 같은 환경 간에 전역적이라는 것입니다.
따라서 이 수정은 더 이국적인 조건에서도 앱의 다른 부분 간에 신뢰할 수 있는 엘리먼트를 전달하는 것을 방지하지 않습니다.
마찬가지로 페이지에 React 사본이 여러 개 있어도
$$typeof 값이 유효함을 확인 및 "동의"할 수 있습니다.

브라우저가 심볼(Symbol)을 지원하지 않으면요?

이 추가 보호를 받지 못합니다.
React는 일관성을 위해 요소에 $$typeof 필드를 여전히 포함하지만 숫자 0xeac7로 설정됩니다.

0xeac7은 "React"처럼 생겼습니다.


참고 : 

바닐라 JavaScript에서 innerHTML을 사용할 때 사이트 간 스크립팅 공격 방지

https://gomakethings.com/preventing-cross-site-scripting-attacks-when-using-innerhtml-in-vanilla-javascript/

 

Preventing cross-site scripting attacks when using innerHTML in vanilla JavaScript

IMPORTANT: some of the information in this article is out-of-date. Please read this update article instead. I generally use innerHTML to inject HTML into an element with vanilla JavaScript. Yesterday, one of my students asked me about the danger of cross-s

gomakethings.com

 

반응형