pfSense Captive Portal login/logout page
Bringing Part of the Portal into Captive Portal.
or:
Where I do post php code.
Prerequisistes
- pfsense installed (version 2.2.5, looks like the story will change for 2.3)
- (separate) ‘intranet’ server
- at least one user without technical background
Aims
Play around and make a dedicated pfSense Captive Portal login/logout page.
Overview
In the community I live in we run a couple of internal services and guard Internet-Access by pfSenses Captive Portal (“CP”).
The Captive Portal intercepts any traffic for a not yet registered IP/MAC-Adress pair (clients come from the local network, so IP-Adresses can resolved to MAC-Adresses, although this can be tricked) and responds with a login page.
Users can either be defined with a local user manager, a RADIUS server or an LDAP server (this is another story and worth another blog post). Upon successfull login, the MAC/IP/username-triple will be saved in a SQLite-database, the user is redirected to the requested URL (redirurl
) and forthcoming traffic will be allowed.
The Captive Portal (with default settings) has a serious drawback: Only standard HTTP traffic can be intercepted, or more precisely: a redirect to the login page only works for HTTP-connections (for good reason). In principle it is possible to use a certificate and intercept/redirect https-traffic, but you need a proper certificate if the users should not be scared away by a certificate warning. Note: Yes, allowing http logins is as good as no logins if you are talking real security.
As a result, the login page of the Captive portal will only be shown if users attempt to access a http (vs https) page. Many users browsers are however configured to initially load (“homepage”) a https page (which is good). These users are confronted with a connection timeout and will be scared that no internet connection is available, our intranet or their computer is broken.
The resulting need for a dedicated login page
One solution (besides many others) is to communicate a dedicated login page (e.g. https://intranet.intern), where after successfull login the Captive Portal redirects to a given page - the redirurl
is just a parameter to the login-page (http://192.168.0.1:8002/?redirurl=http://github.com
for example).
But this would force users to generate traffic to an external page, so the better idea is to redirect the user to a dedicated internal portal-kind-of-page.
The “Captive” Part of the Captive Portal will still work - a not yet registered MAC/IP-pair will get the login page presented.
Am I logged in?
Now, it would be nice if users can see whether they are logged in to the Captive Portal and get a logout-page instead of the login-page if they are. To figure out whether a client is logged in, we resolve its IP and MAC-address and look up the IP/MAC-address in the SQLite3 database. We present the username if the client indeed is logged in (minimal example):
Logout?
The default “solution” of pfSenses Captive Portal is to javascript-wise open a popup-window with a button to log out. What I did not yet mention is that actually, not only username, IP- and MAC addresses of clients are stored, besides other things that happen, a session(id) is created on successfull authentication against the CP. For security reasons the client can only log out by giving a session-id. As in our http-case all traffic can possibly be listened to anyway, the sessionid does not provide much additional security (against ip/mac faking), so we can as well use the information gathered above to log the user out.
Therefore, we make the “login” page (192.168.0.1:8002
) behave differently if requested as 192.168.0.1:8002/logout
. In that case, the IP/MAC and username info is gathered (see above) and the respective session deleted from the database. All that is missing now is a ‘page’ or ‘path’ to request that /logout-URL.
As the default captive portal logout action requires the session id (and passes it to the function captiveportal_disconnect_client
in /etc/inc/captiveportal.inc
), we will also use that function instead of taking care of RADIUS-server notification, firewall-rule-modification etc. ourselves.
To make the code paths and changes read easy, in this example I will query the database again to access the sessionId and call the internal captiveportal_disconnect_client
function. I extracted some code into functions to further improve readability.
More things to do
If you use these templates, make sure to also include them for the error-page (and/or success-page if you use one).
Refine all those ideas.
Use at own risk
The approach outlined here has at least following security issues:
- login via http is easily spoofable
- giving the logout ability makes it possible for attackers to logout any given client
- the resolution of ip to mac is valid only in local networks and everything about it can be faked
- you might run into issues if concurrent logins are enabled on your captive portal
- no SQL sanitization happens in the first code example. I am not sure whether you could fake your MAC or IP adress in a way to cause harm (probably you can).
- The code ignores malconditions and possible return values here and there.
Think twice before throwing this in production!
Other approaches
Use the logout page, it is shown after successfull login, trunk seems to prepare this when the default page is visited (probably in pfSense 2.3).
Having a better idea?
Awesome! Get in contact with me!