web-dev-qa-db-ja.com

Python eval:ビルトインと属性アクセスを無効にしても危険ですか?

Pythonのイントロスペクション機能を使用して物事を掘り下げて再抽出できるため、危険な関数を非表示にしても、 evalは危険 であることは誰もが知っています。たとえば、__builtins__を削除しても、次のようにして取得できます

[c for c in ().__class__.__base__.__subclasses__()  
 if c.__name__ == 'catch_warnings'][0]()._module.__builtins__

ただし、これについて私が見たすべての例では、属性アクセスを使用しています。 および属性アクセスをすべて無効にした場合(Python tokenizerで入力をトークン化して拒否することにより)属性アクセストークンがある場合)?

そして、あなたが尋ねる前に、いいえ、私のユースケースでは、これらのどちらも必要ないので、それほど不自由ではありません。

私がやろうとしていることは、SymPyの sympify 関数をより安全にすることです。現在、入力をトークン化し、いくつかの変換を行い、名前空間で評価します。ただし、属性へのアクセスを許可するため、安全ではありません(実際には必要ありません)。

37
asmeurer

Python 3.6- f-strings

彼らは式を評価でき、

>>> eval('f"{().__class__.__base__}"', {'__builtins__': None}, {})
"<class 'object'>"

ただし、属性アクセスはPythonのトークナイザでは検出されません。

0,0-0,0:            ENCODING       'utf-8'        
1,0-1,1:            ERRORTOKEN     "'"            
1,1-1,27:           STRING         'f"{().__class__.__base__}"'
2,0-2,0:            ENDMARKER      '' 
28
vaultah

evalから戻り値を作成して、 exception outsideevalをスローしようとした場合、printlogrepr、何でも:

eval('''((lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))))
        (lambda f: lambda n: (1,(1,(1,(1,f(n-1))))) if n else 1)(300))''')

これにより、(1,(1,(1,(1...という形式のネストされたタプルが作成されます。その値をprinted(Python 3の場合)、stredまたはrepredにすることはできません。それをデバッグしようとするすべての試みは

RuntimeError: maximum recursion depth exceeded while getting the repr of a Tuple

pprintsafereprも失敗します:

...
  File "/usr/lib/python3.4/pprint.py", line 390, in _safe_repr
    orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
  File "/usr/lib/python3.4/pprint.py", line 340, in _safe_repr
    if issubclass(typ, dict) and r is dict.__repr__:
RuntimeError: maximum recursion depth exceeded while calling a Python object

したがって、これを文字列化する安全な組み込み関数はありません。次のヘルパーが役立つ可能性があります。

def excsafe_repr(obj):
    try:
        return repr(obj)
    except:
        return object.__repr__(obj).replace('>', ' [exception raised]>')

そして、print in Python 2が実際にstrを使用しないという問題があります。/repr、再帰チェックがないため安全ではありません。つまり、上記のラムダモンスターの戻り値を取得します。strreprはできませんが、通常のprintprint_functionではない)が出力します。うまく。ただし、printステートメントを使用して出力されることがわかっている場合は、これを利用してPython 2にSIGSEGVを生成できます。

print eval('(lambda i: [i for i in ((i, 1) for j in range(1000000))][-1])(1)')

クラッシュPython 2 with SIGSEGVバグトラッカーの [〜#〜] wontfix [〜#〜] です。したがって、安全を確保したい場合は、print- the-statementを使用しないでください。 from __future__ import print_function


これはクラッシュではありませんが、

eval('(1,' * 100 + ')' * 100)

実行すると、出力

s_Push: parser stack overflow
Traceback (most recent call last):
  File "yyy.py", line 1, in <module>
    eval('(1,' * 100 + ')' * 100)
MemoryError

MemoryErrorはキャッチでき、Exceptionのサブクラスです。パーサーにはいくつかの stackoverflowsからのクラッシュを回避するために非常に保守的な制限 (意図されたしゃれ)があります。ただし、s_Push: parser stack overflowはCコードによってstderrに出力され、抑制できません。


そして昨日私は尋ねました なぜクラッシュしないようにPython 3.4を修正しないのですか

% python3  
Python 3.4.3 (default, Mar 26 2015, 22:03:40) 
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class A:
...     def f(self):
...         nonlocal __x
... 
[4]    19173 segmentation fault (core dumped)  python3

および Serhiy Storchakaの回答 Pythonコア開発者は、整形式に見えるコードのSIGSEGVをセキュリティ問題と見なさないことを確認しました。

3.4では、セキュリティ修正のみが受け入れられます。

したがって、サニタイズされているかどうかにかかわらず、サードパーティからのコードをPythonで実行することは決して安全であるとは決して言えないと結論付けることができます。

そして Nick Coghlan そして added

また、Pythonコードによって引き起こされたセグメンテーション違反が現在セキュリティバグと見なされない理由に関するいくつかの追加の背景として:CPythonにはセキュリティサンドボックスが含まれていないため、提供するOSに完全に依存していますプロセスの分離。そのOSレベルのセキュリティ境界は、コードが「正常に」実行されているのか、または意図的にトリガーされたセグメンテーション違反の後で変更された状態で実行されているのかには影響されません。

20
Antti Haapala

ユーザーは引き続き、膨大な数に評価される式を入力することでDoSを実行できます。これにより、メモリがいっぱいになり、Pythonプロセスがクラッシュします。たとえば、

'10**10**100'

ビルトインの回復やsegfaultの作成など、より伝統的な攻撃が可能かどうか、私は間違いなく興味があります。

編集:

結局のところ、Pythonのパーサーでさえこの問題があります。

lambda: 10**10**100

定数を事前計算しようとするため、ハングします。

12
asmeurer

私は信じていませんPythonは信頼できないコードに対するセキュリティを備えているように設計されています。公式のPython 2インタプリタ:

eval('()' * 98765)

私の answer から「SIGSEGVを返す最も短いコード」コードゴルフの質問へ。

7
feersum

評価された式に安全でないトークンが含まれていないことを確認するsafe_evalの例を次に示します。 ASTを解釈するliteral_evalのアプローチをとるのではなく、トークンタイプをホワイトリストに登録し、式がテストに合格した場合は実際のevalを使用します。

# license: MIT (C) tardyp
import ast


def safe_eval(expr, variables):
    """
    Safely evaluate a a string containing a Python
    expression.  The string or node provided may only consist of the following
    Python literal structures: strings, numbers, tuples, lists, dicts, booleans,
    and None. safe operators are allowed (and, or, ==, !=, not, +, -, ^, %, in, is)
    """
    _safe_names = {'None': None, 'True': True, 'False': False}
    _safe_nodes = [
        'Add', 'And', 'BinOp', 'BitAnd', 'BitOr', 'BitXor', 'BoolOp',
        'Compare', 'Dict', 'Eq', 'Expr', 'Expression', 'For',
        'Gt', 'GtE', 'Is', 'In', 'IsNot', 'LShift', 'List',
        'Load', 'Lt', 'LtE', 'Mod', 'Name', 'Not', 'NotEq', 'NotIn',
        'Num', 'Or', 'RShift', 'Set', 'Slice', 'Str', 'Sub',
        'Tuple', 'UAdd', 'USub', 'UnaryOp', 'boolop', 'cmpop',
        'expr', 'expr_context', 'operator', 'slice', 'unaryop']
    node = ast.parse(expr, mode='eval')
    for subnode in ast.walk(node):
        subnode_name = type(subnode).__name__
        if isinstance(subnode, ast.Name):
            if subnode.id not in _safe_names and subnode.id not in variables:
                raise ValueError("Unsafe expression {}. contains {}".format(expr, subnode.id))
        if subnode_name not in _safe_nodes:
            raise ValueError("Unsafe expression {}. contains {}".format(expr, subnode_name))

    return eval(expr, variables)



class SafeEvalTests(unittest.TestCase):

    def test_basic(self):
        self.assertEqual(safe_eval("1", {}), 1)

    def test_local(self):
        self.assertEqual(safe_eval("a", {'a': 2}), 2)

    def test_local_bool(self):
        self.assertEqual(safe_eval("a==2", {'a': 2}), True)

    def test_lambda(self):
        self.assertRaises(ValueError, safe_eval, "lambda : None", {'a': 2})

    def test_bad_name(self):
        self.assertRaises(ValueError, safe_eval, "a == None2", {'a': 2})

    def test_attr(self):
        self.assertRaises(ValueError, safe_eval, "a.__dict__", {'a': 2})

    def test_eval(self):
        self.assertRaises(ValueError, safe_eval, "eval('os.exit()')", {})

    def test_exec(self):
        self.assertRaises(SyntaxError, safe_eval, "exec 'import os'", {})

    def test_multiply(self):
        self.assertRaises(ValueError, safe_eval, "'s' * 3", {})

    def test_power(self):
        self.assertRaises(ValueError, safe_eval, "3 ** 3", {})

    def test_comprehensions(self):
        self.assertRaises(ValueError, safe_eval, "[i for i in [1,2]]", {'i': 1})
3
tardyp

localsおよびglobals辞書の制御は非常に重要です。それ以外の場合、誰かがevalまたはexecを渡して再帰的に呼び出すことができます

safe_eval('''e("""[c for c in ().__class__.__base__.__subclasses__() 
    if c.__name__ == \'catch_warnings\'][0]()._module.__builtins__""")''', 
    globals={'e': eval})

再帰的なevalの式は単なる文字列です。

また、グローバルネームスペースのevalおよびexec名を、実際のevalまたはexecではないものに設定する必要があります。グローバル名前空間は重要です。ローカル名前空間を使用する場合、内包表記やラムダなど、別の名前空間を作成するものはすべて、それを回避します

safe_eval('''[eval("""[c for c in ().__class__.__base__.__subclasses__()
    if c.__name__ == \'catch_warnings\'][0]()._module.__builtins__""") for i in [1]][0]''', locals={'eval': None})

safe_eval('''(lambda: eval("""[c for c in ().__class__.__base__.__subclasses__()
    if c.__name__ == \'catch_warnings\'][0]()._module.__builtins__"""))()''',
    locals={'eval': None})

ここでも、safe_evalは文字列と関数呼び出しのみを認識し、属性アクセスは認識しません。

安全な解析を無効にするフラグがある場合は、safe_eval関数自体をクリアする必要もあります。そうでなければ、あなたは単に行うことができます

safe_eval('safe_eval("<dangerous code>", safe=False)')
0
asmeurer