전자금융기반시설 보안 취약점 평가기준 - WEB-SER-043

프로세스 검증 누락 (PV) | 위험도: 4 (높음) | 통제구분: 5.4.2 거래 무결성

XX은행

인터넷뱅킹 계좌이체 시스템

취약점: 단계별 검증 누락

정상 이체 프로세스

1 이체 정보 입력

받는 계좌, 금액, 내용 입력

2 이체 정보 확인

입력 내용 검토 및 확인

3 OTP 인증

보안카드 또는 OTP 번호 입력

4 이체 실행

최종 이체 처리

공격 시나리오

취약한 Java 코드

위험
// ❌ 프로세스 검증 누락
@PostMapping("/transfer/execute")
public String executeTransfer(
    @RequestParam String toAccount,
    @RequestParam int amount) {
    
    // 이전 단계 완료 여부 확인 안 함!
    // OTP 검증 안 함!
    // 세션 토큰 검증 안 함!
    
    Transfer transfer = new Transfer();
    transfer.setToAccount(toAccount);
    transfer.setAmount(amount);
    
    // 바로 이체 실행
    transferService.execute(transfer);
    
    return "success";
}

공격 실행 결과

대기중

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

안전한 Java 코드

권장
// ✅ 프로세스 검증 구현

// 1단계: 이체 정보 입력
@PostMapping("/transfer/step1")
public String inputTransfer(Transfer transfer, 
                           HttpSession session) {
    
    // 프로세스 토큰 생성
    String processToken = UUID.randomUUID().toString();
    session.setAttribute("processToken", processToken);
    session.setAttribute("step1Data", transfer);
    session.setAttribute("currentStep", 1);
    
    return "redirect:/transfer/step2";
}

// 2단계: 이체 정보 확인
@PostMapping("/transfer/step2")
public String confirmTransfer(HttpSession session) {
    
    // Step 1 완료 여부 검증
    if (session.getAttribute("currentStep") == null ||
        (int)session.getAttribute("currentStep") != 1) {
        throw new InvalidProcessException();
    }
    
    // OTP 토큰 생성 및 SMS 발송
    String otpToken = generateOTP();
    session.setAttribute("otpToken", otpToken);
    session.setAttribute("otpExpiry", 
        System.currentTimeMillis() + 180000); // 3분
    
    sendSMS(otpToken);
    session.setAttribute("currentStep", 2);
    
    return "redirect:/transfer/step3";
}

// 3단계: OTP 인증
@PostMapping("/transfer/step3")
public String verifyOTP(String inputOTP, 
                       HttpSession session) {
    
    // Step 2 완료 여부 검증
    if ((int)session.getAttribute("currentStep") != 2) {
        throw new InvalidProcessException();
    }
    
    // OTP 검증
    String savedOTP = (String)session.getAttribute("otpToken");
    long expiry = (long)session.getAttribute("otpExpiry");
    
    if (System.currentTimeMillis() > expiry) {
        throw new OTPExpiredException();
    }
    
    if (!inputOTP.equals(savedOTP)) {
        throw new InvalidOTPException();
    }
    
    // 최종 실행 토큰 생성 (일회용)
    String executeToken = UUID.randomUUID().toString();
    session.setAttribute("executeToken", executeToken);
    session.setAttribute("currentStep", 3);
    
    return "redirect:/transfer/execute";
}

// 4단계: 이체 실행
@PostMapping("/transfer/execute")
public String executeTransfer(String executeToken,
                             HttpSession session) {
    
    // Step 3 완료 여부 검증
    if ((int)session.getAttribute("currentStep") != 3) {
        throw new InvalidProcessException();
    }
    
    // 실행 토큰 검증 (일회용)
    String savedToken = (String)session.getAttribute("executeToken");
    if (!executeToken.equals(savedToken)) {
        throw new InvalidTokenException();
    }
    
    // 토큰 즉시 삭제 (재사용 방지)
    session.removeAttribute("executeToken");
    
    // 프로세스 토큰 검증
    String processToken = (String)session.getAttribute("processToken");
    if (processToken == null) {
        throw new InvalidProcessException();
    }
    
    // 이체 실행
    Transfer transfer = (Transfer)session.getAttribute("step1Data");
    transferService.execute(transfer);
    
    // 모든 세션 데이터 삭제
    session.removeAttribute("processToken");
    session.removeAttribute("step1Data");
    session.removeAttribute("currentStep");
    
    return "success";
}

프로세스 검증 체크리스트

단계별 토큰: 각 단계마다 고유 토큰 생성
이전 단계 검증: currentStep 확인
토큰 일회용: 사용 후 즉시 삭제
시간 제한: OTP 3분 유효기간
세션 정리: 완료 후 모든 데이터 삭제