레이스 컨디션 공격과 동시성 보안 설계

TOCTTOU, 파일 조작, 웹 동시 요청에서 발생하는 레이스 컨디션 공격의 원리와 탐지·방어 방법을 정리한다.

2026-08-14 · 최초 발행 2025-05-26

검사와 사용 사이에 생기는 공격 창

레이스 컨디션 공격은 멀티스레드나 병렬 처리 환경에서 공유 자원에 접근하는 시간차를 악용한다. 여러 프로세스 또는 스레드가 파일, 메모리, 변수 같은 자원을 함께 다룰 때 실행 순서가 달라지면 정상 흐름이 깨질 수 있다. 공격자는 그 틈에 자원 상태를 바꿔 시스템 동작을 방해하거나 권한을 얻으려 한다.

문제는 대체로 자원을 검사하는 단계와 실제 사용하는 단계가 분리되어 있을 때 시작된다. 검사와 사용이 하나의 원자적 연산이 아니면, 공격자는 그 사이에 상태를 변경할 수 있다.

PBSRPAPB["공격자"]SR["공유 자원"]PA["프로세스 A"]PBSRPAPB["공격자"]SR["공유 자원"]PA["프로세스 A"]자원 사용 전 검증취약 시간 간격 이용변경된 상태의 자원 사용자원 검사(check)자원 상태 변경자원 사용(use)

공격이 발생하는 지점

TOCTTOU로 바뀌는 파일 대상

TOCTTOU(Time of Check to Time of Use)는 자원을 확인한 때와 사용하는 때 사이의 간격을 겨냥하는 가장 일반적인 형태다. 권한 확인 뒤 파일을 여는 구조가 대표적이다.

# 취약한 코드 예시
def process_file(filename):
    # 파일 존재 및 권한 확인 (Time of Check)
    if os.access(filename, os.R_OK):
        # 일정 시간 경과...
        # 파일 처리 (Time of Use)
        with open(filename, 'r') as f:
            data = f.read()
            process_data(data)

공격자는 os.access()open() 사이에 심볼릭 링크를 변경해 중요 파일에 접근할 수 있다.

임시 파일 검사와 생성의 간격

임시 파일을 만들기 전에 존재 여부를 확인하는 방식도 경쟁 조건을 만들 수 있다. 검사와 생성이 분리되어 있으면, 공격자는 그 사이에 심볼릭 링크를 생성할 수 있다.

NoYes프로그램이 임시 파일 필요 여부확인파일 존재 여부 검사파일 존재? 파일 생성다른 이름으로 시도파일 사용공격자검사와 생성 사이에심볼릭 링크 생성

동시 요청으로 우회되는 웹 로직

웹 애플리케이션에서는 동일한 요청을 동시에 보내 비즈니스 로직의 순서를 흔들 수 있다. 포인트 충전 과정이 잔액 확인, 차감, 포인트 증가로 나뉘어 있다면 여러 요청이 모두 충분한 잔액이라고 판단할 수 있다. 그 결과 실제 잔액보다 많은 포인트가 충전될 수 있다.

1. 사용자가 포인트 충전 요청
2. 시스템이 잔액 확인
3. 충전 금액만큼 차감 처리
4. 포인트 증가

동기화되지 않은 공유 변수

공유 변수의 읽기·수정·쓰기가 동기화되지 않으면 증가 연산 같은 단순한 작업도 경쟁 조건에 노출된다.

// 취약한 코드 예시
public class UnsafeCounter {
    private int count = 0;

    // 동기화되지 않은 메소드
    public void increment() {
        count++; // 원자적 연산이 아님
    }

    public int getCount() {
        return count;
    }
}

알려진 취약점과 악용 사례

Linux 커널 ptrace 시스템 콜의 CVE-2003-0127은 setuid 실행 파일을 실행하는 과정에서 프로세스 추적이 가능했던 레이스 컨디션 취약점이다. 로컬 권한 상승 공격에 활용됐다.

Dirty COW(CVE-2016-5195)는 Linux 커널의 Copy-On-Write 메커니즘에서 발생한 취약점이다. 읽기 전용 메모리 매핑에 쓰기 작업을 수행하는 과정에서 경쟁 조건이 발생하며, 공격자는 읽기 전용 시스템 파일을 수정할 수 있었다. 이 취약점은 약 9년간 Linux 커널에 존재했다.

비트코인 네트워크에서는 트랜잭션 가변성(Transaction Malleability) 문제와 트랜잭션 검증·처리 사이의 시간차를 이용한 공격이 거론됐다. 일부 거래소에서는 이중 인출 사고가 발생했다.

상태 변경을 막는 구현 방식

공유 상태를 다룰 때는 중간 상태가 외부에 드러나지 않도록 원자적 연산을 사용한다.

// 안전한 코드 예시
import java.util.concurrent.atomic.AtomicInteger;

public class SafeCounter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet(); // 원자적 연산
    }

    public int getCount() {
        return count.get();
    }
}

공유 자원에 대한 검사와 사용을 함께 보호해야 할 때는 락으로 상호 배제를 보장한다.

// 뮤텍스를 사용한 안전한 코드
public class SafeResource {
    private final Object lock = new Object();
    private Resource resource;

    public void useResource() {
        synchronized(lock) {
            // 자원 검사
            if (resource.isAvailable()) {
                // 자원 사용
                resource.use();
            }
        }
    }
}

데이터베이스 작업은 트랜잭션의 ACID 특성을 이용해 일관성을 유지할 수 있다.

-- 트랜잭션을 활용한 안전한 포인트 충전
BEGIN TRANSACTION;
-- 잔액 확인
SELECT balance FROM accounts WHERE user_id = 123 FOR UPDATE;
-- 잔액 차감
UPDATE accounts SET balance = balance - 100 WHERE user_id = 123;
-- 포인트 증가
UPDATE points SET amount = amount + 100 WHERE user_id = 123;
COMMIT;

임시 파일은 안전한 생성 API를 사용해 검사와 생성 사이의 공격 가능성을 줄인다.

# 안전한 임시 파일 생성
import tempfile

# 자동으로 유일한 파일명 생성
with tempfile.NamedTemporaryFile(delete=True) as temp:
    # 파일 사용
    temp.write(b"Data")
    temp.flush()
    # 파일 처리

중요 파일을 처리할 때는 검사 뒤 상태를 다시 확인해 변경 여부를 감지할 수 있다.

def process_sensitive_file(filename):
    initial_stat = os.stat(filename)

    # 파일 검사
    if is_safe_file(filename):
        # 상태 재확인
        current_stat = os.stat(filename)
        if initial_stat.st_ino != current_stat.st_ino:
            raise SecurityException("File changed between checks")

        # 파일 사용
        with open(filename, 'r') as f:
            data = f.read()
            process_data(data)

개발 과정에서 찾고 줄이는 방법

정적 코드 분석은 잠재적인 레이스 컨디션 패턴을 찾는 데 사용된다. ThreadSanitizer, RacerD 같은 동시성 이슈 분석 도구도 활용할 수 있다.

동적 분석과 퍼징에서는 다양한 타이밍으로 입력을 발생시켜 비일관적인 동작을 찾는다. 경쟁 조건 전문 퍼저를 사용하는 방법도 있다. 형식 검증(Formal Verification)은 수학적 모델링과 모델 체킹 도구로 상태 공간을 탐색해 동시성 문제를 검증한다.

동시성 보안은 구현 후에만 확인할 문제가 아니다. 설계 단계에서 위협 모델링을 수행하고, 공유 자원과 경쟁 조건이 발생할 수 있는 지점을 식별한 뒤 안전한 패턴을 적용해야 한다.

설계 단계위협 모델링동시성 취약점 식별안전한 패턴 적용코드 구현정적/동적 분석안전성 검증

CERT 보안 코딩 표준의 동시성 관련 규칙을 따르고 안전한 API와 라이브러리를 선택하는 것도 대응의 일부다. 프로세스 실행 권한과 파일 시스템 접근 권한을 최소화하며, 비정상적인 동시 요청 패턴과 중요 작업의 상태 변경을 기록해 운영 중 징후를 확인해야 한다.

레이스 컨디션동시성 보안TOCTTOU보안 코딩동기화