Recently for a fun project, I decided to set up an automated shared web host to test my coding abilities. I decided to go with a simple name, similar to one of my other domains. I ended up with Chris Host It. Sure its cheesy and not very professional, but this is just a test project after all. The problem was the back end I went with didn’t secure PHP as well as I wanted it to by default, so I had to fix that up. So here is a quick guide on securing PHP when using Virtualmin.
But before I jump ahead to that part, Let me explain why I went with the Back End I did. With so many options like CPanel and ISPConfig, it can be hard to pick the best for your configuration. Well in my case, I ended up going with Virtualmin. My reasons are as follows:
- Its Free!
- It works perfectly on Debian with little configuration.
- It is similar to Webmin, which is what I use to manage some of my servers.
- There is a active community support forum, and paid support if you want a subscription.
- Plugin support for new features and items.
- Easy to use API.
So after setting up my server and getting it how I wanted, I decided to load up a PHP Shell script in a virtual host. Im glad I did, because I realized everything by default was enabled! No directory jailing, functions like exec() and passthru() were enabled, and this was a huge red flag to me! If I didn’t fix this, any virtual site could browse around other users websites and worse, my servers file system! Thankfully Virtualmin creates separate php.ini files for every virtual host, so I can easily jail every user to there own home folders.
So, lets get started with how I did this. The first thing you need is a server with Debian and Virtualmin installed (I will put up a Vitualmin install post later, with screenshots). After this is done, you should lock down your default php.ini that is copied to every virtual host that gets set up.
So, after some research I found the Virtualmin template php.ini to be at
/etc/php5/cgi/php.ini
So I just did some tweaks, and changed the following:
open_basedir = /home/
disable_functions = show_source, system, shell_exec, passthru, exec, popen, proc_open
expose_php = Off
max_execution_time = 30
memory_limit = 32M
Here is why I changed what I did:
- Open_Basedir = Tells PHP this is the ONLY folder it can run from and access. It locks down that users PHP to that folder. Great way to jail people to a folder.
- Disable_Functions = Turn off PHP Functions so people can’t call them. I turned off everything I thought was appropriate. Google each function to see its use, but most are only used for evil. Worst case scenario you can later modify a users php.ini to re-enable the features as needed.
- Expose_PHP = Just to prevent PHP from showing its existence, more preventive then anything.
- max_execution_time = This is here to limit long running scripts that can take up CPU time and Memory.
- memory_limit = Similar to above, except this just sets a physical max. Another way to conserve resources.
Now you will have Virtual Hosts that are a bit more secured, but still not fully Jailed to there homes. Time to fix this with a little scripting, and thanks to Virtualmin, its very easy! When ever Virtualmin calls a script to run it has the ability to automatically define variables for the script, so creating this script took me about 5 minutes. Here is what I came up with:
#!/bin/bash
# we use this to jail PHP right in the users folder, so security stuff
if [ "$VIRTUALSERVER_ACTION" = "CREATE_DOMAIN" ]; then
sed -i "s|open_basedir = .*|open_basedir = $USER_HOME|g" $USER_HOME/etc/php5/php.ini
fi
So what this script does is once a new virtual site is created, it takes the username of the account, and then jails there PHP to that folder locking down that users PHP to there folder only. A perfect, easy way to keep things secured and locked up tight. I uploaded this script to my site so you can easily set up your server the same way if you please.
So lets create a file, I personally put it in /usr/share/virtualmin-scripts/ In this example we name it php_opendir_jail.sh, but you can put it and name it whatever you want.
root:~# mkdir /usr/share/virtualmin-scripts/
root:~# cd /usr/share/virtualmin-scripts/
root:# wget http://servernetworktech.com/uploads/scripts/php_opendir_jail.sh
root:# chmod +x php_opendir_jail.sh
Now that the script is there, time to edit Virtualmin. Go to, and login in to your virtualmin panel as root then go to the following menu:
System Settings > Virtualmin Configuration > Actions upon server and user creation
From here, enter the script and its location into “Command to run after making changes to a server”
/usr/share/virtualmin-scripts/php_opendir_jail.sh
Click Save, and that’s it! From now on every host added will be jailed to there home folder, and PHP will have those dangerous functions locked out. You can now sleep soundly again!
Citation:
http://www.cyberciti.biz/tips/php-security-best-practices-tutorial.html
http://www.virtualmin.com/documentation/id,template_variable_listing
Dear sir,
Your script is not working. I receive the following error:
sed: -e expression #1, char 37: unknown option to `s’
Thank you
I have found the error. It looks like when $USER_HOME is something like “/home/domain-name” sed is not working.
Thank you
So I was attempting to edit /home/username/etc/php.ini manually to test out the jail before using the script to automatically apply it but when i make the changes and restart the webserver it does not seem to change anything.
I created a test.php page that php included /etc/apt/sources.list just to test its function but it always loads the list.
Any ideas what could be preventing the jail from working?
Neverming, turned out the physical server had to be rebooted in order for the changes to take effect. Just rebooting the web service didnt seem to do it. TYVM btw 😀
The 2nd line of the script is not needed.
/home/users/etc/php.ini is just a symbolic link to /home/user/etc/php5/php.ini
When you edit one the other changes as well.
Ah, good catch. That should now be fixed, thanks! also, glad you got it working!
Tried to copy/paste your script from the page instead of using wget. It didn’t work so I grabbed yours with wget instead. Lo-and-behold, they are not the same! 🙂
Word to the wise – grab the file as instructed to instead of copying from the blog entry.
Implemented and working now! Thank you for this!
Has anyone used this script on CentOS
Thank you very much!
Thanks it helps secure my site
the latest version automatically copy setup from /etc/php.ini to /home/user/etc/php5/php.ini