Back to Blog
Race ConditionTOCTTOUDirty COW심볼릭링크

0x0C. Race Condition 공격과 Dirty COW

Race Condition 취약점의 원리와 TOCTTOU 공격, 그리고 9년간 숨어있던 Dirty COW 취약점까지

Race Condition이란?

Race Condition(경쟁 조건)은 다음 상황에서 발생한다:

  • 여러 프로세스가 동일한 데이터에 동시에 접근하여 조작할 때
  • 실행 결과가 특정 순서에 의존할 때

권한이 있는 프로그램(privileged program)에 Race Condition이 존재하면, 공격자는 제어할 수 없는 이벤트에 영향을 주어 프로그램의 출력을 조작할 수 있다.

두 개의 동시 실행 스레드가 공유 자원에 접근할 때, 스레드나 프로세스의 타이밍에 따라 의도치 않게 다른 결과가 발생하는 것이 핵심이다.

Race Condition 개념도

TOCTTOU (Time-Of-Check To Time-Of-Use)

TOCTTOU는 Race Condition의 특수한 유형이다. 리소스 사용 전 조건을 확인하는 과정에서 발생한다.

TOCTTOU 취약한 코드

위 프로그램의 동작을 분석해보면:

  • /tmp 디렉토리에 파일을 쓴다 (world-writable, 모든 사용자가 쓰기 권한 보유)
  • root는 모든 파일에 쓸 수 있으므로, 프로그램은 실제 사용자가 대상 파일에 쓰기 권한이 있는지 확인한다
  • access() 시스템 콜이 real user id로 /tmp/X에 대한 쓰기 권한을 체크한다
  • 체크 후, 파일이 쓰기 모드로 열린다
  • 하지만 open()effective user id (0, 즉 root)를 체크하므로 파일이 열린다

취약점 분석

목표: /etc/passwd 같은 보호된 파일에 쓰기

  • /etc/passwd는 root만 수정 가능하고, 읽기는 모든 사용자가 가능하다

이 목표를 달성하려면 프로그램의 파일 이름을 변경하지 않고 /etc/passwd를 대상 파일로 만들어야 한다. 여기서 Symbolic Link(심볼릭 링크)가 사용된다. 심볼릭 링크는 다른 파일을 가리키는 특수한 파일이다.

문제점

프로그램은 초당 수십억 개의 명령어를 실행하므로, check와 use 사이의 시간 창은 매우 짧다. 이 시간 안에 심볼릭 링크를 변경하는 것은 거의 불가능하다.

  • 변경이 너무 빠르면: access()가 실패한다
  • 변경이 조금 늦으면: 프로그램이 파일 사용을 완료한다
TOCTTOU 타이밍 문제

Race Condition 공격 성공 조건

TOCTTOU 윈도우에서 경쟁에서 이기려면 두 개의 프로세스가 필요하다:

  1. 취약한 프로그램을 루프에서 실행
  2. 공격 프로그램 실행
공격 시나리오

위 그림이 핵심이다.

공격 타이밍

공격 실습

실험 환경 설정

실험 환경 1 실험 환경 2

Race Condition 공격 방법

1. 대상 파일 선택

/etc/passwd에 새 사용자를 추가하기 위해 다음 라인을 추가한다:

passwd 파일 형식

2. 공격 실행

공격 프로세스:

공격 프로세스 코드

취약한 프로세스:

취약한 프로세스 코드

두 프로세스가 서로 경쟁한다: 취약한 프로세스 vs 공격 프로세스

3. 익스플로잇 실행

익스플로잇 실행 결과

대응 방안

1. Atomic Operations

check와 use 사이의 윈도우를 제거하는 방법이다.

Atomic Operations 코드

두 옵션을 함께 사용하면 파일이 이미 존재할 경우 열리지 않는다. 이는 check와 use의 원자성을 보장한다.

이 방식은 아이디어 수준이며, 실제 시스템에 구현되어 있지 않다. 이 옵션을 사용하면 open()이 real user ID만 체크하므로, check와 use를 단독으로 수행하여 연산이 원자적으로 이루어진다.

2. Repeating Check and Use

"경쟁"에서 이기기 어렵게 만드는 방법이다.

반복 체크 방식

심볼릭 링크 생성을 방지하는 방법이다.

world-writable sticky 디렉토리에 대한 sticky symlink 보호를 활성화하려면:

Sticky Symlink Protection 설정

sticky symlink 보호가 활성화되면, sticky world-writable 내의 심볼릭 링크는 symlink 소유자follower(공격자) 또는 디렉토리 소유자와 일치할 때만 따라갈 수 있다.

Symlink Protection 동작 Symlink Protection 예시

Symlink 보호는 symlink 소유자가 follower(프로세스의 EID) 또는 디렉토리 소유자와 일치할 때 fopen()을 허용한다.

취약한 프로그램에서 EID는 root이고, /tmp 디렉토리도 root 소유이므로, root가 생성한 심볼릭 링크가 아니면 프로그램이 따라가지 않는다.

open(..., NOFOLLOW)를 사용하면 심볼릭 링크를 따라가지 않고 원본 파일만 열 수 있다.

4. Principles of Least Privilege

공격자가 경쟁에서 이긴 후의 피해를 방지하는 방법이다.

프로그램은 작업에 필요한 것 이상의 권한을 사용해서는 안 된다.

취약한 프로그램은 파일을 열 때 필요 이상의 권한을 가지고 있다. seteuid()setuid()를 사용하여 권한을 폐기하거나 일시적으로 비활성화할 수 있다.

최소 권한 원칙 적용

Q&A

Q. 최소 권한 원칙을 Buffer Overflow 공격 방어에도 사용할 수 있을까? 취약한 함수 실행 전 root 권한을 비활성화하고, 함수 반환 후 다시 활성화하는 방식으로?

A. 불가능하다. BOF는 권한과 관계없이 buffer, stack과 관련된 취약점이기 때문이다.


Dirty COW 취약점

Dirty COW는 Race Condition 취약점의 흥미로운 사례다.

  • 2007년 9월부터 Linux 커널에 존재했으나, 2016년 10월에 발견 및 공격됨
  • CVE-2016-5195
  • Android를 포함한 모든 Linux 기반 운영체제에 영향
  • 결과:
    • /etc/passwd 같은 보호된 파일 수정 가능
    • 취약점을 이용해 root 권한 획득 가능
  • Linux 커널 버전 4.8.3 이후에 패치됨
  • Dirty COW 공격 실행에는 Ubuntu 12.04가 필요함

Memory Mapping 기초

mmap() 함수

mmap 함수 원형

파일이나 디바이스를 메모리에 매핑하는 함수다. 호출 프로세스의 가상 주소 공간에 새로운 매핑을 생성한다.

  • addr: 매핑된 메모리의 시작 주소
  • length: 매핑된 메모리의 크기
  • prot: 실행, 읽기, 쓰기 같은 원하는 메모리 보호
  • flags: 매핑 업데이트가 같은 영역을 매핑하는 다른 프로세스에 visible한지(공유 메모리처럼), 그리고 업데이트가 기본 파일에 반영되는지 결정
  • fd: 매핑할 파일
  • offset: 파일 내부에서 매핑이 시작되어야 하는 위치를 나타내는 오프셋

munmap() 함수

munmap 함수 원형

파일이나 디바이스의 매핑을 해제한다. 지정된 주소 범위에 대한 매핑을 삭제하고, 해당 범위 내 주소에 대한 이후 참조는 잘못된 메모리 참조를 생성한다.

  • addr: 주소는 페이지 크기의 배수여야 함 (length는 그럴 필요 없음)
  • 반환 값:
    • 성공: 0
    • 실패: -1, errno 설정
Memory Mapping 개념도

MAP_SHARED vs MAP_PRIVATE

MAP_SHARED

매핑된 메모리가 두 프로세스 간의 공유 메모리처럼 동작한다. 여러 프로세스가 같은 파일을 메모리에 매핑할 때, 다른 가상 메모리 주소에 매핑할 수 있지만, 파일 내용이 저장된 물리 주소는 동일하다.

MAP_SHARED 동작

MAP_PRIVATE

파일이 호출 프로세스에 private한 메모리에 매핑된다. 메모리 변경 사항이 다른 프로세스에 보이지 않는다. 원본 메모리의 내용을 private 메모리에 복사해야 한다. 프로세스가 메모리에 쓰려고 하면, OS는 새 물리 메모리 블록을 할당하고 마스터 복사본의 내용을 새 메모리에 복사한다.

MAP_PRIVATE 동작

Copy-On-Write (COW)

동일한 내용을 가진 경우, 다른 프로세스의 가상 메모리가 같은 물리 메모리 페이지에 매핑되도록 하는 기술이다.

fork() 시스템 콜로 자식 프로세스가 생성될 때:

OS는 페이지 엔트리가 같은 물리 메모리를 가리키도록 하여 자식 프로세스가 부모 프로세스의 메모리를 공유하게 한다. 메모리가 읽기만 되면, 메모리 복사가 필요하지 않다.

Copy-On-Write 개념

복사된 메모리 폐기: madvise()

madvise 함수 원형

메모리 사용에 대한 조언을 제공하는 함수다. 커널에 주소 addr에서 시작하고 크기가 length인 주소 범위에 대한 조언이나 지시를 제공한다.

  • advice: 커널에 매핑되거나 공유된 메모리 영역을 어떻게 사용할 것인지 알려주어, 커널이 적절한 read-ahead 및 캐싱 기술을 선택할 수 있게 함
  • MADV_DONTNEED: 커널에 해당 주소 부분이 더 이상 필요 없다고 알림. 커널은 해당 주소의 리소스를 해제하고 프로세스의 페이지 테이블이 원본 물리 메모리를 다시 가리키게
  • 반환 값:
    • 성공: 0
    • 실패: -1, errno 설정

읽기 전용 파일 매핑

읽기 전용 파일 매핑 코드

일반적으로 읽기 전용 메모리에 쓸 수 없다. 그러나 파일이 MAP_PRIVATE로 매핑되면, OS는 예외를 두어 매핑된 메모리에 쓸 수 있게 한다. 단, memcpy() 같은 메모리 연산을 직접 사용하는 대신 다른 경로를 사용해야 한다.

write() 시스템 콜이 그러한 경로다.

write 시스템 콜 사용

/proc/self/mem: 할당된 virtual memory에 접근할 때 이 경로를 사용한다.

메모리 접근 코드 메모리 수정 결과

메모리가 수정되어 변경된 내용을 볼 수 있다. 하지만 변경은 매핑된 메모리의 복사본에만 있으며, 기본 파일은 변경되지 않는다.


Dirty COW 취약점의 원리

Copy-On-Write에서 세 가지 중요한 단계가 수행된다:

  • Step A: 매핑된 메모리의 복사본 생성
  • Step B: 페이지 테이블 업데이트, 가상 메모리가 새로 생성된 물리 메모리를 가리키게 함
  • Step C: 메모리에 쓰기

위 단계들은 본질적으로 원자적이지 않다. 다른 스레드에 의해 중단될 수 있으며, 이것이 Dirty COW 취약점으로 이어지는 잠재적 Race Condition을 만든다.

Dirty COW 원리

madvise()가 Step B와 C 사이에 실행되면:

  1. Step B가 가상 메모리를 2를 가리키게 함
  2. madvise()가 다시 1을 가리키도록 변경 (Step B 무효화)
  3. Step C가 private 복사본 대신 1로 표시된 물리 메모리를 수정
  4. 1로 표시된 메모리의 변경이 기본 파일에 반영되어, 읽기 전용 파일이 수정됨

write() 시스템 콜이 시작될 때, 매핑된 메모리의 보호를 확인한다. COW 메모리임을 보면, 다시 확인하지 않고 A, B, C를 트리거한다.


Dirty COW 공격 구현

기본 아이디어

두 개의 스레드를 실행해야 한다:

  • Thread 1: write()를 사용해 매핑된 메모리에 쓰기
  • Thread 2: 매핑된 메모리의 private 복사본 폐기

이 스레드들을 서로 경쟁시켜 출력에 영향을 줘야 한다.

대상 파일: /etc/passwd

이 파일은 읽기 전용이므로 non-root 사용자는 수정할 수 없다.

passwd 파일 구조

세 번째 필드는 사용자의 User-ID를 나타낸다 (Root는 0). 자신의 레코드(사용자 testcow)의 세 번째 필드를 0으로 변경할 수 있다면, root로 전환할 수 있다.

공격: 메인 스레드

메인 스레드 코드

메모리 매핑 및 스레드 설정:

  1. /etc/passwd 파일을 읽기 전용 모드로 열기
  2. MAP_PRIVATE로 메모리 매핑
  3. 대상 파일에서 위치 찾기
  4. madvise() 스레드 생성
  5. write() 스레드 생성

공격: 두 개의 스레드

두 스레드 코드

write 스레드: 메모리에서 "testcow:x:1001" 문자열을 "testcow:x:0000"으로 교체

madvise 스레드: 페이지 테이블이 원본 매핑된 메모리를 다시 가리키도록 매핑된 메모리의 private 복사본을 폐기

결과

공격 결과

HGU 전산전자공학부 고윤민 교수님의 24-2 컴퓨터 보안 수업을 듣고 작성한 포스트이며, 첨부한 모든 사진은 교수님 수업 PPT의 사진 원본에 필기를 한 수정본입니다.