Tuesday, 3 July 2007

Opening Ports With PHP

In my previous post I described how I'd set up iptables. The important thing to note about that is that I set my default INPUT policy to DROP anything that isn't specifically accepted by a rule. This means that I can easily open and close ports using the iptables command to add or delete rules. For example, the following can be used to open the port for webmin:

/sbin/iptables -A INPUT -p tcp --dport 10000 -m state --state NEW -j ACCEPT

I can then close it again by deleting the rule with:

/sbin/iptables -D INPUT -p tcp --dport 10000 -m state --state NEW -j ACCEPT

Note that:

1. Webmin is always running, and listening, even when the port is closed, so I don't have to issue commands to start and stop it. I just need to open and close the port.

2. I don't have to leave the rule in place for the entire session as the commands above allow and disallow NEW sessions. Other rules in my iptables setup allow established sessions to continue. (Although in fact Webmin tends to stop and start the session as you use different elements of it so it's as well to leave the port open until you are done.)

3. The iptables command can only be used by root.

Now I'd like to be able to do the same thing with SSH but of course there's a catch: if I close port 22 then I can't SSH in to issue the command to open it. Don't get caught out by that one!

However, as mentioned in a previous post, my intention was to use a secret backdoor by getting one of my .php web pages to watch out for a special input and use exec() to run the iptables command. However there is a catch here also: the PHP script runs as apache but the iptables command can only be run by root.

Now there are a couple of ways around this and what I've chosen to do is to use the sudo command. This allows a user to run a command as another user however they have to be given permission in the /etc/sudoers file. I did this by adding the following line to the file (using visudo to edit it - as it tells you that you must in the file itself).

apache ALL = (root) NOPASSWD: /sbin/iptables

This gives apache the ability to run the iptables command without the need for a passwork. Shock! Horror! Isn't that a security problem?

Well, not really, the addition allows apache to run iptables, and that's it, nothing else. It's also important to realise that on my server, you cannot log in as apache, and in fact I am the only user allowed to log into my server at all. I am also the only person who can upload .php files to my server so I am the only person who could install a .php script that uses exec to run iptables as apache. Now even if somebody else did figure out a way to get around that, 'all' they would have achieved is the ability to open and close ports. They'd still need to crack other passwords before they could do anything useful/nasty. Furthermore, the instructions being issued to iptables are reported in my Logwatch report so I'd be made aware of it.

Of course if you don't have the luxury of being the only person who needs access to your server then you may be better to consider another approach. For example you could use your php script to create a file or change a setting in a database to act as a flag for a cron job. You would then create a cron job, which you set to run every couple of minutes, to check the flag and run the iptables command when the flag is set. The downside of course is that after setting the flag you have to wait for the cron job to run before you can get in. The upside is that apache no longer needs the ability to run the iptables command. Apache just sets the flag and the cron job (which you run as root) issues the iptables command.

Incidentally, there's a heap of info about the sudoers file available by typing 'man sudoers' at the Linux command line with loads of examples down at the bottom of the man page. Check it out and you will see that you have a heck of a lot of control over what you do and do not allow sudo to be used for. Given the various other restrictions on my server, I'm happy to allow apache to use sudo to run iptables and can therefore use the following PHP to open and close a port for SSH when I tell it to:

// create an iptables rule to allow access on port 22
exec('/usr/bin/sudo /sbin/iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT')

// delete the iptables rule to allow access on port 22
exec('/usr/bin/sudo /sbin/iptables -D INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT')

Note that my iptables setup (as I described a my previous post) is such that port 22 is open when my server boots, and I leave it that way.

Under normal circumstances, after rebooting the server, I would tell my PHP script to close port 22 and would then leave it closed when I'm not using it. However, I leave it so that the default after a reboot is to have it open. This means that if something should go wrong such that I can no longer access my PHP script to open the port (which I would need to do to change the PHP script), I can get it open again by requesting a reboot.

Watch out for this:

Perhaps the biggest headache that I had in setting this up was that initially I couldn't 'sudo' via exec(). I spent hours looking for the problem and it was driving me crazy because everything I found on the subject indicated that what I was doing was absolutely spot on. The breakthough came when it was suggested that I try this:

echo exec('/usr/bin/sudo /usr/bin/whoami 2>&1');

The result should be 'root' but what I got, courtesy of the bit on the end, was this: 'sudo: sorry, you must have a tty to run sudo'

A search on that told me that the issue related to a setting in /etc/sudoers which is used in Fedora Core 6 namely:

Defaults requiretty

I didn't get entirely to the bottom of why it's there. Something to do with there being circumstances when the password you are entering for sudo can be visible on the screen. Apparently it's a new thing in FC6 and all the advice I saw suggested that the solution was to comment it out (which leaves me wondering if that setting will still be present in FC7). Either way, I'm using sudo in such a way that I'm not typing a password so I don't see that it's an issue.


David said...

Thanks for that sudo tip! I had something running on fc4, but was giving me this error on fc6. What a pain!

LuAn said...

Glad to hear that my posting about it was useful to you.

it_guy said...

Thank you!
With my ISP changing my IP address more often I don't have to re-boot now if I am locked out by iptables.

Harvey said...

The solution.

In the sudo configuration, you have to disable: Defaults requiretty

This permit that you can run command without a valid shell.

this response is for someone else that want the solution.

Great post luan. Thanks.