Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 578 – 파이썬 런타임 감사 훅

Author:
Steve Dower <steve.dower at python.org>
BDFL-Delegate:
Christian Heimes <christian at python.org>
Status:
Final
Type:
Standards Track
Created:
16-Jun-2018
Python-Version:
3.8
Post-History:
28-Mar-2019, 07-May-2019

Table of Contents

번역·라이선스 안내

이 문서는 Open Publication License v1.0 이상에 따라 만든 수정된 한국어 번역본입니다. 수정자: yeokja/yeokja 프로젝트. 수정일: 2026-08-29. 변경 내용: 영어 원문을 한국어로 번역했습니다. 원저자와 저작권 표시는 위 Author 필드와 아래 Copyright 절에 유지했으며, 이 번역은 원저자의 승인이나 보증을 뜻하지 않습니다. 수정되지 않은 기준 원문 · 공식 최신판 · Open Publication License v1.0

Important

This PEP is a historical document. The up-to-date, canonical documentation can now be found at Audit events table.

×

See PEP 1 for how to propose changes.

초록

이 PEP는 파이썬 런타임이 수행하는 동작을 감사 도구에서 확인할 수 있도록 하는 파이썬 API의 추가 사항과 CPython 구현의 구체적인 동작을 설명합니다. 이러한 동작을 확인할 수 있으면 테스트 프레임워크, 로깅 프레임워크 및 보안 도구에서 런타임이 수행하는 동작을 모니터링하고 필요에 따라 제한할 수 있습니다.

이 PEP는 실행 중인 파이썬 애플리케이션에 대한 정보를 제공하는 두 가지 API의 추가를 제안합니다. 하나는 임의의 이벤트를 위한 것이고, 다른 하나는 모듈 임포트 시스템에 특화된 것입니다. 이 API들은 모든 파이썬 구현에서 사용할 수 있도록 하는 것이 목적이지만, 구현에서 사용자에게 정보를 가장 잘 제공할 방법을 자유롭게 결정할 수 있도록 여기서는 사용되는 구체적인 메시지와 값을 지정하지 않습니다. CPython에서 사용될 가능성이 높은 몇 가지 예를 설명을 위해 제공합니다.

이러한 감사 API를 활용하는 파이썬 런타임의 보안 강화에 관한 논의와 권고 사항은 PEP 551을 참조하십시오.

배경

파이썬은 많은 일반 운영 체제에서 광범위한 저수준 기능에 접근할 수 있도록 합니다. 이는 “한 번 작성하여 어디서나 실행”하는 스크립팅에 매우 유용하지만, 파이썬으로 작성된 소프트웨어를 모니터링하기는 어렵게 만듭니다. 파이썬은 네이티브 시스템 API를 직접 사용하므로 기존 모니터링 도구는 컨텍스트가 제한되거나 감사 우회가 발생하는 문제를 겪습니다.

시스템 모니터링에서 동작이 발생했다는 사실은 보고할 수 있지만, 그 동작을 유발한 사건의 순서를 설명할 수 없을 때 컨텍스트가 제한됩니다. 예를 들어 OS 수준의 네트워크 모니터링은 “포트 5678에서 수신 대기가 시작됨”을 보고할 수 있지만, 동작을 유발한 시점의 프로세스 ID, 명령줄, 부모 프로세스 또는 프로그램의 로컬 상태를 제공하지 못할 수 있습니다. 이러한 동작을 방지하기 위한 방화벽 제어 역시 비슷하게 제한되어 있으며, 일반적으로 프로세스 이름이나 현재 사용자와 같은 일부 전역 상태만을 대상으로 하고, 어떤 경우에도 다른 애플리케이션 메시지와 연관 지을 수 있는 유용한 로그 파일을 제공하는 경우는 드뭅니다.

동작에 사용되는 일반적인 시스템 도구라면 통상 그 사용을 보고했겠지만, 파이썬을 통해 API에 접근하면 이를 트리거하지 않는 경우 감사 우회가 발생할 수 있습니다. 예를 들어 감사 대상 시스템에서 HTTP 요청을 수행하기 위해 “curl”을 호출하는 것은 특별히 모니터링될 수 있지만, 파이썬의 “urlretrieve” 함수는 그렇지 않습니다.

장시간 실행되는 파이썬 애플리케이션, 특히 웹 앱처럼 사용자가 제공한 정보를 처리하는 애플리케이션에서는 예기치 않은 동작이 발생할 위험이 있습니다. 이는 코드의 버그 때문일 수도 있고 악의적인 사용자가 의도적으로 유발한 것일 수도 있습니다. 두 경우 모두 정상적인 애플리케이션 로깅이 우회되어 비정상적인 일이 발생했다는 어떠한 표시도 남지 않을 수 있습니다.

또한 파이썬에서는 다소 고유한 특성으로, 임포트 시스템의 검색 경로를 조작하거나 의도한 것보다 앞에 파일을 배치하여 애플리케이션에서 실행되는 코드를 매우 쉽게 바꿀 수 있습니다. 개발자가 사용하려는 모듈과 같은 이름의 스크립트를 만들 때 이러한 일이 자주 발생합니다. 예를 들어 표준 라이브러리 random 모듈을 임포트하려는 random.py 파일이 있습니다.

이 제안은 악의적인 동작을 방지하려고 시도하지 않으므로(그렇게 할 수 있는 몇 가지 새로운 선택지는 제공하지만) 이는 샌드박싱이 아닙니다. 더 자세한 논의는 아래의 Why Not A Sandbox 섹션을 참조하십시오.

변경 사항 개요

이러한 변경의 목표는 애플리케이션 개발자와 시스템 관리자가 기존 모니터링 시스템의 형태나 동작 방식을 규정하지 않고도 파이썬을 해당 시스템에 통합할 수 있게 하는 것입니다.

이를 가능하게 하기 위해 두 가지 API 변경을 제안합니다. 감사 훅과 검증된 열기 훅입니다. 두 훅 모두 파이썬과 네이티브 코드에서 사용할 수 있으므로, 순수 파이썬 코드로 작성된 애플리케이션과 프레임워크는 추가 메시지를 활용할 수 있고, 임베더나 시스템 관리자는 감사가 항상 활성화된 파이썬 빌드를 배포할 수도 있습니다.

여기에서 설명하는 네이티브 API를 제공해야 하는 구현은 CPython뿐입니다. 다른 구현은 순수 파이썬 API를 제공해야 하며, 기반 런타임에 적절한 경우 네이티브 버전을 제공할 수도 있습니다. 감사 이벤트 역시 구현별로 다르게 간주되지만, 일반적인 기능 호환성 보장이 적용됩니다.

감사 훅

호출자를 대신하여 런타임이 수행하는 작업을 관찰하려면, 특정 작업 내부에서 메시지를 발생시키는 API가 필요합니다. 이러한 작업은 일반적으로 Python 런타임 또는 표준 라이브러리의 깊은 내부에서 수행되며, 동적 코드 컴파일, 모듈 임포트, DNS 확인 또는 ctypes와 같은 특정 모듈의 사용 등이 이에 해당합니다.

다음의 새로운 C API를 사용하면 임베더와 CPython 구현자가 감사 훅 메시지를 보내고 받을 수 있습니다.:

# Add an auditing hook
typedef int (*hook_func)(const char *event, PyObject *args,
                         void *userData);
int PySys_AddAuditHook(hook_func hook, void *userData);

# Raise an event with all auditing hooks
int PySys_Audit(const char *event, PyObject *args);

감사 훅을 수신하고 발생시키기 위한 새로운 Python API는 다음과 같습니다.:

# Add an auditing hook
sys.addaudithook(hook: Callable[[str, tuple]])

# Raise an event with all auditing hooks
sys.audit(str, *args)

훅은 언제든지 C에서 PySys_AddAuditHook()을 호출하여 추가할 수 있으며, Py_Initialize()를 호출하기 전에도 가능합니다. 또는 Python 코드에서 sys.addaudithook()을 호출하여 추가할 수 있습니다. 훅은 제거하거나 교체할 수 없습니다. CPython의 경우 C에서 추가한 훅은 전역인 반면, Python에서 추가한 훅은 현재 인터프리터에만 적용됩니다. 전역 훅은 인터프리터 훅보다 먼저 실행됩니다.

관심 있는 이벤트가 발생할 때 코드는 C에서 GIL을 보유한 상태로 PySys_Audit()를 호출하거나 sys.audit()를 호출할 수 있습니다. 문자열 인자는 이벤트의 이름이고, 튜플에는 인자가 포함됩니다. 주어진 이벤트 이름은 인자에 대해 고정된 스키마를 가져야 하며, 이는 (각 x.y 버전 릴리스에 대한) 공개 API로 간주되어야 합니다. 따라서 업데이트된 문서와 함께 기능 릴리스 사이에서만 변경해야 합니다. 오버헤드를 최소화하고 네이티브 코드 훅 구현에서의 처리를 단순화하기 위해 명명된 인자는 지원하지 않습니다.

최대한의 호환성을 위해, 참조 인터프리터 CPython의 이벤트와 동일한 이름을 사용하는 이벤트는 호환 가능한 인자를 사용하도록 모든 노력을 기울여야 합니다. 구현별 이벤트 이름에 구현의 이름 또는 약어를 포함하면 충돌을 방지하는 데에도 도움이 됩니다. 예를 들어 pypy.jit_invoked 이벤트는 ipy.jit_invoked 이벤트와 명확히 구별됩니다. Python 모듈에서 발생시키는 이벤트는 이벤트 이름에 해당 모듈 또는 패키지 이름을 포함해야 합니다.

이벤트 이름은 임의의 UTF-8 문자열일 수 있지만, 구현 간 일관성을 위해 유효한 Python 점 표기 이름을 사용하고 이름에 특정 세부 정보를 인코딩하지 않는 것이 좋습니다. 예를 들어 모듈 이름 spam을 인자로 갖는 import 이벤트가 인자 없이 발생하는 spam module imported 이벤트보다 바람직합니다. 내장 널 문자를 사용하지 마십시오. C를 사용하여 훅을 구현하는 사람들에게 문제가 발생할 수 있습니다.

이벤트가 감사될 때 각 훅은 가능한 한 추가된 순서대로 호출되며, 이벤트 이름과 인자가 전달됩니다. 어떤 훅이 예외가 설정된 상태로 반환하면 이후의 훅은 무시되며, 일반적으로 Python 런타임이 종료되어야 합니다. 훅에서 발생한 예외는 처리되거나 예상된 발생으로 취급되도록 의도된 것이 아닙니다. 이를 통해 훅 구현은 특정 이벤트에 어떻게 응답할지 결정할 수 있습니다. 일반적인 응답은 이벤트를 기록하거나, 예외와 함께 작업을 중단하거나, 운영 체제의 종료 호출을 사용하여 프로세스를 즉시 종료하는 것입니다.

이벤트가 감사되었지만 설정된 훅이 없는 경우 audit()함수는 최소한의 오버헤드만 발생시켜야 합니다. 이상적으로 각 인자는 감사 호출만을 위해 계산된 값이 아니라 기존 데이터에 대한 참조여야 합니다.

훅은 Python 객체일 수 있으므로 인터프리터 또는 런타임 종료 처리 중에 해제해야 합니다. 이러한 작업은 다른 어떤 시점에도 트리거되어서는 안 되며, 예상하지 못한 호출이 관찰되도록 이벤트 훅을 발생시켜야 합니다.

아래의 Suggested Audit Hook Locations에서는 감사 이벤트를 발생시켜야 하는 몇 가지 중요한 작업을 권장합니다. 일반적으로 이벤트는 가능한 가장 낮은 수준에서 발생시켜야 합니다. Python 코드에서 이벤트를 발생시킬지 네이티브 코드에서 발생시킬지 선택할 수 있다면, 네이티브 코드에서 발생시키는 것을 우선해야 합니다.

Python 구현은 감사 이벤트를 발생시키는 작업과 이벤트 스키마를 문서화해야 합니다. sys.addaudithook(print)이 모든 메시지를 표시하는 간단한 방법이 되도록 의도되어 있습니다.

검증된 오픈 훅

대부분의 운영 체제에는 실행할 수 있는 파일과 실행할 수 없는 파일을 구분하는 메커니즘이 있습니다. 예를 들어 권한 필드의 실행 비트, 잠재적인 코드 변조를 감지하기 위한 파일 내용의 검증된 해시 또는 파일 시스템 경로 제한일 수 있습니다. 이는 특정 환경에 승인된 코드만 실행되도록 보장하는 중요한 보안 메커니즘입니다.

대부분의 커널은 커널에 로드되고 실행되는 바이너리를 제한하거나 감사하는 방법을 제공합니다. Python이 소유하는 파일 형식은 일반 데이터로 나타나므로 이러한 기능이 적용되지 않습니다. 이 오픈 훅을 사용하면 Python 임베더가 스크립트를 시작하거나 Python 코드를 가져올 때 운영 체제 지원을 통합할 수 있습니다.

검증된 오픈 훅을 위한 새로운 공개 C API는 다음과 같습니다.:

# Set the handler
typedef PyObject *(*hook_func)(PyObject *path, void *userData)
int PyFile_SetOpenCodeHook(hook_func handler, void *userData)

# Open a file using the handler
PyObject *PyFile_OpenCode(const char *path)

검증된 오픈 훅을 위한 새로운 공개 Python API는 다음과 같습니다.:

# Open a file using the handler
io.open_code(path : str) -> io.IOBase

io.open_code() 함수는 open(abspath(str(pathlike)), 'rb')의 즉시 교체 가능한 대체 함수입니다. 기본 동작은 원시 바이너리 액세스를 위해 파일을 여는 것입니다. 동작을 변경하려면 새 핸들러를 설정해야 합니다. 핸들러 함수는 str 인자만 허용합니다. C API의 PyFile_OpenCode 함수는 UTF-8 인코딩을 가정합니다. 경로는 절대 경로여야 하며, 전체 경로가 올바르게 해석되도록 보장할 책임은 호출자에게 있습니다.

C에서 언제든지, Py_Initialize()를 호출하기 전에도 PyFile_SetOpenCodeHook()을 호출하여 사용자 지정 핸들러를 설정할 수 있습니다. 그러나 훅이 이미 설정되어 있으면 호출이 실패합니다. 훅이 설정된 상태에서 open_code()를 호출하면 훅에 경로가 전달되고 그 반환값이 직접 반환됩니다. 반환되는 객체는 원시 바이트 읽기를 지원하는 열린 파일과 유사한 객체여야 합니다. 오픈 핸들러가 파일 전체를 이미 메모리로 읽은 경우 BytesIO 인스턴스를 사용할 수 있도록 명시적으로 설계되었습니다.

이러한 훅은 CPython에서 자체적으로 트리거되지 않고 _io.open()함수를 가져와 호출할 수 있습니다. 또한 메모리 내 버퍼를 사용하여 호환되는 결과를 반환하도록 _io.BytesIO를 사용할 수 있습니다.

훅이 파일을 로드해서는 안 된다고 판단하면, 다른 로깅을 수행하는 것과 함께 원하는 예외를 발생시켜야 합니다.

파일의 코드와 관련된 모든 가져오기 및 실행 기능은 무조건 open_code()를 사용하도록 변경됩니다. compile(), exec()eval()을 호출해도 이 함수를 거치지 않는다는 점에 유의해야 합니다. 이러한 호출에서 가져온 코드를 포함하는 감사 훅이 파일에서 읽은 코드를 검증할 가장 좋은 기회입니다. 현재 Python에서는 가져오기와 실행이 분리되어 있으므로, 가져온 코드 대부분은 open_code()compile에 대한 로그 훅을 모두 거치게 되며, 따라서 검증 단계를 반복하지 않도록 주의해야 합니다.

의도적으로 코드를 실행할 계획이 없는 파일 액세스에는 이 함수를 사용하지 않을 것으로 예상됩니다. 여기에는 피클, XML 또는 YAML 파일의 로드가 포함되며, 이러한 경우 코드 실행은 일반적으로 의도적인 것이 아니라 악의적인 것으로 간주됩니다. 이러한 작업은 자체 감사 이벤트를 제공해야 하며, 일반 기능(예: Unpickler.load)과 코드 실행(예: Unpickler.find_class)을 구분하는 것이 바람직합니다.

몇 가지 예를 들면, 파일 형식이 일반적으로 실행 비트를 요구하거나(POSIX에서), 인터넷에서 다운로드된 것으로 표시되면 경고를 표시하는 경우(Windows에서), 일반적인 open()대신 open_code()를 사용하는 것이 좋습니다. ZipFile 클래스 를 사용하여 ZIP 파일을 여는 경우에는 open()을 사용해야 하며, zipimport를 통해 열 때는 올바른 의도를 나타내도록 open_code()를 사용해야 합니다. 특정 컨텍스트에 잘못된 함수를 사용하는 코드는 후크를 우회할 수 있으며, 이는 CPython과 표준 라이브러리에서 버그로 간주해야 합니다. 임의의 코드가 존재하는 상황에서 실행되는 모든 소스를 추적하려면 open_code 후크와 감사 후크를 조합하여 사용해야 합니다.

오픈 후크를 변경하기 위한 Python API는 제공되지 않습니다. Python 코드에서 임포트 동작을 수정하려면 importlib에서 제공하는 기존 기능을 사용하십시오.

API 가용성

여기에 추가된 모든 함수는 공개되고 안정적인 API로 간주되지만, 함수의 동작은 구현에 따라 다릅니다. 대부분의 설명은 CPython 구현을 가리키며, 다른 구현도 해당 함수를 제공해야 하지만 동일하게 동작해야 할 의무는 없습니다.

예를 들어, sys.addaudithook()sys.audit()는 존재해야 하지만 아무 작업도 하지 않을 수 있습니다. 이를 통해 코드는 존재 여부를 확인하지 않고도 sys.audit()를 호출할 수 있지만, 해당 호출이 어떤 효과라도 낼 것이라고 가정해서는 안 됩니다. (보안이 중요한 코드에 존재 여부 확인을 포함하면 감사를 우회할 또 다른 경로가 생기므로, 해당 함수는 항상 존재하는 것이 바람직합니다.)

io.open_code(path)는 최소한 항상 _io.open(path, 'rb')를 반환해야 합니다. 이 함수를 사용하는 코드는 발생할 수 있는 일에 대해 더 이상의 가정을 해서는 안 되며, CPython 이외의 구현은 개발자가 후크를 사용하여 이 함수의 동작을 재정의하도록 허용할 필요가 없습니다.

권장되는 감사 후크 위치

sys.audit() 또는 PySys_Audit() 호출에서의 위치와 매개변수는 개별 Python 구현에서 결정해야 합니다. 이를 통해 구현이 해당 플랫폼과 가장 관련성이 높은 연산을 공개할 수 있는 최대한의 자유를 허용하고, 잠재적으로 비용이 많이 들거나 지나치게 많은 이벤트를 피하거나 무시할 수 있게 합니다.

표 1은 모든 구현에서 감사 이벤트를 트리거해야 하는 연산에 대한 제안인 동시에 이벤트 스키마의 예시로도 사용됩니다.

표 2는 필수는 아니지만 CPython에서 제공될 가능성이 높은 추가 예시를 제공합니다.

어떤 연산이 감사 이벤트를 제공하는지는 사용 중인 Python 버전에 해당하는 문서를 참조하십시오.

Table 1: Suggested Audit Hooks
API Function Event Name Arguments Rationale
PySys_AddAuditHook sys.addaudithook Detect when new audit hooks are being added.
PyFile_SetOpenCodeHook cpython.PyFile_SetOpenCodeHook Detects any attempt to set the open_code hook.
compile, exec, eval, PyAst_CompileString, PyAST_obj2mod compile (code, filename_or_none) Detect dynamic code compilation, where code could be a string or AST. Note that this will be called for regular imports of source code, including those that were opened with open_code.
exec, eval, run_mod exec (code_object,) Detect dynamic execution of code objects. This only occurs for explicit calls, and is not raised for normal function invocation.
import import (module, filename, sys.path, sys.meta_path, sys.path_hooks) Detect when modules are imported. This is raised before the module name is resolved to a file. All arguments other than the module name may be None if they are not used or available.
open io.open (path, mode, flags) Detect when a file is about to be opened. path and mode are the usual parameters to open if available, while flags is provided instead of mode in some cases.
PyEval_SetProfile sys.setprofile Detect when code is injecting trace functions. Because of the implementation, exceptions raised from the hook will abort the operation, but will not be raised in Python code. Note that threading.setprofile eventually calls this function, so the event will be audited for each thread.
PyEval_SetTrace sys.settrace Detect when code is injecting trace functions. Because of the implementation, exceptions raised from the hook will abort the operation, but will not be raised in Python code. Note that threading.settrace eventually calls this function, so the event will be audited for each thread.
_PyObject_GenericSetAttr, check_set_special_type_attr, object_set_class, func_set_code, func_set_[kw]defaults object.__setattr__ (object, attr, value) Detect monkey patching of types and objects. This event is raised for the __class__ attribute and any attribute on type objects.
_PyObject_GenericSetAttr object.__delattr__ (object, attr) Detect deletion of object attributes. This event is raised for any attribute on type objects.
Unpickler.find_class pickle.find_class (module_name, global_name) Detect imports and global name lookup when unpickling.
Table 2: Potential CPython Audit Hooks
API Function Event Name Arguments Rationale
_PySys_ClearAuditHooks sys._clearaudithooks Notifies hooks they are being cleaned up, mainly in case the event is triggered unexpectedly. This event cannot be aborted.
code_new code.__new__ (bytecode, filename, name) Detect dynamic creation of code objects. This only occurs for direct instantiation, and is not raised for normal compilation.
func_new_impl function.__new__ (code,) Detect dynamic creation of function objects. This only occurs for direct instantiation, and is not raised for normal compilation.
_ctypes.dlopen, _ctypes.LoadLibrary ctypes.dlopen (module_or_path,) Detect when native modules are used.
_ctypes._FuncPtr ctypes.dlsym (lib_object, name) Collect information about specific symbols retrieved from native modules.
_ctypes._CData ctypes.cdata (ptr_as_int,) Detect when code is accessing arbitrary memory using ctypes.
new_mmap_object mmap.__new__ (fileno, map_size, access, offset) Detects creation of mmap objects. On POSIX, access may have been calculated from the prot and flags arguments.
sys._getframe sys._getframe (frame_object,) Detect when code is accessing frames directly.
sys._current_frames sys._current_frames Detect when code is accessing frames directly.
socket.bind, socket.connect, socket.connect_ex, socket.getaddrinfo, socket.getnameinfo, socket.sendmsg, socket.sendto socket.address (socket, address,) Detect access to network resources. The address is unmodified from the original call.
member_get, func_get_code, func_get_[kw]defaults object.__getattr__ (object, attr) Detect access to restricted attributes. This event is raised for any built-in members that are marked as restricted, and members that may allow bypassing imports.
urllib.urlopen urllib.Request (url, data, headers, method) Detects URL requests.

성능 영향

중요한 성능 영향은 이벤트가 발생하지만 연결된 후크가 없는 경우입니다. 이는 피할 수 없는 경우입니다. 개발자가 감사 후크를 추가하면 성능을 기능과 맞바꾸기로 명시적으로 선택한 것이기 때문입니다. 여기서는 후크가 추가된 경우의 성능 영향은 관심 대상이 아닙니다. 이는 옵트인 기능이기 때문입니다.

Python 성능 벤치마크 모음 [1]을 사용한 분석에서는 유의미한 영향이 나타나지 않았으며, 대부분의 벤치마크가 1.05배 빨라지거나 1.05배 느려지는 범위에 있었습니다.

이 PEP에서 설명하는 감사 지점 집합의 성능 영향은 무시할 수 있는 수준이라고 생각합니다.

거부된 아이디어

감사 후크를 위한 별도 모듈

제안은 가칭 audit이라는 감사 후크용 새 모듈을 추가하는 것입니다. 이렇게 하면 API와 구현이 sys 모듈에서 분리되고, 현재의 변형된 이름 대신 C 함수의 이름을 PyAudit_AddHookPyAudit_Audit으로 지정할 수 있습니다.

이러한 모듈은 항상 존재한다고 보장되는 내장 모듈이어야 합니다. 이러한 훅은 조건 없이 호출 가능 객체여야 합니다. 조건부 가져오기나 호출은 이벤트를 가로채 억제하거나 수정할 기회를 제공하기 때문입니다.

가장 핵심적인 모듈 중 하나이므로, sys 모듈은 모듈 섀도잉 공격으로부터 어느 정도 보호됩니다. 애플리케이션이 계속 실행될 수 있을 만큼 충분히 기능적인 모듈로 sys를 교체하는 작업은 관심 있는 함수 하나만 포함한 모듈을 교체하는 것보다 훨씬 복잡합니다. sys 모듈을 섀도잉할 수 있는 공격자는 이미 파일에서 임의의 코드를 실행할 수 있는 능력을 갖추고 있는 반면, audit 모듈은 검색 경로 어디에든 있는 .pth 파일에 한 줄만 추가하여 교체할 수 있습니다.:

import sys; sys.modules['audit'] = type('audit', (object,),
    {'audit': lambda *a: None, 'addhook': lambda *a: None})

sys 또는 audit에 대한 몽키 패칭 공격에는 이미 여러 보호 계층이 존재하지만, sys.modules에 대한 할당이나 삽입은 감사되지 않습니다.

이 아이디어는 audit에 대한 모든 호출을 쉽게 억제할 수 있으므로 거부되었습니다.

sys.flags에 “감사됨” 모드를 나타내는 플래그

Python이 “보안” 또는 “감사됨” 모드로 실행 중인 시점을 나타내도록 sys.flags에 값을 추가하자는 제안입니다. 이를 통해 애플리케이션은 일부 기능이 활성화되었거나 후크가 추가된 시점을 감지하고 그에 맞게 동작을 변경할 수 있습니다.

현재 감사 후크가 존재할 때 프로그램이 다르게 동작해야 할 정당한 이유는 알지 못합니다.

일반적인 python 진입점을 사용하는지 또는 일부 대체 진입점을 사용하는지와 관계없이 애플리케이션 수준 API인 sys.auditio.open_code는 항상 존재하며 정상적으로 작동합니다. 호출자는 후크가 추가되었는지 확인할 수 없으며(부채널 분석을 수행하는 경우는 제외합니다), 확인할 필요도 없습니다. 호출은 호출자가 이를 피할 필요가 없을 정도로 충분히 빨라야 하며, 프로그램은 추가된 후크가 애플리케이션 성능에 영향을 주지 않을 만큼 빠르도록 보장할 책임이 있습니다.

이것이 “모호성을 통한 보안”이라는 주장은 타당하지만 무관합니다. 모호성을 통한 보안은 다른 보호 메커니즘이 없을 때에만 문제가 되며, 공격을 피하기 위한 첫 단계로 모호성을 사용하는 것은 강력히 권장됩니다(논의는 this article에서 확인하십시오).

이러한 API가 사용 중인지 여부에 따라 애플리케이션이 동작을 변경해야 할 적절한 이유가 없으므로 이 아이디어는 거부되었습니다.

샌드박스를 사용하지 않는 이유

CPython의 샌드박싱은 과거에 여러 차례 시도되었지만, 시도할 때마다 실패했습니다. 본질적으로 문제는 샌드박스 처리된 코드를 실행할 때 특정 기능을 제한해야 하지만, 그 외에는 Python의 정상적인 작동에 사용할 수 있어야 한다는 것입니다. 예를 들어 문자열을 바이트코드로 컴파일하는 기능을 완전히 제거하면 소스 코드에서 모듈을 가져오는 기능도 중단되며, 완전히 제거하지 않으면 간접적으로 해당 기능에 접근할 수 있는 방법이 너무 많습니다. 주어진 연산이 “안전한지” 여부를 일반적으로 판단할 수 있는 실행 가능한 방법은 아직 없습니다. 추가 정보와 참조 문헌은 [2]에서 확인할 수 있습니다.

이 제안은 기능을 제한하려는 것이 아니라, 단순히 해당 기능이 사용되고 있다는 사실을 드러냅니다. 특히 침입 상황에서는 조기 방지보다 탐지가 훨씬 중요합니다(조기 방지는 일반적으로 공격자가 대체적이고 탐지하기 어려운 접근 방식을 사용하도록 유도하기 때문입니다). 감사 후크를 사용할 수 있다는 사실만으로는 Python의 공격 표면이 어떤 방식으로든 변경되지 않지만, 이를 통해 방어자는 현재는 불가능한 방식으로 Python을 자신의 환경에 통합할 수 있습니다.

감사 훅(audit hook)은 작업 발생을 안전하게 방지할 수 있는 능력을 갖추고 있으므로, 이 기능은 어느 정도 수준의 샌드박싱을 제공할 수 있는 능력을 실제로 제공합니다. 하지만 대부분의 경우, 그 의도는 샌드박스를 만드는 것이 아니라 로깅을 가능하게 하는 것입니다.

PEP 551과의 관계

이 API는 원래 PEP 551 Python 런타임에서의 보안 투명성(Security Transparency in the Python Runtime)의 일부로 제시되었습니다.

더 간단한 검토를 위해, 그리고 이 API들이 보안을 넘어 더 폭넓게 적용될 수 있다는 점 때문에, 이제 API 설계는 별도로 제시됩니다.

PEP 551은 Python을 보안 또는 감사된 환경에 통합하는 방법을 논의하는 정보 제공용 PEP입니다.

참고 문헌