Search code examples
phpnginxexecsudoarchlinux

PAM requires password for exec/shell_exec even with NOPASSWD in /etc/sudoers


What I'm trying to do:

Permit the web server user (http) to run either an explicit iptables command or (preferrably) a script that checks the input for syntax before passing it to iptables without the need for a password.

The problem:

In spite of all my searching of the issue at hand, no changes to visudo, sudoers.d or php configurations appear to make a difference for what I'm trying to do.

The relevant conditions of my server:

[weasel@darwyn ~]$ uname -a
Linux darwyn 5.3.7-arch1-1-ARCH #1 SMP PREEMPT Fri Oct 18 00:17:03 UTC 2019 x86_64 GNU/Linux

nginx version: nginx/1.16.1

PHP 7.3.10 (cli) (built: Sep 26 2019 13:40:03) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.10, Copyright (c) 1998-2018 Zend Technologies

Username:                           http
UID:                                33
Gecos field:
Home directory:                     /srv/http
Shell:                              /usr/bin/nologin
No login:                           yes
Primary group:                      http
GID:                                33
Hushed:                             no
Running processes:                  3

Last logs:
09:29 sudo[648]: pam_unix(sudo:auth): auth could not identify password for [http]
09:29 sudo[649]: pam_unix(sudo:account): account http has expired (account expired)
09:29 sudo[650]: pam_unix(sudo:account): account http has expired (account expired)

Steps I've taken to fix the issue:

  • I have enabled debugging in PHP-FPM.

/var/log/php-fpm.log tells me that the calls are being initiated but expecting a password:

"sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper"
  • I have made changes to visudo.

From similar questions I learned that the sudoers file prioritizes the last configuration related to a user. So where I first had

root ALL=(ALL) ALL
weasel ALL=(ALL) ALL
http ALL=(ALL) NOPASSWD: /usr/local/bin/blacklist_ip, /usr/bin/iptables

I moved the entry down to

root ALL=(ALL) ALL
weasel ALL=(ALL) ALL

## Uncomment to allow members of group wheel to execute any command
# %wheel ALL=(ALL) ALL

## Same thing without a password
# %wheel ALL=(ALL) NOPASSWD: ALL

## Uncomment to allow members of group sudo to execute any command
# %sudo ALL=(ALL) ALL

## Uncomment to allow any user to run sudo if they know the password
## of the user they are running the command as (root by default).
# Defaults targetpw  # Ask for the password of the target user
# ALL ALL=(ALL) ALL  # WARNING: only use this together with 'Defaults targetpw'

##
##  Relocated user http permissions for testing
##
http ALL=(ALL) NOPASSWD: /usr/local/bin/blacklist_ip, /usr/bin/iptables

## Read drop-in files from /etc/sudoers.d
## (the '#' here does not indicate a comment)
#includedir /etc/sudoers.d
  • I have made changes to /etc/sudoers.d/90-MyOverrides.

I removed the http entry in visudo and moved it to /etc/sudoers.d/90-MyOverrides.

  • I have tried variations of the sudoers rules.
http ALL=(ALL) NOPASSWD: /usr/local/bin/blacklist_ip, /usr/bin/iptables
http ALL=NOPASSWD: /usr/local/bin/blacklist_ip, /usr/bin/iptables
http ALL=(http:http) NOPASSWD: /usr/local/bin/blacklist_ip, /usr/bin/iptables
http ALL=(ALL) NOPASSWD: /usr/local/bin/blacklist_ip *, /usr/bin/iptables *
http ALL=NOPASSWD: /usr/local/bin/blacklist_ip *, /usr/bin/iptables *
http ALL=(http:http) NOPASSWD: /usr/local/bin/blacklist_ip *, /usr/bin/iptables *
http ALL=(ALL) NOPASSWD: /usr/bin/iptables -L
http ALL=NOPASSWD: /usr/bin/iptables -L
http ALL=(http:http) NOPASSWD: /usr/bin/iptables -L

To make sure this was having some sort of effect, I tested it on my own user

weasel ALL=(ALL) NOPASSWD: /usr/bin/iptables

I was able to execute iptables commands without a password as user weasel, but the php-fpm log was still requiring a password for user http. I also attempted the command using 'sudo -S' in the hope that a null value for the password would work, however it also errors out.

"[sudo] password for http: "
"sudo: no password was provided"
  • I have double-checked access to the exec/shell_exec functions in the php configurations.

I checked both /etc/php/php.ini and /etc/php/php-fpm.conf to ensure that the exec/shell_exec commands weren't disabled and that safe mode wasn't active. The resulting debug information in /var/log/php-fpm.log already proves this, but I wanted to be thorough.

  • I have temporarily chowned the script to user http.

This was a bit redundant, but just to void out the possibility that it mattered.

  • I have tried variations of the exec/shell_exec calls.
exec("sudo blacklist_ip 185.189.12.135");
shell_exec("sudo blacklist_ip 185.189.12.135");
return:
"sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper"

exec("blacklist_ip 185.189.12.135");
shell_exec("blacklist_ip 185.189.12.135");
exec("/usr/bin/iptables -A INPUT -s 185.189.12.135 -j DROP");
shell_exec("/usr/bin/iptables -A INPUT -s 185.189.12.135 -j DROP");
return:
"Fatal: can't open lock file /run/xtables.lock: Permission denied"

exec("sudo /usr/bin/iptables -A INPUT -s 185.189.12.135 -j DROP");
shell_exec("sudo /usr/bin/iptables -A INPUT -s 185.189.12.135 -j DROP");
return:
"sudo: Account expired or PAM config lacks an "account" section for sudo, contact your system administrator"

  • I noticed the PAM config error.

And with a bit of searching found that according to https://www.sudo.ws/troubleshooting.html:

Q) Sudo says 'Account expired or PAM config lacks an "account" section for sudo, contact your system administrator' and exits but I know my account has not expired.

A) Your PAM config lacks an "account" specification. On Linux this usually means you are missing a line like:
    account    required    pam_unix.so
in /etc/pam.d/sudo.

No one ever mentions this as a cause in any other Q&A related to this topic. Nevertheless, for the sake of thoroughness I looked up information regarding PAM and sudo:

#%PAM-1.0
auth            include         system-auth
account         required        pam_unix.so
#account        include         system-auth
session         include         system-auth

It made no difference.

I am out of ideas. I need more information on an appropriate PAM module, making the http user passable by PAM or I need some redirection for further assessment.

EDIT Updated to simplify the information for the sake of brevity.

EDIT Added contents of /etc/php/php-fpm.d/www.conf

[www]

user = http
group = http

listen = /run/php-fpm/php-fpm.sock

listen.owner = http
listen.group = http

pm = dynamic

pm.max_children = 5

pm.start_servers = 2

pm.min_spare_servers = 1

pm.max_spare_servers = 3


access.log = /var/log/php/$pool.access.log

access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"

catch_workers_output = yes

php_flag[display_errors] = on
php_admin_value[error_log] = /var/log/php/fpm-php.www.log
php_admin_flag[log_errors] = on

Solution

  • As per Daan's suggestion in the comments, it turns out that at some point the http account's expiry date was set to a time far in the past so that it was functionally never useable for sudo commands. Perhaps this was a decision to future-proof a system against the possibility of injected exec/shell_exec commands by use of PHP in a web server.

    The solution:

    Daan then recommended a solution

    chage -E -1 http
    

    This set the user's expiry to 'never' and made it useable again. The sudo settings now work as expected.