클라이언트-서버 모델을 사용하는 응용프로그램을 작성할 때, 사용자 인증 정보와 같은 민감-데이터를 클라이언트 측에 저장하면, 클라이언트가 공격에 취약할 경우에 허가되지 않은 방법으로 민감-데이터가 노출될 수 있다.
민감-데이터
사용자 이름, 패스워드, 신용 카드 번호 등 사용자 개인을 식별할 수 있는 기타의 정보
쿠키(cookie)
웹 응용프로그램에서 이 문제에 대한 가장 보편적인 완화책
짧은 문자열로 구성되어 있고, 만약 민감-데이터를 포함하고 있다면(remember-me 기능) 암호화되어야만 한다.
쿠키를 클라이언트에게 제공하고 민감-데이터는 서버에 저장
- 쿠키는 웹서버에 의해 생성되고 일정 기간 동안 클라이언트에 저장됨.
- 클라이언트가 서버에 다시 연결할 때, 클라이언트가 서버에게 자신을 식별하도록 하는 쿠키를 제공하면 서버가 민감-데이터를 제공
쿠키는 XSS(Cross-Site Scripting) 공격으로부터 민감-데이터를 보호하지 못한다.
- XSS 공격이나 클라이언트를 직접 공격해서 쿠키를 얻을 수 있는 공격자는 쿠키를 이용하여 서버로부터 민감-데이터를 손에 넣을 수 있다.
- 서버가 제한시간 경과 후 세션을 무효화하도록 한다면 이러한 위험은 시간에 한정적으로 된다.
XSS (Cross-Site Scripting) 공격
XSS 공격은 웹 애플리케이션의 보안 취약점을 이용하여 악성 스크립트를 삽입하고, 이를 피해갈 수 없는 사용자들에게 스크립트를 실행하도록 유도하는 공격 기법이다.
일반적으로 웹 애플리케이션은 사용자로부터 입력을 받아 화면에 출력하거나 데이터베이스에 저장하는 등의 동작을 수행하는데, XSS 공격은 주로 사용자의 입력 데이터에 악성 스크립트를 포함시켜, 이를 서버로 전달하고 다시 웹 페이지에 표시되는 상황에서 이루어진다.
1. Stored XSS
- 악성 스크립트가 웹 어플리케이션의 데이터베이스에 저장되어 해당 데이터를 조회하는 사용자들에게 스크립트가 실행되도록 한다.
- 피해가 크고 영구적이다.
2. Reflected XSS
- 악성 스크립트가 사용자의 입력을 웹 애플리케이션에서 바로 반사하여 돌려줄 때 발생한다.
- 흔히 검색 결과나 링크를 클릭한 뒤, 주소 표시줄에 악성 스크립트를 포함한 URL이 반사되어 스크립트가 실행되는 형태다.
- 반사된 XSS는 한 번의 클릭이나 악성 URL을 통해 공격이 발생하며, 피해 범위가 제한적일 수 있다.
3. DOM-based XSS
- DOM(Document Object Model)을 조작하여 발생하는 XSS로, 서버로부터의 응답에는 악성 스크립트가 없고, 클라이언트 측에서 스크립트가 실행될 때 발생한다.
- 웹 애플리케이션의 스크립트가 사용자의 입력을 해석하여 동적으로 웹 페이지를 조작하는 경우에 발생할 수 있다.
공격자가 XSS 공격을 성공적으로 수행하면, 클라이언트의 세션을 탈취하거나 클라이언트를 다른 사이트로 리다이렉션 시킨다거나, 클라이언트의 정보를 유출하는 등의 피해를 입힐 수 있다.
따라서 웹 애플리케이션 개발 시 XSS 방어를 위해 적절한 입력 검증, 출력 이스케이핑, 쿠키 보호 등의 보안 조치가 필요하다.
클라이언트의 신뢰할 수 없는 입력은 신중하게 처리하여 XSS 공격에 취약하지 않도록 해야 한다.
Remember-Me
웹 애플리케이션에서 Remember-Me 기능은 사용자가 로그인한 후 웹 브라우저를 닫아도 다음에 다시 해당 웹 사이트를 방문할 때 자동으로 로그인 상태를 유지하는 기능을 말한다.
사용자가 Remember-Me 옵션을 선택하면 웹 애플리케이션은 사용자의 로그인 정보를 쿠키나 세션에 저장하여, 다음 방문 시 자동으로 로그인을 지원한다.
1. 쿠키 사용
- 클라이언트가 로그인할 때 로그인 정보를 쿠키에 저장한다.
- 쿠키에는 만료 기간이 설정되어 있어서 일정 기간 동안 유지되는데, 클라이언트가 다음에 웹 사이트를 방문하면 쿠키를 확인하여 자동으로 로그인 상태를 유지하게 된다.
2. 세션 기반
- 클라이언트가 로그인할 때 로그인 정보를 서버의 세션에 저장한다.
- 세션은 서버 측에서 유지되므로 웹 브라우저를 닫아도 사용자의 로그인 상태가 유지된다.
로그인 정보가 쿠키나 세션에 저장되기 때문에, 악의적인 사용자가 해당 정보를 탈취하면 보안 위험이 발생할 수 있다.
따라서 로그인 정보를 저장할 때에는 적절한 암호화를 사용하고, 쿠키의 만료 기간과 세션의 유효 기간 등을 조절하여 보안을 강화해야 한다.
SSL/TLS 같은 보안 프로토콜을 사용하여 데이터를 암호화하는 것도 중요한 보안 조치다.
부적절한 remember-me 코드
클라이언트의 정보를 쿠키에 그대로 저장하는 코드
Cookie loginCookie = new Cookie("rememberme", username + ";" + new String(password));
- 클라이언트 머신에 엑세스할 수 있는 공격자는 클라이언트로부터 정보를 직접 얻을 수 있다.
솔루션 - 사용자 이름과 안전한 랜덤 문자열을 쿠키에 저장
String newRandom = loginService.getRandomString();
loginService.mapUserForRememberMe(username, newRandom);
Cookie loginCookie = new Cookie("rememberme", username + ";" + newRandom);
- 서버는 사용자 이름과 안전한 랜덤 문자열과의 짝을 유지한다.
- 사용자가 remember-me 기능을 선택하면 제공된 쿠키가 유요한 사용자 이름과 짝이 되는 랜덤 문자열을 포함하고 있는지 검사한다.
- 만약 짝이 맞으면 서버는 사용자를 인증하고, 그렇지 않으면 오류를 반환한다.
Spring Security JWT의 Remember-Me
Spring Security에서 JWT(JSON Web Token) 방식으로 Remember-Me 기능을 구현하려면, 토큰의 유효 기간을 설정하고 클라이언트가 로그인 시 토큰을 발급받아 저장한 후, 만료 기간 내에 다시 요청 시 해당 토큰을 서버로 보내어 자동 로그인을 처리하는 방식을 사용할 수 있다.
Spring Security의 RememberMeAuthenticationFilter를 활용하여 구현하는 방법이 일반적으로 사용된다.
이 필터를 사용하여 토큰을 발급하고, 클라이언트의 요청에서 토큰을 검증하여 사용자를 인증하는 로직을 구현할 수 있다.
Spring Security 설정 변경
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
// 다른 인증이 필요한 URL 패턴 설정
.antMatchers("/api/secure/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginProcessingUrl("/login")
.and()
.logout()
.and()
.rememberMe() // Remember Me 활성화
.key("uniqueAndSecretKey") // Remember Me 토큰에 사용될 키 설정
.rememberMeCookieName("rememberMeToken") // Remember Me 쿠키 이름 설정
.tokenValiditySeconds(604800); // Remember Me 토큰의 유효 기간 설정 (7일)
}
RememberMeAuthenticationFilter 확장
@Component
public class CustomRememberMeFilter extends RememberMeAuthenticationFilter {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Override
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) {
// Remember Me 토큰 발급 로직
String username = ((UserDetails) authResult.getPrincipal()).getUsername();
String token = jwtTokenProvider.createToken(username);
Cookie rememberMeCookie = new Cookie("rememberMeToken", token);
rememberMeCookie.setMaxAge(604800); // Remember Me 토큰의 쿠키 유효 기간 (7일)
rememberMeCookie.setHttpOnly(true);
response.addCookie(rememberMeCookie);
}
// 기타 메서드 오버라이드 또는 필요한 로직 구현...
}
JWT vs Cookie
1. 저장 위치
- JWT 토큰 : JWT 토큰은 서버가 사용자에 대한 정보를 암호화하여 토큰 형태로 발급하고, 이 토큰을 클라이언트에게 전달한다. 클라이언트는 JWT 토큰을 쿠키, 로컬 스토리지, 세션 스토리지 등에 저장할 수 있다.
- 쿠키 : 쿠키는 서버가 클라이언트에게 쿠키를 발급하여 클라이언트의 웹 브라우저에 저장하고, 해당 쿠키를 이용하여 인증을 처리한다. 클라이언트의 모든 요청에는 쿠키가 함께 전송되며, 서버는 해당 쿠키를 사용하여 클라이언트를 인증한다.
2. 서버 상태 유무
- JWT 토큰 : JWT 토큰은 서버에 세션 정보를 저장하지 않고, 토큰 자체에 필요한 정보를 포함한다. 따라서 서버의 상태 정보를 유지할 필요가 없어진다. 이러한 특성으로 JWT는 무상태(Stateless) 서버 구조를 구현할 수 있다.
- 쿠키 : 쿠키는 서버에 클라이언트의 세션 정보를 저장하고 관리한다. 클라이언트의 모든 요청에는 쿠키가 함께 전송되고, 서버는 세션 ID 등을 사용하여 클라이언트를 식별하고 세션 정보를 활용한다. 이러한 특성으로 서버에 상태 정보를 유지하는 Stateful 한 구조가 된다.
3. 보안성
- JWT 토큰 : JWT 토큰에 디지털 서명을 포함하여 토큰의 무결성과 안전성을 보장한다. 서버는 토큰을 검증하여 유효한 토큰인지 확인할 수 있다. 토큰의 만료 시간을 설정하여 보안을 강화할 수 있다.
- 쿠키 : 쿠키도 안전한 옵션으로 설정될 수 있지만 XSS와 CSRF 같은 공격에 노출될 수 있다. HTTPS를 사용하여 쿠키를 암호화하고 보안성을 강화해야 한다.
4. 확장성
- JWT 토큰 : JWT 토큰은 클라이언트에 토큰을 저장하므로 다양한 종류의 클라이언트(웹, 모바일 앱, RESTful API)에서 쉽게 사용할 수 있다.
- 쿠키 : 쿠키도 다양한 클라이언트에서 사용이 가능하지만, 주로 웹 브라우저와 관련된 환경에서 사용된다.
'Java > Secure Coding' 카테고리의 다른 글
[Secure Coding] 마구잡이 파일 업로드를 방지하라 (0) | 2023.08.07 |
---|---|
[Secure Coding] 보안에 민감한 메서드들이 검증된 매개변수를 가지고 호출되도록 보장하라 (0) | 2023.08.06 |
[Secure Coding] 민감한 가변적 클래스에 수정이 불가능한 래퍼를 제공하라 (0) | 2023.08.04 |
[Secure Coding] 민감-데이터의 수명을 제한하라 (0) | 2023.07.24 |
[Secure Coding] Chap1. 안전성 (0) | 2023.07.24 |