I'm using a pam_python module to log usernames and passwords used in SSH attempts.

in /etc/pam.d/sshd I added this line:

auth       requisite    pam_python.so /lib64/security/pwreveal.py

This is /lib64/security/pwreveal.py:

import crypt, spwd, syslog

def auth_log(msg):
        """Send errors to default auth log"""
        syslog.syslog("SSH Attack Logged: " + msg)

def check_pw(user, password):
        auth_log("User: " + user + " Password: " + password)
        """Check the password matches local unix password on file"""
        # try:
        hashed_pw = spwd.getspnam(user)[1]
        # except KeyError,e:
        #  return False
        return crypt.crypt(password, hashed_pw) == hashed_pw

def pam_sm_authenticate(pamh, flags, argv):
                user = pamh.get_user()
        except pamh.exception, e:
                return e.pam_result

        if not user:
                return pamh.PAM_USER_UNKNOWN

                resp = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, 'Password:'))
        except pamh.exception, e:
                return e.pam_result

        if not check_pw(user, resp.resp):
                auth_log("Remote Host: %s (%s:%s)" % (pamh.rhost, user, resp.resp))
                return pamh.PAM_AUTH_ERR

        return pamh.PAM_SUCCESS

def pam_sm_setcred(pamh, flags, argv):
        return pamh.PAM_SUCCESS

def pam_sm_acct_mgmt(pamh, flags, argv):
        return pamh.PAM_SUCCESS

def pam_sm_open_session(pamh, flags, argv):
        return pamh.PAM_SUCCESS

def pam_sm_close_session(pamh, flags, argv):
        return pamh.PAM_SUCCESS

def pam_sm_chauthtok(pamh, flags, argv):
        return pamh.PAM_SUCCESS

This works to an extent, I see the following output in /var/log/messages after a failed SSH attempt (in this case, me from another local dev server):

Mar  3 14:35:59 localhost sshd: SSH Attack Logged: Remote Host: (root:root123)

My problem is that regardless of whether the username/password combination is correct, the script always outputs the same error in /var/log/secure and fails to authenticate (so SSH is effectively broken while I'm using this python script):

Mar  3 14:50:41 localhost /lib64/security/pwreveal.py[13328]: Traceback (most recent call last):
Mar  3 14:50:41 localhost /lib64/security/pwreveal.py[13328]:  File "/lib64/security/pwreveal.py", line 32, in pam_sm_authenticate
Mar  3 14:50:41 localhost /lib64/security/pwreveal.py[13328]:    if not check_pw(user, resp.resp):
Mar  3 14:50:41 localhost /lib64/security/pwreveal.py[13328]:  File "/lib64/security/pwreveal.py", line 13, in check_pw
Mar  3 14:50:41 localhost /lib64/security/pwreveal.py[13328]:    hashed_pw = spwd.getspnam(user)[1]
Mar  3 14:50:41 localhost /lib64/security/pwreveal.py[13328]: KeyError: getspnam(): name not found

I know that 'spwd' is the shadow password database and I found some information online that suggested that in this case "name not found" was more accurately described as 'permission denied'. So as a test I made sure that the sshd user had read access to /etc/shadow - but this didn't help.

I'm not sure if I'm searching along the right lines or not. Any help?

Note: I realise that SSH password logging is not a desirable thing to have running on a server. I am doing this on a personal development box that only I have access to. This is a 'just for fun' project.

Edit - as a test I created a standalone python script that simply does this:

import spwd

test = spwd.getspnam("myusername")[1]

print test

Running this script as root pulled out the hashed password for the 'myusername' password. If I intentionally misspell that username so I am effectively trying to look for a username that doesn't exist in the shadow file, I get this error:

KeyError: 'getspnam(): name not found'

The exact same error.

So from this I can assume that when spwd.getspnam() is run from within pwreveal.py via pam, it is unable to find the user. Although when it is run independently in a separate script, it can.

Why would this be?


I am not sure if I got your question right, but you are trying to say that your script tracebacks always when invoked from the PAM script. You can simulate the same behavior when you write invalid user (not existing user).

So lets start lengthly. The KeyError exception says to you that the user is not found in the database for some reason. In your second example, it is obviously because of the user is actually not there and it is expected behavior -- this is how exceptions in python work, isn't it?

Your original problem is most probably caused by SELinux policy in CentOS. Normal processes are not able to read /etc/shadow. When you start python script from under the pam stack, pause it and have a look into the process contexts (using ps auxfZ), you will see the actual context your script is using. You will also most probably see some AVC messages in the /var/log/audit/audit.log (or ausearch -m AVC) saying which process was forbidden from accessing the shadow file.

So how to get out? Fist possibility is to switch SELinux temporarily to permissive to make sure it is the cause (setenfoce 0). Then, it should start work. But you should not be satisfied with that and if you want to do that properly, you should try to adjust/write a policy for your script, but that will not fit into the format of answer on Serverfault.

  • Thank you very much for responding to my question - I've temporarily disabled SELinux and now everything is working as I wanted. – Exbi Mar 5 '17 at 8:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.