1

I'm trying to find a way to allow my users to request arbitrary portions of a Python object, and have the server return that data without accidentally allowing them to request stuff they're not allowed to have or do things they're not allowed to do.

For example, say the server has this data:

my_stuff = {"alpha": ["bravo", "charlie", {"delta": "echo"}], "foxtrot": "golf"}

I want to allow the user to send an HTTP request like:

/path/to/my/script/?gimme=my_stuff[alpha][2][delta]

and have the request return echo. Similarly, if gimme=foxtrot, I want to return golf.

I can't just pass this off to eval() right? The security implications alone would be Very Bad, but I can't imagine the performance being very good either.

The syntax of the request can change entirely, the requirement is that I allow users to request arbitrary portions of a server-side object.... but just that object, read-only.

Is there a safe/smart way to do this, or am I crazy?

sshashank124
  • 31,495
  • 9
  • 67
  • 76
Daniel Quinn
  • 6,010
  • 6
  • 38
  • 61
  • possible duplicate of [recursive access to dictionary and modification](http://stackoverflow.com/questions/23011146/recursive-access-to-dictionary-and-modification) – Martijn Pieters Apr 16 '14 at 10:34
  • 1
    You have a nested sequence of objects; parse the path into a list of strings and integers and simply resolve that path. Using strings and integers for item access is perfectly safe and not exploitable. – Martijn Pieters Apr 16 '14 at 10:35

1 Answers1

0

Martijn Pieters put me on the right track, but there was still some work to figure out how to get reduce() to traverse the various object types, so I'll outline all of that here.

For my case, my_stuff was actually an object with a bunch of different property types:

class Alpha(object):
    def __init__(self):
        bravo = [1, 2, 3]
        charlie = 7

class Delta(object):
    def __init__(self):
        echo = [Alpha(), Alpha()]
        foxtrot = {
            "golf": "hotel"
        }

So something as simple as reduce(dict.__getitem__, path, my_stuff) wasn't going to do the job here. Using that as a start though, I ended up with this:

def get_parsed_attribute(self, result, field_name):
    try:
        return reduce(self.smart_getattr, field_name.split("__"), result)
    except (AttributeError, IndexError):
        return None  # We can assume that either the value is missing, or
                     # that key doesn't exist.  Either way, there's no harm.


@staticmethod
def smart_getattr(obj, key):
    if isinstance(obj, list):
        return list.__getitem__(obj, int(key))
    if isinstance(obj, dict):
        return dict.__getitem__(obj, key)
    return getattr(obj, key)

This code figures out what kind of object we're dealing with and executes the appropriate getter, so when it's called by reduce() we get the same effect as what we Martijn suggested for my simple dictionary.

Daniel Quinn
  • 6,010
  • 6
  • 38
  • 61