Detecting Captive Portal Delivery on Hak5 Pineapple
I recently wrote about some fixes for the Captive Portal module on the Hak5 Pineapple to make sure redirection and credential capturing was effective. But what if you're trying to do something else - like use the Captive Portal as a means of delivering a message onto targets? Simply delivering the Captive Portal is easy, but it's hard to know when it's displayed and not interacted with.
Now, every red-blooded American should root for Army over Navy. For the most recent Army-Navy week, I walked around the Naval Academy utilizing a Pineapple Tetra to grab local mobile phones and deliver a "Beat Navy" message to the midshipmen and locals. It turns out the effort was largely fruitless as the Naval Academy does not make the midshipmen form up outside from late fall through early spring because it's "too cold." Pussies.
No matter what message you intend to deliver, there are a variety of small edits you need to make throughout the Pineapple to make it possible. In a nutshell, configure PineAP
, configure EvilPortal
, redirect all DNS back to the Pineapple, parse the logs, render a notification to the dashboard.
Craft The IO Message
In reality, only one file is actually necessary to edit, the index.php
base file. However, giving a user a button to click, in this case allowing folks to choose "Go Army" will take them to a second page which was needed to have a PHP file run to log a notification onto the Pineapple dashboard. But in reality, people were highly unlikely to click the button.
To enable some tracking, though, you'll want to make sure the index.php
file is loading an innocuous image. For this, I created a small, transparent, single-pixel .png
image. Save it as portal.png
and just drop it anywhere on the page.
<img src="portal.png">
The image is important for detecting if the captive portal page actually rendered. Mobile devices and browsers all utilize variations of web addresses to determine connectivity, looking for something simple like the word Success
to be returned. The following are some sample URLs that various devices and browsers attempt to access for connectivity determination:
- *.akamaitechnologies.com
- connectivitycheck.android.com
- detectportal.firefox.com
- www.airport.us
When the site is inaccessible, the device will render the captive portal login page for the user to authenticate for access. Images are only loaded in this case and will therefore be detectable in the logs to know the page was rendered. If you were to parse the logs just for page accesses, you may find the mobile devices and browses are doing connectivity checks at a high frequency which would flood the notification panel with false positives.
Redirecting the DNS Entries
While the CaptivePortal should automatically display a message, it doesn't always work. Inevitably, the target is going to use their mobile device for Internet browsing in which case you can redirect their web traffic to your desired page anyway by poisoning the DNS response. The Pineapple should already have dnsmasq
installed as a dependency. If not, it can be installed from the modules interface.
Getting dnsmasq
to redirect all addresses to the local Pineapple at 172.16.42.1
is relatively easy and simply involves editing the /etc/dnsmasq.conf
configuration file. SSH into the Pineapple and add the following line to the top of the file.
addresses=/#/172.16.42.1
Then reload the dnsmasq
service to get the updated configuration into play.
root@TETRA:/etc# service dnsmasq reload
Enabling NGINX Logging
By default, the NGINX
server on the Pineapple does not log. This was probably a decision to keep CPU usage down, IO bus consumption down on the SD cards, and file space considerations under control. Nevertheless, logging needs to be on to discern the difference between just accessing the index.php
file and the rendering of the portal.png
image. The configuration file can be found at /etc/nginx/nginx.conf
.
server {
listen 80;
server_name www;
error_page 404 =200 /index.php;
error_log /tmp/log/nginx/error.log;
access_log /tmp/log/nginx/access.log;
....
As with the DNS configuration, reload the NGINX
service to get the updated configuration into play.
root@TETRA:/etc# service nginx reload
Now the logs will include lines such as:
172.16.42.189 - - [20/Dec/2019:20:52:34 -0500] "GET / HTTP/1.1" 200 867 ....
172.16.42.189 - - [20/Dec/2019:20:52:34 -0500] "GET /portal.png HTTP/1.1" 200 103 ....
Detecting A Page Render
Parsing the /tmp/log/nginx/access.log
file is remarkably easy. A little usage of the grep
and awk
utilities will identify the image file, access times, and IP addresses.
# ensure the files exist
touch /tmp/ep_detected.log
touch /root/ep_notified.log
# parse the logs for relevant entries
cat /tmp/log/nginx/access.log | grep portal.png | awk '{print $4$5 " " $1}' > /tmp/ep_detected.log
# generate notifications
python epnotify.py
But while this is a start, it's still not entirely useful unless you're continuously interacting with the Pineapple on the terminal. (NOTE: Although you could simply save the log to a persistent folder instead of /tmp
for later analysis - but that's not as fun). Save the commands to a script file and configure cron
to execute it every five minutes.
Still, consecutive runs of the script will produce accesses you already knew about. Unfortunately, the Pineapple does not have the traditional diff
or fc
utilities to compare before and after logs. So, as Deadpool would say, this will require "Maximum Effort." Just utilize a simple Python script to compare the current extracted data - ep_detected.log
- with the addresses already reported - ep_notified.log
.
import subprocess
# Open Logged Items
fhl = open("/root/ep_notified.log", "r")
lines = fhl.readlines()
fhl.close()
# Build a List of Already Reported Renders
notifications = []
for line in lines:
notifications.append(line.rstrip())
# Open Detected Items
fhd = open("/tmp/ep_detected.log", "r")
lines = fhd.readlines()
fhd.close()
# Build a List of Known Renders
detections = []
for line in lines:
# Test for Matches and Exclude
if not line.rstrip() in notifications:
detections.append(line.rstrip())
# Send Notifications
for line in detections:
ip = line.split(" ")[1].rstrip()
# build a command string
command = "grep " + ip + " /tmp/dhcp.leases | awk '{print $4}'"
# grab a hostname from the DHCP pool
host = subprocess.check_output(command, shell=True)
# build a command string
command = "notify '[EP]' " + host.rstrip() + " " + ip
# use the Pineapple "notify" command
result = subprocess.check_output(command, shell=True)
# Append New Items to the Logged Items
fhl = open("/root/ep_notified.log", "a")
for line in detections:
fhl.write(line + "\n")
fhl.close()
Save this as epnotify.py
and have it run as part of the cron script. The script utilizes two files, the ep_detected.log
file generated from parsing the server log file and the ep_notified.log
file which keeps track of which items were already displayed on the Pineapple Dashboard. The two lists are compared to determine any new items. It quickly checks the IP addresses from the log against the Pineapple's /tmp/dhcp.leases
file to find hostnames. Then, the Pineapple's /usr/bin/pineapple/notify
utility is used to actually submit a text string to the Dashboard for each new access.
Pure Shell Scripting
EDIT: The python solution worked, but it imposed too high a processing cost even when run every five minutes when the Pineapple was under a load. The following substitution can be made for the final line that called python ep_notify.py
.
cat /tmp/ep_detected.log | while read line;
do
if grep -F "$line" /root/ep_notified.log; then
printf "[*] Found - $line\n"
else
printf "[!] Not Found - $line\n"
ip=`echo $line | awk '{print $2}'`
mac=`grep $ip /tmp/dhcp.leases | awk '{print $2}'`
host=`grep $ip /tmp/dhcp.leases | awk '{print $4}'`
/usr/bin/pineapple/notify "[EP] " $host " " $ip " " $mac
echo $line >> /root/ep_notified.log
fi
done
This modification will read the freshly created ep_detected.log
file line by line and then use grep to locate whether the entry already exists in the ep_notified.log
file. If its a new entry, the script will extract the IP address and use grep
and awk
commands to parse the Pineapple's DHCP leases for a hostname. The combination of hostname and IP address will be posted to the Pineapple's notification system. Lastly, the new entry, denoted by $line
will be appended to the notification log. This should significantly reduce the processing burden on the Pineapple.
Summary
- The
PineAP
module captures nearby devices. - The
EvilPortal
module providesiptables
traffic redirection to a locally hostedindex.php
page to render to targets. - The
DNSmasq
module redirects any devices that don't render the Captive Portal to the locally hosted webserver anyway. - The transparent pixel image allows detecting actual page renders.
- The
grep
andawk
utilities parse the web server logs for actual renderings. - The
Pythonshell script handles de-duplication and pushing notifications to the Pineapple Dashboard.
All of these little components together allow you to know whether a Captive Portal page is rendered even if the targeted user does not interact with it. This allows you to know when things like a "Beat Navy" message, or other messaging campaign content, are in fact displayed on their screens.