Skip to content

PEP 578

Python Runtime Audit Hooks

Abstract

이 PEP는 Python API에 대한 추가 사항과 Python 런타임에서 수행한 작업을 감사 도구에 표시하도록 하는 CPython 구현에 대한 특정 동작을 설명합니다. 이러한 작업에 대한 가시성은 테스트 프레임워크, 로깅 프레임워크 및 보안 도구가 런타임에서 수행하는 작업을 모니터링하고 선택적으로 제한할 수 있는 기회를 제공합니다.

이 PEP는 실행 중인 Python 응용 프로그램에 대한 통찰력을 제공하기 위해 임의의 이벤트에 대한 API와 모듈 가져오기 시스템에 특정한 다른 API를 추가하는 것을 제안합니다. API는 모든 Python 구현에서 사용할 수 있도록 의도되었지만 사용된 특정 메시지와 값은 구현에서 사용자에게 정보를 제공하는 최선의 방법을 자유롭게 결정할 수 있도록 여기에서 지정되지 않습니다. CPython에서 사용될 가능성이 있는 몇 가지 예는 설명을 위해 제공됩니다.

이러한 감사 API를 사용하여 Python 런타임의 보안을 강화하는 방법에 대한 토론 및 권장 사항은 PEP 551을 참조하십시오.

간단한 사용법

def audit(event, args):
    if event == 'code.__new__':
        print(f'audit: {event} with args={args}')
sys.addaudithook(audit)

Overview

오늘날 많은 프로그래밍이 언어로 된 광범위한 생태계의 도움으로 수행됩니다. 따라서 각 프로그램은 주어진 문제를 해결하기 위해 다른 프로그램을 기반으로 합니다. 프로그램이 성장함에 따라 코드베이스에 추가된 다른 사람이 이미 해결한 작업이 점점 더 많이 수행되어야 합니다. 그러나 이것은 본질적으로 모듈의 유지 관리, 릴리스 주기, 문서, 안정성 등과 같은 특정 요소를 기반으로 합니다. 더 중요한 것은 코드가 광고하는 대로 수행해야 한다는 신뢰를 기반으로 한다는 것입니다. 많은 모듈이 오픈 소스이지만 API가 사용되지만 프로그래머는 소스 코드를 읽는 데 덜 귀찮습니다.

그러나 누군가가 의심스러운 활동을 조사하고 다른 사람에게 공개적으로 보고하지 않는 한 오랫동안 발견되지 않을 수 있는 한 릴리스 과정에서 모듈이 악의적인 작업을 수행하는 경우가 많습니다. 이것은 Ruby의 유명한 CSS 관련 라이브러리가 약 2,800만 번 다운로드한 관련 없는 코드에서 eval을 사용하여 GitHub 에 보고되는 보안 사고로 이어지는 몇 달 전과 같이 여러 번 발생했습니다 . Python도 이러한 공격과 다르지 않습니다. 타사 코드가 네트워크 요청, 평가 기능 등과 같은 작업인지 확인하는 구체적인 방법이 없기 때문입니다.

이 PEP는 특정 이벤트에 대해 트리거되는 감사 가능한 후크를 추가할 수 있는 API와 모니터링 등을 위한 추가 정보를 제공하기 위해 기존 시스템에서 사용할 수 있는 방법을 제공합니다. 아래에서 이것이 어떻게 사용될 수 있는지에 대한 간단한 프로그램을 설명할 것입니다.

어떤 수치 작업을 수행하는 프로그램을 작성하고 시리즈의 곱을 계산하려고 한다고 가정해 보겠습니다. 당연히 아래와 같이 쓸 수 있습니다.

# stats.py
from functools import reduce

def product(series):
    return reduce(lambda acc, num: acc * num, series)

우리는 이미 이것을 수행하는 종속성으로 PyPI의 수학 라이브러리를 가지고 있으며 이를 유틸리티 함수로 다시 구현하고 싶지 않습니다. 그래서 우리는 프로그램에서 그것을 사용합니다.

# app.py
import stats

print(stats.product(range(1, 10)))

모듈이 하는 일을 수행한다는 고유한 신뢰가 있으므로 API가 변경되지 않는 한 통계 종속성의 새로운 릴리스에 대해서도 코드를 살펴보는 데 신경쓰지 않아도 됩니다. 한 상태에서 라이브러리는 이제 http 요청을 하고 새 코드는 아래와 같은 악의적인 변경을 얻을 수 있습니다.

# stats.py
from functools import reduce

def product(series):
    import urllib.request
    try:
        urllib.request.urlopen("http://example.com")
    except:
        pass
    return reduce(lambda acc, num: acc * num, series)

코드를 다시 살펴보거나 네트워크 요청이 모니터링되는 샌드박스 환경이 없는 한 수행된 http 활동에 대한 단서가 거의 없습니다. PEP 578은 이를 모니터링할 수 있는 방법을 제공하고 sys.addaudithook생성된 모든 정의된 이벤트 집합에 대한 콜백으로 함수를 추가할 수 있는 sys 모듈에 함수를 추가합니다. 이것은 sys.settrace(trace)모든 함수 호출이 추적 함수를 거치는 것과 유사하지만 이 경우에는 이벤트일 뿐입니다. 목록 은 PEP에서 사용할 수 있습니다. 목록에는 urllib.Request모듈에 추가된 이벤트가 있으므로 urllib 모듈의 모든 http 요청에 대해 이벤트가 트리거됩니다. 콜백은 이벤트의 이름과 이벤트가 트리거되는 인수를 가져옵니다. 그래서 우리는 그것을 필터링 할 수 있습니다

코드를 다시 살펴보거나 네트워크 요청이 모니터링되는 샌드박스 환경이 없는 한 수행된 http 활동에 대한 단서가 거의 없습니다. PEP 578은 이를 모니터링할 수 있는 방법을 제공하고 sys 모듈 sys.addaudithook에 함수를 추가하여 정의된 모든 이벤트 집합에 대한 콜백으로 함수를 추가할 수 있습니다. 이것은 모든 함수 호출이 추적 함수를 거치지만 이 경우에는 이벤트일 뿐인 sys.settrace(trace)와 유사합니다. 목록은 PEP에서 사용할 수 있습니다. 목록에는 모듈에 추가된 urllib.Request 이벤트가 있으므로 urllib 모듈의 모든 http 요청에 대해 이벤트가 트리거됩니다. 콜백은 이벤트의 이름과 이벤트가 트리거되는 인수를 가져옵니다. 그래서 우리는 그것을 필터링 할 수 있습니다.

import sys
import stats

def audit_hook(event, args):
    if event in ['urllib.Request']:
        print(f"Network {event=} {args=}")

sys.addaudithook(audit_hook)

print(stats.product(range(1, 10)))
./python.exe audit_hooks_tut/app.py
Network event='urllib.Request' args=('http://example.com', None, {}, 'GET')
362880

위의 프로그램을 실행하면 이벤트 이름과 이벤트 인수가 제공됩니다. 이 이벤트는 sys.audit(event, args)를 통해 Python 프로그램을 통해 추가할 수 있으며 sys.audit('urllib.Request', req.full_url, req.data, req.headers, req.get_method()) 형식의 공개 함수로 작성된 urllib.request에 대한 감사 이벤트를 볼 수 있으므로 urllib.request를 통해 이루어진 모든 http 요청에 대해 다음을 수행합니다. 이벤트는 hook 함수를 통해 전달됩니다.

Types of audit events

HTTP 요청은 하나의 이벤트이지만 해서는 안 되는 가져오기를 수행하고 있는지 확인하기 위해 모듈에서 가져온 가져오기를 알고 싶은 것과 같은 다른 것들이 있습니다. 위의 예에서 수학과 관련된 코드 또는 종속성이 이 사용 사례에는 필요하지 않지만 urllib.request 모듈에서 내부적으로 가져온 소켓 모듈을 가져오는지 확인하기 위해 다시 작성할 수 있습니다.

import sys
import stats

suspicious_modules = ['socket']

def audit_hook(event, args):
    if event in ['urllib.Request']:
        print(f"Network {event=} {args=}")
    elif event in ['import'] and args[0] in suspicious_modules:
        print(f"Suspicious import action {event=} module={args[0]}")

sys.addaudithook(audit_hook)

print(stats.product(range(1, 10)))
./python.exe audit_hooks_tut/app.py
Suspicious import action event='import' module=socket
Network event='urllib.Request' args=('http://example.com', None, {}, 'GET')
362880

다른 이벤트에는 평가, 컴파일을 사용한 코드 컴파일, 피클링, 파일 열기 등이 포함되며 필요하지 않을 수도 있습니다. 이 이벤트는 Python에서 정의할 수 있으므로 타사 라이브러리가 이벤트 세트를 제공할 수 있도록 프로그램에서 사용자 정의 이벤트를 정의할 수도 있습니다. 예를 들어 stats 모듈과 PyPI의 mod1을 사용할 때 내 프로그램에서도 사용되며 stats 모듈에서 간접적으로 사용하는 경우 내 프로그램의 mod1에 의해 정의된 이벤트가 있는지 모니터링할 수 있습니다. 이 경우 mod1은 감사할 수 있는 stats.py에서 사용하는 도우미 make_request를 제공할 수 있습니다.

# mod1.py
import sys

def make_request(url):
    sys.audit('make_request', url)
    pass
# stats.py
from functools import reduce

def product(series):
    import mod1
    mod1.make_request("http://example.com")
    return reduce(lambda acc, num: acc * num, series)
# app.py
import sys
import stats

suspicious_modules = ['socket']

def audit_hook(event, args):
    if event in ['urllib.Request', 'make_request']:
        print(f"Network {event=} {args=}")
    elif event in ['import'] and args[0] in suspicious_modules:
        print(f"Suspicious import action {event=} module={args[0]}")

sys.addaudithook(audit_hook)

print(stats.product(range(1, 10)))

With mod1 defining a custom audit hook we can audit it from our program as shown. Thus with the above setup it gives the below output.

./python.exe audit_hooks_tut/app.py
Network event='make_request' args=('http://example.com',)
362880

이것은 또한 라이브러리 작성자가 감사 목적으로도 사용할 수 있도록 라이브러리에 대한 후크를 제공할 수 있도록 합니다. 이것은 또한 연결된 구현 PR에 설명된 보다 세분화된 감사를 가능하게 하는 C API 및 기타 수단을 제공합니다. PEP에는 샌드박싱의 제한 사항을 설명하는 섹션도 있습니다. 이 PEP의 성능 영향도 무시할 수 있는 것으로 나타났습니다. 이 PEP는 PEP 551-Python 런타임의 보안 투명성과도 밀접한 관련이 있습니다.

이 트윗에서 Steve Dower가 언급했듯이. 위의 방법은 프로덕션 앱에서 사용하는 것이 좋습니다. PEP 551을 참조하십시오.

See also

Favorite site