Access your servers securely with a Magic URL

Rabbits in Hats

I mentioned the other security measures running on my CCTV server earlier. In a moment, I will share snippets of their configuration, which I use to integrate with the Magic URL functionality – I hope without confusing the matter in hand.

First, though, I'll look at the heart of the Magic URL mechanism: a short Bash script. This main script, which will be run by a cron job, is called /usr/local/etc/magic-url-script. I won't go into every detail, but I'm sure you'll get the idea. You should download a version of the script [5] as opposed to entering it by hand to avoid typos.

The first section (Listing 4, lines 1-27) begins by tidying up any previously added Magic URL IP addresses in the /etc/hosts.allow file, creating the pertinent directory, clearing index files of old data, and creating a timestamp variable called ${DATE}.

Listing 4

Magic URL Bash Script

01 #!/bin/bash
02
03 # Create Magic URL dir if not present
04 mkdir -p /var/www/html/chris-magic-url/
05
06 # Check if I ran last time and tidy up if so
07 CHECK=`tail -n2 /etc/hosts.allow | head -n1`
08
09 if [ "${CHECK}" = "# START MAGIC URL IP ADDRESS #" ] ; then
10  sed -i '/# START MAGIC URL IP ADDRESS #/d' /etc/hosts.allow # delete marker in file
11  sed -i '$d' /etc/hosts.allow # delete last line
12 fi
13
14 # Create date variable
15 DATE=$(date +"%H:%M:%S %m-%d-%Y")
16
17 ########################################################################
18 # Create the first index file and avoid pesky browser caching (this is not infallible)
19 echo -e "<html><head><meta http-equiv=\"cache-control\" content=\"no-cache\"><meta http-equiv=\"expires\"
   content=\"0\"><meta http-equiv=\"expires\" content=\"Tue, 01 Jan 1980 1:00:00 GMT\"><meta http-equiv=\"pragma\"
   content=\"no-cache\"><meta http-equiv=refresh content=\"5;URL='index.html'\">" > /var/www/html/chris-magic-url/index.html
20
21 echo ${DATE} >> /var/www/html/chris-magic-url/index.html
22 ########################################################################
23
24 ########################################################################
25 # Empty second index file but make sure you know it exists and the last time it was written to
26 echo ${DATE} > /var/www/html/chris-magic-url/index2.html
27 ########################################################################
28
29 if [[ ! -z ${GSM} ]]
30  then
31
32     echo "# START MAGIC URL IP ADDRESS #" >> /etc/hosts.allow
33     echo "sshd: ${GSM}" >> /etc/hosts.allow
34
35     echo -e "<html><head><meta http-equiv=\"cache-control\" content=\"no-cache\"><meta http-equiv=\"expires\" content=\"0\"><meta http-equiv=\"expires\" content=\"Tue, 01 Jan 1980 1:00:00 GMT\"><meta http-equiv=\"pragma\" content=\"no-cache\"><meta http-equiv=refresh content=\"5;URL='index2.html'\">" > /var/www/html/chris-magic-url/index.html
36
37     echo -e "<html><head><meta http-equiv=\"cache-control\" content=\"max-age=0\"><meta http-equiv=\"cache-control\" content=\"no-cache\"><meta http-equiv=\"expires\" content=\"0\"><meta http-equiv=\"expires\" content=\"Tue, 01 Jan 1980 1:00:00 GMT\"><meta http-equiv=\"pragma\" content=\"no-cache\">" > /var/www/html/chris-magic-url/index2.html
38
39     echo ${DATE} >> /var/www/html/chris-magic-url//index2.html
40     echo -e "<br>${GSM}" >> /var/www/html/chris-magic-url//index2.html
41     echo -e "<br>Aphorism: Time is short, carpe diem." >> /var/www/html/chris-magic-url//index2.html
42
43     iptables -D Fail2ban-ssh -s ${GSM} -j REJECT
44     iptables -D Fail2ban-apache-chris -s ${GSM} -j REJECT
45     iptables -D Fail2ban-apache-denied -s ${GSM} -j REJECT
46
47     mail -s "Magic triggered ${GSM}" chris@binnie.tld < /dev/null
48     sed -i '/chris-magic-url/d' /var/log/apache2/access.log
49     /etc/init.d/apache2 reload
50 else
51   echo No match
52 fi

As you can see, although it's noisy, it's just a case of overwriting the index.html and index2.html files in the /var/www/html/chris-magic-url directory. I then simply write a timestamp to them so I can see easily in a browser when they were last updated.

In the second section (lines 29-38), I'm calling the IP address I allow to connect to the GSM server (Global System for Mobile Communications, which is the older name for all things related to cellphone data and now called 4G, 3G, etc.). To check inside the web server access logs to see if anyone has triggered the Magic URL and had their IP addresses grabbed, define the GSM variable with:

GSM=`cat /var/log/apache2/access.log | grep --line-buffered chris-magic-url | awk '{print $1}'|tail -n1`

If you've messed around with your Apache logging, you might want to change the print $1 value to print $3, for example. Just copy that line and test it on the command line to get it right.

From here onward, the important stuff happens. To start off, I'm appending the /etc/hosts.allow file with a line mentioning the ${GSM} IP address to allow SSH access, with a comment above it that acts as a marker. In case you're wondering, TCP Wrappers doesn't need to be restarted, because the config is picked up immediately and is "live."

The two next busy-looking blocks of code simply create non-caching HTML files to ease the pain of your browser. If you're using a smartphone, that could be a welcome addition because they try to cache as much as possible to reduce data tariff costs.

That opening line for the if statement (line 29) simply runs through the commands that follow if the ${GSM} variable isn't empty after the Magic URL has been spotted in the web server logs.

The rest of the script (lines 40-53) dutifully creates the index files and then redirects the browser to index2.html when the Magic URL has been successfully triggered.

Now look at lines 44-46 starting with iptables, which are there for fail2ban, if used. They remove entries for the newly allowed dynamic IP address – in my case, one for each filter (i.e., repeated SSH login attempts, non-existent pages, and HTTP 401 login errors). Next, the script then sends email telling me the Magic URL was triggered (line 48).

Finally, I delete all mentions of the Magic URL from the web server's access logs using sed and do a relatively painless Apache reload (lines 49-50) so the logs keep updating correctly.

Note that because of the way some web server logs are created, one "hit" on the website might create two log entries; moreover, as meta refreshes take place (redirecting the browser to index2.html), a few instances of chris-magic-url/ could be generated in the web logs.

As a result, the SSH login window can sometimes be extended to two or three minutes; a third run of the script should clean up /etc/hosts.allow access. You could create another timestamp file to catch only one instance of the Magic URL being triggered rather than rely on the web server logs directly. For my purposes, this oddity doesn't cause concern, but you could certainly finely tune this aspect.

Although you might want a slicker way of solving this problem on a very busy high-traffic production server, you have many other ways to do so, such as by using other temporary files and not reloading Apache config at all. I'll leave you to experiment to your heart's content.

In case you're wondering, the No match section (line 52) can be used if you manually run the script for testing. If no Magic URL entry exists in your web server logs, you will see No match echoed to your screen.

Magicians

The last stage is to run the Bash script every minute. In my /etc/crontab file I have the following reference to run the /usr/local/etc/magic-url-script file:

* * * * *     root     /usr/local/etc/magic-url-script > /dev/null 2>&1

For cron to pick up the new run-every-minute cronjob in the crontab, you might need to run the following command on a current Linux version,

# systemctl restart cron

or this command on the SysV equivalent:

# /etc/init.d/cron restart

Note that I intentionally left the root user running this cron job so that everything works without permission issues, but ideally, you should run it as a non-privileged user. Change the parts that would otherwise need to be run by root and prepend sudo to your privileged commands (e.g., when you append to the /etc/hosts.allow file).

The Great Reveal

The proof is in the pudding, as they say. To gain access to the server, I first visit the Magic URL in a browser on the device on which I want to log in over SSH. I include the Magic URL's htacces login and password and build them into the bookmark of the URL for ease:

https://hack-and-defend:chrisbinnie@cctv.server.tld/chris-magic-url/

The htaccess user hack-and-defend has the password chrisbinnie in this case. The server hostname is cctv.server.tld. Because the Magic URL lives off the web root, I can simply append it to the hostname (the chris-magic-url/ section).

By design, the next thing seen in the browser wouldn't thrill an attacker and immediately pique their interest. If your password is securely passed over the (optional) HTTPS connection and accepted by the web server, you'd be presented with an unglamorous page that simply offers a (mobile-friendly) blank, white page with nothing more than a timestamp displayed. There's nothing of interest to see for anyone that gets this far. In my case it looks like this:

11:11:01 11-11-2016

You can see from the timestamp that the page was dutifully updated by the cron job within the last minute, and because I can see the page, it means I've been allowed access via htaccess.

If you cast your mind back to the HTML echoed by the Bash script, you won't be too surprised to see the page auto-refresh within a few seconds and jostle you onward to the second URL, which appears in the browser's address bar:

https://cctv.server.tld/chris-magic-url/index2.html

On that page, I see an equally perplexing message:

11:12:01 11-11-2016
154.138.22.6
Aphorism: Time is short, carpe diem.

The first line confirms when that page was last updated (a minute after the first index page). The second line is the IP address picked up in the web server logs, and the third line is a shameless geek joke.

Why did I put the joke there? It's to remind me that I only have a minute (or less depending on when I see that page) to spawn an SSH session with my CCTV server before being locked out again. In this way, I prevent lingering, insecure connections. Even on my slow GPRS my smartphone connects within a few seconds without breaking a sweat (I use the Blowfish cipher on SSH, which is a little lighter over slow mobile connections), so this time interval presents no problems.

In my case, I fire up my favorite SSH client on my smartphone, JuiceSSH, and, et voila, my connection is accepted – even using an otherwise unwelcome dynamic IP address – and I'm presented with a login prompt. As I've said, I prefer the belt-and-braces approach to accessing servers. So, at this point, I am also presented with a two-step verification prompt for a code generated by the Google Authenticator app on my smartphone, which is yet another security layer with which to contend.

Although I can only spawn a session within a minute, I'm still allowed SSH access for as long as the session is open, which suits me just fine. I might prune some footage from the storage drive on the server or check out something else of interest.

A reminder that while you're testing this (assuming you're not going for the Fort Knox approach straightaway with iptables, fail2ban, and two-step auth initially) the only file that is really being changing at this stage is for access by sshd via the /etc/hosts.allow file.

To test whether you can log in correctly, just run:

# cat /etc/hosts.allow

If all is well, you should see that your cellphone IP address has now been added to the end to allow SSH access; for example:

# START MAGIC URL IP ADDRESS #
sshd: 123.123.123.123

If you're not sure what your dynamically allocated IP address is, then just ask your friendly, neighborhood Google by entering

https://www.google.com/#q=what%20is%20my ip

in your browser's address bar.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus

Direct Download

Read full article as PDF:

Price $2.95

News

njobs Europe
What:
Where:
Country:
Njobs Netherlands Njobs Deutschland Njobs United Kingdom Njobs Italia Njobs France Njobs Espana Njobs Poland
Njobs Austria Njobs Denmark Njobs Belgium Njobs Czech Republic Njobs Mexico Njobs India Njobs Colombia