As a partner of GovWare 2025, Cisco was tasked with providing a "click-through" captive portal (splash page). Cisco and GovWare provided a wireless network for attendees for the first time in the history of the conference, and because of this, the conference asked for support from Cisco to serve attendees with a terms of service agreement to join the conference Wi-Fi.
This proved to be a much more monumental effort than anticipated, as we provided Network Operations Centre services from the Security Operations Centre, without the ability to deploy hardware or software into the Marina Bay Sands (MBS) infrastructure. In the below statement, I can tell you why we do the things we do as engineers:
"We do these things not because they are easy, but because we thought they were going to be easy"
We originally thought that we could do the captive portal using the Firewall, but because the number of potential attendees could drown the capability on the firewall, we pivoted to using the Identity Services Engine (ISE). ISE requires integration with a Wireless LAN Controller (WLC) to serve a captive portal, but after working with the MBS team to do this, they ran into their own problems around this... aka, handcuffs known as compliance. They were unable to accommodate us because of their need to be compliant with certain standards... even though they did want to make it happen.
We understood their predicament and started looking into alternatives. As I was searching the web for different open-source captive portal tools, I found one that mentioned there is a broadcast that can be done to tell computers on the network there is a captive portal that needs to be accessed. This led me to the revelation of DHCP option 114. In a nutshell, this option allows us to advertise the captive portal in the DHCP lease we hand out. You can read more about it.
With this newfound knowledge, I decided to code up a v1 captive portal that met the RFC 8910 and RFC 8908 requirements for the request type, API endpoints, and responses.
Creating the Captive Portal Page
Initially, I used ngrok to allow us to serve the captive portal via HTTPS, which is a standard OS requirement to connect.
One of the first endpoints needed to make a captive portal work was this one: www.example.com/.well-known/captive-portal
This endpoint needed to respond with JSON explaining that there was a captive portal needed and would present the captive portal URL. Here's an example response from my server:
{"captive":true,"user-portal-url":"http://www.example.com/portal"}
Ideally, the "captive:true" will change to false if the client has accepted the terms. This is an issue for later, and I'll talk about it after we solve some other problems that came up before it.
As soon as the client received the JSON response, they were then directed to that captive portal page. For GovWare 2025, the captive portal page looked like this:
Configuring the DHCP Server
Once I set up this captive portal, I took to my DHCP server and set option 114 to point to www.example.com/.well-known/captive-portal.
I tested my iPhone first, and it worked flawlessly. What didn't work flawlessly was Mac, Linux, Android, Samsung, and Linux.
If you look at RFC Section 2 Paragraph 2, there is wording there that says clients "SHOULD" request option 114 if they support it. In DHCP world, a client broadcasts that they are looking for a server and what they want from that server. If the client doesn't request an option, then the server most likely won't send that option.
Troubleshooting OS-Specific Issues
Most of the OSs requested option 114 but did not honor going to the captive portal. This is because they did additional tests to see if going to the portal was required to allow internet access. If it isn't, then they skip the portal. Some of the OSs would have their own requirements that needed to be added to the captive portal API for them to work. Windows did not ask for the option at all even though it supports captive portals.
Forcing Windows to Get Captive Portal
The first thing I needed to figure out was how to force Windows to get the captive portal option in the DHCP lease. After some searching, most DHCP servers offer an option to "force-send" or "always-send" a DHCP option. We used the Kea DHCP server, and it uses the "always-send" setting to forcefully send the option to clients.
This resolved the Windows problem (and fixed it for certain distributions of Linux too).
Preventing Bypass of the Captive Portal
We know that Android, Samsung, and Linux perform checks to see if they can bypass the portals. To fix this, we needed to configure DNS sinkholing on the firewall to send a few domains into the ether. Once we did that and those checks were failing, we started to see all three OSs bring up the captive portal.
Accommodating Different Versions of MacOS
This left Mac OS, which displayed different behavior between versions. The captive portal worked fine on the newest version (Mac OS 26) but not on older versions. These older versions expected this webpage to be available for the captive portal: '/hotspot-detect.html'
After adding that and having it send a SUCCESS in the HTML, older versions of Mac started accessing the captive portal as well.
Ensuring the Captive Portal Was Hit Just Once
After getting all the different OSs to connect successfully, I then had to prevent them from hitting the captive portal repeatedly. The captive portal was hosted in AWS, which means that we didn't have their MAC addresses to check whether they had hit the portal before or not.
I solved this issue by using fingerprintJs, a JavaScript library that will fingerprint a browser and which guarantees that each fingerprint will be unique. Using this library, I added a fingerprint of each client that hit the captive portal to a local database. I would change the "captive:true" to "captive:false" if I had seen them before. This solved the issue of repeatedly going to the captive portal on subsequent DHCP leases or reconnecting to the network.
Here is how I stored the fingerprints in my local database for later cross-referencing:
Ultimately, we successfully deployed the captive portal for GovWare, with almost 1700 attendees hitting the captive portal to access the conference Wi-Fi. As a first-time endeavor, GovWare was thrilled to have the captive portal and security operations center support from Cisco.
You can see the source code for this project on my GitHub. In the README, you can get more info on what domains need to be sinkholed and any endpoints that need to be created to get all the OSs to work properly.
Check out the other blogs by my colleagues in the GovWare SOC.
About GovWare
GovWare Conference and Exhibition is the region's premier cyber information and connectivity platform, offering multi-channel touchpoints to drive community intel sharing, training, and strategic collaborations.
A trusted nexus for over three decades, GovWare unites policymakers, tech innovators, and end-users across Asia and beyond, driving pertinent dialogues on the latest trends and critical information flow. It empowers growth and innovation through collective insights and partnerships.
Its success lies in the trust and support from the cybersecurity and broader cyber community that it has had the privilege to serve over the years, as well as organisational partners who share the same values and mission to enrich the cyber ecosystem.
We'd love to hear what you think! Ask a question and stay connected with Cisco Security on social media.
Cisco Security Social Media
LinkedIn
Facebook
Instagram
X
