비즈니스 로직 결함: 공격자가 앱에서 단계를 건너뛰어 절대 가져서는 안 될 것을 얻는 방법

발행: (2026년 5월 23일 PM 09:37 GMT+9)
8 분 소요
원문: Dev.to

출처: Dev.to

비즈니스 로직 결함은 코딩 실수 때문에 발생하는 것이 아니라, 애플리케이션이 자체 워크플로우를 지나치게 신뢰하기 때문에 발생하는 취약점입니다. 버퍼 오버플로우를 이용하거나 코드를 주입하는 대신, 공격자는 단순히 단계들을 건너뛰거나, 행동을 반복하거나, 요청 순서를 바꿔서 애플리케이션이 의도하지 않은 결과를 얻습니다. 이런 종류의 결함은 계정 업그레이드, 주문 흐름, 승인 체인, 접근 제어 검사와 같은 다단계 프로세스에서 흔히 나타납니다. 결제 우회가 대표적인 예이지만, 동일한 근본 원인은 다양한 기능에서 반복됩니다.

  • 소유하거나 테스트 허가를 받은 시스템만 테스트하세요
  • 로컬 실험실이나 명확한 범위가 정의된 버그 바운티 프로그램을 이용하세요
  • 허가 없이 운영 시스템에 이러한 기법을 사용하지 마세요
  • 발견 사항은 애플리케이션 소유자에게 책임 있게 보고하세요

로직 결함은 애플리케이션이 단계 순서를 신뢰하고 서버 측에서 각 행동을 검증하지 않을 때 발생합니다. 공격자는 요청을 건너뛰거나, 반복하거나, 순서를 바꿔서 애플리케이션이 도달할 수 없다고 가정한 상태에 도달할 수 있습니다.

흔히 나타나는 시나리오

  • 결제 우회: 결제 단계를 건너뛰어 유료 기능을 무료로 이용
  • 권한 상승: 관리자 엔드포인트에 직접 접근
  • 주문 조작: 다단계 결제 과정에서 아이템 수량이나 가격을 변경
  • 승인 건너뛰기: 워크플로우에서 검토 또는 인증 단계를 건너뛰기

아래 예시는 결제 우회를 이용해 이 원리를 보여줍니다. 애플리케이션은 세 단계 업그레이드 프로세스를 가지고 있습니다: 플랜 선택 → 결제 제출 → 활성화 확인. 확인 엔드포인트는 결제가 실제로 완료되었는지 검증하지 않고 바로 Pro 상태를 부여합니다.

다이어그램은 공격자가 2단계를 건너뛰고 확인 엔드포인트에 직접 요청함으로써 어떻게 우회할 수 있는지를 보여줍니다.

취약한 코드는 사용자가 확인 페이지에 도달했다면 반드시 결제했을 것이라고 가정합니다. 실제 결제 여부를 확인하지 않고 바로 Pro 상태를 부여합니다.

// /upgrade/confirmed.php

// Logic flaw: The code assumes if you are here, you must have paid.
$user_id = $_SESSION['user_id'];

// Directly updating the database to 'pro' status
$sql = "UPDATE users SET membership = 'pro' WHERE id = '$user_id'";
$db->query($sql);

echo "Congratulations! You are now a Pro member.";

공격자는 확인 엔드포인트에 직접 요청을 보내 결제 단계를 우회할 수 있습니다:

curl http:///upgrade/confirmed

서버는 요청을 처리하고 결제 검증 없이 사용자의 멤버십을 pro로 업데이트합니다. 데이터베이스에는 거래가 전혀 일어나지 않았음에도 불구하고 사용자가 Pro 상태인 것으로 표시됩니다.

수정된 코드는 멤버십을 업데이트하기 전에 서버 측에서 결제 완료 여부를 확인합니다. 데이터베이스에 유효한 거래 ID 또는 결제 상태가 있는지 검사해야 합니다.

// /upgrade/confirmed.php (fixed)

$user_id = $_SESSION['user_id'];

// Check for a completed payment transaction using a prepared statement
$stmt = $db->prepare(
    "SELECT id FROM transactions WHERE user_id = ? AND status = 'completed' ORDER BY created_at DESC LIMIT 1"
);
$stmt->bind_param('i', $user_id);
$stmt->execute();
$transaction = $stmt->get_result()->fetch_assoc();

if (!$transaction) {
    http_response_code(403);
    echo "Payment required.";
    exit;
}

// Only grant Pro status after verifying payment
$update = $db->prepare("UPDATE users SET membership = 'pro' WHERE id = ?");
$update->bind_param('i', $user_id);
$update->execute();

echo "Congratulations! You are now a Pro member.";

수정된 버전은 멤버십을 업데이트하기 전에 완료된 거래가 있는지 확인합니다. 결제가 없는 직접 요청은 이제 403 오류를 반환합니다.

비즈니스 로직 결함은 자동 스캐너로 잡기 어렵습니다. 이는 애플리케이션이 수행해야 할 동작을 이해해야 하기 때문입니다. 공격자는 코드를 깨뜨리는 것이 아니라, 의도하지 않은 순서로 코드를 이용합니다. 중요한 작업마다 서버 측에서 상태를 검증하고, 최종 단계에서만 검증하지 마세요. 시스템을 테스트할 때는 단계 건너뛰기, 요청 순서 뒤바꾸기, 단계 사이 값 변경 등을 시도해 보면서 공격자보다 먼저 이러한 빈틈을 찾아야 합니다.

이 글이 도움이 되었다면 좋아요를 눌러주시고, 보안 공부 중인 사람과 공유해주세요. 질문이 있거나, 직접 실험하면서 다른 상황을 겪었거나, 결과를 공유하고 싶다면 아래에 댓글을 남겨 주세요. 보안, 리콘 기법, 혹은 AppSec 전반에 대해 언제든 이야기 나누고 싶습니다.

Feel free to connect with me on LinkedIn

보안, 개발, 혹은 두 분야 모두에 관심 있는 분들과 언제든 연결하고 싶습니다. 무언가를 만들든, 부수든, 혹은 이제 시작하든, 편하게 연락 주세요.

0 조회
Back to Blog

관련 글

더 보기 »

내 스킬

프로젝트를 위한 AI 지시문을 만들고, 설치하고, 관리하세요 — 코딩이 필요 없습니다. CREATE 이름을 정하고, 카테고리를 선택하고, 원하는 것을 설명하세요 — 마법사가 자동으로 구성합니다.