273. 동기화 약점 (TOCTOU: Time of Check to Time of Use)
⚠️ 이 문서는 다중 스레드나 다중 프로세스 환경에서, "검사(Check)하는 시점"과 "실제로 사용하는(Use) 시점" 사이의 아주 짧은 찰나의 틈을 비집고 들어와 데이터를 변조해 버리는 가장 악랄하고 고전적인 보안 취약점인 TOCTOU (검사 시점과 사용 시점의 불일치) 경쟁 조건을 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: TOCTOU는 프로그램이 어떤 자원(파일, 변수, 권한 등)에 접근하기 전에 **'유효한지 검사(Check)'**를 하고 통과한 뒤, **'실제 작업(Use)'**을 수행하기 위해 손을 뻗는 그 0.001초의 틈새에, 다른 해커 스레드가 자원의 상태를 악의적으로 바꿔치기하는 경쟁 조건(Race Condition) 취약점이다.
- 가치: 겉보기에는 완벽한 방어 로직(예: "파일이 존재하는지 확인 $\rightarrow$ 파일에 쓰기")을 짠 것 같지만, 동기화(락)가 결여된 검사는 휴지조각에 불과하다는 동시성 프로그래밍의 가장 뼈아픈 진실을 알려주는 척도다.
- 융합: 이 취약점은 단순히 소프트웨어 버그를 넘어 심각한 권한 상승 탈취나 시스템 파괴로 이어지며, 이를 막기 위해서는 검사와 사용을 하나로 묶는 **원자적 트랜잭션(Atomic Operation)**이나 엄격한 뮤텍스 락(Mutex Lock) 처리가 시큐어 코딩(Secure Coding)의 법적 필수로 요구된다.
Ⅰ. 개요: 찰나의 틈새 (Context & Necessity)
웹 서버에서 사용자가 자기 프로필 사진(profile.png)을 삭제하는 기능을 구현했다고 치자. 개발자는 보안을 위해 이렇게 코드를 짰다.
1. if (파일의 주인이 '현재 유저'인가?) { // [ 검사 (Check) ]
2. // 0.001초의 틈새 발생
3. delete(파일); // [ 사용 (Use) ]
4. }
혼자 쓰는 컴퓨터라면 이 코드는 완벽하다. 하지만 서버는 수천 개의 스레드가 동시에 돌아가는 전쟁터다.
해커는 악성 스크립트를 짜서 1번(검사)과 3번(사용) 사이의 찰나의 순간에, profile.png 파일을 지우고 그 자리에 서버의 핵심 비밀번호 파일인 /etc/shadow로 연결되는 지름길(심볼릭 링크)을 몰래 생성해 버린다.
3번 줄이 실행될 때, 삭제 함수는 해커의 사진이 아니라 서버의 심장(비밀번호 파일)을 무참히 삭제해 버린다. (권한 우회 대참사)
이처럼 **검사할 때(Time of Check)**는 정상이었는데, **사용할 때(Time of Use)**는 해커의 함정으로 바뀌어 있는 악몽을 TOCTOU (톡투) 취약점이라고 부른다.
📢 섹션 요약 비유: 클럽 기도(가드)가 신분증을 검사(Check)하고 "성인이시네요, 들어가세요"라고 문을 열어준 순간, 그 성인이 뒤로 쏙 빠지고 뒤에 숨어있던 미성년자가 번개처럼 문안으로 튀어 들어가는(Use) 수법입니다.
Ⅱ. 실무에서의 대표적 TOCTOU 공격 시나리오
TOCTOU는 주로 파일 시스템 접근이나 메모리 포인터 접근에서 치명적으로 터진다.
1. 파일 심볼릭 링크 (Symlink) 어택
- 백신 프로그램이 임시 폴더(
/tmp/scan.txt)에 바이러스 검사 로그를 쓴다고 가정. - 백신:
if (!exists("/tmp/scan.txt"))$\rightarrow$ (통과, 파일 없음) - 해커의 난입: 그 0.1초 사이에 해커가
/tmp/scan.txt라는 이름으로, 윈도우 부팅 파일(boot.ini)을 가리키는 심볼릭 링크를 냅다 생성함. - 백신:
write("/tmp/scan.txt", "바이러스 없음")$\rightarrow$ 부팅 파일이 덮어씌워져서 컴퓨터가 영원히 켜지지 않게 됨 (System Crash).
2. 은행 잔고 이중 출금 어택
- 은행 서버:
if (잔액 >= 100만 원)$\rightarrow$ (통과) - 해커 스레드 2: (동시에 다른 창에서 100만 원 송금 버튼을 눌러 먼저 빼감! 잔액 0원)
- 은행 서버:
잔액 = 잔액 - 100만 원$\rightarrow$ (내 통장 잔고가 마이너스 100만 원이 되며 은행 돈이 털림).
┌─────────────────────────────────────────────────────────────────────────┐
│ TOCTOU (Time of Check to Time of Use) 공격 흐름 시각화 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [ 정상 프로그램 (Victim) ] [ 해커 프로그램 (Attacker) ] │
│ │
│ 1. Check (검사) │
│ "권한이 정상인가?" ───────── (통과) │
│ │ │
│ 2. ⏳ (CPU 스케줄링으로 인한 멈춤 찰나) 💥 해커 난입! │
│ ├───▶ 1. 정상 파일을 싹 치워버림 │
│ ├───▶ 2. 악성 폭탄 파일로 몰래 교체! │
│ │
│ 3. Use (사용/실행) │
│ 파일 삭제 실행! ─────────────── (💣 쾅!) 해커의 폭탄을 실행해버림 │
│ │
│ ★ 핵심: Check와 Use가 분리되어 있으면 무조건 그 사이에 틈이 생긴다. │
└─────────────────────────────────────────────────────────────────────────┘
[다이어그램 해설] 다중 스레드 OS에서는 CPU가 언제 내 프로그램의 실행을 멈추고(Context Switch) 남의 프로그램을 돌릴지 예측할 수 없다. 1번 줄과 3번 줄이 바로 붙어있어도, 그 사이에 운영체제가 100번 넘게 프로그램을 멈췄다 재개할 수 있다. 그 모든 틈새가 해커에게는 문이 활짝 열린 놀이터가 된다.
Ⅲ. TOCTOU의 방어 대책 (시큐어 코딩)
"틈새를 만들지 마라. 숨 쉴 틈도 없이 한 방에 처리해라."
1. 뮤텍스(Mutex) 락을 통한 임계 구역 설정
- 가장 무식하지만 확실한 방법이다. 검사(Check)부터 사용(Use)이 완전히 끝날 때까지 락을 걸어서 남이 끼어들지 못하게 만든다.
// [ 안전한 코드 ]
synchronized(lock) {
if (잔액 >= 100) {
// 이제 틈새로 해커가 못 들어옴!
출금(100);
}
}
2. 원자적 트랜잭션 (Atomic Operations) 사용
- 데이터베이스나 OS가 제공하는 **'단일 명령어(Atomic)'**를 쓴다.
- 예를 들어, 잔액 출금 시 검사와 출금을 따로 하지 않고, 쿼리문 한 방으로 던진다.
UPDATE accounts SET balance = balance - 100 WHERE balance >= 100;(DB가 알아서 원자성 보장) - 파일 시스템의 경우, 파일을 만들 때
O_CREAT | O_EXCL옵션을 결합해서 열면, OS가 "파일이 존재하면 열지 마(Check)"와 "없으면 열어라(Use)"를 찰나의 틈새 없이 OS 커널 단에서 한 방에 완벽하게 처리해 준다.
Ⅳ. 결론
"보안은 코드가 쓰여진 종이 위가 아니라, 코드가 실행되는 시간의 흐름 위에서 증명된다." TOCTOU 취약점은 개발자가 정적(Static)인 코드의 논리만 맹신하고, 동적(Dynamic)인 스레드 생태계의 무자비한 경쟁 조건(Race Condition)을 무시했을 때 터지는 재앙이다. 모든 시큐어 코딩 가이드라인(KISA, CERT 등)에서 이 TOCTOU를 최고 등급의 위험 요소로 경고하는 이유는, 인간의 눈에는 완벽해 보이는 방어벽이 시간차 공격 앞에서는 환상에 불과함을 보여주기 때문이다. 검사와 실행은 쪼갤 수 없는 하나의 원자(Atom)가 되어야만 한다.
📌 관련 개념 맵
- 근본 원인: 경쟁 조건 (Race Condition), 문맥 교환 (Context Switch)
- 공격 대상 사례: 심볼릭 링크 공격 (Symlink Attack), 데이터베이스 더블 지불
- 해결 기술 1: 상호 배제 (Mutual Exclusion, 락, 세마포어)
- 해결 기술 2: 하드웨어/OS 차원의 원자적 명령어 (Atomic Instructions)
👶 어린이를 위한 3줄 비유 설명
- TOCTOU(톡투)는 놀이공원 직원이 "표 보여주세요" 하고 검사(Check)한 뒤, 고개를 돌려 문을 열어주는(Use) 1초의 틈새를 노리는 거예요.
- 직원이 문을 여는 그 1초 사이에, 표를 낸 착한 친구를 밀쳐내고 나쁜 친구가 대신 쏙 들어가 버리는 얄미운 반칙(해킹)이죠.
- 이걸 막으려면 직원이 표를 검사하자마자, 그 친구의 손목을 꽉 쥔 채로 문안으로 밀어 넣어버리는(원자적 처리, Lock) 수밖에 없답니다!