When working with PHP scripts executed via HTTP (not CLI), you'll encounter permission constraints because the web server typically runs under the www-data user. Here's why standard sudo approaches fail:
<?php
// These won't work as expected:
echo shell_exec('sudo -u targetuser command'); // Silent failure
echo shell_exec('whoami'); // Outputs: www-data
Three primary reasons:
- PHP's shell_exec() doesn't provide an interactive terminal
- Web server users are often restricted in sudoers configuration
- Password prompts can't be handled in non-interactive environments
1. Sudoers File Configuration (Recommended)
Edit /etc/sudoers using visudo:
www-data ALL=(targetuser) NOPASSWD: /path/to/command
www-data ALL=(targetuser) NOPASSWD: /usr/bin/whoami
PHP implementation:
<?php
$output = shell_exec('sudo -u targetuser /path/to/command 2>&1');
echo $output;
2. Setuid Wrapper (Advanced)
Create a C wrapper:
#include <unistd.h>
#include <stdlib.h>
int main() {
setuid(1000); // Target user's UID
system("/path/to/command");
return 0;
}
Compile and set permissions:
gcc wrapper.c -o wrapper
sudo chown root:root wrapper
sudo chmod 4755 wrapper
3. SSH Alternative
For remote commands:
<?php
$connection = ssh2_connect('localhost', 22);
ssh2_auth_pubkey_file(
$connection,
'targetuser',
'/home/targetuser/.ssh/id_rsa.pub',
'/home/targetuser/.ssh/id_rsa'
);
$stream = ssh2_exec($connection, '/path/to/command');
stream_set_blocking($stream, true);
echo stream_get_contents($stream);
- Always specify exact commands in sudoers (no wildcards)
- Regularly audit setuid binaries
- Use SSH keys with restricted commands when possible
- Consider creating a dedicated restricted user instead of using sudo
Check system logs when commands fail:
tail -f /var/log/auth.log
Test command execution flow:
<?php
echo "Attempting raw command:\n";
echo shell_exec('whoami 2>&1');
echo "\nTesting sudo:\n";
echo shell_exec('sudo -u targetuser whoami 2>&1');
echo "\nTesting with full path:\n";
echo shell_exec('sudo -u targetuser /usr/bin/whoami 2>&1');
When trying to run shell commands as a different user from PHP using shell_exec()
, developers often encounter silent failures. The core issue stems from how PHP (running as www-data) interacts with sudo:
<?php
// These common approaches fail:
echo shell_exec('sudo -u myusername -S /usr/bin/whoami'); // Empty output
echo shell_exec('echo "mypass" | sudo -u myusername -S /usr/bin/whoami'); // Also empty
?>
Modern Linux systems implement several security measures that prevent this from working out of the box:
- sudo requires TTY by default (disabled in PHP)
- Password prompting doesn't work in non-interactive shells
- Web server processes are heavily restricted by SELinux/apparmor
The most secure approach is to configure passwordless sudo access for specific commands:
# In /etc/sudoers.d/php_commands
www-data ALL=(myusername) NOPASSWD: /usr/bin/whoami
www-data ALL=(myusername) NOPASSWD: /path/to/your/script.sh
Now your PHP code can work without passwords:
<?php
echo shell_exec('sudo -u myusername /usr/bin/whoami'); // Success!
?>
For more complex scenarios, create a setuid wrapper:
#!/bin/bash
# /usr/local/bin/run_as_user.sh
su - myusername -c "$@"
Set permissions:
sudo chown root:root /usr/local/bin/run_as_user.sh
sudo chmod 4755 /usr/local/bin/run_as_user.sh
PHP usage:
<?php
echo shell_exec('/usr/local/bin/run_as_user.sh "whoami"');
?>
If using PHP-FPM, you can run the pool as your target user:
; /etc/php/7.4/fpm/pool.d/myuser.conf
[myuser]
user = myusername
group = myusername
listen = /run/php/php7.4-fpm-myuser.sock
Then route specific PHP scripts to this pool.
Always follow security best practices:
- Restrict sudo permissions to specific commands only
- Never expose password in PHP code
- Consider using SSH with key authentication instead of sudo
- Audit all commands run through these methods
For remote commands or better security:
<?php
$command = escapeshellarg('whoami');
echo shell_exec("ssh myusername@localhost {$command}");
?>
Configure SSH key authentication first for passwordless access.