3

I am generating dynamic images in a web service. The current setup is:

  • Request image.py
  • image.py does a database lookup to determine what data to use to generate the image
  • image.py calls a sub-script (given by the database; e.g., cats.py) to generate the particular image

So image.py contains standard wrapper code, and cats.py draws cats (dogs.py would naturally draw dogs). I would like to generalize this setup so that users can supply their own plotting code, subject to my pre-provided variables—chiefly the plotting canvas and the math library.

Allowing generic Python scripts to be uploaded is of course a substantial security concern. I would like to keep my server under my control, and avoid it being used to blast spam across the Internet; hosing the server with loops and large data structures is also a concern, but to my knowledge there's not much to do about that (please prove me wrong!).

Will the following measures be sufficient to guard against the most flagrant forms of abuse?

  • Call image.py as a user with limited permissions (to avoid users obtaining critical server information)
  • Disable key functions such as __import__, eval, exec, etc.—by setting them equal to None—to limit capabilities to a controlled environment (to avoid users importing network libraries and other things with nasty potential)

I am destroying the database object and all critical variables prior to calling potential user scripts. One challenge is that I am returning Python errors when they exist, by modifying the content-type of the returned document. This might allow inspection of certain properties, but is also necessary to permit debugging.


I have found these related questions, but I'm not certain they're directly related:


Update: I see from testing that __import__ = None does not actually disable the import keyword. So maybe this concept won't work.

3
  • 1
    See this page for some more discussion, especially the note at the bottom. I don't think your disabling certain keywords method will work!
    – bbayles
    Commented Jun 2, 2015 at 1:34
  • @Bo102010 Good Lord. I guess it's time to figure out the PyPy sandbox (which is pretty opaque to me right now).
    – kyle
    Commented Jun 2, 2015 at 1:38
  • Blacklisting is often flawed (so many things to think of) whitelisting functions may be better. Build a parser to execute only approved commands? Not sure how complex this app will be..Perhaps look for a third party sandbox that has an api to communicate with your app?
    – KDEx
    Commented Jun 2, 2015 at 2:31

2 Answers 2

5

To approach this problem from a different angle, how about creating a domain specific language?

Why give the users the ability to do a plethora of things you don't want them to be able to do, and then attempt to sandbox and contain the power you're giving them? Instead, create a simple language that can only be used for plotting, and interpret it with Python.

So you could create a simple DSL called "Plot" or something. Plot would be a language that allows users to describe their plotting strategies. So your image.py file would load cat.plot, which could be supplied by you or a user.

As was mentioned in the comments, whitelisting of functionality is much better than blacklisting. Don't give the users more power than they need—better to not give the power in the first place rather than trying to contain it.

1
  • 1
    I like this suggestion, and it's a very probably the route that I'll end up taking. I chose Python because my userbase are relative programming novices, and Python is the first language many of them take up; R might be another good option. One issue is that I need to allow for data manipulation, which makes the problem less straightforward.
    – kyle
    Commented Jun 2, 2015 at 13:41
2

@Nathan's answer is pretty spot-on: blacklisting functions leaves some sizable security holes that are easily exploitable (with some Googling). I had no luck following the various instructions to get PyPy sandboxing up and working, and the same with chroot or Jailkit.

Ultimately, I rolled my own using various techniques. I recognize that this is long, but these instructions would have really helped me.

  • Create a spoof user with (relatively) few permissions, image-user
  • Create a chroot jail for image-user, more or less according to these steps
  • Disable all network access for image-user, except for localhost. This allows database connections to go through. Note that iptables seems to choke when "localhost" is whitelisted, it must be "127.0.0.1"; the same goes for MySQLdb connections in Python. The command is iptables -A OUTPUT ! -d 127.0.0.1 -m owner image-user -j DROP
  • Set sshd_config so that the user logs in to its jail, Match user image-user \n ChrootDirectory /path/to/jail
  • Disable non-localhost connections for this user in ssh_config
  • Enable key-based auth for ssh for this user
  • Instead of directly loading image.py through the browser, use a wrapper script which loads the reduced-permissions user through ssh; because there is no network access and the user is in a chroot jail, image-user could, at worst, destroy its chroot structure. This would be bad but quick to fix from a system backup. The wrapper script is PHP, and the call is shell_exec( 'ssh image-user@localhost python image.py --param1 value1 --param2 value2' )

(I hope to expand on this answer, especially since futzing with iptables can really hose a server; I'm setting this overview while the events are fresh in my mind)

1
  • Hey! It's been a long time. Just curious how this played out in practice?
    – Nathan
    Commented Oct 15, 2022 at 2:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .