Last active
December 20, 2015 17:59
-
-
Save mikofski/6172243 to your computer and use it in GitHub Desktop.
[SudoNhim web security SyntaxChecker](http://stackoverflow.com/questions/10661079/restricting-pythons-syntax-to-execute-user-code-safely-is-this-a-safe-approach) & [JF Sebastian Eval is Evil](http://stackoverflow.com/a/9558001/1020470)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
tree = ast.Module() | |
tree.body = [ast.Expr()] | |
tree.body[0].value = ast.Lambda() | |
tree.body[0].value.args = ast.arguments() | |
tree.body[0].value.args.args = [ast.Name(id=a, ctx=ast.Param()) for a in args] | |
tree.body[0].value.args.vararg = None | |
tree.body[0].value.args.kwarg = None | |
tree.body[0].value.args.defaults = [] | |
tree.body[0].value.body = ast.parse(expr).body[0].value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ast | |
import operator as op | |
# supported operators | |
operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, | |
ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor} | |
args = ['x', 'y', 'z_0'] | |
def eval_expr(expr): | |
""" | |
>>> eval_expr('2^6') | |
4 | |
>>> eval_expr('2**6') | |
64 | |
>>> eval_expr('1 + 2*3**(4^5) / (6 + -7)') | |
-5.0 | |
""" | |
return eval_(ast.parse(expr).body[0].value) # Module(body=[Expr(value=...)]) | |
def eval_(node): | |
if isinstance(node, ast.Num): # <number> | |
return node.n | |
elif isinstance(node, ast.operator): # <operator> | |
return operators[type(node)] | |
elif isinstance(node, ast.BinOp): # <left> <operator> <right> | |
return eval_(node.op)(eval_(node.left), eval_(node.right)) | |
elif isinstance(node, ast.Name): | |
if node.id not in args: | |
raise Exception('%s is not in the list of args' % node.id) | |
else: | |
raise TypeError(node) | |
# You can easily limit allowed range for each operation or any intermediate result, | |
# e.g., to limit input arguments for a**b: | |
def power(a, b): | |
if any(abs(n) > 100 for n in [a, b]): | |
raise ValueError((a,b)) | |
return op.pow(a, b) | |
operators[ast.Pow] = power | |
# Or to limit magnitude of intermediate results: | |
import functools | |
def limit(max_=None): | |
"""Return decorator that limits allowed returned values.""" | |
def decorator(func): | |
functools.wraps(func) | |
def wrapper(*args, **kwargs): | |
ret = func(*args, **kwargs) | |
try: | |
mag = abs(ret) | |
except TypeError: | |
pass # not applicable | |
else: | |
if mag > max_: | |
raise ValueError(ret) | |
return ret | |
return wrapper | |
return decorator | |
eval_ = limit(max_=10**100)(eval_) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ast | |
allowed_functions = set([ | |
#math library | |
'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', | |
'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', | |
'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', | |
'frexp', 'fsum', 'gamma', 'hypot', 'isinf', 'isnan', 'ldexp', | |
'lgamma', 'log', 'log10', 'log1p', 'modf', 'pi', 'pow', 'radians', | |
'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc', | |
#builtins | |
'abs', 'max', 'min', 'range', 'xrange' | |
]) | |
allowed_node_types = set([ | |
#Meta | |
'Module', 'Assign', 'Expr', | |
#Control | |
'For', 'If', 'Else', | |
#Data | |
'Store', 'Load', 'AugAssign', 'Subscript', | |
#Datatypes | |
'Num', 'Tuple', 'List', | |
#Operations | |
'BinOp', 'Add', 'Sub', 'Mult', 'Div', 'Mod', 'Compare' | |
]) | |
safe_names = set([ | |
'True', 'False', 'None' | |
]) | |
class SyntaxChecker(ast.NodeVisitor): | |
def check(self, syntax): | |
tree = ast.parse(syntax) | |
self.passed=True | |
self.visit(tree) | |
def visit_Call(self, node): | |
if node.func.id not in allowed_functions: | |
raise SyntaxError("%s is not an allowed function!"%node.func.id) | |
else: | |
ast.NodeVisitor.generic_visit(self, node) | |
def visit_Name(self, node): | |
try: | |
eval(node.id) | |
except NameError: | |
ast.NodeVisitor.generic_visit(self, node) | |
else: | |
if node.id not in safe_names and node.id not in allowed_functions: | |
raise SyntaxError("%s is a reserved name!"%node.id) | |
else: | |
ast.NodeVisitor.generic_visit(self, node) | |
def generic_visit(self, node): | |
if type(node).__name__ not in allowed_node_types: | |
raise SyntaxError("%s is not allowed!"%type(node).__name__) | |
else: | |
ast.NodeVisitor.generic_visit(self, node) | |
if __name__ == '__main__': | |
x = SyntaxChecker() | |
while True: | |
try: | |
x.check(raw_input()) | |
except Exception as e: | |
print e |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ast | |
allowed_functions = set([ | |
#math library | |
'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', | |
'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', | |
'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', | |
'frexp', 'fsum', 'gamma', 'hypot', 'isinf', 'isnan', 'ldexp', | |
'lgamma', 'log', 'log10', 'log1p', 'modf', 'pi', 'pow', 'radians', | |
'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc', | |
#builtins | |
'abs', 'max', 'min', 'range', 'xrange' | |
]) | |
allowed_node_types = set([ | |
#Meta | |
'Module', 'Assign', 'Expr', | |
#Control | |
'For', 'If', 'Else', | |
#Data | |
'Store', 'Load', 'AugAssign', 'Subscript', | |
#Datatypes | |
'Num', 'Tuple', 'List', | |
#Operations | |
'BinOp', 'Add', 'Sub', 'Mult', 'Div', 'Mod', 'Compare' | |
]) | |
safe_names = set([ | |
'True', 'False', 'None' | |
]) | |
class SyntaxChecker(ast.NodeVisitor): | |
def check(self, syntax): | |
tree = ast.parse(syntax) | |
self.visit(tree) | |
def visit_Call(self, node): | |
if node.func.id not in allowed_functions: | |
raise SyntaxError("%s is not an allowed function!"%node.func.id) | |
else: | |
ast.NodeVisitor.generic_visit(self, node) | |
def visit_Name(self, node): | |
try: | |
eval(node.id) | |
except NameError: | |
ast.NodeVisitor.generic_visit(self, node) | |
else: | |
if node.id not in safe_names and node.id not in allowed_functions: | |
raise SyntaxError("%s is a reserved name!"%node.id) | |
else: | |
ast.NodeVisitor.generic_visit(self, node) | |
def generic_visit(self, node): | |
if type(node).__name__ not in allowed_node_types: | |
raise SyntaxError("%s is not allowed!"%type(node).__name__) | |
else: | |
ast.NodeVisitor.generic_visit(self, node) | |
if __name__ == '__main__': | |
x = SyntaxChecker() | |
while True: | |
try: | |
x.check(raw_input()) | |
except Exception as e: | |
print e |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment