[시큐어코딩 가이드] 2-3-1. 경쟁조건: 검사시점과 사용시점(TOCTOU)

시간 및 상태

통시 또는 거의 동시에 여러 코드 수행을 지원하는 병렬 시스템이나 하나 이상의 프로세스가 동작되는 환경에서 시간 및 상태를 부적절하게 관리하여 발생할 수 있는 보안약점이다.

 

경쟁조건: 검사시점과 사용시점(TOCTOU)

정의

병렬시스템(멀티프로세스로 구현한 응용프로그램)에서는 자원(파일, 소켓 등)을 사용하기에 앞서 자원의 상태를 검사합니다. 하지만, 자원을 사용하는 시점(Time Of Use)과 검사하는 시점(Time Of Check)이 다르기 때문에, 검사하는 시점에 존재하던 자원이 사용하던 시점에 사라지는 등 자원의 상태가 변하는 경우가 발생합니다. 이와 같은 문제는 동기화 오류뿐만 아니라 교착상태 등과 같은 문제가 발생할 수 있습니다.

 

안전한 코딩기법

변수, 파일과 같은 공유자원을 여러 프로세스가 접근하여 사용하는 경우 동기화 구문을 사용하여 한 번에 하나의 프로세스만 접근 가능하도록 해야하며 성능에 미치는 영향을 최소화하기 위해 임계영역 주변만 동기화 구문을 사용합니다.

 

코드예제

다음은 안전하지 않은 코드예제입니다. 공유된 파일을 사용할 때 파일을 불러온 후 실제로 파일을 사용하는 부분이 실행되기 전 짧은 시간에도 다른 사용자 또는 프로그램에 의해 파일이 사라져 원하는 기능을 실행할 수 없는 경우를 보여 주는 코드입니다.

#경쟁조건: 검사시점과 사용시점(TOCTOU)(안전X)
def write_shared_file_bad(filename, content):
    if os.path.isfile(filename) is True:
        f = open(filename, 'w')
        f.seek(0, io.SEEK_END)
        f.write(content)
        f.close()

def start_bad():
    filename = 'secure/temp_bad.txt'
    content = f"start time is {datetime.datetime.now()}"
    my_thread = threading.Thread(target=write_shared_file_bad, args=(filename, content))
    my_thread.start()

start_bad()

 

위의 코드를 실행하기 전에 앱 폴더(secure)내에 temp_bad.txt를 생성합니다. 하단 터미널창에서 python 앱폴더/파일명.py(secure/view3.py)를 실행합니다. 실행 후 temp_bad.txt를 확인해 보면 아래와 같이 작성되어 있습니다.

 

다음은 안전한 코드예제입니다. 파일 검사 후 파일이 삭제되거나 변동되는 것을 예방하기 위해 lock을 사용하여 각 쓰레드에서 공유자원에 접근하는 것을 통제하는 코드입니다.

#경쟁조건: 검사시점과 사용시점(TOCTOU)(안전O)
lock = threading.Lock()
def write_shared_file_good(filename, content):
    with lock:
        if os.path.isfile(filename) is True:
            f = open(filename, 'w')
            f.seek(0, io.SEEK_END)
            f.write(content)
            f.close()

def start_good():
    filename = 'secure/temp_good.txt'
    content = f"start time is {datetime.datetime.now()}"
    my_thread = threading.Thread(target=write_shared_file_bad, args=(filename, content))
    my_thread.start()

start_good()

 

 

아래코드도 안전한 코드예제로 다른 예제입니다. temp_add에 값을 넣은 후 해당 파일을 삭제하는 코드입니다.

#추가코드(안전O)
lock = threading.Lock()
def write_shared_file(filename, content, command):
    with lock:
        if command == 'WRITE':
            if os.path.isfile(filename) is True:
                f = open(filename, 'w')
                f.seek(0, io.SEEK_END)
                f.write(content)
                f.close()
        elif command == 'DELETE':
            if os.path.isfile(filename) is True:
                os.remove(filename)

def start():
    filename = 'secure/temp_add.txt'
    content = f"start time is {datetime.datetime.now()}"
    my_thead_read = threading.Thread(target=write_shared_file, args=(filename, content, 'WRITE'))
    my_thead_delete = threading.Thread(target=write_shared_file, args=(filename, content, 'DELETE'))

    my_thead_read.start()
    my_thead_delete.start()

start()

 

 

 

시간 및 상태/경쟁조건: 검사시점과 사용시점(TOCTOU)

[참고문헌] Python 시큐어코딩 가이드(2022) / KISA(한국인터넷진흥원)