Pythonは、ソースコード(.py)が無くても「.pyc」というコンパイル済みファイルだけで動きます。ソースを読んで安全性を判断するスキャンツールやレビューは、この経路をまるごと素通りしてしまいます。2026年8月13日にarXivで公開された査読前の研究が、PyPIの約104万成果物を実測し、この「検査と実行のギャップ」が机上の空論ではないことを示しました。
今すぐ全社的な対応が必要な脆弱性ではありません。ただし「ソースを見たから大丈夫」という前提は成り立たないことは、脆弱性管理やソフトウェア調達の前提として押さえておく価値があります。
この記事でわかること
- .pyc(Pythonバイトコード)とは何で、自社のどこに潜んでいるか
- PyPIの実測で分かった流通量と、なぜ「ソースを見れば安全」が崩れるのか
- 実際に悪用された事例と、情シスが今できる現実的な確認方法
.pyc(Pythonバイトコード)とは何か
.pycとは、Pythonのソースコードを実行用の中間形式(バイトコード)に変換したファイルです。人が読める.pyと違い中身は命令列なので、テキストエディタで開いても意味が読み取れません。特殊な代物ではなく、Pythonを一度でも動かせば自動的に生成されます。モジュールをimportすると __pycache__ フォルダに モジュール名.cpython-311.pyc のような形で書き出され、次回以降の起動を速くする仕組みです。
「うちはPythonを使っていない」は本当か
自前で導入した記憶がなくても、製品に同梱されて動いていることがあります。監視・資産管理エージェント、バックアップや運用管理ツール、ネットワーク機器やアプライアンスの管理機能、CI/CDのランナー、各種クラウドの操作用CLIなど、内部にPythonランタイムを持つ製品は珍しくありません。該当するかはバージョンによっても異なるため、推測せず実機で確認するのが確実です。
- Linux/macOS:
which -a python3、python3 -V、find / -name "__pycache__" -type d 2>/dev/null | head - Windows:
where python、where python3、製品インストール先にpython.exeやpython3*.dllが無いか - ソースを伴わない.pycの洗い出し:
.pycが__pycache__の外に、対応する.py無しで置かれていないか
研究は何を測ったのか
研究「Beyond Source: An Empirical Study of Python Bytecode Security Risks」(Baihong Chen、Tian Xie、Wen Li/arXiv:2608.12853、2026年8月13日投稿)は、Pythonバイトコードを「セキュリティ上の成果物」として、PyPIでの流通量・解析ツールの実力・CPythonランタイムの頑健性の3方向から実測した調査です。
PyPIにどれだけ流通しているのか
| 測定対象 | 件数 |
|---|---|
| 収集したPyPI成果物(アーティファクト) | 1,034,843 |
| バイトコードを含む成果物 | 7,388 |
| 含まれていた.pycファイル | 228,578 |
| うち、成果物内に対応するソースが無い.pyc | 28,193 |
7,388件は成果物全体の約0.7%で、多数派ではありません。しかし28,193個は「配布物の中にソースが同梱されていない.pyc」であり、パッケージを展開してソースを読むタイプの検査では原理的に中身を確認できません。
デコンパイルすれば中身は読めるのか
読めるケースは多い、というのが結果です。CPython 3.8〜3.14向けの対象ファイル204,904件のうち204,901件で、少なくとも1つのデコンパイラがソースコードを出力しました。ただし論文は、これは「出力できた」ことの計測であって「元と同じ動作をするソースだと検証したわけではない」と明確に断っています。読み違えやすい点です。
さらにツール自体が脆いことも示されました。PyPIから採取した実物のバイトコードでもデコンパイラが例外やタイムアウトを起こし、意図的に改変したバイトコードではプロセスごと落ちる事例も出て、合計17種類の異常パターンが観測されています。解析を妨害する目的でわざと壊れたバイトコードを仕込むという発想が成立してしまいます。
ランタイム自体への影響
細工したバイトコードをCPythonへ流し込むファジングでは、スタック単位で重複排除した1,009件の実行時異常が得られました。うち261グループはメモリ破壊の可能性を示す挙動で、91.7%以上が「ドキュメント上、安全でないと明示された取り込み境界」を越えて実行に到達しています。そしてこれらは通常のPythonソースコードからは1件も再現しませんでした。バイトコードはソースとは別の入力面としてインタプリタに届いている、というのが論文の主張です。
なぜ「ソースを見れば安全」が崩れるのか
ツールの手抜きではなく、Pythonの仕様どおりの挙動です。公式ドキュメントに2つの根拠があります。
1つは、ソースファイルが存在しなくてもバイトコードファイルをimportできる仕組みが標準で用意されていること。importlib.machinery.SourcelessFileLoader は、公式ドキュメントで「バイトコードファイルを(すなわちソースコードファイルが存在しない状態で)importできる」ローダーとして定義されています。
もう1つはキャッシュの妥当性検証です。.pycには、元の.pyの更新時刻とサイズで検証する方式のほかにハッシュを使う方式があり、後者には「checked」と「unchecked」があります。unchecked(未検証)のハッシュ方式では、Pythonは.pycが存在すればそれを有効なものとみなします(公式ドキュメントの記述)。中身が.pyと食い違っていても、そのまま実行され得ます。
この2つが組み合わさると、「配布物の.pyを読んでレビューを通したのに、実際に実行されるのは別の内容の.pyc」という状況が仕様の範囲内で成立します。
実際に悪用された例はあるのか
あります。2023年、セキュリティ企業ReversingLabsが、PyPIで公開されていた fshec2 というパッケージで、悪意ある処理を full.pyc 側に隠す手口を報告しました。同梱の2つのソースファイルは一見なんの変哲もなく、実際の情報収集(ユーザー名・ホスト名・ディレクトリ一覧の取得、外部サーバからのコマンド取得)は.pyc側にありました。読み込みには通常のimport文ではなく importlib が使われており、検知回避を狙ったものとみられています。同社は2023年4月17日にPyPIへ報告し、パッケージは同日削除。当時PyPI側の解析ツールはバイナリの中身まで分析できておらず、「PYCファイルの直接実行を悪用した、おそらく初めての攻撃」と評価されました。
現場目線で言えば、これは「棚卸しの穴」の話
正直なところ、この研究を読んで真っ先に思ったのは「また台帳に載らないものが増えた」でした。台帳にあるソフトへのパッチ適用だけでも手一杯なところに、SBOMや依存ライブラリの管理が乗り、さらに「同梱されたPythonランタイム」「その中の.pyc」まで見ろと言われても追い切れません。しかも.pycはインストーラが勝手に作るので、誰も導入を意思決定していないのに存在しているという、いちばん厄介な種類の資産です。
ですから優先度は高くありません。月例パッチや悪用確認済みの脆弱性が先です。そのうえで、次の2点だけ頭の隅に置いておけば十分だと考えます。
- 調達・レビューの前提を書き換える:外部から持ち込むPythonパッケージや内製ツールについて「ソースをレビューした=中身を確認した」と記録するのをやめ、ソースを伴わない.pycの有無を受け入れ時の確認項目に1行足す。
- 異常時の調査手順に加える:インシデント調査でPythonが絡んだとき、.pyだけ見て「不審な点なし」と結論しない。
__pycache__の外にある.pycや、対応する.pyが無い.pycを確認する。
技術的に手を打つなら
環境を固めたいサーバでは次のオプションが使えます。副作用(起動が遅くなる、想定外のツールが動かなくなる)があるため本番適用前に検証してください。
-Bオプション、または環境変数PYTHONDONTWRITEBYTECODEを非空文字列に設定すると、import時に.pycを書き出さなくなります。--check-hash-based-pycs alwaysを指定すると、checked/uncheckedを問わず、すべてのハッシュ方式の.pycを対応するソースと突き合わせて検証します(更新時刻方式の.pycには影響しません)。- pipの
pip install --no-compileは、インストール時にソースをバイトコードへコンパイルしません。
とはいえ、個別設定より効くのは地道な運用です。「出所の不明なパッケージを業務端末やサーバに入れない」という当たり前を、開発チームや業務部門にどう浸透させるかのほうがはるかに大きな差になります。啓発資料はIPAの対策のしおりが使いやすく、体制面の整理は中小企業の情報セキュリティ対策ガイドラインが土台になります。
この研究の限界と留意点
- 査読前のプレプリントです。今後の査読で数値や解釈が変わり得ます。1本の論文から過度に一般化しないでください。
- デコンパイル成功率は「ソースを出力できた」ことの計測で、出力が元と同じ動作をすることは検証されていません。「デコンパイルすれば安心」とは読めません。
- ファジングの結果は、公式に「安全でない」と示された経路へ不正なバイトコードを与えたものです。遠隔から直ちに悪用できる脆弱性の話ではありません(任意の.pycを置ける時点で相応の侵害が成立しています)。また、PyPIのバイトコードの大半は正当な理由によるもので、.pycが含まれる=悪意あり、ではありません。
まとめ
- Pythonはソースが無くても.pycだけで動く。unchecked方式のハッシュ.pycは「存在すれば有効」と扱われ、ソース中心の検査では中身を確認できない経路が仕様として存在します。
- 実測でもギャップは確認された。PyPIの約104万成果物のうち7,388件がバイトコードを含み、28,193個はソースを伴わない.pycでした。2023年にはこれを悪用したパッケージ(fshec2)が実際に見つかっています。
- 緊急対応は不要、前提の書き換えだけ。受け入れ確認とインシデント調査の手順に「ソースを伴わない.pycの有無」を1行足すところから始めれば十分です。
関連記事
- サプライチェーン攻撃とは?仕組みと種類をわかりやすく解説
- スロップスクワッティングとは?AI生成コードの新脅威
- 依存ライブラリの脆弱性更新、研究が示す実態
- Goモジュール悪用、削除後もプロキシに残る脅威
出典
- Baihong Chen, Tian Xie, Wen Li「Beyond Source: An Empirical Study of Python Bytecode Security Risks」arXiv:2608.12853(2026年8月13日投稿、査読前): https://arxiv.org/abs/2608.12853
- Python公式ドキュメント「The import system - Cached bytecode invalidation」: https://docs.python.org/3/reference/import.html#pyc-invalidation
- Python公式ドキュメント「importlib.machinery.SourcelessFileLoader」: https://docs.python.org/3/library/importlib.html#importlib.machinery.SourcelessFileLoader
- Python公式ドキュメント「Command line and environment(-B / PYTHONDONTWRITEBYTECODE / –check-hash-based-pycs)」: https://docs.python.org/3/using/cmdline.html
- pip公式ドキュメント「pip install」: https://pip.pypa.io/en/stable/cli/pip_install/
- ReversingLabs「When byte code bites: Who checks the contents of compiled Python files?」: https://www.reversinglabs.com/blog/when-python-bytecode-bites-back-who-checks-the-contents-of-compiled-python-files
- IPA「対策のしおり」: https://www.ipa.go.jp/security/guide/shiori.html
- IPA「中小企業の情報セキュリティ対策ガイドライン」: https://www.ipa.go.jp/security/guide/sme/index.html
