전자금융기반시설 보안 취약점 - 자동화 공격 방어

자동화 도구 악용 (Automated Attack) | 위험도: 4 (높음) | 통제구분: 5.4.1 자동화 공격 방어

카카오뱅크

인터넷뱅킹 로그인 시스템

취약점: 자동화 공격 방어 미흡

로그인

⚠️ 방어 기능 상태:

CAPTCHA: 비활성화
Rate Limiting: 없음
로그인 시도 제한: 무제한
계정 잠금: 미적용

실시간 공격 통계

총 시도 횟수: 0
초당 요청 (RPS): 0
시도한 패스워드: 0
경과 시간: 00:00

자동화 공격 시나리오

취약한 코드

위험
// ❌ 자동화 공격 방어 없음

@PostMapping("/login")
public ResponseEntity login(
    @RequestParam String username,
    @RequestParam String password) {
    
    // Rate Limiting 없음!
    // CAPTCHA 없음!
    // 로그인 시도 횟수 제한 없음!
    
    User user = userRepository
        .findByUsername(username);
    
    if (user != null && 
        passwordEncoder.matches(password, 
            user.getPassword())) {
        return ResponseEntity.ok("Success");
    }
    
    // 실패 시 즉시 응답 (타이밍 공격 가능)
    return ResponseEntity
        .status(401)
        .body("Invalid credentials");
}

// API 엔드포인트 보호 없음
@GetMapping("/api/users")
public List getUsers() {
    // 누구나 무제한 호출 가능!
    return userRepository.findAll();
}

// 크롤링 봇 차단 없음
// robots.txt 없음
// User-Agent 검증 없음
// IP 기반 차단 없음

공격 실행 결과

대기중

공격 시나리오를 선택하세요

안전한 코드

권장
// ✅ 자동화 공격 방어 적용

// 1. Rate Limiting (Bucket4j 사용)
@Service
public class RateLimitService {
    
    private final Map cache = 
        new ConcurrentHashMap<>();
    
    public Bucket resolveBucket(String key) {
        return cache.computeIfAbsent(key, k -> {
            // 1분에 5번만 허용
            Bandwidth limit = Bandwidth.classic(
                5, 
                Refill.intervally(5, Duration.ofMinutes(1))
            );
            return Bucket.builder()
                .addLimit(limit)
                .build();
        });
    }
}

@PostMapping("/login")
public ResponseEntity login(
    @RequestParam String username,
    @RequestParam String password,
    HttpServletRequest request) {
    
    String clientIP = getClientIP(request);
    
    // IP 기반 Rate Limiting
    Bucket bucket = rateLimitService
        .resolveBucket(clientIP);
    
    if (!bucket.tryConsume(1)) {
        return ResponseEntity
            .status(429)
            .body("Too many requests. Try again later.");
    }
    
    // 로그인 시도 횟수 체크
    int attempts = loginAttemptService
        .getAttempts(username);
    
    if (attempts >= 5) {
        // 5회 실패 시 15분 잠금
        if (!loginAttemptService.isUnlocked(username)) {
            return ResponseEntity
                .status(423)
                .body("Account locked. Try in 15 min.");
        }
    }
    
    // CAPTCHA 검증 (3회 실패 후)
    if (attempts >= 3) {
        if (!verifyCaptcha(request)) {
            return ResponseEntity
                .status(400)
                .body("Invalid CAPTCHA");
        }
    }
    
    User user = userRepository
        .findByUsername(username);
    
    // 타이밍 공격 방어 (일정 시간 대기)
    Thread.sleep(1000);
    
    if (user != null && 
        passwordEncoder.matches(password, 
            user.getPassword())) {
        
        // 성공 시 시도 횟수 리셋
        loginAttemptService.resetAttempts(username);
        
        return ResponseEntity.ok("Success");
    }
    
    // 실패 시 시도 횟수 증가
    loginAttemptService.loginFailed(username);
    
    return ResponseEntity
        .status(401)
        .body("Invalid credentials");
}

// 2. 로그인 시도 관리 서비스
@Service
public class LoginAttemptService {
    
    private final int MAX_ATTEMPT = 5;
    private final int LOCK_TIME_MINUTES = 15;
    
    private LoadingCache attemptsCache;
    private LoadingCache lockCache;
    
    public void loginFailed(String key) {
        int attempts = attemptsCache.get(key);
        attempts++;
        attemptsCache.put(key, attempts);
        
        if (attempts >= MAX_ATTEMPT) {
            lockCache.put(key, 
                LocalDateTime.now()
                    .plusMinutes(LOCK_TIME_MINUTES));
        }
    }
    
    public boolean isUnlocked(String key) {
        LocalDateTime lockTime = lockCache.get(key);
        if (lockTime == null) return true;
        
        return LocalDateTime.now().isAfter(lockTime);
    }
}

// 3. IP 기반 차단
@Component
public class IpBlockingFilter extends OncePerRequestFilter {
    
    private Set blockedIps = 
        ConcurrentHashMap.newKeySet();
    
    @Override
    protected void doFilterInternal(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain chain) {
        
        String clientIP = getClientIP(request);
        
        // 차단된 IP 확인
        if (blockedIps.contains(clientIP)) {
            response.sendError(403, "IP Blocked");
            return;
        }
        
        chain.doFilter(request, response);
    }
}

// 4. Bot 감지 및 차단
@Component
public class BotDetectionFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain chain) {
        
        String userAgent = request.getHeader("User-Agent");
        
        // 의심스러운 User-Agent 차단
        if (isSuspiciousBot(userAgent)) {
            response.sendError(403, 
                "Automated access detected");
            return;
        }
        
        chain.doFilter(request, response);
    }
    
    private boolean isSuspiciousBot(String ua) {
        if (ua == null || ua.isEmpty()) return true;
        
        String[] botPatterns = {
            "bot", "crawler", "spider", "scraper",
            "python-requests", "curl", "wget"
        };
        
        String lowerUA = ua.toLowerCase();
        for (String pattern : botPatterns) {
            if (lowerUA.contains(pattern)) {
                return true;
            }
        }
        
        return false;
    }
}

// 5. CAPTCHA 검증
private boolean verifyCaptcha(HttpServletRequest request) {
    String captchaResponse = 
        request.getParameter("g-recaptcha-response");
    
    if (captchaResponse == null) return false;
    
    // Google reCAPTCHA v3 검증
    RestTemplate restTemplate = new RestTemplate();
    String url = "https://www.google.com/recaptcha/api/siteverify";
    
    Map params = new HashMap<>();
    params.put("secret", RECAPTCHA_SECRET);
    params.put("response", captchaResponse);
    
    RecaptchaResponse response = 
        restTemplate.postForObject(url, params, 
            RecaptchaResponse.class);
    
    return response != null && 
           response.isSuccess() && 
           response.getScore() > 0.5;
}

자동화 공격 방어 체크리스트

Rate Limiting: IP당 요청 횟수 제한 (분당 5회)
계정 잠금: 5회 실패 시 15분 잠금
CAPTCHA: 3회 실패 후 CAPTCHA 요구
타이밍 공격 방어: 일정 시간 응답 지연
Bot 감지: User-Agent 검증
IP 차단: 의심스러운 IP 자동 차단
모니터링: 비정상 트래픽 실시간 감지