pam_unix.so for unprivileged processes

Hey,

Some daemons allow authentication through PAM (ngircd, nginx).
However the pam_unix.so module requires the supplicant to be EUID 0.

See the code here: https://cgit.freebsd.org/src/tree/lib/libc/gen/getpwent.c#n715

In general I though maybe this limitation could be removed, so that any process with access to spwd.db could use it.
But this comes with some issues as spwd.db is often recreated resetting its permissions.

Is there some solution I am missing?
 
[…] the supplicant […]
Perhaps the term arbitrator is more adequate.​
[…] the pam_unix.so module requires the supplicant to be EUID 0. […]
Yeah, no, wellllll, to be precise, it does not require require. It just changes code paths:​
  • You are root → attempt opening /etc/spwd.db first. Should this fail, fall back and attempt opening /etc/pwd.db second.​
  • You are any user but root → directly attempt opening /etc/pwd.db. The other file is not looked at.​
Now, /etc/pwd.db contains something like * (an invalid password [hash]) in the password field (if any, I haven’t verified that now), so authentication cannot succeed for obvious reasons.​
In general I though maybe this limitation could be removed, so that any process with access to spwd.db could use it.
You are aware /etc/spwd.db contains the (albeit hashed) password of root? Then you can run your daemons as root right away. No, /etc/master.passwd and /etc/spwd.db really do not belong into the hands of any unprivileged process, never. The typical approach is to delegate the authentication step to a lean suid’d binary (e. g. security/pam_helper or security/unix-selfauth-helper), but it has been met with criticism, too, see D34322.​
Is there some solution I am missing?
Now pam_unix(8) is not the only PAM providing authentication facilities, is it.​
 
You are aware /etc/spwd.db contains the (albeit hashed) password of root?​
Yes
Then you can run your daemons as root right away
Not without the password? Or am I missing something?

Consider a simple daemon like `ngircd` that supports PAM authentication.
So I need to run this daemons as root just to let them be able to authenticate via pam_unix.so?
Would it not be much less harmful if I could allow it to just read the password hashes instead?

In debian you would just add the daemons user to the shadow group.

I couldn't find this EUID requirement in Linux-PAM either.


Now pam_unix(8) is not the only PAM providing authentication facilities, is it.
Sadly we don't support that many modules. There is for instance no module to specify a custom password file, as far as I know.

I don't so why there has to be a EUID 0 guard when the permission to read the database should be enough. Is checking for EUID 0 not just redundant if not limiting.
 
Would it not be much less harmful if I could allow it to just read the password hashes instead?
No. Exposing hashes is not much better than exposing passwords, it allows unlimited offline attacks. Just running all daemons as root isn't the solution either of course, as already stated, only very small portions of code should ever run privileged and thus have access to the hashes.

In debian you would just add the daemons user to the shadow group.
Which means every daemon running as this user has full access to the hashes. Any RCE vulnerability in any of the daemons could be used to steal the whole password database and use it for an offline attack, cracking the passwords.

pam_unix(8) as it is typically implemented has a serious design flaw. As far as PAM is concerned, nothing is specified regarding privileges needed to do some authentication, and it's safe to assume the expectation is not to need any. But this breaks with pam_unix(8) because it just wraps the classic Unix authentication: hash a supplied password and compare the result with the stored hash (for which, of course, you must be able to read that hash).

LinuxPAM implemented a special case for authenticating as yourself by including a suid-root helper binary that's automatically invoked by pam_unix(8). Authenticating as yourself is typically needed for screen lockers. The risk with a suid-root binary in the PAM stack is that a malicious user could invoke it directly instead of via PAM and thus bypass any other restrictions (like rate-limiting the authentication attempts). Therefore, it could be abused as an "oracle" to crack passwords using brute-force. Restricting the binary to only ever authenticate as yourself makes it much less useful for such abuse.

Screen lockers like e.g. xscreensaver started to rely on this behavior of LinuxPAM. Where they previously shipped their own suid-root helpers, these were dropped. As this didn't work with FreeBSD's pam_unix(8) any more, I tried to implement the same thing here, but our PAM maintainer was strictly against including that, offering an alternative using pam_exec(8). That's how I finally created security/unix-selfauth-helper. It does the same thing LinuxPAM is doing, but must be explicitly configured. Good enough to "fix" screen lockers.

Still all of this is nothing but an ugly hack around the fundamental design issue with pam_unix(8). Really solving it would require to design and implement an authentication service that's used for Unix auth instead of directly accessing the hashes, this service could then enforce policies (like, see above, rate limiting) and NO other code would ever need to see the hashes. But that's not an easy thing to do.

There's already a secure authentication mechanism that requires neither accessing any secrets nor ever exchange secrets over the wire: kerberos. If you want a secure setup that "just works", setup some kerberos KDC and configure pam_krb5(8).
 
Any RCE vulnerability in any of the daemons could be used to steal the whole password database and use it for an offline attack, cracking the passwords.
With the correct hashing algorithm and password requirements cracking the root password could take millennia. For me that's a small compromise compared to giving my daemon root permissions outright.

I don't disagree that pam_unix(8) is currently flawed. But encouraging daemons to become root for using pam_unix(8) can't be a solution and neither is developing an SUID helper binary. A specialized password checking service would be great but we don't have that yet.

Are we really going to pretend that requiring daemons to be root to do something they would only need access to the password store for isn't a security vulnerability or at least a bug?

And still no one has explained to me why the clause should be present. You can argue that it's a bad design to allow unprivileged daemons to read the password store but in the current implementation even if you did give a daemon read access to the password store, pwdbopen() will still not read it for you!

Anyway thanks for the kerberos recommendation. I'll into it but I like the simplicity of using generic user accounts and groups to facilitate services.

I currently just patched this if-statement in libc and am running that. This works better for me than pretending pam_unix.so doesn't exist or giving my ircd root permissions.
 
With the correct hashing algorithm and password requirements cracking the root password could take millennia.
I see three shady assumptions here:
  • It's impossible to enforce "secure" passwords, at least at a larger scale
  • Correlated here: Cracking passwords often doesn't mean "reversing" the hash function, instead brute-force is applied (there's a reason hash algorithms like argon2 have been developed to make that expensive as well)
  • The root password is by far not the only one you want to keep secret

For me that's a small compromise compared to giving my daemon root permissions outright.
Which nobody should ever recommend...

I don't disagree that pam_unix(8) is currently flawed. But encouraging daemons to become root for using pam_unix(8) can't be a solution and neither is developing an SUID helper binary.
A SUID helper for anything requiring root privileges is a solution, given that helper is as simple as ever possible.

Regarding the "classic" Unix authentication, the issue is not the helper itself. It's again this design requiring to compare the hashes, so the helper sidesteps any other protections like e.g. rate limiting, making it useful as an oracle for online attacks on the passwords, even when the attacker can't obtain the hashes. That's why using Unix authentication for anything else but actual system logins (using login(1) and friends) is a bad idea.

Authenticating as yourself is a special case, it's not perfect, but using a helper for that is "okayish".

A specialized password checking service would be great but we don't have that yet.
That would be the only way to somehow fix Unix authentication, so it's usable by custom clients. It would still be less secure than kerberos, as long as the actual secrets need to be exchanged.

Are we really going to pretend that requiring daemons to be root to do something they would only need access to the password store for isn't a security vulnerability or at least a bug?
It's only a security vulnerability when used for things it's not designed for. The existence of pam_unix(8) might give the impression it would be fit for usage by any client, but it isn't unless the design is fixed.

And still no one has explained to me why the clause should be present.
It's there because otherwise the call will just fail. That is unless you fiddle with permissions on the actual databases like you did, which is really a gaping security hole.

Anyway thanks for the kerberos recommendation. I'll into it but I like the simplicity of using generic user accounts and groups to facilitate services.
Kerberos is well usable on Unix systems and can replace Unix authentication if wanted, you'll still deal with the same users and groups. If you like, read about the history. Its first deployment was a campus network (large scale, lots of untrusted users and clients, therefore existing authentication schemes were all deemed much too insecure).

I currently just patched this if-statement in libc and am running that. This works better for me than pretending pam_unix.so doesn't exist or giving my ircd root permissions.
That just makes your insecure approach of loosening permissions on the database with the password hashes work. Well, you've been warned. ?
 
Back
Top