ユーニックス総合研究所

  • home
  • archives
  • python-eval

頭が悪い人のPythonのevalの使い方

  • 作成日: 2022-03-21
  • 更新日: 2023-12-25
  • カテゴリ: Python

頭が悪い人のPythonのevalの使い方

Pythonの組み込み関数にevalがあります。
evalは式を評価します。

これらの関数は使い方を間違えると、システムの脆弱性になる可能性がある危険を持っている関数です。
頭が悪い人はこれらの関数を使うと痛い目を見るかもしれません。

この記事では頭が悪い人のevalの使い方を紹介します。

関連記事

頭が悪い人のPythonのevalの使い方
頭がいい人のPythonのexitの使い方
状態遷移による文字列パースのテクニック【Python】
形態素解析で代名詞+助詞+名詞を文章から抜き出す【Python, 自然言語処理, Janome】
在宅・未経験、Pythonで稼ぐ方法は?【取引・宣伝・広告】

evalの使い方

evalは引数の文字列を解析して、Pythonの式として評価し、その結果を返す関数です。
↓のように足し算などを計算できます。

result = eval('1 + 2')  
print(result)  # 3  

式を評価する関数なので、↓のように文を実行することはできません。

eval('import os')  

上記のコードを実行すると↓のようにエラーになります。

Traceback (most recent call last):  
  File "/tmp/evalstmt.py", line 1, in <module>  
    eval('import os')  
  File "<string>", line 1  
    import os  
    ^  
SyntaxError: invalid syntax  

外部から変数を注入する

evalの第1引数には評価する式の文字列を渡しますが、第2引数にはglobalsを指定できます。
これはグローバルスコープの変数の定義です。
たとえば↓のように使います。

result = eval('a + 2', {'a': 1})  
print(result)  # 3  

↑ではglobals{'a': 1}を設定しています。
こうすると第1引数の式で変数aを使うことができます。

第3引数にはlocalsを指定できます。
これはローカル変数の定義です。

result = eval('a + 2', {}, {'a': 1})  
print(result)  # 3  

↑のように式で使うことができます。

外部から関数を注入する

globalslocalsには整数の他に関数なども渡すことができます。

def hello():  
    print('Hello')  
    return 1  

result = eval('hello()', {'hello': hello})  
print(result)  # 1  

↑のコードを実行すると↓の結果になります。

Hello  
1  

組み込みオブジェクトの利用を制限する

evalのglobals__builtins__を指定すると、評価時に使える組み込み関数などを制限することができます。
__builtins__Noneを指定すると組み込み関数が使えなくなります。

eval('print(1)', {'__builtins__': None})  

↑のコードを実行すると↓の結果になります。

Traceback (most recent call last):  
  File "/tmp/evalbuiltins.py", line 1, in <module>  
    eval('exec("hige")', {'__builtins__': None})  
  File "<string>", line 1, in <module>  
TypeError: 'NoneType' object is not subscriptable  

「Noneは呼び出しできない」と書かれています。
組み込みのprint()Noneになっているわけですね。

print()のみを使えるようにしたい場合は↓のようにします。

eval('print(1)', {'__builtins__': {'print': print}})  

↑のコードを実行すると↓の結果になります。

1  

たとえばこの指定でlist()などを呼び出してもうまくいきません。

eval('list()', {'__builtins__': {'print': print}})  
Traceback (most recent call last):  
  File "/tmp/evallist.py", line 1, in <module>  
    eval('list()', {'__builtins__': {'print': print}})  
  File "<string>", line 1, in <module>  
NameError: name 'list' is not defined  

頭が悪い人のevalの使い方

evalの仕様について解説してきましたが、evalの使い方で注意が必要なのは、第1引数の指定方法です。
evalは式を評価しますが、組み込み関数なども呼び出すことができます。
これらの組み込み関数にはシステム側に影響を与えることも出来る関数なども含まれています。

たとえばWebアプリを作っていたとします。
そのWebアプリの機能で、ユーザーが入力した足し算の結果を表示する機能があったとします。
この機能を実装するにあたって、ユーザーの入力をevalに渡して、evalに足し算を計算させようとしたとします。

コードで言うと↓みたいな感じです。

def my_view():  
    expr = get_user_input()  # ユーザーの入力を取得して  
    result = eval(expr)  # evalに計算させて  
    render_result(result)  # 結果を出力  

こういったコードを書いてしまうと、Webアプリの脆弱性になってしまいます。
ソフトウェア開発では「ユーザーの入力は一切信用しない」という掟があります。
この掟を破ってしまうと↑のようなコードを書くことになってしまいます。

たとえばユーザーの入力に、危険な組み込み関数を呼び出すコードが書かれていたらどうなるでしょうか?
evalはおかまいなしに式を評価して実行してしまいます。
↑のようなコードを書くと、Webアプリのユーザーは自由にサーバー側のシステムにいたずらすることが出来るようになります。

というわけで、これは頭の悪い人のコードです。
もっとも、こういったミスはわれわれ凡人はみんなやることがあります。
ですので万が一頭の悪いコードを書いてしまっても気にしないようにしてください。
もちろん修正は必要ですけど。

おわりに

今回は頭の悪い人のevalの使い方を解説しました。
頭の悪い人と言っても、われわれはみな頭が悪い人になる可能性を秘めています。
要はそれを予防する意識が必要だということです。

ヒューマンエラーは0にすることはできませんが、0に近づけることは出来ます。
0に近づけていきたいところですね。

🦝 < evalはけっこうこわい関数

🐭 < 使う場合は要注意