Captive Portal Using PHP and iptables Firewall on Linux
This experiment will show you, how to build a simple captive portal using PHP and iptables on Linux Ubuntu.
Most linux commands used in this experiment are suitable for Debian based Linux (just like what I use in this experiment, Ubuntu Linux). Some of them may have slight differences to be implemented on any other non-Debian-based Linux distros such as Centos, RHEL, or OpenSUSE.
Warning! I’m not responsible for any damages to your computer and/or your computer network configuration that caused by following this experiment. Do this AT YOUR OWN RISK! Use this article as educational and/or experimental purpose only. This articles is not intended for production use nor public use. But if you found any information in this article is useful, I will be very grateful.
First, here the basic concepts on how the captive portal works:
In this experiment I uses:
In this point forward, if you’re following to practices this articles I assumes that you already have an ubuntu box installed and ready to use and all the required packages as well.
Configuring the network interfaces
I have two network interfaces, eth0 and eth1. eth0 is connected to the internet, and eth1 is connected to the hotspot access point LAN. I have this configuration on /etc/network/interfaces
file:
# file: /etc/network/interfaces auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
auto eth1
iface eth1 inet static
address 192.168.3.1
network 192.168.3.0
netmask 255.255.255.0
The eth0 is connected to the internet and it’s IP address is assigned by DHCP by the network. You may have to configure it manually if it’s IP address is not assigned by DHCP. However the network interface (eth1) which is connected to the hotspot LAN has IP address 192.168.3.1 on 192.168.3.0/24 network. This will be going to be the portal IP Address and acts as user’s gateway IP address.
You may edit the /etc/network/interfaces
file by using nano
or vi
from command line as root. Change your network configuration to match your network settings, save the file and restart the networking services by issuing the following command to apply changes:
$ sudo /etc/init.d/networking restart
Enable IP forwarding (routing) on Linux Ubuntu so every packets from the hotspot are forwarded to the internet
Enable the IPv4 packet forwarding by uncommenting the following line on /etc/sysctl.conf
file
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
$ sudo iptables -A POSTROUTING -t nat -o eth0 -j MASQUERADE
At this point, any other computer connected to the hotspot (eth1) which is using your computer as gateway (192.168.3.1) are able to connect to the internet through your computer internet connection (eth0).
Capturing user IP and MAC address
a “splash” page is used for capturing the user IP and MAC address and display the page to user when they were redirected to this page by the portal system.
Here’s an example on how to capture user’s IP and MAC address using PHP:
<?php // capture their IP address $ip = $_SERVER['REMOTE_ADDR']; // this is the path to the arp command used to get user MAC address // from it's IP address in linux environment. $arp = "/usr/sbin/arp"; // execute the arp command to get their mac address $mac = shell_exec("sudo $arp -an " . $ip); preg_match('/..:..:..:..:..:../',$mac , $matches); $mac = @$matches[0]; // if MAC Address couldn't be identified. if( $mac === NULL) { echo "Access Denied."; exit; } ?>
From the example above, user’s IP and MAC address are stored in variables $ip and $mac respectively. For getting their MAC address, we uses ‘sudo /usr/sbin/arp -an
’ command. Please note that for this command to work, the linux user ‘www-data’ (default user for Apache webserver daemon’) must be in the sudoers file.
Add www-data as sudoers in linux box
First, you have to be root to add user as sudoers. Execute this command:
$ sudo visudo
Then add the following line at the bottom of the file, so that www-data user can execute arp command without entering a password
www-data ALL=NOPASSWD: /usr/sbin/arp
Save the file.
Create a simple HTML form for user to submit (and agree the portal Terms of Service if any).
Here’s an example of the form in PHP:
<form method="post" action="process.php"> <input type="hidden" name="mac" value="<?php echo $mac; ?>" /> <input type="hidden" name="ip" value="<?php echo $ip; ?>" /> <input type="submit" value="OK" style="padding:10px 20px;" /> </form>
User’s IP and MAC address are embedded in the form as hidden field for further processing in the firewall. Save the file with name index.php in the document root directory of your web portal. So now it’s accessible from a web browser. Try http://localhost
or http://192.168.3.1
from your linux box web browser to test the portal page as we will redirect user to this address.
Redirecting every “unknown user” HTTP traffic to the “splash” portal
I uses a similiar logic as explained by Andy Bev in his wiki but with some little differences. Here’s the command on how to redirect every HTTP traffic to portal using iptables. PLease note that you have to be root in order to be able to modify the iptables table.
1. Create a new chain named ‘internet’ in mangle table with this command
sudo iptables -t mangle -N internet
2. Send all HTTP traffic from eth1 to the newly created chain for further processing
sudo iptables -t mangle -A PREROUTING -i eth1 -p tcp -m tcp --dport 80 -j internet
3. Mark all traffic from internet chain with 99
sudo iptables -t mangle -A internet -j MARK --set-mark 99
4. Redirect all marked traffic to the portal
sudo iptables -t nat -A PREROUTING -i eth1 -p tcp -m mark --mark 99 -m tcp --dport 80 -j DNAT --to-destination 192.168.3.1
OK from now on, every HTTP request from eth1 will be redirected to the portal page.
Bypass firewall redirection rules when users submit themself from the splash page
When user click the submit button from the portal splash page, their IP and MAC address will be submitted to the process.php file. In this file their MAC address will be “entered” into iptables firewall so that they won’t be redirected to the portal splash page anymore. The logic is, executing the following command from PHP (process.php) so that the redirection logic in the firewall will be bypassed:
sudo iptables -t mangle -I internet 1 -m mac --mac-source USER-MAC-ADDRESS-HERE -j RETURN
And remove their “redirection” connection track. How? From Andy Bev wiki, we can do that with a simple script.
Create an executable file /usr/bin/rmtrack
and put the following code inside
/usr/sbin/conntrack -L |grep $1 |grep ESTAB |grep 'dport=80' |awk "{ system("conntrack -D --orig-src $1 --orig-dst " substr($6,5) " -p tcp --orig-port-src " substr($7,7) " --orig-port-dst 80"); }"
Change the file permission to 755 or 700 for better safety with the following command:
$ chmod 755 /usr/bin/rmtrack
With those rmtrack, we can remove user’s connection track with the following command
$ sudo rmtrack USER_IP_ADDRESS
Here’s an example of process.php file:
<?php if( isset( $POST['ip'] ) && isset ( $_POST['mac'] ) ) { $ip = $_POST['ip']; $mac = $_POST['mac']; exec("sudo iptables -I internet 1 -t mangle -m mac --mac-source $mac -j RETURN"); exec("sudo rmtrack " . $ip); sleep(1); // allowing rmtrack to be executed // OK, redirection bypassed. // Show the logged in message or directly redirect to other website echo "User logged in."; exit; } else { echo "Access Denied"; exit; } ?>
Don’t forget to add the following line to the sudoers file so that the iptables and rmtrack command can be executed by the web server:
www-data ALL=NOPASSWD: /sbin/iptables www-data ALL=NOPASSWD: /usr/bin/rmtrack [0-9]*.[0-9]*.[0-9]*.[0-9]*
After the user had been logged in, they won’t be redirected to the portal again because of the iptables bypassing rules command. That’s it! Congratulations, A (very simple) captive portal using PHP and iptables has been successfully created.
How to remove the user from the iptables bypassing rules so that they have to be authenticated again?
Here’s on how to do it. Delete user’s bypassing iptables rules with the following command:
sudo iptables -D internet -t mangle -m mac --mac-source USER_MAC_ADDRESS -j RETURN
and remove their connection track (again):
sudo rmtrack USER_IP_ADDRESS
if you know user’s IP address, both command can be easily done by a simple PHP script below:
<?php // get the user IP address from the query string $ip = $_GET['ip']; // this is the path to the arp command used to get user MAC address // from it's IP address in linux environment. $arp = "/usr/sbin/arp"; // execute the arp command to get their mac address $mac = shell_exec("sudo $arp -an " . $ip); preg_match('/..:..:..:..:..:../',$mac , $matches); $mac = @$matches[0]; // if MAC Address couldn't be identified. if( $mac === NULL) { echo "Error: Can't retrieve user's MAC address."; exit; } // Delete it from iptables bypassing rules entry. while( $chain = shell_exec("sudo iptables -t mangle -L | grep ".strtoupper($mac) ) !== NULL ) { exec("sudo iptables -D internet -t mangle -m mac --mac-source ".strtoupper($mac)." -j RETURN"); } // Why in this while loop? // Users may have been logged through the portal several times. // So they may have chances to have multiple bypassing rules entry in iptables firewall. // remove their connection track. exec("sudo rmtrack " . $ip); // remove their connection track if any echo "Kickin' successful."; ?>
Save it to a file named kick.php
in your web server document root. Get into your web browser and put the following URL to the address bar to kick someone from the iptables bypassing rules:
http://192.168.3.1/kick.php?ip=USER_IP_ADRESS
And you’re DONE!
Feel free to modify the code to suit your requirements or asks something you don’t understand in making this (very simple) captive portal. I’ll try to answer and explain if I able to.
If you use visudo and add www-data with /usr/bin/arp, you don’t need “sudo” before the “/usr/sbin/arp -an” in the splash php. If you add “sudo”, the command doesn’t give any result.
Thanks for that
I had to say thanks, because I use this tutorial – and I have to say, it works nice! 🙂 I modified your code according to my project, and it’s still great.
So – thank you! 🙂
I’m just happy someone read something I put together.
Well, I find something more.
In the rmtrack-part, inside the awk’s quotation marks, you have to put a \ symbol before every quotation mark and string symbol (except $1).
So, in the proper way the awk-stuff looks like this:
awk “{ system(\”conntrack -D –orig-src $1 –orig-dst \” substr(\$6,5) \” -p tcp –orig-port-src \” substr(\$7,7) \” –orig-port-dst 80\”); }”
After Successful authentication or ip-mac registration how do i redirect user to it’s original url. I am using iptables & php.
If the ip tables are setup correctly then any registered device should forward to the original URL.
In wich way do I have to modify this, if I want to let other other services (like SSL) pass thought the captive portal? So I mean everything is blocked until the user enters his credentials and not only port 80?
There is a line specifying port 80 on the instructions which reads
sudo iptables -t mangle -A PREROUTING -i eth1 -p tcp -m tcp –dport 80
The dport (destination port) is being set to port 80
You could set dport to 443 for https or 22 for ssh
So do 1 to 4 again but this time maybe step one call the chain secure, step two dport 443, step 3 tag with 97 and complete with the new information step 4
Does that makes sense?