동시 또는 거의 동시 수행을 지원하는 병렬 시스템이나 하나 이상의 프로세스가 동작되는 환경에서 시간 및 상태를 부적절하게 관리하여 발생할 수 있는 보안약점이다.
공유자원(예: 파일)을 여러 프로세스가 접근하여 사용할 경우, 동기화 구문을 사용하여 한 번에
하나의 프로세스만 접근 가능하도록(synchronized, mutex 등) 하는 한편, 성능에 미치는 영향을
최소화하기 위해 임계코드 주변만 동기화 구문을 사용한다.
예를 들어, 프로그램이 실행될 때 사용자가 파일에 액세스할 권한이 있는지 확인하는 시나리오를 생각해보자. 파일이 실제로 액세스되기 전에 권한이 변경되면 공격자가 이 시간 차이를 이용하여 파일에 무단 액세스할 수 있는 기회가 있다.
class FileMgmtThread extends Thread {
private String manageType = "";
public FileMgmtThread (String type) {
manageType = type;
}
// 멀티쓰레드 환경에서 공유자원에 여러프로세스가 사용하여 동시에 접근할 가능성이 있어 안전
하지 않다.
public void run() {
try {
if (manageType.equals("READ")) {
File f = new File("Test_367.txt");
if (f.exists()) {
BufferedReader br
= new BufferedReader(new FileReader(f));
br.close();
}
} else if (manageType.equals("DELETE")) {
File f = new File("Test_367.txt");
if (f.exists()) {
f.delete();
} else { … }
}
} catch (IOException e) { … }
}
}
public class CWE367 {
public static void main (String[] args) {
FileMgmtThread fileAccessThread = new FileMgmtThread("READ");
FileMgmtThread fileDeleteThread = new FileMgmtThread("DELETE");
// 파일의 읽기와 삭제가 동시에 수행되어 안전하지 않다.
fileAccessThread.start();
fileDeleteThread.start();
}
}
이 예제는 파일을 대한 읽기와 삭제가 두 개의 스레드에 동작하게 되므로 이미 삭제된 파일을
읽으려고 하는 레이스컨디션이 발생할 수 있다.
Race Condition은 두 개 이상의 프로세스가 공용 자원을 병행적으로(concurrently) 읽거나 쓸 때, 공용 데이터에 대한 접근이 어떤 순서에 따라 이루어졌는지에 따라 그 실행 결과가 달라지는 상황을 말한다.
따라서 다음 예제와 같이 동기화 구문인 synchronized를 사용하여 공유자원 (Test_367.txt)에
대한 안전한 읽기/쓰기를 수행할 수 있도록 한다.
class FileMgmtThread extends Thread {
private static final String SYNC = "SYNC";
private String manageType = "";
public FileMgmtThread (String type) {
manageType = type;
}
public void run() {
// 멀티쓰레드 환경에서 synchronized를 사용하여 동시에 접근할 수 없도록 사용해야한다.
synchronized(SYNC) {
try {
if (manageType.equals("READ")) {
File f = new File("Test_367.txt");
if (f.exists()) {
BufferedReader br
= new BufferedReader(new FileReader(f));
br.close();
}
} else if (manageType.equals("DELETE")) {
File f = new File("Test_367.txt");
if (f.exists()) {
f.delete();
} else { … }
}
} catch (IOException e) { … }
}
}
}
public class CWE367 {
public static void main (String[] args) {
FileMgmtThread fileAccessThread = new FileMgmtThread("READ");
FileMgmtThread fileDeleteThread = new FileMgmtThread("DELETE");
fileAccessThread.start();
fileDeleteThread.start();
}
}
import os
def read_file(filename):
# 파일이 존재하는지 확인
if not os.path.exists(filename):
print("파일이 존재하지 않습니다.")
return
# 파일이 읽기 가능한지 확인
if not os.access(filename, os.R_OK):
print("파일을 읽을 수 없습니다.")
return
# 파일을 읽기
with open(filename, 'r') as file:
contents = file.read()
print("파일 내용:", contents)
filename = "example.txt"
read_file(filename)
위의 코드에서 TOCTTOU 취약점은 파일의 존재 여부와 읽기 권한을 확인하는 부분이다. 공격자는 이러한 체크 사이에 파일을 변경할 수 있으며, 이로 인해 보안 문제가 발생할 수 있다.
TOCTTOU 취약점을 방지하기 위해서는 파일을 읽기 전에 파일 상태를 고정해야 한다. 이를 위해서는 파일을 열고 읽는 동작을 동기화하여 수행해야 한다. 이를 위해 파일을 열고 읽을 때까지 파일의 상태를 변경하지 않도록 해야 한다.
import os
def read_file(filename):
# 파일이 존재하는지 확인
if not os.path.exists(filename):
print("파일이 존재하지 않습니다.")
return
# 파일이 읽기 가능한지 확인
if not os.access(filename, os.R_OK):
print("파일을 읽을 수 없습니다.")
return
# 파일을 읽기
with open(filename, 'r') as file:
contents = file.read()
print("파일 내용:", contents)
filename = "example.txt"
# 파일을 읽기 전에 파일 상태를 고정
os.utime(filename, None)
read_file(filename)
위의 코드에서 os.utime(filename, None)은 파일의 마지막 접근 시간을 현재 시간으로 업데이트하는 것으로, 파일의 상태를 변경하지 않고 파일을 읽는 동작을 동기화 시킨다.
TOCTTOU 취약점을 완화하기 위해서는 보안 검사와 작업이 동시에 이루어지도록 보장하는 것이 중요. 파일 잠금, 액세스 제어 목록 및 트랜잭션 메커니즘과 같은 기술을 사용하여 TOCTTOU 취약점과 관련된 위험을 줄일 수 있다. 또한, 적절한 오류 처리 및 사용자 입력 유효성 검사도 전반적인 시스템 보안에 기여할 수 있다.