본문 바로가기
Hack/System

Python Import as an Execution Boundary: An Empirical Study of Bugs, Vulnerabilities, and Analysis Gaps

by Becoming a Hacker 2026. 9. 16.
반응형

  • 저자: Baihong Chen, Wen Li
  • 공개일: 2026-09-13
  • 원문: arXiv 2609.14791
  • 원문 버전: arXiv v1
  • 분석 기준일: 2026-09-16
  • Tags: Python, Import Security, Code Execution, Plugin Loading, Generated Code, Supply Chain

본 글은 원 논문의 주요 기술적 내용을 이해하기 쉽게 요약·정리한 글입니다. 자세한 내용은 상단의 원문 링크를 참고하세요.

한눈에 보기

이 논문의 핵심은 import를 단순한 의존성 로딩 문법이 아니라 애플리케이션의 정상 로직이 시작되기 전에 코드가 실행되는 보안 경계로 봐야 한다는 것이다.

Python은 최초 import 시 모듈의 top-level statement, 패키지 __init__.py, 전이 import, class/function 정의 시점 표현식, custom loader의 exec_module, native extension의 PyInit_*를 실행할 수 있다. 따라서 import만으로 파일·네트워크·프로세스·자격증명·보안 설정에 접근하거나 전역 상태를 변경할 수 있다.

연구진은 공개 보안 권고와 PyPI 프로젝트 이력을 함께 분석했다. 최종적으로 다음을 확인했다.

  • 프로젝트 이력 버그: 1,302개 저장소의 1,429건
  • import 관련 advisory 취약점: 31건
  • Python import activation과 구분한 application-data boundary 사례: 38건
  • 초기화 과정에서 실제 활성화되는 advisory 취약점: 20건
  • 이 20건 중 High/Critical: 18건(90.0%)
  • 임의 코드 실행으로 이어진 사례: 14건(70.0%)
  • generated Python이 관련된 사례: 8건(40.0%)
  • 재현 가능한 pre-fix/fixed 쌍으로 구성한 ImportVulBench: 228쌍, 11개 버그 유형

중요한 결론은 “import graph에 문제가 있는가”만 보는 정적 검사가 충분하지 않다는 점이다. 실제 취약 동작은 어떤 코드가, 어느 초기화 단계에서, 어떤 process authority와 environment state를 가진 채 실행되는가에 의해 결정된다.

연구 배경

기존 Python 공급망 연구는 주로 dependency confusion, typosquatting, malicious package처럼 “어떤 패키지가 선택되는가”에 집중했다. 이 논문은 의도한 정상 패키지가 이미 선택된 이후를 다룬다.

연구진은 import surface를 다음처럼 분리한다.

  1. Selection surface: 어떤 module·package·symbol·loader가 선택되는가
  2. Activation surface: 선택된 artifact가 초기화 과정에서 무엇을 실행하는가

예를 들어 module shadowing은 selection 문제다. 반면 정상 패키지의 __init__.py가 plugin을 자동 탐색하거나, class body가 생성 코드에 포함된 표현식을 평가하거나, native extension이 PyInit_*에서 상태를 변경하는 것은 activation 문제다.

이 구분은 점검 범위를 바꾼다. 패키지 이름과 해시가 정상이고 dependency pinning이 되어 있어도, import activation 자체가 공격 입력·환경 상태·생성 코드·동적 plugin 선택과 연결되면 보안 문제가 남는다.

공격 모델 / 전제 조건

논문은 하나의 통합 공격자를 가정한 exploit 논문이 아니라, 확인된 버그·취약점 집합을 분석한 실증 연구다. 공통 보안 모델은 다음과 같다.

  • 의도된 legitimate package가 이미 선택되어 있다.
  • 공격자는 package 자체를 악성 패키지로 바꿀 필요가 없다.
  • 공격자 영향력은 입력 데이터, 설정, 환경변수, 생성 artifact, plugin metadata, resource 또는 package가 초기화 중 참조하는 상태를 통해 전달될 수 있다.
  • 취약 동작은 애플리케이션이 package API를 명시적으로 호출하기 전, import·framework auto-import·plugin discovery·native initialization에서 실행된다.
  • 실제 영향은 importing process의 권한과 capability에 한정되지만, 그 process가 CI runner, 개발자 workstation, web server 또는 privileged service라면 파일·credential·network·subprocess 권한까지 그대로 노출될 수 있다.

모든 import 오류가 공격 가능한 것은 아니다. 연구의 프로젝트 이력 표본은 가용성 버그가 대부분이며, 공격자 제어 입력이나 보안 영향이 확인되지 않은 일반 결함도 포함한다. 보안 점검에서는 반드시 attacker-controlled state → import activation → security-sensitive effect의 연결을 별도로 입증해야 한다.

핵심 Root Cause

구조적인 원인은 Python import가 다음 네 역할을 하나의 동작에 결합하기 때문이다.

  1. 대상 탐색과 resolution
  2. module object 생성 및 sys.modules 등록
  3. module/package 초기화 코드 실행
  4. caller namespace에 결과 binding

특히 module은 초기화가 끝나기 전에 sys.modules에 들어간다. 전이 import나 순환 의존성이 이 module을 다시 참조하면 존재하지만 완전히 초기화되지 않은 partial state를 관찰할 수 있다.

더 중요한 보안 불변조건은 다음과 같다.

“의존성 선언을 읽거나 module을 불러오는 행위는 애플리케이션의 신뢰되지 않은 입력을 실행하거나 process의 보안 상태를 변경하지 않아야 한다.”

Python에서는 이 불변조건이 언어 차원에서 보장되지 않는다. top-level statement, package initializer, decorator, default argument, class body, annotation semantics, plugin loader, custom loader, native initializer가 import 경로에서 실행될 수 있기 때문이다.

그 결과 취약점이 만들어지는 지점과 실제 실행 지점이 분리될 수 있다. 생성기가 untrusted value를 Python source에 삽입하고, 이후 다른 구성요소가 생성된 module을 import할 때 payload가 class-definition 단계에서 실행되는 식이다. 코드 생성 시점만 검사하거나 import graph만 검사하면 이 경계를 놓친다.

핵심 공격 원리

공격 관점에서 중요한 activation 경로는 다음과 같다.

  • Module-level execution: import 즉시 top-level I/O, subprocess, global mutation, registration 수행
  • Package initialization: 하위 module import 전에 __init__.py와 eager re-export가 실행됨
  • Definition-time execution: decorator, default argument, base class, class body, 일부 annotation이 정의 시점에 평가됨
  • Dynamic/plugin loading: entry point나 runtime metadata가 선택한 module을 host process 권한으로 로드함
  • Native initialization: extension 로딩이 PyInit_*와 native dependency 초기화를 실행함
  • Generated-code activation: 이전 단계에서 생성된 Python source가 나중에 import되면서 실행됨

논문의 대표 사례인 datamodel-code-generator 취약점(CVE-2026-54655)은 이 마지막 유형을 잘 보여준다. 공격자가 제어하는 JSON Schema의 x-python-type 값이 generated annotation에 충분히 제한되지 않은 채 들어갔고, 생성된 module을 import할 때 class body의 statement로 실행됐다. 취약 버전은 0.51.0 이상 0.60.1 이하이며, 0.60.2에서 수정됐다. 이 사례는 공식 GitHub Advisory수정 커밋에서 확인할 수 있다.

수정은 단순 문자열 치환을 강화하는 방식이 아니라, 입력을 ast.parse(..., mode="eval")로 파싱한 뒤 허용된 type-annotation AST 형태인지 검사한다. statement와 call 같은 실행 가능한 구조를 code generation 전에 차단한다.

공격 흐름

일반화한 흐름은 다음과 같다.

  1. 공격자가 schema, configuration, environment, plugin metadata, resource 또는 generated artifact에 영향을 준다.
  2. 정상 package 또는 generator가 이 값을 저장하거나 Python code·import target·초기화 상태에 반영한다.
  3. 개발자, CI, framework 또는 애플리케이션이 해당 module/package를 import한다.
  4. Python이 정상 로직 호출 전에 top-level, package initialization, definition-time expression, loader 또는 native initializer를 실행한다.
  5. 공격자 영향 데이터가 executable syntax, dynamic target, security-sensitive state 또는 process capability에 도달한다.
  6. importing process의 권한으로 코드 실행, credential 접근, 인증 우회, 세션 상태 변경, 정보 노출 또는 서비스 중단이 발생한다.

이 흐름에서 2단계와 4단계가 시간적으로 떨어질 수 있다는 점이 핵심이다. 따라서 생성 결과물과 import 지점을 서로 다른 팀·파이프라인이 관리하면 trust boundary가 쉽게 사라진다.

성공 조건 / 실패 조건

성공 조건

  • 공격자가 import 과정에서 참조되는 입력·환경·plugin·생성 코드에 영향을 줄 수 있어야 한다.
  • 영향을 받은 경로가 실제 Python/framework/native initialization 중 실행되어야 한다.
  • 검증이나 allowlist가 실행 가능한 syntax·target·state 변화를 차단하지 못해야 한다.
  • 해당 module이 공격자 영향 값이 남아 있는 상태로 최초 import되어야 한다.
  • importing process에 공격 목표에 필요한 파일·네트워크·credential·subprocess 권한이 있어야 한다.
  • generated-code 유형이라면 생성된 파일이 실제로 import되어야 한다. 생성만 되고 사용되지 않으면 activation되지 않는다.

실패 조건

  • 입력이 code generation 전에 문법·AST allowlist로 제한된다.
  • plugin 대상이 신뢰 가능한 목록·서명·고정된 entry point로 제한되고 로드 전 검증된다.
  • optional dependency, resource, environment 접근이 명시적 API 호출 시점까지 지연된다.
  • import-time side effect가 제거되어 import가 선언과 안전한 초기화만 수행한다.
  • 각 plugin failure가 격리되어 하나의 실패가 전체 discovery 또는 host 초기화를 중단하지 못한다.
  • 생성 artifact가 실행 전 별도 검사되며, 낮은 권한의 격리된 process에서만 import된다.
  • 공격자가 영향을 준 값과 import 실행 사이의 data flow가 끊긴다.

연구진의 실험 환경

  • CPU: Intel Core i9-13900F, 13세대
  • 메모리: 64GB
  • OS: Ubuntu 22.04
  • 각 benchmark variant는 새로운 process에서 실행해 이전 import의 sys.modules와 global state가 다음 실험을 오염시키지 않게 했다.
  • 비교 도구: Pylint 4.0.7의 R0401(cyclic-import), Pyright 1.1.404의 reportMissingImports
  • custom rule이나 plugin은 사용하지 않았다.

데이터 수집은 OSV, GitHub Security Advisories, CVE List V5, NVD와 PyPI 프로젝트 저장소 이력을 결합했다. raw advisory는 총 31,398건이었다. 프로젝트 이력에서는 1,577,832개 logical commit group을 metadata prefilter로 33,437개(2.1%)까지 줄이고, patch-causal FSM으로 16,673개(49.9%)를 남긴 뒤 수동 확인했다.

rejection audit는 단계별로 서로 겹치지 않는 385개 표본을 3번, 즉 각 단계 1,155건씩 검토했다. metadata prefilter의 pooled miss는 2/1,155, 0.173%(95% Wilson CI 0.047-0.629%)였다. patch-causal FSM은 0/1,155였고 95% 상한은 0.331%였다. 다만 이 audit는 초기 검색 vocabulary가 애초에 찾아내지 못한 commit은 측정하지 않는다.

주요 실험 결과

버그·취약점 분포

프로젝트 이력 1,429건 중 circular import가 945건(66.1%), incorrect target/resolution이 177건(12.4%), optional dependency 처리가 114건(8.0%), import-time failure/termination이 99건(6.9%)이었다. 상위 네 유형이 1,335건(93.4%)을 차지했다.

초기화 과정에서 활성화된 프로젝트 이력 사례는 1,347건이다. 이 중 module-level code가 865건(64.2%), package initialization이 459건(34.1%)으로 합계 1,324건(98.3%)이었다.

그러나 빈도와 위험도는 달랐다. dynamic loading은 15/1,347건(1.1%)뿐이지만 13/15건(86.7%)이 authority effect를 보였고, 전체 authority 사례 33건 중 13건(39.4%)을 차지했다. 일반 초기화는 넓은 커버리지를 위해, dynamic/plugin loading은 높은 보안 민감도를 위해 각각 우선 점검할 가치가 있다.

advisory의 보안 영향

초기화 중 활성화된 advisory 취약점 20건 중 18건이 High/Critical이고, 14건이 임의 코드 실행, 5건이 인증 우회, 계정 탈취와 credential theft가 각각 3건이었다. 결과는 multi-label이므로 합계가 20을 넘을 수 있다.

수정 패턴

1,429건의 수정 중 import target 변경이 567건(39.7%), cycle 제거가 394건(27.6%), import 지연이 165건(11.5%)이었다. 세 방식이 1,126건(78.8%)을 차지했다.

이 결과는 취약 조건을 없애는 것과 activation timing을 바꾸는 것이 다르다는 점을 보여준다. cycle이나 missing import가 구조적으로 남아 있어도, partial state를 참조하는 시점을 늦추거나 optional path에 guard를 두면 실제 import-time failure는 사라질 수 있다.

기존 도구의 한계

축약 benchmark에서 Pylint는 적용 가능한 pre-fix cycle 30/30, Pyright는 missing import 48/48을 보고했다. 그러나 fix 후 경고가 사라진 비율은 Pylint 9/30(30.0%), Pyright 18/48(37.5%)에 불과했다.

원 프로젝트에서는 Pylint가 30쌍 모두 분석했지만 pre-fix 경고는 14/30(46.7%)만 보고했고, fix 후 사라진 것은 4/30(13.3%)이었다. Pyright는 적용 가능한 23쌍 중 21쌍을 분석해 pre-fix 12건을 보고했지만 fix 후 사라진 경고는 0/21이었다.

즉 기존 도구는 import graph의 구조적 조건을 찾을 수 있지만, initialization order·guard·execution timing이 바뀌어 실제 버그가 제거됐는지는 별도 의미 분석 없이는 판단하기 어렵다.

실제 발견된 취약점 / 사례

논문은 20개의 initialization-activated advisory가 10개 package, 9개 project, 14개 distinct root cause에 해당한다고 정리한다. 그중 가장 구체적으로 공개 검증 가능한 사례는 CVE-2026-54655다.

  • 입력 경계: 외부 JSON Schema의 x-python-type
  • 생성 지점: datamodel-code-generator
  • activation 지점: 생성된 Python module의 import와 class definition
  • 영향: importing interpreter/CI runner 권한의 arbitrary code execution
  • 필요한 사용자 동작: 공격자 제공 schema를 code generation에 사용하고 생성물을 import해야 함
  • CVSS: 7.8 High, AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
  • 영향 버전: 0.51.0-0.60.1
  • 수정 버전: 0.60.2

이 사례의 가치가 큰 이유는 공격 입력과 실행 시점이 분리된다는 데 있다. schema 검토자는 이를 단순 type override로 볼 수 있고, 생성 코드 import 담당자는 원래 입력의 신뢰도를 알지 못할 수 있다.

저자 주장 vs 실제 증명 범위

실험으로 뒷받침된 범위

  • 공개 advisory와 최근 3년 범위의 active PyPI-linked public repository에서 확인한 분포
  • source·patch·실행 chain을 확인한 1,429개 project-history 버그
  • 20개 initialization-activated advisory의 severity와 consequence
  • 228개 축약 pre-fix/fixed 프로그램에서의 재현
  • 특정 Pylint/Pyright 버전과 built-in rule의 비교 결과

과도하게 일반화하면 안 되는 범위

  • 전체 Python 생태계의 취약점 발생률을 측정한 연구가 아니다.
  • private repository, 비공개 취약점, 오래되거나 비활성인 프로젝트, 다른 언어 package ecosystem은 포함되지 않는다.
  • 1,429건 대부분은 공격 가능한 취약점이 아니라 개발 이력에서 확인된 import 관련 버그다.
  • project-history 1,429건과 advisory 31건은 모집단 성격이 다르므로 직접적인 위험률 비교로 사용하면 안 된다.
  • 축약 benchmark 결과를 원 프로젝트 탐지 성능으로 그대로 해석할 수 없다. 실제 프로젝트에서는 dependency·configuration·layout 차이로 탐지율이 더 낮았다.
  • rejection audit는 reduction 단계의 miss만 측정하며, 초기 query vocabulary가 놓친 사례는 포함하지 않는다.

기존 공격 / 기존 점검 방식과의 차이

기존 공급망 점검은 패키지 이름, provenance, lockfile, hash, typosquatting, dependency confusion에 집중한다. 이 논문은 정상 package가 선택된 이후의 실행 경계를 본다.

또한 단순 SAST import rule과도 다르다. cycle이나 unresolved import를 찾는 것에서 끝나지 않고 다음을 함께 추적한다.

  • import root
  • activation mechanism
  • initialization order
  • 현재 보이는 partial module state
  • environment/resource state
  • dynamic loader가 선택한 실제 target
  • 생성 코드의 origin과 import 시점 effect
  • process authority

즉 “import graph가 이상한가”가 아니라 “import가 어떤 security-sensitive effect를 언제 발생시키는가”를 검증한다.

연구의 한계와 주의해서 볼 부분

  • import 관련성 판단과 분류에는 수동 판단이 포함된다. 연구진은 385건 독립 표본에 대한 reviewer agreement를 측정했지만 모든 retained candidate를 독립적으로 중복 확인한 것은 아니다.
  • fixing commit의 parent가 실제 affected release와 완전히 동일하지 않을 수 있다.
  • 공개 advisory는 공개된 문제에 편향되고, 프로젝트 이력은 공개·최근 활성 PyPI 프로젝트에 편향된다.
  • availability bug가 압도적으로 많은 프로젝트 이력과 security advisory를 섞어서 “import 버그의 70%가 RCE”처럼 표현하면 잘못이다. 70%는 20개의 initialization-activated advisory 안에서의 비율이다.
  • 논문은 import-time security review의 필요성을 강하게 뒷받침하지만, 모든 import side effect가 취약하다고 증명하지는 않는다.

공개 PoC / Exploit / Tool / Artifact 분석

논문은 https://github.com/awen-li/ImportMine에 수집·축약 설정, query vocabulary, corpus, evidence patch, ImportVulBench, tool output, validator를 공개했다고 적는다. 디렉터리 기준 source of truth는 ImportVulCorpusImportVulBench라고 명시한다.

그러나 2026-09-16 확인 시 해당 저장소 URL은 404를 반환했다. 따라서 현재 시점에는 논문 전체 실험을 재현할 수 있는 공개 artifact가 실제로 접근 가능하다고 확인할 수 없다. 문서에는 “공개를 주장함”과 “현재 접근·재현 가능함”을 구분해야 한다.

반면 CVE-2026-54655의 GitHub Advisory, 별도 PoC 링크, 수정 commit은 현재 확인 가능했다. Advisory에는 취약 데이터 흐름, 영향 버전, class-definition-time activation, 수정 방식이 공개돼 있다. 다만 이 PoC는 ImportMine 전체 연구 artifact가 아니라 논문이 포함한 개별 advisory 사례를 재현하는 자료다.

레드팀 / 모의해킹에서 어떻게 활용할까

Python 기반 서비스·CI·plugin platform 점검에서 import를 단순 startup 단계로 넘기지 말고 별도 attack surface로 취급할 수 있다.

우선순위는 다음이 적절하다.

  1. 외부 입력으로 Python source나 schema client를 생성하는 pipeline
  2. entry point·namespace·metadata 기반 plugin auto-discovery
  3. web server·worker 시작 시 import되는 module의 top-level I/O와 subprocess
  4. __init__.py의 eager re-export와 security configuration 변경
  5. decorator·default argument·class body에서 읽는 environment/credential
  6. importlib, custom loader, exec_module, native extension 초기화

검증 시 단순히 import 성공 여부만 보지 말고 격리된 process에서 파일, 네트워크, child process, environment read, credential access, global security setting 변경을 관찰해야 한다. sys.modules를 공유하면 최초 import에서만 나타나는 동작을 놓칠 수 있으므로 각 테스트는 fresh process에서 수행하는 것이 좋다.

운영 시스템에서는 승인된 test artifact만 사용하고, 실제 credential이나 외부 production endpoint가 없는 격리 환경에서 실행해야 한다. 예상하지 못한 subprocess·network·file write가 보이면 자동으로 중단하고 evidence만 보존한다.

실무 가치 평가

높음. 새로운 단일 exploit technique을 제시하는 논문이라기보다 Python 보안 점검의 사각지대를 체계적으로 정의한 연구다.

레드팀에서는 “정상 패키지이므로 import는 안전하다”는 가정을 제거하고, code generation·plugin system·CI·framework auto-import를 연결해서 보는 점검 모델로 활용할 가치가 크다. 특히 dynamic loading은 표본 수는 작지만 authority effect 비중이 높았고, generated Python은 advisory 보안 문제에서 과대표집됐다.

다만 현재 ImportMine 저장소 접근이 확인되지 않아 corpus와 benchmark를 직접 재실행하지 못한 점은 재현성 평가에 큰 제약이다. 따라서 당장은 논문의 분류 체계와 공개된 개별 advisory를 점검 체크리스트로 활용하고, 저장소가 공개되면 benchmark를 내려받아 내부 Python SAST·sandbox·EDR 탐지 검증에 사용하는 것이 적절하다.

결론

Python import는 dependency resolution이 아니라 code execution boundary다. 보안 검토는 정상 package 선택 이후에도 계속되어야 한다.

가장 넓은 탐지 범위는 module-level code와 package initialization에서 나오지만, 보안 영향이 큰 지점은 dynamic/plugin loading, generated code, definition-time execution, native initialization처럼 전통적인 import graph 밖에 있는 경로다.

따라서 좋은 점검은 세 질문을 함께 답해야 한다.

  1. 무엇이 import되는가?
  2. import 과정에서 어떤 코드가 언제 실행되는가?
  3. 그 코드가 어떤 공격자 영향 데이터와 process 권한을 갖는가?

이 세 가지를 연결해야 단순 dependency 문제와 실제 취약한 import activation을 구분할 수 있다.

반응형

댓글