[CS스터디] XSS,CSRF 공격

Yewon Jeong·2023년 5월 21일
0

CS 스터디

목록 보기
4/19

XSS(Cross-Site Scripting) 공격이란

웹 사이트에 악성 스크립트를 삽입하여 개발자가 의도하지 않은 행동을 수행시키는 공격이다. XSS 공격은 일반적으로 애플리케이션 호스트 자체보다 사용자를 목표로 삼는다.
결과적으로 쿠키 및 세션정보가 탈취되어 개인정보와 같은 민감한 정보가 노출되거나, 악성 프로그램 다운로드, 웹 페이지 변조등의 피해를 사용자가 겪게 된다.
가장 널리 알려진 웹 보안 취약점 중 하나이다.

사용자의 입력을 받아 동적으로 HTML을 조작하는 웹 어플리케이션이 주요 공격 대상이 된다.


XSS 공격 방법

xss 공격에는 3가지 유형이 있다.

1. Stored XSS

서버 측 데이터베이스에 악성 스크립트가 저장된 후 해당 스크립트가 사용자에게 동적으로 제공되어 실행되는 공격. 게시판, 댓글 기능, 사용자 프로필, 채팅등의 인풋을 통해 악성 스크립트를 주입하고 업로드 한 뒤, 사용자가 해당 콘텐츠를 열면 악성스크립트가 실행되게 된다.

예를 들어 댓글창을 통해 onClick 이벤트가 발동되면 자바스크립트가 실행될수록 설정된 이미지 태그를 업로드 하고,

아래와 같이 이미지를 눌렀을때 자바스크립트가 실행되는 걸 볼 수 있다.

즉 이벤트가 정의된(onload onerror onclick) html 태그,
또는 javascript: 로 시작하는 링크등으로 xss 공격을 시도 할 수 있다.

<a href="javascript:alert('XSS')">XSS</a>
 <script>alert(document.cookie)</script>

+)위 케이스는 xss-game 이라는 사이트를 통해 체험해 볼 수 있다.
https://xss-game.appspot.com/

2. Relected XSS

Reflective XSS는 공격 코드를 사용자의 HTTP 요청에 삽입한 후, 해당 공격 코드를 서버 응답 내용에그대로 반사(Reflected)시켜 브라우저에서 실행하는 공격 기법이다.
공격자가 악성 스크립트를 포함한 링크를 생성하고 사용자가 해당 URL을 클릭하면 악성 스크립트가 실행되도록 한다.
만약

https://www.xssgame.com?query=<script>alert("개인정보 탈취")</script>

이런식으로 링크를 생성해서 사용자에게 메일을 보낸다거나 sns 매체에 노출시켜 사용자가 클릭을 하게 되는 경우 스크립트가 실행되게 된다.

해당 페이지에 검색 input이 있을 경우 검색 키워드로 스크립트를 삽입하여 보내고 서버가 응답으로 악성 스크립트를 포함한 html 문서를 보낸다면 사용자 브라우저가 해당 스크립트에서 실행되는 것이다.

<html>
<body>
"<script>alert(document.cookie)</script>"의 검색 결과: 
</body>
</html>

그런데 이 유형의 공격은 브라우저 자체에서 차단하는 경우가 많아서 공격을 성공시키기 어렵다.

3. DOM BASED XSS

DOM BASED XSS는 스크립트로 DOM 객체를 제어하는 과정에서 발생한다.
웹 페이지에 있는 사용자 입력값을 적절하게 처리하기 위한 자바스크립트의 검증 로직을 무효화하는 것을 목표로 한다.
url 뒤에 #javascript:alert('hello world'); 을붙여서 링크를 생성한뒤 클릭하면 alert 창이 띄워질 것이다.
(프래그먼트 식별자라고도 하는 # 뒤의 부분은 요청과 함께 서버로 전송되지 않는다.)

Relected XSS 공격과 유사해 보일 수 있는데 Reflected XSS는 서버의 악성 스크립트를 보낸 뒤 응답 페이지에 악성 스크립트가 포함되어 브라우저로 전달되면서 공격하는 것인 반면, DOM BASED XSS는 서버에 스크립트를 보내지 않고 DOM또는 문서 개체 모델을 수정하는 공격을 실행한다는 점에서 차이가 있다.

XSS 공격을 막는 법

XSS 방어 코드는 클라이언트측과 서버측에 모두 적용해야 한다. 1차적으로 클라이언트 측 코드로 검증하더라도 공격자가 중간에 변조할 수 있기 때문에 서버에서도 사용자 입력값을 최종적으로 검증해야 한다.

안전한 코딩 기법

  • 외부 입력값 또는 출력값에 스크립트가 삽입되지 못하도록 문자열 치환 함수를 사용하여 &<>*‘/() 등을

로 치환한다.

이렇게 인코딩하면 사용자는 <script><script>로 보이지만 HTML 문서에서는 &lt;script&gt; 로 나타나서 브라우저에서 일반 문자로 인식하고 스크립트로 해석되어 실행되지는 않는다.
  • HTML 태그를 허용해야 하는 게시판에서는 허용할 HTML
    태그들을 화이트리스트로 만들어 해당 태그만 지원하도록 한다.
  • 자바스크립트에서는 encodeURI(), encodeURIComponent() 함수를 통해 이스케이프 기능을 제공한다.
    외부로부터 받은 데이터를 사용해 DOM에 변화를 주는 코드가 실행되기 전에 입력값을 이스케이프 해주면 XSS 공격을 막을 수 있다. 이스케이프 후 decodeURI() 함수를 호출해야 스크립트 실행 방지와 함께 원하는 결과를 DOM에 출력할 수 있다.

안전하지 않은 코드 예시

<html>
<body>
 <script>
 const query = "<script>alert('hello world')<"+"/script>";
 async function req() {
 // 사용자가 에디터와 같은 입력 폼에 입력한 데이터를 서버에 저장
 const response = await fetch(`/vuln/search?q=${query}`, { method: 'GET' })
 const data = await response.text();
 // 외부로부터 받은 데이터(HTML 코드)를 아무런 검증 없이 DOM으로 기록
 document.write(data);
 }
 req();
 </script>
</body>
</html>

안전한 코드 예시

외부로부터 받은 데이터를 이스케이프 처리 후 사용

<html>
<body>
 <script>
 const query = "<script>alert('hello world')<"+"/script>";
 async function req() {
 const response = await fetch(`/vuln/search?q=${query}`, { method: 'GET' })
 const data = await response.text();
 // 외부로부터 받은 데이터를 이스케이프 처리 후 사용
 document.write(decodeURI(encodeURIComponent(data)));
 }
 req();
 </script>
</body>
</html>

encodeURIComponent 함수는 내장 함수로 사용이 쉽고 가볍다는 장점이 있지만, 모든 XSS 공격 패턴에
대응할 수는 없다. 따라서, XSS 검증을 위한 별도의 필터링 함수를 구현하거나, 개발자 커뮤니티에서 널리
사용되고, 안정적이며, 지속적으로 관리되고 있는 라이브러리를 사용해 문자열을 변환해야 한다.

<html>
 <head>
 <script src="https://cdn.rawgit.com/yahoo/xss-filters/master/dist/xss-filters.js"></script>
 </head>
 <body>
 <script>
 async function req() {
 ...
 // xss-filters 라이브러리를 사용해 문자열을 이스케이프 처리
 document.write(xssFilters.inHTMLData(data));
 }
 req();
 </script>
 </body>
</html>

React 사용 시

기본적으로, React DOM은 JSX 렌더링 전에 코드 내의 모든 값을 이스케이프 처리한다. 다시 말해서, 리액트 애플리케이션 내에서 명시적으로 악성 스크립트를 주입하는 것은 불가능하다. ReactJS에서 강제하는 규칙을
따르는 것만으로도 클라이언트측 XSS 공격을 방어할 수 있다.
하지만 ReactJS는 HTML 입력값을 그대로 렌더링할 수 있는 dangerouslySetInnerHTML 함수를 제공한다.
만약 개발자가 사용자 게시글 표현 등을 위해 이 함수를 사용할 경우 XSS 공격에 노출될 수 있다.

function possibleXSS() {
 return {
 __html:
 '<img
 src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg“
 onload="alert(1)">
 </img>',
 };
 }

const App = ( ) => (
 // XSS에 취약한 함수를 사용해 HTML 코드 데이터를 렌더링
 <div dangerouslySetInnerHTML={possibleXSS()} />
);
ReactDOM.render(<App />, document.getElementById("root"));

가급적이면 dangerouslySetInnerHTML 함수를 사용하지 않는 것이 좋겠지만, 서비스 개발을 위해 부득이하게
사용이 필요한 경우 dompurify와 같이 이스케이프 기능을 제공하는 라이브러리를 사용해 code sanitizing 후 사용해야 한다. (code sanitizing -> 코드를 미리 선 파싱 해서 위험요소들을 모두 제거한 후 렌더링하는 것)

<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.4.0/purify.min.js"></script>
...
function possibleXSS() {
 return {
 __html:
 // dompurify 라이브러리를 사용해 입력값을 이스케이프 처리
 DOMPurify.sanitize('<img src="https://upload.wikimedia.org/wikipedia/commons/
 a/a7/React-icon.svg" onload="alert(1)"></img>'),
 };
}
const App = ( ) => (
 <div dangerouslySetInnerHTML={possibleXSS()} />
);
ReactDOM.render(<App />, document.getElementById("root"));
DOMPurify.sanitize('<img src=x onerror=alert(1)//>'); // becomes <img src="x">
DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // becomes <svg><g></g></svg>
DOMPurify.sanitize('<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>'); // becomes <p>abc</p>
DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math><mi></mi></math>
DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // becomes <table><tbody><tr><td>HELLO</td></tr></tbody></table>
DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><li><a href="//google.com">click</a></li></ul>

CSRF(Cross-Site Request Forgery)

사이트 간 요청 위조.사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격. 사용자의도와 무관하게 서버를 공격하게 된다.

공격 과정

  1. 사용자는 웹 애플리케이션에 로그인하여 인증을 받는다.
  2. 공격자는 사용자가 접속한 악성 웹사이트나 피싱 이메일 등을 통해 사용자의 브라우저에서 악성 코드를 실행한다.
  3. 악성 코드에 의해 자동으로 서버에 악성 요청을 전송.
  4. 악성 요청은 사용자의 인증 정보를 포함하며, 서버는 이 요청이 유효한 것으로 인식하여 사용자의 권한으로 요청을 처리.

안전한 코딩기법

클라이언트측에서 렌더링을 처리하는 경우 서버 기능 호출 전 서버로부터 CSRF 토큰(서버는 이 토큰이 헤더에 포함되어 있는 경우에만 DB의 수정을 허가한다.)을 받아와 요청 헤더 내에 삽입해야 한다.

const App = () => {
 const getData = async () => {
 // 서버에 기능 호출 전 먼저 CSRF 토큰을 받아와 헤더에 저장
 const response = await axios.get('getCSRFToken');
 axios.defaults.headers.post['X-CSRF-Token'] = response.data.csrfToken;
 // CSRF 토큰이 설정된 상태에서 서버 기능 호출
 const res = await axios.post('api/patched', {
 username: ‘hello_user’,
 useremail: ‘test@email.com’,
 });
 document.write(res.data);
 };
 React.useEffect(() => { getData(); }, []);
 return <div>react-test</div>;
};
ReactDOM.render(<App />, document.getElementById("root"));

JWT 토큰 저장 위치

jwt 토큰을 LocalStorage에 저장했울 경우 XSS에 취약하다.반면
Cookie는 HttpOnly옵션을 통해 HTTP 통신외에는 Cookie 접근이 불가능 하기 때문에 Script에서 Cookie를 읽을 수 없게 한다. 이로 인해 XSS공격을 예방할 수 있다.

CSRF 공격에 대해서는 위에 언급한 대로 서버 요청 전에 csrf 토큰을 받아와 헤더에 삽입하고 서버에서 이 토큰을 확인하고 db에 접근하는 방식으로 예방할 수 있다.(CSRF는 기본적으로 브라우저에서 자동으로 보내주는 Cookie를 통해 공격이 이루어진다, jwt token을 Cookie가 아닌 Header 넣고 요청을 보내는 방법을 모색해 볼 수 있는데 이 과정에서 HttpOnly 옵션을 해제해야 하기 때문에 좋은 방법은 아닌 것 같다.)

추가적으로 Cookie Referer Check을 통해 요청의 출처 Domain이 서버가 허용한 Domain인지 체크하는 과정을 도입할 수 있다. 일반적으로 Referer Check로만 대부분의 CSRF를 방어할 수 있다고 한다.

결과적으로 jwt 토큰은 쿠키에 저장하는 것이 더 안전하다고 생각한다.


참고
https://4rgos.tistory.com/1
https://www.leafcats.com/42
https://xss-game.appspot.com/level1
https://mslilsunshine.tistory.com/21
https://swk3169.tistory.com/23
https://berr-my.tistory.com/entry/XSS-%EA%B3%B5%EA%B2%A9
https://www.isac.or.kr/upload/JavaScript_%EC%8B%9C%ED%81%90%EC%96%B4%EC%BD%94%EB%94%A9_%EA%B0%80%EC%9D%B4%EB%93%9C2022%EB%85%84.pdf
https://www.ibm.com/docs/ko/sva/10.0.4?topic=configuration-prevention-cross-site-request-forgery-csrf-attacks
https://cjw-awdsd.tistory.com/48

profile
일단 하는 중

0개의 댓글