자동화 도구 악용 (Automated Attack) | 위험도: 4 (높음) | 통제구분: 5.4.1 자동화 공격 방어
인터넷뱅킹 로그인 시스템
⚠️ 방어 기능 상태:
// ❌ 자동화 공격 방어 없음
@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;
}