Pythonのeval()関数を徹底解説!文字列コードの実行と潜在的な危険性


 

Pythonでプログラミングをしていると、「文字列として書かれたPythonの式を、コードとして実行したい」という特殊なニーズに遭遇することがあります。例えば、数式計算アプリでユーザーが入力した文字列の数式を評価したり、設定ファイルをより柔軟に扱ったりする場合です。このようなときに役立つのが、Pythonの組み込み関数である**eval()関数です。この記事では、eval()関数の基本的な使い方から、その強力な機能、そして特に注意すべき潜在的なセキュリティリスク**までを初心者にもわかりやすく解説しますします。

 

eval()関数とは?文字列をPythonコードとして評価する

 

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

 

基本的な使い方

 

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

  1. expression (必須): 評価したいPythonの式を含む文字列です。

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

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

 

例:単純な数式の評価

 

Python
 
result1 = eval("10 + 20")
print(f"10 + 20 の評価結果: {result1}") # 出力: 10 + 20 の評価結果: 30

result2 = eval("len([1, 2, 3])")
print(f"len([1, 2, 3]) の評価結果: {result2}") # 出力: len([1, 2, 3]) の評価結果: 3

result3 = eval("'Hello'.upper()")
print(f"'Hello'.upper() の評価結果: {result3}") # 出力: 'Hello'.upper() の評価結果: HELLO

このように、文字列として定義されたPythonの式が、まるで直接書かれたかのように実行されています。

 

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

 

eval()は、デフォルトでは現在の実行環境(スコープ)にある変数や関数にアクセスできます。しかし、globalslocals引数を使って、評価環境を制御できます。

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

  • locals: 評価時に利用可能なローカル名前空間を指定します。これも辞書形式です。

Python
 
x = 10
y = 5

# デフォルトのスコープで実行 (xとyにアクセス可能)
print(f"デフォルトスコープでの評価: {eval('x * y')}") # 出力: デフォルトスコープでの評価: 50

# globals引数で環境を制限 (xとyにアクセスできない)
# 空の辞書を渡すと、組み込み関数も使えない
try:
    print(eval('x * y', {}))
except NameError as e:
    print(f"エラー(x, yが定義されていない): {e}")
    # 出力: エラー(x, yが定義されていない): name 'x' is not defined

# 必要な変数のみをglobalsに渡す
my_globals = {'a': 100, 'b': 20}
print(f"my_globalsでの評価: {eval('a + b', my_globals)}") # 出力: my_globalsでの評価: 120

# 組み込み関数も使えるようにする
my_globals_with_builtins = {'a': 100, 'b': 20, '__builtins__': None} # 組み込み関数を完全に無効化
try:
    print(eval('max(a, b)', my_globals_with_builtins))
except TypeError as e:
    print(f"エラー(maxが利用できない): {e}")
    # 出力: エラー(maxが利用できない): 'NoneType' object is not callable

# 組み込み関数の一部だけを許可する(より安全な方法)
limited_builtins = {'__builtins__': {'max': max, 'min': min}}
my_safe_globals = {'my_val': 50, **limited_builtins}
print(f"安全なglobalsでのmax評価: {eval('max(my_val, 100)', my_safe_globals)}") # 出力: 安全なglobalsでのmax評価: 100

globalslocalsを適切に制御することで、eval()がアクセスできる範囲を制限し、セキュリティリスクを軽減することができます。

 

eval()関数の最大の落とし穴:セキュリティリスク

 

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

 

なぜ危険なのか?

 

eval()は、与えられた文字列が合法なPythonコードであれば、**どんなコードでも実行できてしまいます。**これにより、悪意のあるユーザーがシステムに損害を与えるコードを実行する可能性があります。

 

悪用される例

 

Python
 
# 悪意のあるユーザーが入力した文字列だと仮定
malicious_code_string = "__import__('os').system('rm -rf /')" # Linux/macOSの場合、全てのファイルを削除するコマンド

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

上記の例では、osモジュールをインポートし、システムコマンドを実行してファイルを削除するという非常に危険な操作をeval()が実行できてしまいます。同様に、機密データへのアクセス、ネットワーク通信、無限ループの生成など、あらゆる悪意のある操作が可能です。

 

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

 

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

  1. 信頼できるソースからの入力のみ: eval()に渡す文字列は、完全に信頼できるソース(自分で書いたコードの一部、信頼できる設定ファイルなど)からのみ取得してください。

  2. globalslocalsで環境を制限: __builtins__を無効化したり、必要な関数や変数のみを明示的に許可したりすることで、悪意のあるコードがアクセスできる範囲を最小限に抑えます。

    Python
     
    # 算術演算のみを許可する例
    safe_globals = {
        '__builtins__': {
            'abs': abs, 'all': all, 'any': any, 'bool': bool, 'complex': complex,
            'dict': dict, 'divmod': divmod, 'float': float, 'hash': hash, 'int': int,
            'len': len, 'list': list, 'max': max, 'min': min, 'pow': pow,
            'range': range, 'round': round, 'set': set, 'slice': slice, 'str': str,
            'sum': sum, 'tuple': tuple, 'type': type, 'zip': zip
            # 必要最小限の組み込み関数のみを許可
        }
    }
    
    user_expression = "10 * 5 + max(3, 7)"
    try:
        result = eval(user_expression, safe_globals)
        print(f"安全な評価結果: {result}")
    except Exception as e:
        print(f"安全な評価中にエラー: {e}")
    
    # 悪意のあるコードを試す(アクセスが制限されているためエラーになる)
    malicious_expr = "__import__('os').getcwd()"
    try:
        eval(malicious_expr, safe_globals)
    except NameError as e: # __import__ が含まれていないため NameError
        print(f"安全な環境で悪意のあるコードをブロック: {e}")
    

    これはセキュリティを強化する有効な方法ですが、すべての潜在的な脆弱性を完全に排除することは非常に困難です。

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

    • 数式の評価: ast.literal_eval()numexpr などのライブラリは、安全に数式を評価できます。

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

    • 動的なメソッド呼び出し: getattr()関数を使用します。

 

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

 

Pythonには、文字列としてコードを実行するもう一つの関数**exec()関数**があります。

  • eval(): Pythonの式(expression)を評価し、その結果を返す

    • 例: 1 + 2, len([1,2]), "Hello".upper()

  • exec(): Pythonの**文(statement)**を実行し、結果を返さない

    • 例: x = 10, if x > 5: print(x), def func(): pass

exec()eval()と同様に、セキュリティ上のリスクが非常に高いため、信頼できないソースからの入力に対しては絶対に使用してはいけません。

 

まとめ

 

Pythonのeval()関数は、文字列として定義されたPythonの式を動的に評価し、その結果を返す強力な組み込み関数です。これにより、柔軟なプログラムの作成が可能になります。しかし、その強力さゆえに、信頼できないソースからの入力をeval()で直接実行することは、深刻なセキュリティリスクを引き起こす可能性があります。

  • eval(expression, [globals, [locals]]): 文字列のPython式を評価し、結果を返します。

  • globalslocals引数を使って、評価時の環境を制限できます。

  • 最大の注意点: 信頼できない文字列の評価には絶対に使用してはいけません。重大なセキュリティ脆弱性の原因となります。

  • 多くの場合、**ast.literal_eval()**や他の安全なライブラリ、あるいはgetattr()などの代替手段を検討すべきです。

  • exec()関数は文を実行し結果を返さない点でeval()と異なりますが、同様にセキュリティリスクが高いです。

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


 

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

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

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

■テックジム東京本校

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

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

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

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