Pythonコードインジェクション

evalとexecによるPythonコードインジェクション

説明

Pythonのeval()とexec()は、単なる文字列変換のAPIではありません。eval()は文字列をPythonの式として、exec()はPythonの文の集まりとして解釈し、アプリケーションの権限で実行します。HTTPリクエストなどの信頼できない入力を渡すと、攻撃者がアプリケーションの制御フローを変更するPythonコードを指定できます。

グローバル・ローカル名前空間を制限したり、__builtins__のマッピングを変更したりしても、安全なサンドボックスにはなりません。ast.literal_eval()はPythonコードを実行しませんが、Pythonの文書にはメモリ、スタック、CPUを枯渇させる可能性が示されています。敵対的な入力を処理する汎用的な代替手段にはなりません。

想定される影響

  • アプリケーションがアクセスできるデータの閲覧、変更、漏えい
  • アプリケーションの状態や制御フローの変更
  • プロセス権限で利用できるファイル、ネットワーク、OS機能の実行
  • 例外、終わらない計算、過剰なリソース消費によるサービス停止

コマンドの実行によってOS上の権限が自動的に昇格するわけではありません。実際の影響は、プロセスの権限、利用できるモジュール、シークレット、ネットワークアクセスの範囲によって変わります。

対処方法

  • 動的評価を取り除いてください。JSONなどの定義済み形式が必要ならjson.loads()で解析し、入力サイズを制限したうえで、構造、型、値の範囲を検証し、データとしてだけ扱ってください。JSONの解析でも大きな入力はリソースを消費します。
  • ユーザーに操作を選ばせる場合は、サーバーが管理する少数の識別子を定義済みの呼び出し可能オブジェクトに対応付け、直接呼び出してください。許可した名前でも、文字列に埋め込んでeval()やexec()に渡さないでください。
  • 制限したグローバル名前空間、変更した__builtins__、単純な文字列置換、sanitizeという名前のヘルパーをセキュリティ境界として使わないでください。
  • 信頼できないPythonの実行が製品の明示的な要件である場合は、アプリケーションプロセスから分離してください。強化した別の実行環境で、CPU、メモリ、実行時間、ファイルシステム、ネットワーク、権限を制限してください。

例

変更前

python
from flask import Flask, request

app = Flask(__name__)

@app.get("/evaluate")
def evaluate():
    expression = request.args.get("expression", "")
    result = eval(expression)  # 危険: リクエスト文字列をPythonコードとして実行
    return {"result": str(result)}

変更後

python
from flask import Flask, abort, request

app = Flask(__name__)

def service_status():
    return "healthy"

def application_version():
    return "2.0"

OPERATIONS = {
    "status": service_status,
    "version": application_version,
}

@app.get("/operation")
def run_operation():
    operation = OPERATIONS.get(request.args.get("name", ""))
    if operation is None:
        abort(400, description="Unsupported operation")

    return {"result": operation()}  # サーバーが登録したオブジェクトを直接呼び出す

対策例ではリクエスト値をPython構文として解釈しません。サーバーが定義したマッピングのキーとしてだけ使い、選択した呼び出し可能オブジェクトを直接実行します。

参考資料