XSS와 CSRF에 대해 알아보자 ❕
1. XSS (Cross-Site Scripting)
해커가 웹페이지에 악성 스크립트를 삽입하고, 사용자가 이를 실행하도록 하는 형태의 공격 기법
XSS(크로스 사이트 스크립팅)는 공격자가 사용자 브라우저에 악성 스크립트를 주입시켜 사용자의 세션을 가로채거나 사용자가 원치 않는 동작을 하도록 만드는 공격을 말한다.
해커는 악성 스크립트를 실행한 사용자의 개인정보 및 쿠키정보를 탈취하고 악성코드 감염, 웹페이지 변조 등의 공격을 한다. XSS 공격은 주로 스크립트 언어와 취약한 코드를 대상으로, 웹 애플리케이션에서 사용자로부터 입력 받은 값을 제대로 검사하지 않아 발생되며 공격 대상이 주로 사용자이다.
대표적인 공격 방식
- Stored XSS (Persistent XSS)
- 악성 스크립트가 서버에 저장되었다가 사용자들이 페이지를 열 때 실행되는 방식
- 서버에 저장되어 지속적으로 공격하기 때문에 많은 피해가 발생할 수 있다.
- 서버 DB에 해당 악성 스크립트가 저장되면, 게시물을 열람한 모든 사용자들에게 공격이 가해진다.
- Reflected XSS
- 악성 스크립트가 포함된 요청이 서버로 전송되었다가 응답되면서 브라우저에서 실행되는 방식
- 사용자에게 입력받은 값을 서버에서 되돌려주는 곳에서 발생하며, 사용자가 버튼 또는 링크를 클릭함으로써 악성 스크립트가 실행되고 공격이 가해진다.
- DOM Based XSS
- 클라이언트 측에서 발생하며, 웹 페이지의 DOM을 조작하여 악성 스크립트를 실행하는 방식
- 악의적인 스크립트로 인해 클라이언트 측 코드가 원래 의도와는 다르게 실행된다.
예시
공격자가 게시물에 악의적으로 <script>alert(document.cookie)</script>
같은 스크립트를 넣어 업로드했다면, 사용자들이 해당 게시물에 접근할 때마다 쿠키 정보를 출력하는 alert창이 뜨게 된다.
대응 방안
- 사용자의 개인 정보는 쿠키 대신 서버에 저장한다.
- 쿠키에 직접 접근하는 것을 방지하기 위해 `httpOnly` 옵션을 설정한다.
- 정보를 암호화한다.
- 입력값에서
<
,>
등을 사용해 태그를 조작할 수 없도록 한다. (<
,>
같은 문자열로 변환)
(React DOM은 입력된 값을 렌더링하기 전에 이스케이프 하므로, XSS 공격을 방지한다. 하지만 이런 방식 외에도 XSS 공격을 가할 수 있는 방법은 다양하기 때문에 입력값을 제한하거나 XSS 대응 방안을 계속해서 생각해야 한다.)
2. CSRF (Cross-Site Request Forgery)
해커가 인증된 사용자의 권한을 탈취해 사용자가 의도하지 않은 행위를 하도록 만드는 공격 기법
사용자가 웹사이트에 로그인한 상태(특정 사이트에 권한이 있는 상태)에서 CSRF 공격 코드가 삽입된 위조 페이지를 열어 의도하지 않은 요청을 하게 된다. 웹사이트는 위조된 요청이 믿을 수 있는 사용자로부터 발송된 것이라 알고 해당 요청을 실행한다.
예시
공격자가 A사이트에 CSRF 공격 코드가 삽입된 게시물을 등록했을 때, A사이트에 로그인한 사용자가 해당 게시물을 열람한다면 공격자가 의도한 공격이 실행된다.
만약 <img src=”http://test.com/logout” />
가 포함되어있다면 이미지는 GET 요청을 실행하므로 사용자를 강제로 로그아웃시키고, 특정 버튼을 클릭하게 유도해서 사용자의 비밀번호를 변경하는 요청까지도 실행시킬 수 있다.
또, 사용자가 A서버에 인증된 상태에서 CSRF 공격 코드를 실행했을 때 다른 웹 사이트로 이동되고, 그 사이트에서 사용자가 의도하지 않은 요청을 A서버에 보내는 방법도 있다.
대응 방안
- *Referer 체크
- 어떤 도메인에서 요청이 왔는지 확인하는 방법
- CSRF 토큰 검증
- 사용자 세션에 임의의 난수 값을 저장하고 사용자의 요청마다 해당 난수 값을 포함시켜 전송시키면서, 서버에서 발급한 난수 값이 맞는지 검증하는 방법
- 쿠키 SameSite 옵션 설정
- 크로스 사이트 요청 시 쿠키가 전송되지 않도록 설정
- 추가 인증 수단
- CAPTCHA (기계는 인식할 수 없으나 사람은 쉽게 인식할 수 있는 텍스트, 이미지를 통해 사람과 기계를 구별하는 프로그램)
* Referer ?
Referer은 HTTP 헤더 중 하나로, 현재 페이지를 요청한 이전 페이지의 URI 정보를 표시한다.
웹 서버는 이를 확인해 해당 요청이 어디에서 왔는지 알 수 있고 로그 분석이나 CSRF 방어에 활용이 가능하다. ^하지만 Referer 헤더는 클라이언트에서 수정이 가능하므로 참고 정보로만 사용하는 것이 좋다.^
구글에서 네이버를 검색해 클릭해 들어간 후 document.referrer
을 확인하면 다음과 같이 https://www.google.com
인 것을 확인할 수 있다.
참고로, Http 리퍼러를 정의한 RFC에서 referrer를 referer라고 잘못 친 것에서 기인하여 HTTP 리퍼러는 HTTP referer라고 불린다. - 위키백과
XSS와 비교
XSS는 사용자가 접속 사이트를 신뢰한다는 점을 이용해 공격하는 것이라면, CSRF는 웹 사이트가 클라이언트를 신뢰한다는 점을 이용해 공격하는 것이다. 따라서 XSS는 클라이언트에서 공격이 발생되고, CSRF는 서버에서 공격이 발생된다.
3. CSRF 토큰 검증
CSRF 공격을 막으려면 사용자가 한 요청이 본인이 한 것이 맞다는 것을 검증하는 과정이 필요하다. 이때 CSRF 토큰을 사용하는 방법이 있다.
나는 본 요청을 보내기 전에 CSRF 토큰을 검증하는 요청을 먼저하고, 그 다음 본 요청을 실행하는 방법으로 설계했다.
const postForm = async (data) => {
await getCsrfToken();
await postForm(data);
}
이런식으로 본 요청인 `postForm()` 요청 전, CSRF 토큰 요청`getCsrfToken()`을 먼저한다.
흐름
- 토큰 생성
- 클라이언트는 GET 요청을 제외한 요청에서, 본 요청을 보내기 전에 CSRF 토큰 생성 API를 먼저 요청한다.
- CSRF 토큰 생성 API에서는 랜덤값 csrf token을 생성해 HTTP 헤더에 담고, csrf token 값으로 인코딩한 csrf secret 토큰을 생성해 쿠키에 담는다. (csrf token을 HTTP 헤더가 아닌 응답값으로 전달해, form의 숨겨진 필드에 설정하는 방법도 있다)
- 토큰 전송
- 서버는 생성된 csrf token, csrf secret 토큰을 클라이언트에게 전송한다.
- 클라이언트는 본 요청을 보낼 때, 헤더에는 csrf token을, 쿠키에는 csrf secret을 담아 전송한다.
- 토큰 검증
- 서버는 요청을 받을 때마다 HTTP 헤더에 담긴 csrf token을 이용해 csrf secret을 검증한다.
- csrf secret이 유효하면 요청을 처리하고, 그렇지 않다면 요청을 거부한다.
+ 토큰 생성 시 주의할 점
- csrf 토큰 생성 요청 후 바로 본 요청을 실행하기 때문에, csrf token과 csrf secret의 유효기간을 아주 짧게 잡아줘 토큰을 재사용하지 못하도록 해야 한다.
- csrf secret 토큰을 담은 쿠키는 크로스 사이트에서 전송이 불가능하게 SameSite 설정하고, 브라우저에서 읽지 못하게 HttpOnly을 설정해준다.
+ Referer과 Csrf 토큰 검증 방식을 사용했을 때, 공격을 어떻게 막을 수 있다는 걸까?
Case1 : 악의적인 게시물을 업로드해, 사용자들이 해당 버튼을 클릭했을 때 악의적인 요청을 보내는 경우.
-> CSRF 토큰 요청 API가 요청되지 않았으므로, csrf token과 csrf secret이 없어 요청이 거부된다.
Case2 : 인증된 사용자를 공격 사이트로 이동시킨 뒤 내 서버 API를 요청해 악의적인 요청을 하는 경우.
-> Referer 체크로 요청 사이트를 제한할 수 있고, 또 해당 사이트의 요청에서는 쿠키에 SameSite 설정한 csrf secret이 포함되지 않으므로 공격을 막을 수 있다. 게다가 CSRF 토큰 요청 API가 요청되지 않았으므로, csrf token과 csrf secret이 없어 요청이 거부된다.
=> 개발자가 작성한 요청 코드에 대해서만 CSRF 토큰 요청 API가 요청되고, 위조 사이트에서 쿠키를 읽지 못하므로 CSRF 공격을 방어할 수 있다. 또한 서버에서 CSRF 토큰을 위한 추가 메모리나 저장공간을 사용하지 않으므로 효율적으로 검증할 수 있다.
4. 마무리
처음 CSRF 토큰 검증 과정에 대해 공부할 때, 이해하는데 조금 시간이 걸렸다. 먼저 CSRF 공격에 대해 정확히 아는 것이 중요해 보인다.
CSRF 토큰 검증 방법에 관한 글들을 살펴보니, POST 요청이 필요한 페이지에 접근했을 때 바로 CSRF 토큰을 요청한 뒤 클라이언트 form의 숨겨진 필드에 토큰을 담고, 실제 POST 요청 시 토큰을 함께 보내는 방법이 있었다.
나는 이 방식이 아닌 본 요청을 실행하기 직전 CSRF 토큰 요청 API을 실행하는 로직을 택했다. POST 요청이 필요한 페이지에 접근했을 때, 요청을 보내지 않고 페이지를 나간다면 불필요한 API가 호출되기 때문이다. 그리고 본 요청을 보내기 직전에 짧은 유효시간을 갖는 CSRF 토큰을 발급해 바로 사용한다면 발급된 토큰을 제거하는 로직도 필요없어진다는 장점도 있다.
그리고 로그아웃 같이 유저의 동작과 관련된 기능은 GET 요청이 아닌 POST 요청으로 변경하는 것이 좋다. 만약 GET 요청에 대해서도 CSRF 토큰을 발급하고 검증한다면 너무 많은 요청이 발생하고, 오히려 비효율적일 것이다.
참고자료 😃
https://nordvpn.com/ko/blog/xss-attack/
https://devscb.tistory.com/123
'Web' 카테고리의 다른 글
[Web] Servlet & JSP (0) | 2024.05.04 |
---|---|
[Web] JWT 토큰 인증 (Access Token & Refresh Token) (0) | 2024.04.07 |
[Web] Cookie & Session & Token 인증 (0) | 2024.04.06 |
[Web] 웹 스토리지 (localStorage & sessionStorage) (0) | 2024.04.02 |
[Web] Web Server & WAS (0) | 2024.03.30 |