소프트웨어 개발보안 가이드 분석(2021) : 반복된 인증시도 제한 기능 부재

반복된 인증시도 제한 기능 부재란?

반복된 인증 시도 제한 기능 부재 취약점이란 웹 애플리케이션 또는 시스템이 사용자 인증 과정에서 일정 시도 횟수 이상의 로그인 실패를 감지하고 이를 제한하지 않을 때 발생하는 보안 취약점입니. 이 취약점으로 인해 공격자가 브루트 포스 공격(Brute Force Attack) 또는 사전 공격(Dictionary Attack) 등을 통해 사용자의 인증 정보를 획득할 수 있습니다. 반복된 인증시도 제한 기능 부재 취약점은 인증 시도 횟수에 대한 제한이 없거나 취약한 경우 발생합니다.

 

공격 메커니즘

반복된 인증 시도 제한 기능 부재 시 발생할 수 있는 공격 메커니즘은 다음과 같습니다.

  1. 브루트 포스 공격(Brute Force Attack): 공격자가 가능한 모든 조합을 시도하여 사용자의 계정에 접근하는 방법입니다.
  2. 사전 공격(Dictionary Attack): 공격자가 사전에 정의된 단어 조합(널리 사용되는 문자열, 숫자 등)을 이용하여 사용자의 계정 정보를 알아내는 방법입니다.

인증 시도를 제한하는 기능이 존재하지 않을 경우, 브루트 포스 공격과 사전 공격 등에 취약해져 공격자에게 무방비 상태로 노출될 위험이 있습니다.

 

취약한 웹 애플리케이션의 예

다음은 인증 시도 제한을 하지 않는 취약한 웹 애플리케이션의 예시 코드입니다.

import java.util.HashMap;
import java.util.Map;

public class InsecureLoginAttemptExample {
    private Map<String, String> userDatabase = new HashMap<>(); // 사용자 정보 저장
    private Map<String, Integer> loginAttempts = new HashMap<>(); // 로그인 시도 횟수 저장

    public InsecureLoginAttemptExample() {
        // 예시 사용자 추가
        userDatabase.put("user", "password123");
    }

    public boolean login(String username, String password) {
        // 사용자 인증 로직
        if (!userDatabase.containsKey(username)) {
            System.out.println("사용자를 찾을 수 없습니다.");
            return false;
        }

        String storedPassword = userDatabase.get(username);

        if (!password.equals(storedPassword)) {
            System.out.println("비밀번호가 일치하지 않습니다.");
            // 로그인 시도 횟수 증가 로직이 없음
            return false;
        }

        // 로그인 성공
        System.out.println("로그인 성공!");
        return true;
    }

    public static void main(String[] args) {
        InsecureLoginAttemptExample example = new InsecureLoginAttemptExample();
        // 반복된 인증 시도가 가능한 상황
        example.login("user", "wrongPassword1");
        example.login("user", "wrongPassword2");
        example.login("user", "wrongPassword3");
        // 이러한 반복된 시도를 제한하지 않아 공격자가 브루트 포스 공격을 시도할 수 있음
    }
}

이 코드 예시에서는 user라는 사용자에 대해 잘못된 비밀번호로 여러 번 로그인을 시도할 수 있습니다. 로그인 시도 횟수를 추적하거나, 잘못된 시도가 일정 횟수를 초과할 경우 추가 조치를 취하는 로직이 존재하지 않습니다. 이러한 코드는 브루트 포스 공격에 매우 취약한 상태입니다.

 

시큐어코딩 적용 방법

위와 같이 인증 시도를 제한하는 로직이 존재하지 보안에 취약한데요, 반복된 인증시도 제한 기능 부재 취약점은 다음과 같은 방법을 통하여 취약점을 보안할 수 있습니다.

  1. 인증 시도 제한: 사용자가 일정 횟수 이상 로그인에 실패하면 계정을 일시적으로 잠그거나 추가적인 인증 절차를 요구하도록 구현합니다.
  2. 지연 시간 도입: 일정 횟수 인증 실패 시, 다음 인증 시도까지 일정 시간을 대기하도록 설정함하여 브루트 포스 공격이나 사전 공격 등의 효율을 크게 저하시킵니다.
  3. CAPTCHA 도입: 인증 시도가 일정 횟수 이상 실패한 경우, 사용자에게 CAPTCHA를 통한 추가적인 인증을 요구하여 실사용자임 인증과 더불어 봇에의한 자동화된 공격을 방지합니다.

다음은 인증 시도 제한 기능을 구현하여 로그인에 연속으로 실패할 경우 계정을 잠그는 예시 코드입니다.

import java.util.HashMap;
import java.util.Map;

public class SecureLoginAttemptExample {
    private Map<String, String> userDatabase = new HashMap<>(); // 사용자 정보 저장
    private Map<String, Integer> loginAttempts = new HashMap<>(); // 로그인 시도 횟수 저장
    private static final int MAX_LOGIN_ATTEMPTS = 3; // 최대 로그인 시도 횟수

    public SecureLoginAttemptExample() {
        // 예시 사용자 추가
        userDatabase.put("user", "password123");
        loginAttempts.put("user", 0); // 초기 로그인 시도 횟수 0으로 설정
    }

    public boolean login(String username, String password) {
        // 사용자 인증 로직
        if (!userDatabase.containsKey(username)) {
            System.out.println("사용자를 찾을 수 없습니다.");
            return false;
        }

        // 로그인 시도 횟수 초과 검사
        if (loginAttempts.get(username) >= MAX_LOGIN_ATTEMPTS) {
            System.out.println("계정이 잠겼습니다. 관리자에게 문의하세요.");
            return false;
        }

        String storedPassword = userDatabase.get(username);

        if (!password.equals(storedPassword)) {
            System.out.println("비밀번호가 일치하지 않습니다.");
            int currentAttempts = loginAttempts.get(username);
            loginAttempts.put(username, currentAttempts + 1); // 로그인 시도 횟수 증가
            return false;
        }

        // 로그인 성공 시 로그인 시도 횟수 초기화
        loginAttempts.put(username, 0);
        System.out.println("로그인 성공!");
        return true;
    }

    public static void main(String[] args) {
        SecureLoginAttemptExample example = new SecureLoginAttemptExample();
        // 로그인 시도
        example.login("user", "wrongPassword1");
        example.login("user", "wrongPassword2");
        example.login("user", "wrongPassword3");
        // 이제 사용자 계정이 잠김
        example.login("user", "password123");
    }
}

이 코드는 사용자가 로그인을 시도할 때 마다 loginAttempts 맵을 사용하여 해당 사용자의 로그인 시도 횟수를 추적합니다. 만약 사용자가 지정된 최대 로그인 시도 횟수인 MAX_LOGIN_ATTEMPTS를 초과하면, 시스템은 더 이상의 로그인 시도를 허용하지 않고 계정이 잠겼음을 알립니다. 로그인에 성공하면 사용자의 로그인 시도 횟수는 0으로 초기화됩니다. 이러한 방식으로, 애플리케이션은 브루트 포스 공격에 대한 기본적인 방어책을 갖출 수 있습니다. 실제 애플리케이션에서는 로그인 시도 횟수를 데이터베이스나 다른 영구적인 저장소에 저장하고, 더 복잡한 보안 조치를 적용할 수 있습니다.

  • 이전 소프트웨어 개발보안 가이드 분석(2021) : 무결성 검사 없는 코드 다운로드
  • 다음 소프트웨어 개발보안 가이드 분석(2021) : 경쟁조건: 검사시점과 사용시점(TOCTOU)