JWT를 사용하여 웹 사이트 공격에 대비하기 (with.Spring Security) 본문
Summary
이 글은 Spring Security와 JWT를 사용하여 웹 사이트 보안을 강화하는 방법에 대한 내용을 다룬다. 우선 JWT의 개념과 Spring Security에서 JWT를 검증하는 방법, 그리고 웹 사이트 공격 종류와 JWT를 통해 이에 대비하는 방법에 대해 알아보자.
JWT (Json Web Token)
JWT(Json Web Token) 웹에서 사용되는 JSON 형식의 토큰에 대한 표준 규격으로 주로 사용자의 인증(Authentication) 또는 인가(Authorization) 정보를 서버와 클라이언트 간에 안전하게 주고 받기 위해서 사용된다.
JWT의 특징
- 두 개체에서 JSON객체를 사용하여 자가수용적(self-contained) 방식으로 정보를 안전성 있게 전달한다.
- 자가수용적(self-contained) : JWT 안에 인증에 필요한 모든 정보를 자체적으로 지니고 있음
- HMAC 알고리즘 또는 RSA를 사용하여 디지털 서명되기 때문에 더욱 신뢰할 수 있다
- claim이라는 JSON형식 데이터 단위로 정보를 담고 있는데 일일이 요청이 있을 때마다 데이터베이스 또는 외부 시스템에 접근하지 않고 claim을 사용해 역할과 권한을 확인
Umbrella 프로젝트에서 Session이 아닌 JWT 방식을 사용하기로 한 이유는 클라이언트와 서버에 가해지는 부하를 줄이고, 추후 MSA 방식으로 변환하기 위해 서버의 확장성을 높이기 위함이다.
Session 방식이 아닌 JWT 방식을 사용하기로 결정한 뒤, 고민거리가 되었던 것은 Session 방식에 비해 취약한 보안성이었다.
JWT 보안 취약점
- Server 에서 사용하는 알고리즘 공개
- JWT에서 Signature 해시 알고리즘으로 HMAC, RSA, NONE 과 같은 여러 개의 가능성을 둔다는 것은 해커에게 다양한 중간자 공격의 기회를 제공하는 것이나 다름없다
- Solve
- 단일 알고리즘을 사용하여 공격 기회를 줄인다
- PayLoad Segment 중요 정보 노출
- Header와 PayLoad Segment 는 단순히 base64URL 형으로 변환되는데 이는 공격자가 쉽게 PayLoad Segment 의 정보를 식별할 수 있다
- Solve
- PayLoad 내부에 작성된 데이터는 프로그램에 영향을 주지 않는 정보를 담는 것이 좋다
- 중요 데이터는 외부 통신으로 주고 받기 보다는 데이터베이스에 사용자 Key 값을 기준으로 독립적으로 저장해두었다가 내부망을 통해 조회 사용되도록 설계하는 것이 바람직하다
- secretKey 유출 시 추가 피해 발생
- 애플리케이션 전제 기준 유일 값으로 secretKey를 가지고 있을 경우 secretKey 유출 시 다른 사용자에게 2차 피해가 발생할 수 있다
- Sovle
- 사용자 ID 별로 고유 식별자를 생성해 별도의 세션으로 가지고 있어 공격자가 특정 사용자의 secretKey를 알아낸다 하더라도 다른 사용자에게 2차 피해가 발생하지 않도록 한다
http://www.riss.kr/search/detail/DetailView.do?control_no=e491b76f46c45e0c47de9c1710b0298d&keyword=jwt&p_mat_type=1a0202e37d52c72d
www.riss.kr
손진광(JinKwang Son). "JWT 기법 기반 인증 서버 취약점 분석 및 시큐어 코딩 적용 방안." 한국정보과학회 학술발표논문집 2016.12 (2016): 835-83
Server에서 사용하는 알고리즘 공개와 PayLoad Segment 중요 정보 노출과 관련된 보안 취약점의 경우 Solve 의 내용대로 적용하는 것으로 어느 정도 취약점을 보안할 수 있었다.
하지만 secretKey 유출 시의 보안 취약점의 경우 사용자마다 고유의 식별자로 개개인에게 다른 secretKey 를 할당하는 방법의 구현에 있어서 어려움을 겪었기 때문에 우선 보류해둔채 프로젝트를 진행 중에 있다.
이에 대한 대안으로 HashiCorp Vault 를 사용하여 중요한 정보, 예를 들어 비밀번호, API 키, 토큰 등을 저장하는 방식을 생각하고 있다. 이에 대한 내용은 추후 다른 게시글에 다루는 것으로 하겠다.
Verify JWT in Spring Security
Spring Security는 인증, 권한 부여 및 일반적인 공격으로부터 보호 기능을 제공하는 프레임워크입니다. 명령형 및 반응형 애플리케이션 모두를 보호하기 위한 최고의 지원을 제공하며, Spring 기반 애플리케이션 보안의 표준으로 자리 잡고 있습니다.
Spring Security :: Spring Security
If you are ready to start securing an application see the Getting Started sections for servlet and reactive. These sections will walk you through creating your first Spring Security applications. If you want to understand how Spring Security works, you can
docs.spring.io
아래의 코드는 다음과 같은 절차를 나타낸다. 클라이언트의 요청에서 Refresh Token을 추출하여 유효성 검증 후 만약 유효성 검증을 통과했다면 Access Token에 대해서 유효성 검증을 실시한다.
Access Token이 유효성 검증에 성공할 경우 해당 Access Token을 다시 HTTP Header 에 담아 클라이언트에게 보낸다.
만약 Access Token이 유효성 검증에 실패할 경우에는 두 가지 경우로 나뉜다.
1) Access Token 의 유효 기간이 만료된 경우
- 이 경우에는 Access Token 을 재발급하여 클라이언트에게 전달한다.
2) Access Token 이 유효하지 않을 경우
- 기존에 클라이언트에 제공했던 Access Token과 이번 요청에 담겨있던 Access Token 이 일치하지 않는 경우인데, 이 경우에는 예외처리를 통해 HttpStatus Forbidden (403)을 클라이언트 측에 전달하여 접근을 거부한다.
CODE
if (jwtService.isTokenValid(extractRefreshToken.get()) == PASS) {
checkAccessToken(response, extractAccessToken, email);
checkAndSaveAuthentication(extractEmail);
}
...
private void checkAccessToken(HttpServletResponse response, String accessToken, String email) {
switch (jwtService.isTokenValid(accessToken)) {
case PASS:
jwtService.sendAccessToken(response, accessToken);
break;
case REISSUE:
jwtService.sendAccessToken(response, jwtService.createAccessToken(email));
break;
default:
throw new JwtException(ACCESS_TOKEN_ERROR_M);
}
}
...
private void saveAuthentication(User user) {
UserContext authenticatedUser = new UserContext(
user.getEmail(),
user.getPassword(),
user.getId(),
user.getNickName(),
roleUtil.addAuthoritiesForContext(user)
);
Authentication authentication = new UsernamePasswordAuthenticationToken(
authenticatedUser,
null,
authoritiesMapper.mapAuthorities(authenticatedUser.getAuthorities())
);
SecurityContext securityContext = createSecurityContext(authentication);
SecurityContextHolder.setContext(securityContext);
}
Website Attack
JWT가 가지고 있는 보안 취약점에 대한 고찰 및 그에 대한 대비를 간단하게나마 해뒀으니, 이번에는 웹 사이트 자체에 대한 공격에 대해 어떤 방식으로 대비하면 좋을지 생각해보도록 하자
우선 대표적인 웹 사이트 공격으로는 두 가지가 존재한다.
XSS (Cross-Site Scripting) 이란?
XSS는 웹 상에서 가장 기초적인 취약점 공격 방법 중 하나로, 악의적인 사용자가 공격하려는 사이트에 스크립트를 삽입하는 기법이다. 이 취약점은 사용자로부터 입력받은 값을 검사하지 않고 사용할 경우 발생하며, 공격에 성공하면 사이트에 접속한 사용자는 삽입된 코드를 실행하게 된다. 보통 의도치 않은 행동을 수행시키거나 민감한 정보를 탈취하는 등의 공격이 일어날 수 있다. 이 공격 방식은 단순하지만 많은 웹사이트들이 방어 조치를 해두지 않아 공격을 받는 경우가 많으며, 여러 사용자가 접근 가능한 게시판 등에 코드를 삽입하는 경우도 많다.
CSRF (Cross-Site Request Forgery) 란?
CSRF는 사용자의 의지와 무관하게 공격자가 의도한 행위(CRUD)를 웹사이트에 요청하게 하는 공격으로, 특정 웹사이트가 사용자의 웹 브라우저를 신용하는 상태를 노리는 것이다. 사용자가 로그인한 상태에서 공격자가 만든 페이지를 열면 웹사이트는 위조된 공격 명령이 믿을 수 있는 사용자로부터 발송된 것으로 판단하게 되어 공격에 노출된다. 이를 위해 공격자는 희생자가 로그인 상태이고 공격자가 만든 피싱 사이트에 접속하는 것을 조건으로 한다.
결론적으로 CSRF 공격의 주요 목적은 권한을 도용하는 것이며, XSS 공격의 주요 목적은 세션 혹은 쿠키를 탈취하는 것이다. 나는 이 두 공격에 대비하여 Refresh Token을 httpOnly와 Secure 옵션을 적용한 ResponseCookie 에 담고 AccessToken을 HttpHeader를 통해 클라이언트측으로 전달하는 방식을 사용했다.
우선 Refresh Token과 Access Token을 모두 클라이언트 측에 전달하는 이유부터 설명하자면, 만약 Access Token만을 이용해 서버와 클라이언트가 통신을 할 경우 Access Token이 탈취당하는 것만으로도 공격자는 해당 유저의 권한을 얻을 수 있다. 이에 대비하여 Refresh Token 또한 함께 클라이언트 측으로 전달하여 두 토큰을 모두 온전한 상태로 가지고 있어야만 검증에 통과할 수 있도록 한 것이다.
이렇게 설정한 가장 근본적인 이유는 바로 CSRF 공격과 XSS 공격에 대비하기 위함이다.
XSS 공격에 대한 대비
httpOnly Cookie 방식으로 저장된 정보는 XSS 공격을 해도 자바스크립트 내에서 httpOnly 쿠키에 접근이 불가능하기 때문에 XSS 방어가 가능하다. 그러나 XSS 취약점을 노려 API 콜을 공격자가 요청한다면 httpOnly Cookie 값도 함께 전달되어 유저의 권한을 탈취할 수 있다. 만약 Access Token만 httpOnly Cookie에 담아 전달할 경우 위의 방법을 통해 공격자가 손쉽게 유저의 권한을 탈취하여 이용할 수 있다. 하지만 Access Token과 Refresh Token을 각각 클라이언트에게 전달하는 방식을 사용할 경우, 공격자가 httpOnly Cookie의 Refresh Token을 탈취했다고 해도 Access Token까지 탈취하지 못할 경우 두 토큰이 모두 유효하지 않기 때문에 Forbidden 에러를 전달받게 된다.
CSRF 공격에 대한 대비
XSS 공격과 굉장히 비슷한 방식으로 대비가 가능하다. httpOnly Cookie 방식으로 저장된 Refresh Token을 CSRF 공격을 통해 공격자가 탈취하여 서버로 전송한다고 해도 Refresh Token 만으로는 인증 절차를 통과할 수 없기에, 유저 권한으로 CRUD 작업을 수행할 수 없다. XSS 공격 때와 동일하게 공격자는 Access Token까지 탈취하지 못했다면 두 토큰이 모두 유효하지 않기 때문에 서버로부터 Forbidden 에러를 전달받게 된다.
느낀 점
JWT 인증을 도입하는 과정에서 Spring Security에 대한 이해도가 그 전에 비해 굉장히 상승했다는 것을 느끼고있다. 단순히 Spring Security를 사용하는 것이 아니라 어떤 보안 취약점이 있는지에 대한 고민, 그리고 취약점에 어떻게 대응할 것인지에 대한 고민을 통해 Spring Security의 사용에 있어서 한층 더 성정했다는 느낌을 받을 수 있었다.
무언가를 배우는 것에 있어서 나의 성장이 뒷받침된다면 굉장히 즐겁게 배울 수 있다는 것을 다시 한 번 깨닫게 된 계기가 된 것 같다.
Spring Security를 익히며, 그리고 JWT 인증을 도입하면서 느꼈던 고민과 어려움, 나에게 찾아와주었던 갖가지 에러 그리고 미처 예상하지 못했던 버그까지 추후 기회가 되면 포스팅해보자 한다.
이 포스팅에서 서술하고 싶은 느낀 점은 아무래도 웹 사이트 공격(CSRF, XSS)에 관한 것이다. 사실 포스팅을 작성하고 있는 지금도 이 두 공격에 대해서 잘 이해했다는 느낌이 들지는 않는다. CSRF 공격과 XSS 공격이 정확히 무엇을 대상으로 진행되는 공격인지 그리고 공격자는 이 공격들을 통해서 무엇을 얻을 수 있는지에 대한 의문이 계속해서 꼬리를 물며 떠올랐다.
기술이 발전하는 과정에 있어서 이 두 공격들을 통해서 공격자가 가할 수 있는 공격 방식 또한 발전할 것이고 내가 단편적으로만 알고 있는 지식만으로는 이 공격들을 완벽히 방어해낸다는 것이 불가능에 가까울 것이라고 느꼈다.
이번 포스팅을 계기로 하여, 웹 사이트 공격에 대한 공부를 조금씩이나마 시작해보려고 한다. 지금보다 조금 더 나은 지식을 가지고 웹 사이트 공격에 관한 포스팅을 할 수 있는 기회가 생긴다면 매우 기쁠 것이다.
이번 느낀 점에서 누누히 언급했듯이 아직 웹 사이트 공격에 대한 지식이 매우 부족합니다. 당연히 이 글에도 잘못된 정보가 있을 수 있으며, 만약 잘못된 정보를 발견할 경우 피드백을 남겨준다면 매우 감사하겠습니다.
깃허브 주소 : https://github.com/BullChallenger/Umbrella
GitHub - BullChallenger/Umbrella: 개발자들을 위한 흥미로운 협업 툴
개발자들을 위한 흥미로운 협업 툴. Contribute to BullChallenger/Umbrella development by creating an account on GitHub.
github.com
출처
https://lucete1230-cyberpolice.tistory.com/23
XSS와 CSRF 특징 및 차이
안녕하세요 오늘은 XSS와 CSRF를 알아보고 두 공격의 차이점을 알아보도록 할께요! XSS(Cross-Site Scripting)란? 크로스 사이트 스크립팅(사이트 간 스크립팅 )은 SQL injection과 함께 웹 상에서 가장 기초
lucete1230-cyberpolice.tistory.com
https://velog.io/@meme2367/%EC%9C%A0%EC%A0%80-%EC%9D%B8%EC%A6%9D-XSS-CSRF
유저 인증 - XSS, CSRF
계정정보를 요청에 담아 보내는 방식. HTTP 요청에 인증할 수단에 비밀번호를 넣는다.해커가 HTTP 요청을 가로채서 계정정보를 알 수 있다. 최악의 인증방식클라이언트 -> 서버 첫 요청 시 서버는
velog.io
'Dev' 카테고리의 다른 글
HTTP (1) | 2024.02.24 |
---|---|
Transaction (0) | 2024.02.20 |
Spring Security Session 과 RestAPI (0) | 2023.08.17 |
In-Memory NoSQL DBMS Redis (0) | 2023.06.18 |
JVM Runtime Data Area Structure (1) | 2023.05.08 |