2-factor authentication & writing PAM modules for Ubuntu

Download

2ndfactor.c

The problem

Passwords are often seen as a weak link in the security of today’s I.T. infrastructures. And justifiably so:

  • re-usability, which we’re all guilty of, guarantees that credentials compromised on a system can be leveraged on many others. And given the world we live in, password re-use is inevitable, we just have too many accounts in too many places.
  • plain text protocols are still used to transmit credentials, and the result is that they are exposed to network sniffing. This is worsened by the increase in wireless usage which broadcasts information. Telnet, FTP, HTTP come to mind but they aren’t the only ones.
  • lack of encryption on storage is a flaw that too often makes it way into architecture design. How many databases have we heard about getting hacked & dumped? How many have we not heard about?
  • password simplicity & patterns are also factors weakening us against bruteforce attacks.

So far, the main counter measure we’ve see out there is complexity enforcement. Sometimes IP restriction, or triggering warnings on geographic inconsistencies (Gmail, Facebook). But these barely help alleviate problem.

A solution

One hot solution that is making its way into critical systems (banks, sensitive servers) is Multi-factor authentication, and by “multi” we’ll stick to 2-factor authentication (2FA) because, well 3 factor authentication might be getting a little cumbersome :). The goal is to have more than one mean of establishing identity. And as much as possible, the means have to be distinct in order to reduce the chances of having both mechanisms compromised.

Let’s see how to implement 2FA on an Ubuntu server for SSH. Ubuntu uses PAM (Pluggable Authentication Modules) for SSH authentication among other things. PAM’s name speaks for itself, it’s comprised of many modules that can be added or removed as necessary. And it is pretty easy to write your own module and add it to SSH authentication. After PAM is done with the regular password authentication it already does for SSH, we’ll get it to send an email/SMS with a randomly generated code valid only for this authentication. The user will need access to email/cell phone on top of valid credentials to get in.

Implementation

Let’s do an ls on /lib/security, this is where the pam modules reside in Ubuntu.

Let’s go ahead and create our custom module. First, be very careful, we’re messing with authentication and you risk locking yourself out. A good idea is to keep a couple of sessions open just in case. Go ahead and download the source for our new module.

Take a look at the code, you’ll see that PAM expect things to be laid out in a certain way. That’s fine, all we care about is where to write our custom code. In our case it starts at line 35. As you can see, the module takes 2 parameters, a URL and the size of the code to generate. The URL will be called and passed a code & username. It is this web service that will be in charge of dispatching the code to the user. This step could be done in the module itself but here we have in mind a centrally managed service in charge of dispatching codes to multiple users.

Deploying the code is done as follows:

gcc -fPIC -lcurl -c 2ndfactor.c
ld -lcurl -x --shared -o /lib/security/2ndfactor.so 2ndfactor.o

If you got errors, you probably need to first:

apt-get update

apt-get install build-essential libpam0g-dev libcurl4-openssl-dev

Do an ls on /lib/security again and you should see our new module, yay!

Now let’s edit /etc/pam.d/sshd, this is the file that describes which PAM modules take care of ssh authentication, account & session handling. But we only care about authentication here. The top of the file looks like:

# PAM configuration for the Secure Shell service

# Read environment variables from /etc/environment and
# /etc/security/pam_env.conf.
auth       required     pam_env.so # [1]
# In Debian 4.0 (etch), locale-related environment variables were moved to
# /etc/default/locale, so read that as well.
auth       required     pam_env.so envfile=/etc/default/locale

# Standard Un*x authentication.
@include common-auth

The common-auth is probably what takes care of the regular password prompt so we’ll add our module call after this line as such:

auth       required     2ndfactor.so base_url=http://my.server.com/send_code.php code_size=5

The line is pretty self descriptive: this is an authentication module that is required (not optional), here’s its name and the parameters to give it.

send_code.php can be as simple as:

<?php mail( "{$_GET['username']}@mail_server.com", "{$_GET['code']}" ) ; ?>

Or a complex as you can make it for a managed, multi-user, multi-server environment.

Lastly, edit /etc/ssd/sshd_config and change ChallengeResponseAuthentication to yes. Do a quick

/etc/init.d/ssh restart

for the change to take effect.

That’s it! try and ssh in, the code will be dispatched and you will be prompted for it after the usual password. This was tested on Ubuntu 10.04 32b / Ubuntu 10.04.2 64b / Ubuntu 11.04 64b / Ubuntu 12.04 64b.

A few disadvantages of this 2FA implementation worth mentioning
  • more steps required to get in
  • doesn’t support non TTY based applications
  • relying on external services (web service, message delivery), thus adding points of failure. Implementing a fail-safe is to be considered.
  • SSH handles key authentication on its own, meaning a successful key auth does not go through PAM and thus does not get a chance to do the 2nd factor. You might want to disable key authentication in sshd’s config.

16 Comments

  1. I am trying to run it in Fedora but it is not working. It doesnt give any error it just dont appear what the pam module that I wrote. I made a pam_module that display a message. Can you guide me how to do it in Fedora?

  2. What i want is…I have written my script for taking image of Login person. I run this script at the start of System it works.But what if i run this at event when user login fail, lets say after 3 wrong attempts.
    Is there any need to make PAM module to do this?
    I don’t have any experience with pam programing.

    As you are looking smart in PAM so i can hope you have my answer/solution.
    This is my college project.And some more modules are there but its enough for me to learn this thing first.
    Thank you. I am waiting for reply.[mail:-sxxxxxxxxxxxxxxx1@gmail.com]

    • Sachin,

      I’m not going to do your college project for you :), but I’m happy to give pointers.

      The easiest way to keep track of how many failed attempts have happened is to log them into a file in /tmp for example.

      You can have your PAM module check that file before anything at the beginning of the pam_sm_authenticate function.
      First, add the current attempt to the file.
      Then check that same file to see how many attempts occurred recently
      – If there weren’t many attempts, just “return PAM_SUCCESS”
      – Otherwise, if there were, you can proceed with the extra check (2nd factor)
      Lastly don’t forget to cleanup the file every now and then so it doesn’t grow to an unmanageable size.

      That’s if you decide to use a PAM module but you don’t have to; you might be interested in http://ben.akrin.com/?p=1290 if you want to avoid the complexity of a PAM module. With ForceCommand you can use the scripting language of your choice and the operating principle would be the same as described above.

  3. Hello! I’m trying to get this to work on my ArchLinux box, but to no avail. I have the libraries installed, and the header files, but I am unable to get it to work with SSH. I get:

    15:29:56 localhost sshd[1963]: PAM unable to resolve symbol: pam_sm_authenticate
    15:29:56 localhost sshd[1963]: PAM unable to resolve symbol: pam_sm_setcred
    15:30:05 localhost sshd[1963]: error: PAM: Module is unknown for USER from IP
    15:30:12 localhost sshd[1963]: error: PAM: Module is unknown for USER from IP
    15:30:16 localhost sshd[1963]: error: PAM: Module is unknown for USER from IP

    The prompt never even shows up. Any idea on what I need to do? If you’re not too busy, I’d appreciate some advice. Thank you!

        • Hey Ben, I managed to get something working but I’m not quite sure how or why. It may have been a different compiling/linking process (I ended up changing a lot of other things though; basing it off of a different tutorial, the URL of which I’ve since forgotten.)

          Anyway, I compiled it like so:

          gcc -fPIC -DPIC -shared -rdynamic -o 2ndfactor.so /usr/lib/libpython2.7.so 2ndfactor.c

          Thanks so much for taking time to try to help with this, though.

    • Thank you for sharing your findings. I haven’t had time to get that ArchLinux VM working but I plan on it in the near future. If you changed some code in 2ndfactor.c I’d be interested in knowing what it looks like.

      I’m glad you got it working!

  4. Hi,

    thanks for the module.

    I want to change the Ubuntu login to have only one user with 2 or multi passwords. Depending on what password you enter, you will have access to a different folder (/home, /home1, etc).

    Do you know if it’s possible with PAM to do this? I don’t know and will be great to know it, to continue searching or discard it.

    Thank you in advance. Regards.

    • Do you really, absolutely have to do this based on the password? Different credentials lead to different home dirs that’s nothing new but as you know it’s usually based on the username not the password. Anyway, I’m sure this can be done (there’s always a way) but you can’t really do that by adding a PAM module to your chain; it seems as though you will want to code your own version of the module that does authentication rather.

      A look at /etc/pam.d/sshd points to:
      > 10 # Standard Un*x authentication.
      > 11 @include common-auth

      So then we look at /etc/pam.d/common-auth:
      > 17 auth [success=2 default=ignore] pam_unix.so nullok_secure

      and it looks like pam_unix.so is the module you want to make your own version of.

      But you know, the more I think about it, the more I think it’s tricky. It’s tricky because your session gets instantiated post-authentication by the SSH server, so somehow you’d want whichever modifications you make in the authentication module to be able to carry through the rest of the PAM chain back to SSH which actually instantiates your shell; not impossible but it will definitely add complexity.

      That’s as far as I have time to go on this idea, it looks like you are going to have to get your hands dirty to get something of the sort working. Have you thought about maybe just adding a prompt to ask users which home dir they want?

      I hope this helps a little.

  5. Hi,

    first of all thanks for the answer.

    It’s not my original idea, I asked to do that, so…I have to do or make something similar. It looks a little bit complex to me do that modules modifications.

    The idea is that if someone makes you (facet to face, with violence) to access to your pc, you can type the second password and a non-sensibility data home appears, without important files and some looks-like important garbage, so you real encrypted home is safe and the attackers let you go. In the normal life, you use the first password to access to your real home.

    My another idea to do that is to make 2 different users, with 2 different ecnrypted homes, in the normal way, and not show on the GMD login the users name, so nobody knows how many users there are, so you can type the second username and access the second home folder, but I don’t know if makes the same effect or what.

    Thanks for your help and time.

  6. Hi,

    There is a security problem with the PAM module in your post. I couldn’t find your email address on this blog so I’m just putting it here into the comments.

    Specifically, the problem is the following code to generate the tokens:
    /* generating a random one-time code */
    char code[code_size+1] ;
    unsigned int seed = (unsigned int)time( NULL ) ;
    srand( seed ) ;
    for( i=0 ; i<code_size ; i++ ) {
    int random_number = rand() % 10 ; // random int between 0 and 9
    code[i] = random_number + 48 ; // +48 transforms the int into its equivalent ascii code
    }
    code[code_size] = 0 ; // because it needs to be null terminated

    rand() will always return the same sequence for a given seed. In this case you call srand() to seed the RNG, but the only input is the current timestamp. So it is possible for an attacker to create valid tokens by calling srand(current_time_t) and applying the same transform as you do in the algorithm above. Due to clock skew, he won't have exactly the same time down to the second, but it would be easy to guess (and can probably be checked using other services on the server that print the current time, such as HTTP error pages etc.)

    A better way would be to get the random values from /dev/random on linux. I will be modifying the code for this purpose, along with making it call a shell script rather than an HTTP service.

    • This is a very good point, I actually thought about it initially but I considered the code random enough.

      The main idea behind 2 factor auth isn’t to find the most mathematically random number, it is to use a second medium for authentication. Now of course we’re not going to hardcode “1234″ but even then I’d get an SMS and know that something is going on.

      When you think about it, sending 4 digits code offers only 10000 permutations which is statistically about a day’s worth of work at 1 attempt per second. Not very secure but hopefully I’ve gotten off my ass after 100 SMSes :)

      I concede that it is a shortcoming and that while making security software, I might as well make it 100% right. I’ll fix it, thank you for bringing this up.

      Also, if all you are trying to do is fire a shell script, you might be interested in http://ben.akrin.com/?p=1290 , you will be pleased to know that it uses /dev/urandom :D

      take care.

      • Ok fixed :)

        Random code generation looks like that now:
        /* generating a random one-time code */
        char code[code_size+1] ;
        unsigned int random_number ;
        FILE *urandom = fopen( “/dev/urandom”, “r” ) ;
        fread( &random_number, sizeof(random_number), 1, urandom ) ;
        fclose( urandom ) ;
        snprintf( code, code_size+1,”%u”, random_number ) ;
        code[code_size] = 0 ; // because it needs to be null terminated

Leave a Reply

Your email address will not be published. Required fields are marked *


− two = 2

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>