Running Untrusted Python Code in AWS-LAMBDA using Exec or Eval

Viewed 1222

Been doing a ton of research. I am a mere padawan, however, I have a project where I must run a user's untrusted Python3 code from a website.

I also apologize in advance if this question has some moving parts.

  • I am looking for an as safe as possible approach. This doesn't need to be 100% perfect unless there is a big risk of leaking extremely sensitive data.

Main questions:

  • Does my AWS-lambda plan run an extreme risk for leaking sensitive data?
  • Are there any other simple precautions that I should take which could make this work safer in AWS-lambda?
  • Are there ways for exec() to break out of the AWS-lambda container and make any other network connections if all I have connected to it is the single AWS-api-gateway for the REST call?
  • Do I even need to limit __builtins__ and locals, or are AWS-lambda containers safe enough?

BackGround

It seems most companies use Kubernetes and Docker containers to execute untrusted python code (such a Leetcode, Programiz, or hackerRank).

See these helpful links:

My Plan

I am thinking that I can POST my arbitrary Python code to an AWS Lambda Function as a microservice, using their containerization/scaling rather than build my own. In the Lambda container, I can just run the code through a simple exec or eval function, perhaps with some limitation like this:

"

safe_list = ['math','acos', 'asin', 'atan', 'print','atan2', 'ceil', 'cos', 'cosh', 'de grees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] 
    safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ]) 
    safe_dict['abs'] = abs
    exec(userCode,{"**__builtins__"**:None},safe_dict )

Special Note:

  • I am not too concerned about infinite loops or crashing things, because I will just timeout and tell the user to try again.
  • All I need to do is run pretty simple python code (generally less than a few lines) and return exceptions, stdout, prints, and run a check on the result. Need to run:
    • Math operators, lists, loops, lambda functions, maps, filters, declare methods, declare classes with properties, print.
  • This doesn't need to be a perfect project for hundreds of thousands of users. I just want to have a live site for a resume booster and maybe make a little money on ads to help with costs.
  • If there are severe limitations, I can eventually implement it in Kubernetes (as in the above link), but hopefully, this solution will work well enough.
  • I just want this to work relatively well and not take too long to build or cost too much money.
  • I do not want to leak any sensitive information.

Security things I am already planning on doing:

  • AWS lambda: Limit the time out to around 1-2 seconds
  • AWS lambda: Limit the memory usage to 128mb
  • My Own Code: Use regex to make sure no one is passing in double underscores badstuff
  • Keeping this microservice as minimal as possible (only connecting a single AWS-API-gateway).

Other notes:

  • I don't think I can use restrictedPython or PyPy's sandbox feature in AWS Lambda because I don't have access to those dependencies OOB. I'm hoping that those are not necessary for this use case.
  • If it's impossible to do this with exec(), are there safe python interpreters on GitHub or someplace that I can literally copy-paste into files in AWS-lambda and just call them?
  • I am planning on allowing the user to print from exec with something like this:

"

@contextlib.contextmanager
def stdoutIO(stdout=None):
    old = sys.stdout
    if stdout is None:
        stdout = StringIO()
    sys.stdout = stdout
    yield stdout
    sys.stdout = old

    
with stdoutIO() as s:
    try:
        exec(userCode)
    except:
        print("Something wrong with the code")
print( s.getvalue())
print(i)

Please let me know if you have any questions or suggestions.

___ Edit ** adding architecture diagram ___

I think this is pretty much it as for the architecture fr now

2 Answers

I'm looking into this too and interested in whether AWS lambda can be used to run untrusted code. Mostly your logic looks reasonable, but a couple of things stick out

Use regex to make sure no one is passing in double underscores badstuff

Don't do this! It's impossible (or virtually impossible) to check if a string containing python code is malicious or not. Think of someone asking you to call this:

eval(base64.b64decode(b'cHJpbnQoInRoaXMgaXNuJ3QgYmFkLCBidXQgeW91IGRvbid0IGtub3cgdGhhdCEiKQ=='))

Is that safe? Is it going to be picked up by a regex? Fine you could mock eval() but what next? I've absolutely no doubt that a half competent python developer could find a route round it.

Half secure is worse than not secure at all

You'll start to trust it, build another feature that relies on the semi-secure layer and end up getting hacked.


Solutions

  1. Use a language or feature that lots of people and big companies rely on, that way the risk of a breach is small (and if someone does find a zero-day vulnerability, there will be someone more profitable to hack than you). Javascript might be a good option here if you can use it, you might also try pypy or RustPython compiled to web assembly.
  2. Accept that your code is not at all secure, and rely on the OS/PAAS to protect you. In other words, let the user run whatever they like in the AWS lambda, and let Amazon isolate it.

My Problem

I want to use the second option, but I'm worried about a hostile developers capacity to taint a lambda container, which is then used by another innocent developer who falls victim.

Let's say we have an AWS lambda that effectively calls eval(user_written_code) and returns the result, it might also set a bunch of environment variables which can be references in user_written_code.

The lambda prevents them doing anything malicious when the code is called - e.g. the lambda user credentials don't have access to anything scary. They can make requests, use up memory, eat CPU, hang - all that's taken care of by AWS.

But how do I stop the following vector:

  1. Malicious user executes code which mocks a builtin method, when the method is called the current code context and environment variables are posted to some server.
  2. Innocent user executes their code on the same lambda container, AWS lambda cleverly reuses the process to improve performance.
  3. The innocent user's code and credentials (environment variables) get posted to the malicious users server.

I'd be interested to know if anyone else has confirmed one way or the other whether there's an effective way to prevent lambda containers getting tainted like this?

Interesting post, I'm also looking at using the existing AWS infrastructure to run untrusted python code instead of using docker containers which could get expensive real quick. I've only started looking into this but my preference was to use restrictedPython to add some safe guards.

I'm pretty new to both python dev and lambda's but you mention that restrictedPython isn't available out of the box, surely all you would need to do is include it as a dependency in a zip and upload it to a lambda? Unless I'm missing something?

Also not sure if you've seen this but I found this vid pretty helpful: https://www.youtube.com/watch?v=sL_syMmRkoU

Related