[PYTHON] Python에서 안전하지 않은 사용자 입력으로부터 수학 방정식을 평가하십시오.
PYTHONPython에서 안전하지 않은 사용자 입력으로부터 수학 방정식을 평가하십시오.
사용자가 수학 방정식 (표현식)을 입력 한 다음 해당 방정식을 웹 사이트에서 제공하는 데이터 (상수)와 비교하여 평가하는 웹 사이트가 있습니다. 필요한 수학 연산에는 기호, 산술 연산, min (), max () 및 기타 기본 함수가 포함됩니다. 샘플 방정식은 다음과 같습니다.
max(a * b + 100, a / b - 200)
파이썬을 사용하여 eval ()을 간단히 수행 할 수 있지만, 우리 모두는 이것이 사이트를 손상시킬 수 있음을 알고 있습니다. 수학 방정식 평가의 안전한 접근 방법은 무엇입니까?
해결법
-
==============================
1.써드 파티 패키지가 없으면 파이썬에서 이것을 비교적 쉽게 할 수있다.
써드 파티 패키지가 없으면 파이썬에서 이것을 비교적 쉽게 할 수있다.
그러나 CPU 고갈 및 메모리 고갈 준비와 같은 몇 가지 문제가 있습니다.이 방법은이 방법과 관련이 없으며 다른 방법에도 문제가 있습니다.
다음은 주제에 대한 전체 블로그 게시물입니다. 여기에는 관련된 요지가 있습니다. 아래는 샘플 코드입니다.
"""" The orignal author: Alexer / #python.fi """ import opcode import dis import sys import multiprocessing import time # Python 3 required assert sys.version_info[0] == 3, "No country for old snakes" class UnknownSymbol(Exception): """ There was a function or constant in the expression we don't support. """ class BadValue(Exception): """ The user tried to input dangerously big value. """ MAX_ALLOWED_VALUE = 2**63 class BadCompilingInput(Exception): """ The user tried to input something which might cause compiler to slow down. """ def disassemble(co): """ Loop through Python bytecode and match instructions with our internal opcodes. :param co: Python code object """ code = co.co_code n = len(code) i = 0 extended_arg = 0 result = [] while i < n: op = code[i] curi = i i = i+1 if op >= dis.HAVE_ARGUMENT: # Python 2 # oparg = ord(code[i]) + ord(code[i+1])*256 + extended_arg oparg = code[i] + code[i+1] * 256 + extended_arg extended_arg = 0 i = i+2 if op == dis.EXTENDED_ARG: # Python 2 #extended_arg = oparg*65536L extended_arg = oparg*65536 else: oparg = None # print(opcode.opname[op]) opv = globals()[opcode.opname[op].replace('+', '_')](co, curi, i, op, oparg) result.append(opv) return result # For the opcodes see dis.py # (Copy-paste) # https://docs.python.org/2/library/dis.html class Opcode: """ Base class for out internal opcodes. """ args = 0 pops = 0 pushes = 0 def __init__(self, co, i, nexti, op, oparg): self.co = co self.i = i self.nexti = nexti self.op = op self.oparg = oparg def get_pops(self): return self.pops def get_pushes(self): return self.pushes def touch_value(self, stack, frame): assert self.pushes == 0 for i in range(self.pops): stack.pop() class OpcodeArg(Opcode): args = 1 class OpcodeConst(OpcodeArg): def get_arg(self): return self.co.co_consts[self.oparg] class OpcodeName(OpcodeArg): def get_arg(self): return self.co.co_names[self.oparg] class POP_TOP(Opcode): """Removes the top-of-stack (TOS) item.""" pops = 1 def touch_value(self, stack, frame): stack.pop() class DUP_TOP(Opcode): """Duplicates the reference on top of the stack.""" # XXX: +-1 pops = 1 pushes = 2 def touch_value(self, stack, frame): stack[-1:] = 2 * stack[-1:] class ROT_TWO(Opcode): """Swaps the two top-most stack items.""" pops = 2 pushes = 2 def touch_value(self, stack, frame): stack[-2:] = stack[-2:][::-1] class ROT_THREE(Opcode): """Lifts second and third stack item one position up, moves top down to position three.""" pops = 3 pushes = 3 direct = True def touch_value(self, stack, frame): v3, v2, v1 = stack[-3:] stack[-3:] = [v1, v3, v2] class ROT_FOUR(Opcode): """Lifts second, third and forth stack item one position up, moves top down to position four.""" pops = 4 pushes = 4 direct = True def touch_value(self, stack, frame): v4, v3, v2, v1 = stack[-3:] stack[-3:] = [v1, v4, v3, v2] class UNARY(Opcode): """Unary Operations take the top of the stack, apply the operation, and push the result back on the stack.""" pops = 1 pushes = 1 class UNARY_POSITIVE(UNARY): """Implements TOS = +TOS.""" def touch_value(self, stack, frame): stack[-1] = +stack[-1] class UNARY_NEGATIVE(UNARY): """Implements TOS = -TOS.""" def touch_value(self, stack, frame): stack[-1] = -stack[-1] class BINARY(Opcode): """Binary operations remove the top of the stack (TOS) and the second top-most stack item (TOS1) from the stack. They perform the operation, and put the result back on the stack.""" pops = 2 pushes = 1 class BINARY_POWER(BINARY): """Implements TOS = TOS1 ** TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] print(TOS1, TOS) if abs(TOS1) > BadValue.MAX_ALLOWED_VALUE or abs(TOS) > BadValue.MAX_ALLOWED_VALUE: raise BadValue("The value for exponent was too big") stack[-2:] = [TOS1 ** TOS] class BINARY_MULTIPLY(BINARY): """Implements TOS = TOS1 * TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 * TOS] class BINARY_DIVIDE(BINARY): """Implements TOS = TOS1 / TOS when from __future__ import division is not in effect.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 / TOS] class BINARY_MODULO(BINARY): """Implements TOS = TOS1 % TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 % TOS] class BINARY_ADD(BINARY): """Implements TOS = TOS1 + TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 + TOS] class BINARY_SUBTRACT(BINARY): """Implements TOS = TOS1 - TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 - TOS] class BINARY_FLOOR_DIVIDE(BINARY): """Implements TOS = TOS1 // TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 // TOS] class BINARY_TRUE_DIVIDE(BINARY): """Implements TOS = TOS1 / TOS when from __future__ import division is in effect.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 / TOS] class BINARY_LSHIFT(BINARY): """Implements TOS = TOS1 << TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 << TOS] class BINARY_RSHIFT(BINARY): """Implements TOS = TOS1 >> TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 >> TOS] class BINARY_AND(BINARY): """Implements TOS = TOS1 & TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 & TOS] class BINARY_XOR(BINARY): """Implements TOS = TOS1 ^ TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 ^ TOS] class BINARY_OR(BINARY): """Implements TOS = TOS1 | TOS.""" def touch_value(self, stack, frame): TOS1, TOS = stack[-2:] stack[-2:] = [TOS1 | TOS] class RETURN_VALUE(Opcode): """Returns with TOS to the caller of the function.""" pops = 1 final = True def touch_value(self, stack, frame): value = stack.pop() return value class LOAD_CONST(OpcodeConst): """Pushes co_consts[consti] onto the stack.""" # consti pushes = 1 def touch_value(self, stack, frame): # XXX moo: Validate type value = self.get_arg() assert isinstance(value, (int, float)) stack.append(value) class LOAD_NAME(OpcodeName): """Pushes the value associated with co_names[namei] onto the stack.""" # namei pushes = 1 def touch_value(self, stack, frame): # XXX moo: Get name from dict of valid variables/functions name = self.get_arg() if name not in frame: raise UnknownSymbol("Does not know symbol {}".format(name)) stack.append(frame[name]) class CALL_FUNCTION(OpcodeArg): """Calls a function. The low byte of argc indicates the number of positional parameters, the high byte the number of keyword parameters. On the stack, the opcode finds the keyword parameters first. For each keyword argument, the value is on top of the key. Below the keyword parameters, the positional parameters are on the stack, with the right-most parameter on top. Below the parameters, the function object to call is on the stack. Pops all function arguments, and the function itself off the stack, and pushes the return value.""" # argc pops = None pushes = 1 def get_pops(self): args = self.oparg & 0xff kwargs = (self.oparg >> 8) & 0xff return 1 + args + 2 * kwargs def touch_value(self, stack, frame): argc = self.oparg & 0xff kwargc = (self.oparg >> 8) & 0xff assert kwargc == 0 if argc > 0: args = stack[-argc:] stack[:] = stack[:-argc] else: args = [] func = stack.pop() assert func in frame.values(), "Uh-oh somebody injected bad function. This does not happen." result = func(*args) stack.append(result) def check_for_pow(expr): """ Python evaluates power operator during the compile time if its on constants. You can do CPU / memory burning attack with ``2**999999999999999999999**9999999999999``. We mainly care about memory now, as we catch timeoutting in any case. We just disable pow and do not care about it. """ if "**" in expr: raise BadCompilingInput("Power operation is not allowed") def _safe_eval(expr, functions_and_constants={}, check_compiling_input=True): """ Evaluate a Pythonic math expression and return the output as a string. The expr is limited to 1024 characters / 1024 operations to prevent CPU burning or memory stealing. :param functions_and_constants: Supplied "built-in" data for evaluation """ # Some safety checks assert len(expr) < 1024 # Check for potential bad compiler input if check_compiling_input: check_for_pow(expr) # Compile Python source code to Python code for eval() code = compile(expr, '', 'eval') # Dissect bytecode back to Python opcodes ops = disassemble(code) assert len(ops) < 1024 stack = [] for op in ops: value = op.touch_value(stack, functions_and_constants) return value
-
==============================
2.면책 조항 : 저는 Alexer가 다른 대답의 코드에서 언급 한 사람입니다. 솔직히 말하면, 관련 코드 프로젝트의 99 %가 무관 한 프로젝트에서 거짓말을하고 몇 분 안에 POC를 함께 채울 수 있기 때문에 바이트 코드 파싱 접근법을 반쯤 농담으로 제안했습니다. 그것은 말하자면, 그 자체로 아무 문제가 없어야합니다. 이 작업에 필요한 더 복잡한 기계 일뿐입니다. 실제로 코드를 분해하고 (화이트리스트와 비교하여 opcodes를 확인), 상수와 이름이 유효한지 확인한 다음, 평범하고 사악한 것으로 실행하면됩니다. 실행 중에 편집 성 추가 검사를 삽입하는 기능 만 잃어 버려야합니다. (또 다른 면책 조항 : 나는 여전히 eval을 사용하여 충분히 편안함을 느끼지 못할 것이다)
면책 조항 : 저는 Alexer가 다른 대답의 코드에서 언급 한 사람입니다. 솔직히 말하면, 관련 코드 프로젝트의 99 %가 무관 한 프로젝트에서 거짓말을하고 몇 분 안에 POC를 함께 채울 수 있기 때문에 바이트 코드 파싱 접근법을 반쯤 농담으로 제안했습니다. 그것은 말하자면, 그 자체로 아무 문제가 없어야합니다. 이 작업에 필요한 더 복잡한 기계 일뿐입니다. 실제로 코드를 분해하고 (화이트리스트와 비교하여 opcodes를 확인), 상수와 이름이 유효한지 확인한 다음, 평범하고 사악한 것으로 실행하면됩니다. 실행 중에 편집 성 추가 검사를 삽입하는 기능 만 잃어 버려야합니다. (또 다른 면책 조항 : 나는 여전히 eval을 사용하여 충분히 편안함을 느끼지 못할 것이다)
어쨌든, 나는 지루한 순간을 보냈다. 그래서 나는 똑똑한 방법으로 이것을하기위한 몇 가지 코드를 작성했다. 바이트 코드 대신 AST를 사용합니다. 그것은 컴파일 ()하는 추가 플래그 일뿐입니다. (또는 그냥 ast.parse (), 어쨌든 모듈에서 유형을 원할 것이기 때문에)
import ast import operator _operations = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.div, ast.Pow: operator.pow, } def _safe_eval(node, variables, functions): if isinstance(node, ast.Num): return node.n elif isinstance(node, ast.Name): return variables[node.id] # KeyError -> Unsafe variable elif isinstance(node, ast.BinOp): op = _operations[node.op.__class__] # KeyError -> Unsafe operation left = _safe_eval(node.left, variables, functions) right = _safe_eval(node.right, variables, functions) if isinstance(node.op, ast.Pow): assert right < 100 return op(left, right) elif isinstance(node, ast.Call): assert not node.keywords and not node.starargs and not node.kwargs assert isinstance(node.func, ast.Name), 'Unsafe function derivation' func = functions[node.func.id] # KeyError -> Unsafe function args = [_safe_eval(arg, variables, functions) for arg in node.args] return func(*args) assert False, 'Unsafe operation' def safe_eval(expr, variables={}, functions={}): node = ast.parse(expr, '<string>', 'eval').body return _safe_eval(node, variables, functions) if __name__ == '__main__': import math print safe_eval('sin(a*pi/b)', dict(a=1, b=2, pi=math.pi), dict(sin=math.sin))
바이트 코드 버전에 대해서도 마찬가지입니다. 허용 목록에 대해 작업을 검사하고 이름과 값이 유효한지 확인하면 AST에서 eval을 호출하여 도망 갈 수 있습니다. (그러나 다시, 나는 여전히 그것을하지 않을 것이다. 편집증 때문에. 그리고 편집증은 eval이 관련 될 때 좋다)
from https://stackoverflow.com/questions/26505420/evaluate-math-equations-from-unsafe-user-input-in-python by cc-by-sa and MIT license
'PYTHON' 카테고리의 다른 글
[PYTHON] Django : FormView에 동적 (비 모델) 데이터를 미리 채우는 방법은 무엇입니까? (0) | 2018.11.06 |
---|---|
[PYTHON] pandas.ExcelWriter를 사용하여 Excel 열 너비를 자동으로 조정하는 방법이 있습니까? (0) | 2018.11.06 |
[PYTHON] argparse 및 python을 사용하여 사전을 인수로 허용 [duplicate] (0) | 2018.11.06 |
[PYTHON] 현재 시간이 Python의 datetime 모듈을 사용하여 지정된 범위 내에 있는지 어떻게 알 수 있습니까? (0) | 2018.11.06 |
[PYTHON] 16 진수를 16 진수로 변환하는 방법 (0) | 2018.11.06 |