사용자 열거(User enumeration)를 허용하는 것은 보안에 민감합니다.

 

사용자 열거(User enumeration)는 웹 애플리케이션 데이터베이스에 존재하는 사용자의 이름을 추측하는 기능을 말합니다. 이것은 웹사이트의 “로그인 / 로그아웃 / 비밀번호 찾기” 등을 수행할 때 발생할 수 있습니다.

유저가 잘못된 이름으로 웹사이트에 “로그인”을 시도할 때, 웹 애플리케이션은 “올바른 사용자의 이름이 아닙니다.”와 유사한 메시지와 함께 사용자의 이름이 존재하지 않는다는 사실을 드러내서는 안됩니다. 대신, “잘못된 자격증명입니다.” 처럼 인증 과정에서 사용자의 이름이나 암호가 잘못되었는지를 추측할 수 없는 일반적인 메시지를 표시해야 합니다.

만약 유저 관리 기능이 사용자 이름이 존재하는지에 대한 정보를 공개하는 경우, 공격자는 무차별 대입 공격을 통해 피싱, 패스워드 추측 등을 용이하게 할 수 있는 많은 양의 유효한 사용자 이름을 검색할 수 있습니다.

다음과 같은지 자문해보세요.

  • 애플리케이션이 데이터베이스에 사용자 이름이 존재하지는지를 드러냅니다: 웹 사이트의 다른 사용자가 사용중이지 않은 유효한 유저이름을 선택해야 하는 “회원가입 / 로그인” 부분 등을 제외하고는, 대부분의 경우 이러한 종류의 유출을 피할 수 있습니다.
  • 사용자 이름과 관련된 요청에 속도 제한이나 CAPTCHA 보호가 적용되어있지 않습니다.

추천되는 안전한 코딩 관습

사용자가 사용자 이름과 관련된 요청을 수행할 때 유효한 사용자 이름과 잘못된 사용자 이름 간의 차이점을 발견하는 것이 불가능해야 합니다.

  • 오류 메시지는 일반적이어야 하며 사용자 이름이 유효한지 여부를 공개하지 않아야 합니다.
  • 응답 시간은 유효한 사용자 이름과 유사해야 합니다.
  • CAPTCHA 및 기타 속도 제한 솔루션을 구현해야 합니다.

민감한 코드의 예

스프링 시큐리티 웹 애플리케이션에서 다음과 같은 경우 사용자 이름이 누출됩니다.:

  • loadUserByUsername 메서드의 인수로 사용되는 문자열이 예외 메시지에서 사용되는 경우.
public String authenticate(String username, String password) {
  // ....
  MyUserDetailsService s1 = new MyUserDetailsService();
  MyUserPrincipal u1 = s1.loadUserByUsername(username);

  if(u1 == null) {
    throw new BadCredentialsException(username+" doesn't exist in our database"); // 민감한 코드
  }
  // ....
}
public String authenticate(String username, String password) {
  // ....
  if(user == null) {
      throw new UsernameNotFoundException("user not found"); // 민감한 코드
  }
  // ....
}
DaoAuthenticationProvider daoauth = new DaoAuthenticationProvider();
daoauth.setUserDetailsService(new MyUserDetailsService());
daoauth.setPasswordEncoder(new BCryptPasswordEncoder());
daoauth.setHideUserNotFoundExceptions(false); // 민감한 코드
builder.authenticationProvider(daoauth);

규칙을 준수한 해결책

스프링 시큐리티 웹 애플리케이션에서는

  • 잘못된 사용자인지 암호인지에 관계없이 동일한 메시지를 사용해야 합니다:
public String authenticate(String username, String password) throws AuthenticationException {
  Details user = null;
  try {
    user = loadUserByUsername(username);
  } catch (UsernameNotFoundException | DataAccessException e) {
    // 유저이름이 존재하지 않음을 드러내지 않으려면 이 exception 의 이유를 숨겨야합니다.
  }
  if (user == null || !user.isPasswordCorrect(password)) {
    // 유저는 "Bad credential" 메시지가 유저 이름과 관련있는지 혹은 비밀번호 때문인지를 추측할 수 없어야 합니다.
    throw new BadCredentialsException("Bad credentials");
  }
}
DaoAuthenticationProvider daoauth = new DaoAuthenticationProvider();
daoauth.setUserDetailsService(new MyUserDetailsService());
daoauth.setPasswordEncoder(new BCryptPasswordEncoder());
daoauth.setHideUserNotFoundExceptions(true); // 규칙을 준수한 코드
builder.authenticationProvider(daoauth);

참고


If you like SONARKUBE, don’t forget to give me a star. :star2:

원문으로 바로가기

Star This Project