Behind basic auth

Basic authentication is one of the simples methods of providing credentials to access resources. Today I’m going to take a look on it and provide you with an example of a web page utilizing this mechanism written with PHP and Apache.

How does basic authentication work?

On request to a protected resource, the server sends to the client following header:

WWW-Authenticate: Basic realm="Provide your credentials"

In response client needs to send following header:

Authentication: Basic <base64-encoded credentials>

One lesser known fact is that the credentials can be entered in URL:

http://username:password@example.com/

In this case, although they are entered in the URL, browser still sends them as proper HTTP headers. This means that if TLS is used, this data is encrypted. Note: the protocol requires encoding and not encrypting the credentials. If you want to send them securely, you need to use HTTPS, otherwise in case of MITM attacks, it’s easy to retrieve them.

Basic auth example with Apache and PHP

Why those two? Because I’ve never used them and I was curious how it works. And from time to time I need a break from Python, so I’ve decided not to go with Flask or Django this time.

The project structure is following:

gonczor@gonczor:~$ cd /var/www/html
gonczor@gonczor:/var/www/html$ tree -a
.
├── index.html
├── index.php
└── protected
    ├── .htaccess
    └── main.php

1 directory, 4 files

The protected directory has .htaccess file defining access methods:

$ cat protected/.htaccess
AuthUserFile /home/gonczor/.htpasswd
AuthName "Please Enter Password"
AuthType Basic
Require valid-user

As you can see the realm will show Please Enter Password when user enters the site. Credentials are created with htpasswd -c /home/gonczor/.htpasswd gonczor command. It’s important to keep the .htpasswd file outside of the places where web server can access.

Since Apache by default does not allow .htaccess files, proper config file also needs to be modified. In my case it was /etc/apache2/sites-available/000-default.conf, where I needed to add following config allowing to override configuration within /var/www/html/ directory:

<Directory "/var/www/html">
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
    Order allow,deny
</Directory>

Dive into the communication

Now, let’s see what happens when we try to access the resources. In order to look under the hood we’ll use curl. Lines with stars are curl’s logs, right arrows are communication to the server and left ones are communication from server.

$ curl -v http://gonczor:gonczor@192.168.1.107/protected/main.php\?q\=123
*   Trying 192.168.1.107...
* TCP_NODELAY set
* Connected to 192.168.1.107 (192.168.1.107) port 80 (#0)
* Server auth using Basic with user 'gonczor'
> GET /protected/main.php?q=123 HTTP/1.1
> Host: 192.168.1.107
> Authorization: Basic Z29uY3pvcjpnb25jem9y
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Dec 2019 21:09:44 GMT
< Server: Apache/2.4.29 (Ubuntu)
< Content-Length: 62
< Content-Type: text/html; charset=UTF-8
<
* Connection #0 to host 192.168.1.107 left intact
Welcome to the protected part of the site</br>You sent me: 123* Closing connection 0

As you can see the credentials were sent in the Authorization header. When we decode them we get the provided credentials:

$ echo -n "Z29uY3pvcjpnb25jem9y" | base64 -D
gonczor:gonczor

That’s actually good news, because headers are not visible when HTTPS is used and they are not logged by default by the server:

192.168.1.105 - - [24/Dec/2019:22:08:03 +0100] "GET /protected/main.php HTTP/1.1" 401 679 "-" "curl/7.64.1"
192.168.1.105 - gonczor [24/Dec/2019:22:08:17 +0100] "GET /protected/main.php HTTP/1.1" 200 207 "-" "curl/7.64.1"

As you might have noticed the ip addresses vary in the examples. That’s because I still haven’t figured out how to set static IPs on my router 😛 Traditional methods have not been useful 🙁

Bonus

Since it’s Christmas time, I’d like to wish you all the best. Happy, calm time with your nearest and dearest, interesting security cases you will find before black hats do and fulfillment with whatever you do. And make it snow:

ruby -e 'C=`stty size`.scan(/\d+/)[1].to_i;S=["2743".to_i(16)].pack("U*");a={};puts "\033[2J";loop{a[rand(C)]=0;a.each{|x,o|;a[x]+=1;print "\033[#{o};#{x}H \033[#{a[x]};#{x}H#{S} \033[0;0H"};$stdout.flush;sleep 0.1}'

Resources

Wikipedia on basic authentication

Project on Gitlab

See also