공부하면서 기록한 내용입니다. 틀린 내용이 있을 수 있습니다.
[ XSS란? ]
XSS는 공격자가 악의적인 스크립트를 주입하여, 해당 화면을 보는 사용자들에게 비정상적인 기능을 수행하게 하는 것
[ 왜 일어나는가? ]
<script class="xss">
const cookie = document.cookie;
// cookie 요청 api 호출
// cookie 전송!
</script>
이런 스크립트가 본문에 주입되면, 다른 사용자들이 본문을 볼 경우, 해당 스크립트가 실행되어 나도 모르는 사이에 나의 쿠키 정보가 외부로 유출될 수 있고, 탈취자가 마음대로 나의 정보를 사용할 수 있게 되는 것이다.
html에서 <
기호는, tag를 open하는 역할을 하고, 따라서 적절한 태그가 있다면, element로서 역할을 하게 된다. (파싱 시 그렇게 파싱이 된다)
예를 들어,
<div>I'm Really Happy >< </div>
I'm Really Happy ><
라는 문자가 div 태그 내에 있을 때, html 파싱 에러가 발생하는 것을 확인할 수 있다. 태그를 열고 닫는 기호가 존재하고, 잘못 파싱되고 있기 때문이다.
[ 예방 방법 ]
악의적인 스크립트 예방을 방지하기 위해서는, 개발자가 의도하거나 개발한 부분 외적인 부분에서 의도하지 않은 동작이 수행되지 않도록 하면 된다.
escape 방법은 프론트엔드단에서 동작에 영향을 미칠 수 있는 문자열들을 다른 문자로 대체하여, 스크립트가 실행되지 않도록 하는 것이다. <
를 <
, >
를 >
로 변환하여 표현하면, 화면에서는 정상적으로 표현된다.
<!-- in the code -->
<body>
<h1><></h1>
</body>
화면에서는 이렇게 표현된다.
또한, 다양한 특수 기호들을 치환하여, XSS 공격을 예방할 수 있다.
< = <
> = >
공백 =
& = &
" = "
...
이러한 처리는 사용자의 글을 서버에서 문자열을 치환하여 저장하거나, 아니면 프론트엔드 단에서 문자열을 치환하여 사용자에게 보여주는 방식으로 산다.
[ In React ]
React에서는 이러한 XSS 공격에 대해서 자유롭다.
우선, React는 JSX 문법을 사용하여 컴포넌트를 작성한다. 작성하게 되면 다음과 같은 모양이 된다.
const Component = () => {
return <div className="component">JSX Element</div> // JSX
}
이는 겉보기에는 html과 동일한 모양을 하고 있지만, babel 등 트랜스파일러를 통해 다음과 같은 모양으로 트랜스파일 되고, 브라우저가 읽는다.
React.CreateElement(Component, { className: 'component' }, "JSX Element")
ReactDOM은 jsx에 삽입된 모든 값을 렌더링하기 전에 escape 된다. 따라서 스크립트 형식의 데이터가 들어오더라도, 자동으로 escape 처리가 되어 스크립트의 역할을 수행할 수 없다. 앱에 명시적으로 작성하지 않은 부분은 스크립트로 실행되지 않으므로, 리액트를 사용함에 있어서 해당 부분을 걱정하지 않아도 된다.
다만, dangerouslySetInnerHTML
프로퍼티를 이용하여 html을 주입할 수는 있다.
다른 사이트에서 사용자의 요청을 조작하는 공격 방식
생성된 요청이 사용자의 동의를 받았는지 확인할 수 없는 웹 애플리케이션의 CSRF의 취약점을 이용한다. XSS는 사용자의 정보를 탈취하는 것에 가까운 공격이라면, CSRF는 이미 인증된 사용자의 요청을 조작하여 무단으로 진행하기 위한 목적으로 사용되는 것에 가깝다. 그렇다고 CSRF 공격이 사용자의 데이터가 노출되지 않는다는 뜻은 아니다.
CSRF 공격은 데이터의 값을 변경하는 요청을 대상으로 한다. 이러한 요청에는 제품 구입, 계정 설정, 기록 삭제, 비밀번호 변경, 문자 전송 등이 있다. 공격자는 자금 송금이나 로그인 정보 변경 등 원하는 요청을 위조한 뒤, 이메일이나 웹사이트에 요청이 삽입된 하이퍼링크를 심어 놓는다. 사용자가 해당 하이퍼링크를 클릭하면 요청이 자동으로 전송된다. 심지어는 공격자가 전체 서버 접근 권한을 탈취해 웹 애플리케이션과 API 등 서비스 전체를 마음대로 통제하게 될 위험이 있다.
따라서 XSS 공격과는 달리, CSRF는 인증된 사용자 정보가 필요하다.
[ CSRF 공격 예방법 ]
CSRF 토큰 사용하기
사용자의 세션에 임의의 난수 값을 저장하고, 사용자의 요청시 해당 값을 포함하여 전송시킨다. 백엔드 단에서는 요청을 받을 때 세션에 저장된 토큰값과 요청 파라미터로 전달받는 토큰 값이 일치하는 지 검증 과정을 거치는 방법이다.
[ 사용자의 정보를 storage에 저장할까, 아니면 쿠키에 저장할까? ]
특징 | 취약한 공격 | |
---|---|---|
Cookie | key, value쌍으로 이루어져 있는 문자열 형태의 자료 구조. 클라이언트 쿠키 저장소에 저장됨 | CSRF |
Storage | key, value 쌍을 이루어, key 값을 통해 value를 참조할 수 있는 저장소 | XSS |
MDN 공식 문서에서는 정보를 저장할 때 Storage를 사용하는 것을 권장하고 있다. XSS 공격 같은 경우는 서버에서 저장 시 신경 써서 저장하면 escape 처리가 가능하고, React를 사용하는 경우(React 뿐만 아니라 아마 프론트엔드 라이브러리, 프레임워크라면 모두 지원하지 않을까 예상한다(안 찾아봤음).) 기본적으로 escape를 지원하기 때문에 XSS 공격의 위협으로부터 상대적으로 안전하다.
cookie 기반의 인증인 경우는, REST API를 요청할 때, withCredential 조건 하에 자동으로 정보가 담겨져서 요청이 이루어진다. 따라서 CSRF 공격에 의해, 버튼 클릭 하나만으로도 모든 쿠키 정보가 요청 시 담겨지게 되므로 이를 방어하기 위한 추가적인 조치가 필요하다고 할 수 있다.
반면에 storage에 토큰을 저장하는 경우, 일반적으로 ajax를 이용하여 클라이언트와 백엔드가 데이터 통신을 하게 된다. POST, PUT, DELETE 메서드를 이용하여 ajax 통신을 할 때, 일반적으로 토큰을 authorization header에 담아서 사용자를 인증하는 과정을 거친다. 이 부분까지 위조 하기 위해서는 XSS 공격이 추가적으로 필요한데, React 환경 상에서는 이 부분을 공격하는 것은 쉽지 않다.
[React.js] JSX와 XSS(Cross Site Scripting) 공격