9

I'm working on an application where people (anyone) can upload code (Java and possibly C(++) to start with) that will be compiled and run on the server. This is of course a huge security risk and it will be necessary that all this is sandboxed properly. This sandboxing is out of the scope of this question though. Assume that is have been taken care of.

Next to this, there will be functions in the system that will rely on shell commands and PHP's exec(), shell_exec(), etc. functions. The commands that will need to be executed aren't very many, mainly java(c), gcc, g++, etc. It would be fairly easy to make a list of the commands that we would need, if necessary. It will not be possible for a user to execute other commands than the once we decide. For example, someone uploads some java code and asks the server to compile it. Then the server will run javac. The user's input can only change javac's parameters (that are escaped using escapeshellarg()).

I'm wondering what security precautions I should still take. I was planning to use PHP's safe mode, so that only files in the safe_mode_exec_dir could be executed. I was also planning to have the ownership of the shell files set to root:www-data so that www-data cannot change permissions or ownership, and furthermore to have the permissions something like rwxr-xr-- so that www-data cannot modify the file. However, safe mode has been removed from PHP as of 5.4.0. What is the current way to do stuff like this?

Would it be safer to have these shell commands run by an entirely different user, that doesn't even have access to any other directories than safe_mode_exec_dir? How would I then go about that?

Another option would be to have PHP only maintain a list of things that need to be done, and let a cronjob run every minute or so, by a restricted user, to walk through the list and perform the necessary operations. Would that be a safer approach? Because of the up-to-one-minute delay, I would prefer to do this from PHP directly.

I have full access to my server but due to policies I am not allowed to use virtualisation.

10
  • Voting to close as too broad; anyway, I'd run everything in a newly spinup vm which is put and runs offline; then kill the vm. Commented Feb 16, 2015 at 14:17
  • This might be overkill but I'm thinking about using containers to run the uploaded code or a new namespace to make sure everything is properly sandboxed. Namespaces is (for what I know) the way LXC is securing its VMs and it can be pretty lightweight. Of course that solution is still pretty heavy but creating an LXC vm or a namespace only takes a couple of seconds so it can be an option. Commented Feb 16, 2015 at 14:19
  • @Johnride thanks very much. Unfortunately, I'm not allowed to use virtualisation. For the rest, I have full access to the server. Commented Feb 16, 2015 at 14:22
  • @CamilStaps namespaces are not virtualization. They are part of the kernel and I think this is the best way for you. Have a look at namespaces and cgroups : en.wikipedia.org/wiki/Cgroups#NAMESPACE-ISOLATION Commented Feb 16, 2015 at 17:49
  • 1
    @Volomike then edit it to something more explicit Commented Apr 15, 2015 at 17:58

4 Answers 4

6
+100

In my opinion, a good and simple way to get a secure environment to compile and run scripts is an LXC container.

You say you cannot use virtualization but technically it is just process isolation. Like a chroot on steroids. I have a container host that is itself a VM and I've got no issue with it so far. Really LXC is not virtualization and should suit your needs.

Here are some intro links :

For example, you can create a template container per game with all the required libraries to compile and run the uploaded code. This template can be secured and limited as you want for cpu, ram and disk IO. I think it is also a good idea to turn off the network either with lxc.network.flag = down or lxc.network.type = empty

Then, when the code is uploaded, you can clone the template container, put the code in it and have it build and run the code.

All this would be done by a shell script called from php, or by a succession of php system calls but that does not sounds good.

Using unprivileged containers is a must for the kind of stuff you want to do as it provides an additional security layer.

I recommend using Ubuntu 14.04 as the LXC host. I think that a tweaked busybox template with the proper tools to compile and run the code is the lightest container you can get.

Here is the idea I get :

// clone the prepared template
lxc-clone -o myTemplate -n newContainer

// put the code archive in the new container
cp path/to/code path/to/container/and/where/you/want

// Start the container as a daemon
lxc-start -n newContainer -d

// Then launch the right script for the type of code in the container
lxc-attach -n newContainer -- su containeruser -c /path/to/script.sh

So the small job is to create the template with the required libs. The other job is to write the script that is called in the end.

Good luck with your project, I hope this helps.

Sign up to request clarification or add additional context in comments.

1 Comment

One extra suggestion: I'd use some kind of queuing between the PHP script and the script actually starting the execution so that PHP only has to have access to a single queue directory. The executor script could run in a loop, check if there's anything to do, move the job to a directory unreachable by the PHP script, and make sure the machine does not get overloaded by limiting the number of parallel executions. When there's nothing to do, a small sleep can both avoid busy waiting and still provide reasonable responsiveness.
1

realistically, when giving people this kind of freedom, the only real safe step is to spin up a new virtual instance for each user session and 'burn' it as soon as the session closes. If you want some sort of permanence, cat their input, and run it on a new instance next time they visit. even this has HUGE scope for being exploited but damage to your system should be limited.

3 Comments

Thanks very much, I understand how this approach would be preferable. Unfortunately, I'm not allowed to use virtualisation. For the rest, I have full access to the server. Other ideas?
well, personally I'd implement a pharser/peuedo shell that takes user input, compares it to a list of COMPLETE comands(including arguments and folder locations) that you are satisified are safe and discards ALL other inputs. it would be fustratingly restrictive but i guess you could expand it over time. edit -- but you'd have to make sure they could not get the shell killed by unexpected behaviour... you'd have to suspend all user processes the instant that your psuedo shell dropped or users would get elevated permissions.
Yes, that makes sense. I have a list of commands that are allowed to be executed. The parameters are a bit tricky but I'm sure we can come up with something. Thanks a lot!
1

You want to create a service which enables to compile java program and launch them.

I undestand that your question is not about how to launch those program securely because you assume that it has been taken care of.

So you want to know how to securely launch a shell script, in which you will give the name of the source code, and the arguments to javac.

First, you have many things to do.

The fact that you want to use system calls implies that you will allow exec in your whole Virtualhost. So if your FTP password, one of your PHP files, or anything is compromised, an attacker can upload a script, a binary program and execute it.

  1. You have to have your web site partition and temporary folders used by PHP in noexec mode in fstab
  2. You have to limit the files you can access within PHP with open_basedir restrictions, which has survived from the end of safe_mode. You will then allow with open_basedir the directory (which of course does not have the noexec flag)
  3. The directory containing your script and the script will have root.www-data permissions, and will not be writable by www-data
  4. You will have to protect the arguments passed to the script (which you have done with escapeshellarg) and any potential injection in the filename of the java file (but we assume that it will be renamed before being transmitted in order to avoid filename collision in the different java files you will received)

6 Comments

Thanks. What do you mean with pt 1 though? The temporary folder and the /var/www folder are both not in fstab, they're in the main filesystem. Only swap is in fstab in my installation.
In case your website is compromised, having a non-executable partition avoid that the attacker could upload a binary of its own, and launch it with a system call. In fact, every directory that is inside your open_basedir allowed path should not be writeable by www-data OR be a noexec partition (because we always want a place to write, like temporary directory)
So you're saying either point 1 or point 2 is sufficient? I don't feel like repartitioning, obviously.
You can consider using mount --bind to change the attribute of a directory inside an existing partition. Points 1 and 2 are two different things : Point 2 deny executing other binaries already installed in your system (/bin /usr/bin, ...), Point 1 avoids that a file which has been uploaded despite your precautions could be executed.
Yes you can, bind the dir on itself even if its not a true disk partition. $ sudo mount --bind -o noexec test test $ cd test $ chmod +x test.sh $ ./test.sh Permission denied $ sudo umount test $ ./test.sh hello
|
1

Secure mode in PHP is not as secure as you might think.

Functions like safe_mode_exec_dir limits the directory (or directories) from which you can run an application or script, but this application or script could call another outside that path (or paths).

Everyone recommends you use LXC and I agree with all of them. It is the best solution to your problem, but I'll put my two cents.

You could prepare a LXC container configured for each user of your application:

Create a local user for every user (separating privileges to avoid access or modifying files from another user) and login into your LXC container using SSH to execute your tasks inside it:

This solution will allow you to execute tasks as quick as you login into your SSH account without waiting for creating/starting a LXC container every time you need it to execute your tasks with a little overhead maintaining it running.

You could send and receiving files to/from the LXC using SCP:

And you could "keep clean" from background processes sending (using root user) a killall (-9) with argument "-u", ect or using "pkill -u ..." or drastically restarting the container.

You could even recreate the user (deleting its home directory) before each time you want to run the task (and before uploading necessary working files to LXC container).

Best regards.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.