From 1b453ec0df074db8f4a42e621599f5b806602441 Mon Sep 17 00:00:00 2001 From: Matthew Newville Date: Thu, 11 Jul 2024 13:29:17 -0500 Subject: [PATCH] disallow string.format(), improve security of f-string evaluation --- asteval/asteval.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/asteval/asteval.py b/asteval/asteval.py index 5d33c9a..091b5cf 100644 --- a/asteval/asteval.py +++ b/asteval/asteval.py @@ -44,9 +44,9 @@ import time from sys import exc_info, stderr, stdout -from .astutils import (HAS_NUMPY, UNSAFE_ATTRS, ExceptionHolder, ReturnedNone, Empty, - make_symbol_table, numpy, op2func, valid_symbol_name, - Procedure) +from .astutils import (HAS_NUMPY, UNSAFE_ATTRS, UNSAFE_ATTRS_DTYPES, + ExceptionHolder, ReturnedNone, Empty, make_symbol_table, + numpy, op2func, valid_symbol_name, Procedure) ALL_NODES = ['arg', 'assert', 'assign', 'attribute', 'augassign', 'binop', 'boolop', 'break', 'bytes', 'call', 'compare', 'constant', @@ -492,10 +492,11 @@ def on_formattedvalue(self, node): # ('value', 'conversion', 'format_spec') fstring_converters = {115: str, 114: repr, 97: ascii} if node.conversion in fstring_converters: val = fstring_converters[node.conversion](val) - fmt = '{0}' + fmt = '{__fstring__}' if node.format_spec is not None: - fmt = f'{{0:{self.run(node.format_spec)}}}' - return fmt.format(val) + fmt = f'{{__fstring__:{self.run(node.format_spec)}}}' + else: + return fmt.format(__fstring__=val) def _getsym(self, node): val = self.symtable.get(node.id, ReturnedNone) @@ -546,6 +547,7 @@ def node_assign(self, node, val): def on_attribute(self, node): # ('value', 'attr', 'ctx') """Extract attribute.""" + ctx = node.ctx.__class__ if ctx == ast.Store: msg = "attribute for storage: shouldn't be here!" @@ -554,20 +556,23 @@ def on_attribute(self, node): # ('value', 'attr', 'ctx') sym = self.run(node.value) if ctx == ast.Del: return delattr(sym, node.attr) - - # ctx is ast.Load - if not (node.attr in UNSAFE_ATTRS or - (node.attr.startswith('__') and - node.attr.endswith('__'))): + # + unsafe = (node.attr in UNSAFE_ATTRS or + (node.attr.startswith('__') and node.attr.endswith('__'))) + if not unsafe: + for dtype, attrlist in UNSAFE_ATTRS_DTYPES.items(): + unsafe = isinstance(sym, dtype) and node.attr in attrlist + if unsafe: + break + if unsafe: + msg = f"no safe attribute '{node.attr}' for {repr(sym)}" + self.raise_exception(node, exc=AttributeError, msg=msg) + else: try: return getattr(sym, node.attr) except AttributeError: pass - # AttributeError or accessed unsafe attribute - msg = f"no attribute '{node.attr}' for {self.run(node.value)}" - self.raise_exception(node, exc=AttributeError, msg=msg) - return None def on_assign(self, node): # ('targets', 'value') """Simple assignment."""