[주요정보통신기반시설 웹 애플리케이션 취약점 분석·평가 21일 완성 코스] 13강 — [Day 13] 프로세스 검증 누락
[Day 13] 프로세스 검증 누락: 다단계 로직과 데이터 정합성 파괴를 막는 법
안녕하세요! Hackerstory 공부방의 IT/보안 튜터 정다은입니다. 👋
어느덧 **'주요정보통신기반시설 웹 애플리케이션 취약점 분석·평가 21일 완성 코스'**의 13번째 시간입니다! 절반을 넘어서며 조금 더 깊이 있는 **비즈니스 로직 취약점(Business Logic Vulnerability)**의 세계로 들어왔습니다.
지난 시간에는 비밀번호를 찾는 과정에서 발생하는 허점을 다룬 [Day 12] 취약한 비밀번호 복구 절차를 공부했었죠. 오늘은 사용자가 회원가입, 상품 결제, 본인인증 등 여러 단계를 거쳐 진행되는 프로세스(Step-by-step Process)를 우회하거나 데이터를 변조할 수 있는 '프로세스 검증 누락' 취약점에 대해 다뤄보겠습니다.
단순한 입력을 검증하는 것(SQLi, XSS 등)을 넘어, 웹 애플리케이션의 흐름(Flow)을 제대로 제어하지 못할 때 발생하는 치명적인 취약점이니 집중해서 따라와 주세요!
1. 개념 설명: 프로세스 검증 누락이란?
💡 정의
**'프로세스 검증 누락(Process Verification Omission)'**은 다단계(Multi-step)로 이루어진 비즈니스 로직에서 서버가 사용자의 현재 단계 진행 상태(State)나 입력된 데이터의 정합성을 철저히 검증하지 않아 발생하는 취약점입니다.
웹 애플리케이션의 핵심 비즈니스 로직은 대부분 여러 단계를 거칩니다.
- 결제 프로세스: 장바구니 담기(Step 1) ➡️ 배송지 입력(Step 2) ➡️ 결제 금액 확정(Step 3) ➡️ PG사 결제 요청(Step 4) ➡️ 주문 완료(Step 5)
- 회원가입 프로세스: 약관 동의(Step 1) ➡️ 본인 인증(Step 2) ➡️ 정보 입력(Step 3) ➡️ 가입 완료(Step 4)
이때 서버가 "이 사용자가 정말 Step 2를 통과하고 Step 3에 도달했는가?" 혹은 **"Step 3에서 보낸 결제 금액이 Step 1에서 정해진 금액과 동일한가?"**를 세션 기반으로 검증하지 않으면 문제가 발생합니다.
🛑 주요 발생 유형
- 단계 건너뛰기 (Step Skipping / Forceful Browsing)
- 본인 인증 단계(Step 2)를 거치지 않고 곧바로 가입 완료 URL(Step 4)을 직접 호출하여 가입을 완료함.
- 파라미터 변조 (Parameter Tampering)
- 결제 요청 시 클라이언트(브라우저)에서 전송하는
amount(금액) 파라미터를 50,000원에서 100원으로 수정하여 결제함.
- 결제 요청 시 클라이언트(브라우저)에서 전송하는
- 상태 무효화 우회 (State Invalidation Bypass)
- 이미 처리 완료된 주문 프로세스를 요청 파라미터 재전송(Replay)을 통해 중복 처리되게 만듦.
2. 취약점 동작 원리 및 예시
그렇다면 실제 코드 수준에서는 왜 이런 취약점이 발생하는지, 취약한 예시와 안전한 예시를 통해 살펴보겠습니다.
🚨 시나리오: 쇼핑몰 결제 금액 변조 및 단계 우회
[취약한 코드 예시 (Java / Spring Controller)]
@PostMapping("/pay/submit")
public ResponseEntity<?> processPayment(@RequestParam String orderId,
@RequestParam int amount,
HttpServletRequest request) {
// ❌ 취약점 1: 클라이언트가 보낸 amount(결제 금액)를 서버 재검증 없이 그대로 사용함
// ❌ 취약점 2: 이전 단계(장바구니 확인, 재고 확인 등)를 거쳐왔는지 세션 상태를 검증하지 않음
boolean result = paymentService.pay(orderId, amount);
if (result) {
return ResponseEntity.ok("결제가 성공적으로 완료되었습니다.");
}
return ResponseEntity.status(500).body("결제 실패");
}
[공격 시나리오]
- 사용자는 1,000,000원짜리 노트북을 장바구니에 담습니다.
- 결제 버튼을 누르는 순간 Burp Suite 같은 프록시 툴을 이용해 HTTP Request 패킷을 캡처합니다.
- POST 요청 파라미터인
amount=1000000을amount=1로 변경하여 서버로 전송합니다. - 서버는 DB에 저장된 실제 가격과 비교하지 않고 클라이언트가 보낸
amount=1을 기반으로 PG사에 결제 승인을 요청합니다. - 결과적으로 공격자는 1원만 내고 100만 원짜리 상품을 구매하게 됩니다.
[보안 대책이 적용된 코드 예시 (Java / Spring Controller)]
취약점을 해결하기 위해서는 ① 세션 상태 기반의 단계 검증과 ② DB에 저장된 원본 데이터와의 정합성 검증이 필수적입니다.
@PostMapping("/pay/submit")
public ResponseEntity<?> processPayment(@RequestParam String orderId,
HttpSession session) {
// ✅ 1. 세션 상태 검증: 사용자가 전 단계(배송지 입력 및 결제 준비 단계)를 올바르게 통과했는지 확인
String currentStep = (String) session.getAttribute("PAYMENT_STEP");
if (!"STEP_READY_TO_PAY".equals(currentStep)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("올바르지 않은 접근 순서입니다.");
}
// 로그인된 사용자 정보 세션에서 가져오기
User user = (User) session.getAttribute("loginUser");
// ✅ 2. 서버 측 데이터 정합성 검증: DB에서 해당 주문의 '실제 금액'을 조회
Order pendingOrder = orderService.getPendingOrder(orderId, user.getId());
if (pendingOrder == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("주문 정보가 존재하지 않습니다.");
}
// 클라이언트가 보낸 금액 파라미터를 믿지 않고, DB에서 가져온 실제 가격으로 결제 진행
int actualAmount = pendingOrder.getCalculateTotalAmount();
boolean result = paymentService.pay(orderId, actualAmount);
if (result) {
// ✅ 3. 결제 완료 후 세션 상태 업데이트 또는 파기 (재요청 방지)
session.removeAttribute("PAYMENT_STEP");
return ResponseEntity.ok("결제가 성공적으로 완료되었습니다.");
}
return ResponseEntity.status(500).body("결제 실패");
}
3. 실무 진단 및 분석 방법 (Diagnostic Method)
보안 진단원으로서 웹 애플리케이션의 프로세스 검증 누락 항목을 점검할 때는 다음과 같은 수순으로 진단을 수행합니다.
🔍 1) 단계별 순서 우회 진단 (Forceful Browsing)
- 진단 절차:
- 다단계 프로세스(예: 회원가입 1단계 ~ 4단계)를 정상적으로 진행하며 각 단계의 URL 및 Request 데이터를 기록합니다.
- 새로운 세션(또는 인증되지 않은 세션)으로 접속하여 1~3단계를 거치지 않고 곧바로 **4단계(완료 처리 URL)**에 Direct GET/POST 요청을 보냅니다.
- 판정 기준:
- 이전 단계를 거치지 않았음에도 4단계 결과가 정상 수행된다면 취약(Vulnerable).
- "이전 단계를 먼저 진행해주세요" 등의 에러 메시지와 함께 차단되면 양호(Safe).
🔍 2) 중요 파라미터 변조 진단 (Parameter Manipulation)
- 진단 절차:
- 결제, 수량 변경, 포인트 차감 등의 기능 동작 시 HTTP Request를 프록시 툴로 캡처합니다.
- 금액(
price,amount), 수량(quantity), 할인율(discount), 인증 여부(isAuth=Y) 등의 파라미터 값을 임의로 변경(음수, 0, 극단적인 적은 금액 등)하여 전송합니다.
- 판정 기준:
- 변조된 파라미터 값 그대로 처리가 완료되거나 결제가 성공하면 취약(Vulnerable).
- 서버에서 데이터 불일치를 감지하여 요청을 거부하면 양호(Safe).
4. 프로세스 검증 누락 대응 방안 (Remediation)
이 취약점을 근본적으로 막기 위해서는 **"클라이언트에서 전송되는 그 어떤 값도 신뢰하지 않는다"**는 보안의 기본 원칙을 세워야 합니다.
[클라이언트 (브라우저)] [서버 (Server)]
│ │
│ 1. 상품 선택 (노트북) │
├───────────────────────────────────────>│ (DB 조회: 1,000,000원 확인)
│ │ (Session 저장: cart_price=1000000)
│ │ (Session 저장: step=STEP_1_DONE)
│ 2. 결제 요청 (수량: 1) │
├───────────────────────────────────────>│ (Session step 검증)
│ │ (DB/Session 금액 기반 결제 승인)
│ <──────────────────────────────────────┤
│ 3. 결제 완료 │
상태 기반 세션 관리 (Server-side Session State)
- 사용자의 단계 이행 상태를 서버 세션에 기록하고 관리하세요.
- 예:
session.setAttribute("STEP", "AUTH_COMPLETED") - 다음 단계 로직에 진입할 때 반드시 이전 단계 세션 값이 유효한지 검증하고, 처리 완료 시 세션 상태를 갱신하거나 삭제하세요.
서버 측 데이터 정합성 재검증 (Data Integrity Validation)
- 결제 금액, 상품 수량, 할인 쿠폰 적용 여부 등 비즈니스 핵심 데이터는 절대 클라이언트의 Form 파라미터나 Cookie 값으로 판단하지 마세요.
- 클라이언트에서는
productId와 같은 최소한의 식별자만 전달받고, 실제 금액 계산은 DB 정보를 바탕으로 서버 내부에서 로직을 수행해야 합니다.
트랜잭션 일회성 토큰 (Transaction / Nonce Token) 사용
- 각 단계별로 일회성 토큰을 발급하여 다음 단계 제출 시 함께 제출받고, 검증 즉시 폐기함으로써 중복 제출 및 단계 우회를 방지할 수 있습니다.
5. 핵심 정리 📝
| 구분 | 주요 내용 |
|---|---|
| 취약점 개요 | 비즈니스 로직 처리 단계의 순서 및 입력 데이터의 정합성 검증이 누락되어 발생하는 우회 공격 |
| 주요 공격 유형 | - 단계 건너뛰기 (본인인증, 약관동의 우회)<br>- 파라미터 변조 (결제 금액, 상품 수량 조작) |
| 진단 방법 | - 프록시 툴을 이용해 중간 단계를 생략하고 완료 URL direct 호출<br>- POST 요청 내 금액/수량/상태 파라미터 변조 후 전송 |
| 보안 대책 | - 서버 세션 기반의 프로세스 상태 관리 구현<br>- 핵심 데이터(금액 등)는 DB를 참조하여 서버 측에서 최종 재계산 |
🚀 다음 챕터 예고
오늘은 서비스의 비즈니스 논리와 프로세스를 파괴할 수 있는 프로세스 검증 누락에 대해 알아보았습니다. 개발 단계에서 "사용자가 순서대로 클릭하겠지"라는 안일한 생각이 시스템 전체의 신뢰도를 무너뜨릴 수 있다는 점, 꼭 기억해 주세요!
다음 시간에는 웹 애플리케이션 공격의 대명사이이자, 서버 권한 탈취(RCE)로 이어질 수 있는 아주 치명적인 취약점인 [Day 14] 악성 파일 업로드를 다룹니다.
웹 서버에 침투하기 위해 공격자가 웹셸(WebShell)을 어떻게 업로드하는지, 그리고 이를 완벽히 방어하기 위한 확장자 검증 및 저장소 분리 정책을 자세히 배워보겠습니다.
고생 많으셨습니다. 다음 시간에 만나요! 질문은 댓글로 편하게 남겨주세요. 😊
댓글 0
로그인하면 댓글을 작성할 수 있습니다.