Subject to some other non-PAM web-based authentication (openid in this case, but it could be anything really), I want to issue a token value to the user in-band with respect to that other authentication (i.e. in a web page shown to the user), which is out-of-band with respect to PAM. That token should then be usable for a short period of time to make a single PAM authorisation to sshd.
That is, if you can log into the web-based authentication, you should then be able to log into the ssh system.
So on one side I need a PAM module (which I will write in Python) to check the tokens, and on the other side I need something (a command line tool) to issue the tokens. To complete the loop, I need some database (the filesystem) to store the tokens on the server side.
So here's the code. The PAM module comes complete with documented security vulnerability which allows anyone to delete certain files on your file system. ho ho.
First the token creator:
#!/usr/bin/python import base64 import os import pickle import sys import time VALIDTIME = 60 tokenbits = os.urandom(8) token = base64.b64encode(tokenbits, "+-") print token fn = token + ".token" fh = open(fn, 'w') obj = (sys.argv[1], time.time() + VALIDTIME) pickle.dump(obj, fh)
and secondly the PAM module, which is based on the PAM module in my previous post:
import os import syslog import pickle import time def pam_sm_authenticate(pamh, flags, argv): syslog.syslog("start benc") pamh.authtok if pamh.authtok == None: syslog.syslog("got no password in authtok - trying through conversation") passmsg = pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, "Monkeyballs?") rsp = pamh.conversation(passmsg) syslog.syslog("response is "+rsp.resp) pamh.authtok = rsp.resp # so we should at this point have the password either through the # prompt or from previous module syslog.syslog("got password: "+pamh.authtok) # now look for token # SECURITY BUG TODO: we're using this to make a path so we need to make sure # we're not being directed out into some other directory. We know the # range of characters that can be used in a token so we can reject if # anything other than those exists. # Especially as we delete the token file on use - otherwise could delete # arbitrary files on the system... tfn = "/root/" + pamh.authtok + ".token" if os.path.exists(tfn): fh = open(tfn) tokendata = pickle.load(fh) tokenuser = tokendata[0] tokentime = tokendata[1] fh.close() os.remove(tfn); # will remove the token even if it was for the wrong user # not sure if there's any security different wrt leaving it there if # its the wrong user? if tokentime < time.time(): syslog.syslog("token time expired") return pamh.PAM_AUTH_ERR if tokenuser != pamh.user: syslog.syslog("token user "+tokenuser+" does not match requested user "+pamh.user) return pamh.PAM_AUTH_ERR return pamh.PAM_SUCCESS return pamh.PAM_AUTH_ERR def pam_sm_setcred(pamh, flags, argv): return pamh.PAM_SUCCESS
this made me think of you and your pam adventures:
ReplyDeletehttp://code.google.com/p/google-authenticator/