White-listing Linux UUIDs for MFA using PAM

Are you trying to deploy MFA on Linux machine but don't want every account to use it? Well here's an option for you using good ol' PAM!


Firstly, I have long been a fan of Multi-factor authentication (MFA) since it is RELATIVELY easy for users to use while making password bypasses significantly harder. However, the benefits of MFA get really shallow when I'm needing to execute a non-interactive command across several dozen systems using something like SSH. Sure, it could be done...if I sat there mashing my phone or fob every few seconds. This got me thinking there MUST be a way to white-list IDs in an MFA environment to allow them to run autonomous authentication like in this scenario, however such documentation alluded me long enough for me to figure a workaround. Good thing too because it forced me to actually learn something! Shout out to all the hacker heads bobbing out there. You know what I'm talking about.

Anyway, in my particular scenario I'm using Duo MFA, though I would suspect others would work in similar fashion if they integrate with PAM. I didn't have a tremendous amount to go off of per the vendor documentation, and that's where my trouble started.

The Duo MFA installer works by (among other things) loading it's own Pluggable Authentication Module (PAM) into the Linux OS, thereby allowing it to be triggered after the normal password authentication has succeeded. When a user logs in, certain PAMs get executed to determine whether or not the user name is authorized to login. Password validation is the primary piece of this, but MFA PAM will check to make sure you are who you say you are before allowing you in by either prompting for a temporary PIN or pushing a request to your mobile. Assuming you acknowledge the PIN/PUSH correctly, your login will proceed.

This waterfall effect of PAMs was very interesting to me. As I began to learn how to manipulate this flow I began to see the crucial importance of order of operation in PAM. For example, in my target PAM, SSHD.so, I learned if I moved the call to the MFA PAM higher in the stack I could effectively bypass password authentication all together as long as I responded to my mobile device push within the appropriate time frame. Of course this would be bad, so don't do that! It is also important what keyword is associated with the event. There are a couple of possibilities, but I'll just focues on the ones I ran into below:


		#%PAM-1.0
		auth	required	pam_sepermit.so
		auth	substack     	password-auth
		auth	required	pam_env.so
		auth	required	pam_deny.so
		auth	include      	postlogin
		...
  		

The key word 'required' means if the line fails, the entire module will exit failed without invoking anything further lines down the stack. Normal logins work like this because they are designed to either fail or succeed. As stated above however, MFA adds a step. Take a look at the next example which includes a call to the MFA PAM:


                #%PAM-1.0
                auth    required        pam_sepermit.so
                auth    substack        password-auth
                auth    required        pam_env.so
                auth    sufficient      pam_mfa.so
                auth    required        pam_deny.so
                auth    include         postlogin
                ...
                

Here we see how a custom PAM (pam_mfa.so) can be inserted, however it's labeled as 'sufficient'. This works almost opposite of 'required' in that, if it succeeds, the entire process will exit successfully without invoking anything below. Failures on the other hand merely fall through to the next line in the stack, which is the pam_deny.so. Examples could be a brute force attempt to the SSH login or getting a push to your mobile that you don't respond to fast enough.

Now, let's take a look at my final example.


                #%PAM-1.0
                auth    required        pam_sepermit.so
                #auth    substack        password-auth
                auth    required        pam_env.so
                auth    sufficient      pam_succeed_if.so uid = 1666
                auth    sufficient      pam_mfa.so
                auth    required        pam_deny.so
                auth    include         postlogin
                ...
                

Here we get to the REALLY fun part. UUIDs.

It turns out there special PAMs which further modify the behavior of a login, like 'pam_succeed_if.so'. Essentially this is a logic statement that looks for particular UUIDs attempting to login. Assuming that a valid user UID hits, the authentication will drop out successfully BEFORE invoking the MFA PAM. On the other hand, if a valid UUID is attempting to login that isn't white-listed here, like Joe User for example, the MFA PAM will be invoked like normal and send a SMS/Push to Joe's device. Now we have MFA accounts like Joe running along with non-MFA accounts like Service Account. Cool balance between security and functionality, am I right?

Oh yeah, for those paying close attention, the commented out 'auth substrack password-auth' is because I'm using keys to authenticate rather than passwords for the '1666' UUID SSH connections...because hard coding passwords for remote Service Accounts makes my skin crawl.

So that's basically it; three examples to demonstrate the evolution of my SSH daemon using MFA AND white-listed UUIDs. Obviously this is probably the hard way of doing this, but, when you don't have other options available this could work for you. Just make sure you are clear with your Security team before implementing anything like this in production. :)

Cheers!