Two-Factor Authentication System for Apache and SSH

One-Time Day Password/PIN

The approach I'm going to take here is to have your secondary authentication password change daily instead of more frequently. This allows the mod_auth_basic approach described above to work. I won't go into the details here, but suffice it to say that every time the password changes, an immediate re-authentication is required, which is not the behavior you want.

Let's go with a six-digit numeric pin code and have that delivered to a mobile phone at midnight every night. I'm a big fan of Pushover, which is a service that pushes instant notifications to mobile phones and tablets from your own scripts and application.

To set this up, create a bash script:

vim /home/ubuntu/

Now add the following lines:

1  #!/bin/bash
2  ppwd=`od -vAn -N4 -tu4 < /dev/urandom | tr -d '\n' | tail -c 6`
3  curl -s -F "token=id" -F "user=id" -F "message=$ppwd"
4  htpasswd -b /home/ubuntu/.htpasswd jameslitton $ppwd
5  echo $ppwd | base64 >/home/ubuntu/.2fac

Line 2 produces a random six-digit PIN code and assigns it to a variable called ppwd. Line 3 sends the PIN to the Pushover service for delivery to your mobile phone. Line 4 updates the .htpasswd file with the new password, and last but not least, Line 5 stores a copy of the PIN in a format that you can recover, as you will see later on.

Now save the script, and make it executable:

chmod +x /home/ubuntu/

To complete this solution, all you need to do is schedule the script to run, via cron, at midnight each night:

crontab -e
00 00 * * * /home/ubuntu/

Making It Web-Accessible

You certainly could leave it there and call it done, but suppose you didn't receive your code and want to force a change. Or, perhaps you gave someone temporary access to your site, and now you want to force a password change to ensure that that person no longer can access the site. You always could SSH to your server and manually run the script, but that's too hard. Let's create a Web-accessible PHP script that will take care of this for you.

To start, change the ownership of your script so your Web server can run it:

chown www-data:www-data /home/Ubuntu/

Now you need to create a new folder to hold your script and create the PHP script itself that allows a new "key" to be run manually:

mkdir /vaw/www/twofactor
vim /var/www/twofactor/index.php 

1  <?php
2  exec('/home/ubuntu/');
3  header('Location:');
4  ?>

Because it's conceivable that you're needing to force a new key because you didn't receive the previous one, you need to make sure the folder that holds this script does not require authentication. To do that, you need to modify the Apache configuration:

vim /etc/apache2/sites-available/default-ssl

Now add the following below the Directory directive for /var/www:

<Directory /var/www/twofactor/>
        satisfy any

Now let's configure ownership and restart Apache:

chown -R www-data:www-data /var/www/twofactor
Service apache2 restart

So thinking this through, it's conceivable that the Pushover service could be completely down. That would leave you in a bad situation where you can't access your site. Let's build in a contingency for exactly this scenario.

To do this, let's build a second script that grabs a copy of your PIN (remember the .2fac file that you saved earlier) and e-mails it to you. In this case, let's use your mobile carrier's e-mail to SMS bridge to SMS the message to you.

Start by installing mailutils if you haven't done so already, and be sure to select the Internet option:

apt-get install mailutils

Now create the second script:

vim /home/Ubuntu/

Then add the code:

ppwd=`cat /home/ubuntu/.2fac | base64 --decode`
echo " " | mail -s $ppwd

Don't forget to change the file's ownership:

chown www-data:www-data /home/ubuntu/
chown www-data:www-data /home/ubuntu/.2fac

With that out of the way, now you need to modify the PHP script:

vim /var/www/twofactor/index.php 

Replace line 2 with the following:

2  if (isset($_GET["sms"])) {
3    exec('/home/ubuntu/');
4    } else {
5    exec('/home/ubuntu/');
6    }

Then create two bookmarks, so that any time you want to generate a new PIN and have it sent to you via Pushover, you simply can click the link and it's done. The second bookmark will send a copy of the existing PIN to the e-mail address of your choice in the unlikely event that the Pushover service is unavailable.

  • 2Factor =

  • 2Factor—SMS =

Extending to SSH

Extending this solution to cover SSH is really pretty simple. The key is to use the little-known ForceCommand directive in your sshd_config file. This forces the SSH dæmon to run a script before spanning the terminal session.

Let's start with the script:

vim /home/ubuntu/

Now add the following lines:

1  #!/bin/bash
2  code=`cat .2fac | base64 --decode`
3  echo -ne "Enter PIN: "
4  while IFS= read -r -s -n1 pass; do
5    if [[ -z $pass ]]; then
6       echo
7       break
8    else
9       echo -n '*'
10      input+=$pass
11   fi
12 done
13 if [ $code = $input ];
14 then
15   sleep 1
16   clear
17   /bin/bash
18 else
19   sleep 1
20   curl -s -F "token=id" -F "user=id" -F "message=$input"
21 fi

Line 2 loads the PIN into a variable. Lines 3–12 prompt for the PIN and echo a star back for each key press. Line 13 compares the user's input to the PIN. If they match, lines 14–17 clear the screen and start a bash session. If the user's input does not match the PIN, lines 18–21 send a notification to Pushover so you know a failure occurred and then ends the session.

Let's configure the SSH dæmon to run the script:

vim /etc/ssh/sshd_config

Now add the following to the top of the file:

ForceCommand /home/ubuntu/

Figure 2. Two-Factor Request from SSH

This approach works great. The only limitation is no backspaces. If you press the wrong key, your session will be terminated, and you'll have to try again.

There you have it, a poor-man's two-factor authentication implementation with very little effort and from my experience, it's rock solid!