You’re encountering several related problems here — both security and technical. Let’s go through the technical reason why your sudo
call fails via PHP, and then address the security implications.
Why your sudo
call doesn’t work via PHP
1. sudo
requires a TTY
By default, sudo
is configured (in /etc/sudoers
) to require a tty for password prompts. This breaks any attempt to echo a password into it from a non-interactive process like PHP-FPM or Apache.
Check this setting:
sudo grep 'requiretty' /etc/sudoers /etc/sudoers.d/* 2>/dev/null
If you see:
Defaults requiretty
This must be disabled for PHP-sudo password use to even be possible. For www-data
, you’d need something like:
Defaults:www-data !requiretty
You can safely add this using:
sudo visudo
2. sudo
doesn’t accept password via echo
reliably
Even without requiretty
, sudo
does not reliably accept passwords from stdin
unless it’s running interactively. You’re doing:
exec('echo "' . $request['password'] . '" | sudo -S /usr/local/bin/mycommand 2>&1', $output, $retval);
This sometimes works in a shell because the shell can allocate a TTY and provide stdin
correctly. PHP’s exec()
doesn’t behave that way. In many configurations, especially under php-fpm
, it won’t be able to pipe the password properly.
3. Sudo prompts for a password as www-data, not your own user
You’re authenticating as the www-data
user. If www-data
has no sudo rights or isn’t allowed to run /usr/local/bin/mycommand
, then sudo
will always fail even if the password is correct.
Why this is dangerous
Your note about allowing "
in the password is a red flag.
You’re building a shell command using:
'echo "' . $password . '" | sudo -S ...'
This is vulnerable to shell injection. Even if you escape quotes, other characters like ;
, backticks, or $()
could still be exploited.
An attacker could craft a password like:
" | rm -rf / #"
And your code would effectively run that command as www-data
.
Solutions
A. Don’t use passwords and sudo
in this way
It’s not secure or reliable to run sudo
with password authentication in PHP. If you’re OK allowing a specific command to be run by PHP, the proper way is:
1. Allow passwordless sudo for the specific command:
Edit with visudo
:
www-data ALL=(ALL) NOPASSWD: /usr/local/bin/mycommand
2. In PHP, simply do:
exec('sudo /usr/local/bin/mycommand 2>&1', $output, $retval);
This is more secure and avoids storing or handling a password at all.
You can still require a password in PHP logic (not for sudo), and only run the command if the input password matches an expected value stored securely.
if (hash_equals($expected_password, $_POST['password'])) {
exec('sudo /usr/local/bin/mycommand 2>&1', $output, $retval);
} else {
http_response_code(403);
}
This avoids all the pitfalls: no TTY issues, no echo
, no shell injection, no sudo password prompt.
B. If you absolutely must use passworded sudo
Not recommended, but:
- Disable
requiretty
for www-data
in sudoers.
- Use
proc_open()
in PHP to manage stdin/stdout explicitly.
- Escape everything rigorously.
However, this is brittle, and again: do not do this in production.
Conclusion
Your current approach fails because sudo
expects a TTY and does not reliably read passwords piped via echo
when run from PHP. Even worse, your method opens the door to shell injection vulnerabilities.
Best practice: allow passwordless sudo
for specific commands (not general use), and control access via your PHP logic, not by trying to pass passwords to sudo
.
Let me know if you want help writing the sudoers line safely or structuring the command restrictions.