When considering how secure something is, you should assume the attacker is able to log in as your www-data user, and have a shell. This does not mean your attacker really can get a shell, but there can be plenty of ways how the attacker can execute arbitrary shell commands; they could be results of library bugs like shellshock, or oversights in your code.
The safest way
The best way of doing this is, like @AlexeyVesnin said, to use a specialized process to do the work. Have this process listen on a (unix domain, so it isn't reachable from the network) socket, read its input from said socket, sanitize the input(!), do the work, and return some result to the socket, from where the web server script reads results. Any authorization (user needs to enter some password on the web site to be allowed to do this) should be handled in that process, and the www-data user should not be able to read any of that authorization data.
The mostly-safe sudo way
In case your budget, or your skill, doesn't make this best approach feasible, using the sudo approach isn't completely wrong, but you should restrict sudo permissions to the minimum you actually need. For example, let's assume you want to show current disk I/O data on your web site, for which you need to call
make a script, in this example
/var/www/bin/get-disk-io, that extracts the data you need:
/usr/sbin/iotop -b -n 1 | /usr/bin/head -2
- Do not allow the caller of the script to pass any parameters, i.e. the script shouldn't access
$2, ... ,
- In case you absolutely have to use parameters, sanitize them in your script first. Especially pay attention to whitespace or any unusual characters that might be hidden in parameters.
- Make sure to state the full path to every command in that script, so attackers can't replace any of those commands with their own
- Allow access to this script, and only this script, in
- Make sure
env_reset is in the defaults policy in your sudoers file
Note that this is still safer than a suid program, since (a) you can restrict who can execute it, so an attacker who gains the ability to execute a program under a different user won't be able to use it, (b) you can't pass arbitrary parameters to it, (c) the environment is sanitized by
sudo, and (d) you can make sudo log when the command gets executed, so you have an audit trail that a suid program doesn't give you.
The suid way
Note: Do not do this unless you know what you're doing. If you're asking, or reading, this question on stackexchange, you probably don't. So don't do this.
If you set the owner of an executable to a certain user (
chown user /my/executable/file), then set the suid bit (
chmod u+s /my/executable/file), then that program will run under the permissions of that user every time it is executed. So, for example,
chown root /bin/cat; chmod u+s /bin/cat will run every invocation of
cat with root rights, which means you can read every file on the system independent of its access rights. Do not try this on a system that is connected to a network, not even for 5 minutes to "check if it works".
- On many unix systems, this works for compiled programs only, not for scripts that need an interpreter
- In some configurations, for example if you run the program under
s bit has no effect
- In NFS configurations, root often doesn't have access to files on the NFS server, so this might even reduce what you're able to do.
The restricted-suid-Linux way
Linux has a capabilities system, that works almost like suid, but a much finer granularity. For example, if you need a process to be able to use port numbers below 1024, in classical unix systems, you need to run the command as root, often meaning you need to do the above
chown/chmod u+s stuff, which allows using those port numbers, but also grants access to the filesystem. Linux allows you to give the port capability without everything else that comes with root:
setcap cap_net_bind_service+EP /path/to/my/program
man 7 capabilities to learn more. If you seriously consider giving suid to a program, think twice if setting a capability isn't a better idea.