How to secure a sudo - powered script

I'd amend your list of criteria for protecting a script a little. Given this - or a similar - entry in /etc/sudoers:

www-data ALL=(ALL) NOPASSWD: /usr/local/sbin/mycommand

we can state that the script:

  • must be writeable only by the root user
  • must be readable and executable by the root user
  • must be in a hierarchy of directories that can only be written by root
  • must validate its input "sufficiently" for the use, and reject anything else
  • should have the smallest set of privileges necessary to carry out its task (not necessarily setuid root)
  • should define its PATH before using any external commands
  • should set all variables to a known value before using them
  • should generate an audit trail to show not only when and how it was called, but also the resulting action (think logger)

Additionally, in many cases there is no real need for a script to run as root - it can run setgid, or even setuid to some other account. In the general case consider these options to avoid granting full root access to the script.

For SELinux environments it may be possible to create a policy that prevents the script from doing anything unexpected. Capabilities such as CAP_NET_ADMIN are more finely grained than blanket root privileges and might also be worth considering.

In the specific case you've outlined, where you want to validate a single IPv4 address and pass it to iptables, you might be able to get away with validating the IP address as a series of non-specific octets. In this case 444.555.666.999 might be accepted as plausible, knowing that iptables itself will reject anything that isn't a real IP address. At one extreme you might decide that matching the RE /^[0-9.]+$/ is enough to be happy passing the value to iptables. At the other, well there are plenty of answers on StackExchange and in other places that address the issue of validating an IP address. Some better than others.

Special cases to consider are RFC1918 addresses, multicast addresses, and your own external IP address range. Oh, and the reserved block formerly known as Class E. Do you need IPv6 support?

What will happen if your script is called hundreds of times a minute? Do you need to prepare for this eventuality? Will your iptables chain overflow? If you think you're going to need hundreds of rules in your chain it will be [more efficient to use the ipset extension to iptables rather than a linear list. Here's a good tutorial. In terms of protection, it allows you to build sets of thousands (if not tens of thousands) of similar rules that can run without significantly slowing the traffic flowing through your rulesets.

Suddenly your apparently straightforward requirement is quite complex.


One of the best thing is to use the "Digest_Spec" possibility in the sudoers file, to validate the checksum of your executable

Extract of the man page:

If a command name is prefixed with a Digest_Spec, the command will only match successfully if it can be verified using the specified SHA-2 digest.

Using openssl, to generate the checksum:

$ openssl dgst -sha224 /usr/local/sbin/mycommand
 SHA224(/usr/local/sbin/mycommand)=
         52246fd78f692554c9f6be9c8ea001c9131c3426c27c88dbbad08365 

Then in your sudoers file (on the same line):

 www-data ALL=(ALL) NOPASSWD: 
    ssha224:52246fd78f692554c9f6be9c8ea001c9131c3426c27c88dbbad08365
    /usr/local/sbin/mycommand

Named pipe approach. As root, run

mkfifo -m 666 /tmp/foo
/tmp/readpipe.sh &

And can, as user www-data then write to the pipe

echo test >>/tmp/foo

readpipe.sh in its simplest form (perl with taint would be better) :

#!/bin/sh
while read A </tmp/foo
do
 echo received $A     
done