Pythonのexec()関数を徹底解説!文字列コードの動的実行と重大なセキュリティリスク


 

Pythonでプログラミングをしていると、「文字列として書かれたPythonの文を、コードとして実行したい」という特殊なケースに遭遇することがあります。例えば、高度なプラグインシステムでユーザーが提供するコードスニペットを読み込んだり、動的なスクリプト生成を行ったりする場合です。このようなときに役立つのが、Pythonの組み込み関数である**exec()関数です。この記事では、exec()関数の基本的な使い方から、その強力な機能、そして特に注意すべき潜在的なセキュリティリスク**までを初心者にもわかりやすく解説します。

 

exec()関数とは?文字列をPythonの「文」として実行する

 

Pythonのexec()関数は、引数として渡された文字列をPythonの文(statement)として実行する組み込み関数です。これにより、プログラムの実行中に動的にコードブロックを生成し、その場で実行することが可能になります。eval()関数が「式」を評価して値を返すのに対し、exec()関数は「文」を実行し、結果を返しませんNoneを返します)。

 

基本的な使い方

 

exec()関数は、主に1つの必須引数と、2つのオプション引数を取ります。

  1. object (必須): 実行したいPythonの文を含む文字列、またはコードオブジェクトです。

  2. globals (オプション): 文を実行する際に使用できるグローバル名前空間(辞書)です。

  3. locals (オプション): 文を実行する際に使用できるローカル名前空間(辞書)です。

 

例:単純な文の実行

 

Python
 
code_string = """
x = 10
y = 20
result = x + y
print(f"計算結果: {result}")
"""

exec(code_string)
# 出力: 計算結果: 30

# exec()は結果を返さない
returned_value = exec("a = 5")
print(f"exec()の戻り値: {returned_value}") # 出力: exec()の戻り値: None

# exec()で定義した変数は、デフォルトでは実行後のスコープに残らない(注意)
try:
    print(a)
except NameError as e:
    print(f"エラー(aが定義されていない): {e}")
    # 出力: エラー(aが定義されていない): name 'a' is not defined

 

globalslocals引数による実行環境の制御

 

exec()は、デフォルトでは呼び出し元の現在の環境に影響を与えますが、globalslocals引数を使うことで、実行環境を明示的に制御できます。これにより、exec()で定義された変数や関数を、実行後に取得することが可能になります。

  • globals: 実行時に利用可能なグローバル名前空間を指定します。辞書形式で、キーが変数名、値がその変数の値となります。__builtins__キーを含めない場合、組み込み関数も利用できなくなります。

  • locals: 実行時に利用可能なローカル名前空間を指定します。これも辞書形式です。通常、exec()で値を書き込みたい場合はlocalsに可変な辞書を渡します。

Python
 
# globalsとlocalsを使った例
my_globals = {'data': [1, 2, 3]}
my_locals = {} # exec()で定義される変数を格納するための辞書

exec("""
total = sum(data)
average = total / len(data)
""", my_globals, my_locals)

print(f"localsから取得したtotal: {my_locals['total']}")   # 出力: localsから取得したtotal: 6
print(f"localsから取得したaverage: {my_locals['average']}") # 出力: localsから取得したaverage: 2.0

この例では、exec()によってtotalaveragemy_locals辞書に書き込まれ、実行後にそれらの値にアクセスできています。

 

exec()関数の最大の落とし穴:壊滅的なセキュリティリスク

 

exec()関数は非常に強力ですが、その強力さゆえに、eval()関数以上に深刻なセキュリティリスクを伴います。**信頼できないソース(例: ユーザーからの入力、外部ファイル)から取得した文字列をexec()で直接実行することは、絶対にしてはいけません

 

なぜ危険なのか?

 

exec()は、与えられた文字列が合法なPythonコードであれば、**どんな文でも実行できてしまいます。**これには、変数の定義、関数の定義、クラスの定義、ファイル操作、システムコマンドの実行など、プログラムが実行できるあらゆる操作が含まれます。これにより、悪意のあるユーザーがシステムに損害を与えるコードを実行する可能性が非常に高くなります。

 

悪用される例

 

Python
 
# 悪意のあるユーザーが入力した文字列だと仮定
malicious_code_string = """
import os
os.system('rm -rf /') # Linux/macOSの場合、全てのファイルを削除するコマンド
# あるいは、機密情報を外部に送信するコードなど
# with open('/etc/passwd', 'r') as f:
#    print(f.read()) # システムの重要なファイルの内容を表示
"""

# もし exec() を安易に使うと...
# exec(malicious_code_string)
# この行が実行されると、システムに致命的な損害を与える可能性があります!

上記の例では、exec()が悪意のあるコードを何の制約もなく実行し、ファイル削除や機密情報の漏洩といった非常に危険な操作を引き起こす可能性があります。

 

安全にexec()を使用するための対策

 

exec()を使用する必要がある場合は、以下の対策を厳重に行ってください。

  1. 信頼できるソースからの入力のみ: exec()に渡す文字列は、完全に信頼できるソース(自分で書いたコードの一部、信頼できる管理者が提供するスクリプトなど)からのみ取得してください。ユーザーからの入力など、信頼できないデータに対しては決して使用しないでください。

  2. globalslocalsで環境を厳しく制限: __builtins__を空の辞書{}にするか、厳選した組み込み関数のみを許可することで、悪意のあるコードがアクセスできる範囲を最小限に抑えます。

    Python
     
    # 算術演算とprintのみを許可する(極端な例)
    safe_globals = {
        '__builtins__': {
            'print': print,
            'sum': sum,
            'len': len,
            'max': max,
            'min': min,
            'range': range,
            'int': int,
            'float': float,
            'str': str,
            # その他、必要最小限の組み込み関数のみをリストアップ
        }
    }
    
    user_script = """
    my_list = [10, 20, 30]
    total = sum(my_list)
    print(f"合計: {total}")
    # import os # この行はNameErrorでブロックされる
    """
    
    try:
        exec(user_script, safe_globals, {})
    except Exception as e:
        print(f"安全な環境でコード実行中にエラー: {e}")
    

    これはセキュリティを強化する有効な方法ですが、すべての潜在的な脆弱性を完全に排除することは非常に困難であり、専門的な知識が必要です。サンドボックス環境の構築が理想的ですが、それはさらに複雑です。

  3. より安全な代替手段の検討: 多くのケースで、exec()を使う代わりに、より安全な代替手段があります。

    • 設定ファイル: json, yaml, configparser などの標準ライブラリを使用して、安全に設定を読み込みます。

    • プラグインシステム: importlibモジュールを使ってモジュールを動的にロードしたり、デザインパターン(例: ファクトリパターン)を利用したりする方が安全です。

    • 外部プロセス実行: サブプロセスを分離し、権限を制限した状態で外部スクリプトを実行する方が、はるかに安全です。

 

exec()関数とeval()関数の違い

 

前述のとおり、exec()eval()はどちらも文字列としてコードを実行しますが、その挙動と目的が異なります。

特徴exec(object, [globals, [locals]])eval(expression, [globals, [locals]])
実行対象Pythonの文 (statement)Pythonの式 (expression)
戻り値なし (None)式の評価結果を返す
使用例変数定義、関数定義、クラス定義、ループ、条件分岐算術計算、関数呼び出し、属性アクセス
危険性極めて高い(システム全体に影響を与えうる)高い(任意のコード実行が可能)

 

まとめ

 

Pythonのexec()関数は、文字列として定義されたPythonの文を動的に実行する非常に強力な組み込み関数です。これにより、プログラムの実行中にコードブロック全体を動的に操作する柔軟性が得られます。しかし、その強力さゆえに、信頼できないソースからの入力をexec()で直接実行することは、システムに壊滅的なセキュリティリスクを引き起こす可能性があります。

  • exec(object, [globals, [locals]]): 文字列のPython文を実行し、結果を返しません。

  • globalslocals引数を使って、実行時の環境と変数の取得を制御できます。

  • 最大の注意点: eval()以上に危険であり、信頼できない文字列の実行には絶対に使用してはいけません。

  • 多くの場合、より安全な代替手段(json, yaml, importlib, サブプロセスなど)を検討すべきです。

この関数を使用する際は、その利便性と引き換えに発生しうる危険性を十分に理解し、厳重なセキュリティ対策を講じるか、より安全な代替手段を検討するようにしてください。


 

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座