이 글은 아래 원문을 번역한 글로 의역이 있을 수 있습니다. 정확한 의미를 파악하고 싶으신 분은 원문을 참고해주시기 바랍니다.
원문: https://www.stackhawk.com/blog/react-xss-guide-examples-and-prevention/
저작권 정보:
XSS(cross-site 스크립팅)가 무엇인지와 React 응용 프로그램에 대한 XSS 공격을 방지하는 방법을 이해합니다.
웹은 수년간 기술, 프레임워크, 복잡성 및 유틸리티 측면에서 크게 성장해 왔습니다.오늘날, 10억 명 이상의 사람들이 매일 수천 개의 웹사이트를 방문합니다.그 결과 인터넷에는 항상 사용자 자격 증명, 신용카드 상세 내역 등의 민감한 데이터가 넘쳐납니다.
따라서 개발자는 해커가 사용자의 데이터를 오용하기 위해 악용할 수 있는 일반적인 취약성을 알고 있어야 합니다.이러한 취약성 중 하나가 크로스 사이트 스크립팅(XSS)입니다.이 글에서는 XSS의 정의와 XSS가 사용자에게 미치는 영향에 대해 설명합니다.또한 React가 XSS 공격으로부터 앱을 얼마나 보호하고 React 응용 프로그램을 XSS에 대한 면역성을 갖게 하는 방법에 대해서도 설명합니다.
XSS 공격:개요
JavaScript는 모든 응용 프로그램의 클라이언트 측 중심에 있습니다.이는 응용 프로그램의 프런트엔드가 브라우저에서 실행되는 JavaScript 코드와 거의 비슷하기 때문입니다.
이를 설명하기 위해 온라인 거래의 간단한 예를 들어 보겠습니다.웹 사이트에서 트랜잭션을 수행하면 입력 필드에서 자격 증명을 가져와 처리하는 JavaScript가 실행됩니다.그러나 개발자는 추가 JavaScript를 쉽게 실행하여 해당 정보에 유해한 작업을 수행할 수 있습니다.
그게 바로 XSS입니다.공격자는 응용 프로그램의 취약성을 이용하여 일부 악의적인 스크립트를 사용자의 브라우저에 주입하여 XSS 공격을 수행할 수 있습니다.이것으로 XSS 공격이 무엇인지 알게 되었습니다.이제 예를 들어 XSS 공격이 어떻게 발생할 수 있는지 알아보겠습니다.
XSS 공격은 어떻게 발생합니까?
가장 일반적인 유형의 XSS 공격은 DOM 기반의 XSS 공격입니다.DOM을 직접 변환하면 공격자가 악의적인 JavaScript를 포함하는 데이터를 DOM에 쉽게 주입할 수 있습니다.
다음 HTML 코드를 고려합니다.빈 <div> 요소를 사용하여 기본적인 마크업을 렌더링할 뿐입니다.
<html>
<body>
<div id="validation">
</div>
<input placeholder="Enter your referral code below" />
<button>Submit</button>
</body>
</html>
상기의 코드는, Submit 버튼이 있는 페이지에 <input> 요소를 렌더링 합니다. Submit 버튼을 누르면 함수가 기동됩니다.함수 내에서 사용자가 입력한 내용을 평가합니다.그런 다음 빈 <div> 요소 내부의 결과에 따라 사용자에게 피드백 메시지를 제공합니다.
const validationElement=document.getElementById('validation');
const validationMessage=`Oops! This seems like an invalid referral code.`
validationElement.append(validationMessage);
append 메서드를 사용하여 빈 <div> 요소 내에서 메시지를 렌더링합니다.그러나 이로 인해 응용 프로그램의 취약성이 노출됩니다.다음 JavaScript 코드를 고려합니다.
Oops! This seems like an invalid referral code.
<script>
...
alert('Congrats! You've won a prize');
...
</script>
공격자는 기본적으로 검증 메시지를 악의적인 <script>와 함께 렌더링합니다.이는 어플리케이션이 <div>의 append() 메서드를 사용하여 직접 DOM을 수정했기 때문에 가능했습니다.공격자는 <script> 내에서 사용자의 기밀 및 기밀 정보를 도용하는 코드를 작성할 수 있습니다.비슷한 이유로, innerHTML을 통해 DOM을 직접 변환하면 응용 프로그램이 잠재적인 XSS 공격에 노출됩니다.
따라서 XSS 공격은 사용자에게 놀라움을 줄 수 있습니다.그러나 프런트엔드 프레임워크는 큰 발전을 이루었으며 이러한 공격에 대한 즉각적인 보호를 제공합니다.React가 이러한 상황에 어떻게 대처하고 XSS 공격으로부터 애플리케이션을 얼마나 보호하는지에 대해 알아보겠습니다.
React XSS는 완전무결(Foolproof)합니까?
다행히 React는 XSS 공격으로부터 애플리케이션을 보호하기 위해 몇 가지 작업을 수행합니다.React의 이전 섹션의 코드를 다음과 같이 다시 작성합니다.
import './App.css';
import {useState} from 'react';
function App() {
const [validationMessage,setValidationMessage]=useState('');
const validateMessage=async()=>{
setTimeout(()=>{
setValidationMessage('Invalid referral code')
},1000)
}
return (
<div className="App">
<input placeholder="Enter your referral code"/>
<button onClick={validateMessage}>Submit</button>
<div>
{validationMessage}
</div>
</div>
);
}
export default App;
이전과 마찬가지로 validationMessage 함수를 기동하는 <input> 요소가 있습니다.setTimeout을 사용하여 validationMessage 함수 내에서 설정한 상태 검증 메시지를 만들었습니다.마지막으로 JSX를 사용하여 빈 <div> 요소 내에 validationMessage 를 출력합니다.
<div>
{validationMessage}
</div>
React는 자동 이스케이프를 사용하여 출력 요소 및 내부의 데이터를 출력합니다.validationMessage 내의 모든 것을 문자열로 해석하고 추가 HTML 요소를 렌더링하지 않습니다.즉, validationMessage 에 어떤 <script>태그가 침입한 경우 React는 이를 무시하고 문자열로 렌더링합니다.
이를 설명하기 위해 다음과 같이 validationMessage 메서드를 약간 변경합니다.
const validateMessage=async()=>{
setTimeout(()=>{
setValidationMessage(`Invalid referral code, <script></script>`)
},1000)
}
지금 체크하면 <script> 태그는 DOM 요소가 아닌 문자열로 렌더링됩니다.
![](https://blog.kakaocdn.net/dn/dAvAuV/btrIdeiDMNh/xWJHEwDDROuPcwQK7xwhI0/img.png)
이제 <script> 태그로 둘러싸인 JavaScript는 실행되지 않습니다. 따라서 위의 동작은 DOM 기반 XSS 공격을 실행하려는 공격자로부터 응용 프로그램을 보호합니다.리액트의 공식 문서에서도 이 점에 대해 언급하고 있습니다.따라서 JSX를 사용하여 일부 콘텐츠 또는 데이터를 조건부로 DOM에 출력하면 XSS 공격으로부터 보호할 수 있습니다.
그렇다면 React 애플리케이션이 모든 종류의 XSS 공격으로부터 안전하다는 의미입니까?JSX를 사용하여 요소 또는 문자열을 출력하는 사용 사례만 검토했습니다.실제로 HTML 요소를 JSX 내부에서 직접 DOM에 렌더링해야 한다면 어떻게 해야 할까요?
리액트에서 HTML 요소를 동적으로 렌더링
HTML 요소를 직접 렌더링하는 가장 일반적인 사용 사례는 블로그 애플리케이션입니다.일반적인 블로그 어플리케이션에서는 HTML 요소의 조합으로 블로그를 수신합니다.이러한 요소는 블로그의 내용을 래핑하여 형식을 유지합니다.
서버에서 블로그를 가져와 DOM에서 렌더링하는 작은 React 구성 요소가 있다고 가정합니다.다음 코드를 살펴봅시다.
import './App.css';
function App() {
const blog=`
<h3>This is a blog title </h3>
<p>This is some blog text. There could be <b>bold</b> elements as well as <i>italic</i> elements here! <p>
`
return (
<div className="App">
<div>
{blog}
</div>
</div>
);
}
export default App;
컴포넌트 안에는 적절한 HTML 요소로 포장된 블로그의 내용을 저장하는 블로그 변수가 있습니다.JSX 내에서 직접 블로그 변수를 출력하면 문자열로 해석됩니다.
DOM 기반 XSS 공격으로부터 애플리케이션을 보호하지만 사용자의 경험을 망칩니다.따라서 블로그를 문자열로 렌더링하는 대신 마크업으로 렌더링해야 합니다.그러면 콘텐츠가 전용 HTML 태그와 함께 렌더링됩니다.
React를 사용하면 dangerouslySetInnerHTML라는 prop을 사용하여 이를 수행할 수 있습니다. 이 prop을 일반 컨테이너 요소에 전달할 수 있습니다.컨테이너 내에서 렌더링할 HTML 마크업이 값인 키가 _html인 객체를 가져옵니다.
<div className="App">
<div dangerouslySetInnerHTML={{__html:blog}}>
</div>
</div>
지금 다시 확인하면 블로그의 형식이 의도된대로 보여지는 것을 볼 수 있습니다.
블로그 변수에 포함된 모든 HTML 요소가 DOM에 올바르게 렌더링됩니다.하지만, 이것은 우리를 원점으로 돌려보낸다!응용 프로그램에는 XSS 취약성이 있으며 공격자는 블로그 변수 내에 일부 악의적인 스크립트를 주입할 수 있습니다.
실제로 dangerouslySetInnerHTML prop에는 의도적으로 "dangerous"이라는 단어가 포함되어 있어 사용할 때 주의해야 한다는 것을 알려줍니다.리액트의 공식 문서에서도 이 점에 대해 언급하고 있습니다.
React에서 데이터 삭제
DOM 기반 XSS 공격으로부터 응용 프로그램을 보호하려면 DOM에서 렌더링하기 전에 HTML 요소가 포함된 데이터를 삭제해야 합니다.사용할 수 있는 라이브러리가 많이 있습니다.그러한 라이브러리 중 하나가 DOMPurify입니다.React 어플리케이션에서 어떻게 사용할 수 있는지 알아보겠습니다.
먼저 다음 명령을 실행하여 React 응용 프로그램에 DOMPurify를 설치합니다.
npm i dompurify
이것을 사용하려면 , 상단의 라이브러리에서 다음과 같이 DOMPurify 를 Import 합니다.
import DOMPurify from 'dompurify';
blog의 sanitized 버전을 포함하는 새로운 변수 sanitizedBlog를 만듭니다.
const sanitizedBlog=DOMPurify.sanitize(blog)
마지막으로 blog내의 dangerouslySetInnerHTML prop대신 sanitizedBlog를 사용할 수 있습니다.
<div className="App">
<div dangerouslySetInnerHTML={{__html: sanitizedBlog}}>
</div>
</div>
모든 것이 동일하게 동작하지만 sanitizedBlog는 악의적인 XSS 주입으로부터 보호됩니다.sanitize-html-react 같은 다른 라이브러리도 있습니다.여기서 테스트해 보거나 DOMPurify의 구조에 대해 자세히 알아볼 수 있습니다.
리액트에서의 Escape Hatches가 XSS 공격을 일으킬 수 있다
React 어플리케이션의 DOM 요소에 대한 참조가 필요한 경우가 많습니다.React는 findDOMName 및 createRef를 이스케이프 해치로 제공합니다.이러한 메서드는 DOM 요소를 직접 참조합니다.다음 코드를 고려합니다.
import './App.css';
import {useEffect, createRef} from 'react';
function App() {
const divRef=createRef();
const data="lorem ipsum just some random text"
useEffect(()=>{
divRef.current.innerHTML="After rendering, this will display"
},[])
return (
<div className="App">
<div className="container" ref={divRef}>
{data}
</div>
</div>
);
}
export default App;
divRef라는 ref를 가진 단순한 <div>가 있습니다. 컴포넌트의 DOM이 로드되면 이 <div> 내의 내용을 innerHTML를 사용하여 변경합니다.공격자는 useEffect 내 <div> 의 innerHTML을 오버라이드하여 일부 악의적인 스크립트를 쉽게 주입할 수 있습니다.
요령은 간단하다.innerHTML을 사용하여 DOM을 변환하지 마라! 이것은, DOM 를 직접 변경하는 경우와 같은 상황입니다.ref를 사용하여 HTML 요소 내부에 일부 내용을 추가하는 경우 대신 innerText를 사용하십시오.
useEffect(()=>{
divRef.current.innerText="After rendering, this will display"
},[])
이제 공격자가 divRef를 통해 일부 <script> 태그를 삽입할 수 있어도 응용 프로그램에서 문자열로 렌더링됩니다.이런 패턴은 드물기 때문에 항상 참조를 사용하여 직접 DOM을 변환하지 않도록 해야 합니다.
요점
React 애플리케이션을 XSS로부터 보호하는 것은 한 번의 작업으로 끝나지 않습니다.XSS 공격으로부터 React 애플리케이션을 보호하는 가장 좋은 방법은 코드베이스에서 초기에 예측하는 것입니다.그런 다음 응용 프로그램의 규칙 또는 코딩 지침을 정의할 수 있습니다.팀의 모든 개발자는 이 가이드라인에 따라 작성한 코드가 XSS나 기타 취약성에 노출되지 않도록 할 수 있습니다.
어플리케이션에서 XSS를 방지하는 방법은 다음과 같은 방법입니다.
- 서버 또는 서드파티API에서 어플리케이션으로 유입되는 모든 데이터를 검증합니다.이것에 의해, 애플리케이션이 XSS 공격에 대응하거나, 경우에 따라서는 XSS 공격을 막을 수도 있습니다.
- DOM을 직접 변환하지 마십시오.다른 내용을 렌더링해야 할 경우 innerHTML 대신 innerText를 사용합니다. react에서 findDOMName이나 createRef 등의 이스케이프 해치를 사용할 때는 매우 주의해야 합니다.
- 항상 JSX를 통해 데이터를 렌더링하고 React가 보안 문제를 처리하도록 하십시오.
- 특정 사용 사례에서만 dangerouslySetInnerHTML 을 사용하세요.이를 사용할때 DOM에서 렌더링하기 전에 모든 데이터를 삭제해야 합니다.
- 자체 검사 기술을 쓰는 것을 피하십시오.그 자체로 전문지식을 필요로 하는 별개의 과목이다.
- 적절한 라이브러리를 사용하여 데이터를 삭제합니다.몇 가지 방법이 있지만, 각 사용 사례에 대한 장단점을 비교한 후 사용 사례를 진행해야 합니다.
리액트 애플리케이션을 빌드할 때 위의 사항을 코딩 지침으로 사용할 수 있습니다.
결론
개발자로서 안전하고 안전한 애플리케이션을 구축하는 방법을 배워야 합니다. XSS는 오랫동안 존재했던 가장 일반적인 응용 프로그램 취약성 중 하나입니다. DOM 기반의 XSS 공격에 대해 설명했습니다만, 그 밖에도 마찬가지로 중요하고 위험한 것이 있습니다. XSS에 대해 자세히 알아보려면 다음과 같은 유용한 가이드를 참조하십시오.
이 글은 Siddhant Varma에 의해 작성되었습니다. 시드한트는 프런트 엔드 엔지니어링에 관한 전문 지식을 가진 풀 스택 JavaScript 개발자입니다.그는 인도에서 여러 스타트업을 확장한 경험이 있으며 Ed-Tech 및 의료 산업에서 제품을 제조한 경험이 있습니다.시드한트는 가르치는 것에 대한 열정과 글쓰기에 대한 재주가 있다.그는 또한 많은 졸업생들에게 프로그래밍을 가르쳤으며, 그들이 더 나은 미래 개발자가 될 수 있도록 도왔다.