EvilPortal: Update to 3.0 (#29)

🎉 🎉
This commit is contained in:
Josh
2018-06-28 21:01:22 -04:00
committed by Sebastian Kinne
parent 03aeb6158c
commit 30826cc50e
20 changed files with 1894 additions and 865 deletions

File diff suppressed because it is too large Load Diff

188
EvilPortal/executable/executable Normal file → Executable file
View File

@@ -1,133 +1,99 @@
#!/usr/bin/python
#!/bin/bash
#
# Evil Portal
# Newbi3
# This is the python control script to handle all iptables things for creating and managing a Captive Portal
#
#Modified by oXis for the Wifi Pineapple (OpenWRT)
import sys
import os
# Written by Sitwon and The Doctor.
# Copyright (C) 2013 Project Byzantium
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.
arp () { cat /proc/net/arp; } # arp function
CLIENTS_FILE = "/tmp/EVILPORTAL_CLIENTS.txt"
WHITE_LIST = ["172.16.42.42"]
IPTABLES=/usr/sbin/iptables
ARP=arp
IP=172.16.42.1
case "$1" in
'init')
def revoke_client(ip_address=None):
global CLIENTS_FILE
# Convert the IP address of the client interface into a netblock.
CLIENTNET=`echo $IP | sed 's/1$/0\/24/'`
if not ip_address:
print "An ipaddress is expected."
return
# Exempt traffic which does not originate from the client network.
$IPTABLES -t mangle -I PREROUTING -p all ! -s $CLIENTNET -j RETURN
f = open(CLIENTS_FILE, 'r')
lines = f.readlines()
f.close()
# Traffic not coming from an accepted user gets marked 99.
$IPTABLES -t mangle -A fwmark -j MARK --set-mark 99
f = open(CLIENTS_FILE, 'w')
for line in lines:
if line == ip_address + "\n":
os.system("iptables -t nat -D PREROUTING -s " + line.rstrip('\n') + " -j ACCEPT")
else:
f.write(line)
f.close()
# Traffic which has been marked 99 and is headed for 80/TCP or 443/TCP
# should be redirected to the captive portal web server.
$IPTABLES -t nat -A prerouting_rule -m mark --mark 99 -p tcp --dport 80 -j DNAT --to-destination $IP:80
# Need to activate HTTPS on the nginx server of the PineAP, so for now HTTPS traffic is dropped.
#$IPTABLES -t nat -A prerouting_rule -m mark --mark 99 -p tcp --dport 443 -j DNAT --to-destination $IP:443
# for use with dns spoff
$IPTABLES -t filter -A forwarding_rule -p udp --dport 53 -j ACCEPT
$IPTABLES -t nat -A prerouting_rule -m mark --mark 99 -p udp --dport 53 -j DNAT --to-destination $IP:53
def authorize_client(ip_address=None):
global CLIENTS_FILE
$IPTABLES -t filter -A input_rule -p tcp --dport 80 -j ACCEPT #Webserver
#$IPTABLES -t filter -A input_rule -p tcp --dport 443 -j ACCEPT #Webserver
$IPTABLES -t filter -A input_rule -p tcp --dport 1471 -j ACCEPT #PineAP admin page
$IPTABLES -t filter -A input_rule -p tcp --dport 22 -j ACCEPT #SSH
if not ip_address:
print "An ipaddress is expected."
return
# All other traffic which is marked 99 is just dropped
$IPTABLES -t filter -A forwarding_rule -m mark --mark 99 -j DROP
# Even on INPUT rule
$IPTABLES -t filter -A input_rule -m mark --mark 99 -j DROP
f = open(CLIENTS_FILE, 'a+')
lines = f.readlines()
if ip_address + "\n" not in lines:
os.system("iptables -t nat -I PREROUTING -s " + ip_address + " -j ACCEPT")
f.write(ip_address + "\n")
f.close()
exit 0
;;
'add')
# $2: IP address of client.
CLIENT=$2
# Isolate the MAC address of the client in question.
CLIENTMAC=`$ARP -n | grep ':' | grep $CLIENT | awk '{print $4}'`
def stop_evilportal():
"""
stop_evilportal
Stop EvilPortals
"""
global CLIENTS_FILE, WHITE_LIST
# Add the MAC address of the client to the whitelist, so it'll be able
# to access the mesh even if its IP address changes.
$IPTABLES -t mangle -I fwmark -m mac --mac-source $CLIENTMAC -j RETURN
$IPTABLES -A INPUT -m mac --mac-source 74:da:38:5a:03:66 -p udp --dport 53 -j ACCEPT
if os.path.isfile(CLIENTS_FILE):
# Remove rule for each accepted client
[os.system("iptables -t nat -D PREROUTING -s " + line.rstrip('\n') + " -j ACCEPT") for line in open(CLIENTS_FILE, 'r')]
exit 0
;;
'remove')
# $2: IP address of client.
CLIENT=$2
# Delete the clients file
os.remove(CLIENTS_FILE)
# Isolate the MAC address of the client in question.
CLIENTMAC=`$ARP -n | grep ':' | grep $CLIENT | awk '{print $4}'`
# Stop HTTP Redirection
os.system("iptables -t nat -D PREROUTING -s 172.16.42.0/24 -p tcp --dport 80 -j DNAT --to-destination 172.16.42.1:80")
# Delete the MAC address of the client from the whitelist.
$IPTABLES -t mangle -D fwmark -m mac --mac-source $CLIENTMAC -j RETURN
# Remove DNS Policy
os.system("iptables -D INPUT -p tcp --dport 53 -j ACCEPT")
exit 0
;;
'purge')
CLIENTNET=`echo $IP | sed 's/1$/0\/24/'`
# Purge the user defined chains
$IPTABLES -t mangle -F fwmark
$IPTABLES -t nat -F prerouting_rule
$IPTABLES -t filter -F input_rule
$IPTABLES -t filter -F forwarding_rule
$IPTABLES -t mangle -D PREROUTING -p all ! -s $CLIENTNET -j RETURN
$IPTABLES -t nat -D prerouting_rule -m mark --mark 99 -p udp --dport 53 -j DNAT --to-destination $IP:53
def start_evilportal():
"""
start_evilportal
Start EvilPortals IP table based captive portal
"""
global CLIENTS_FILE, WHITE_LIST
exit 0
;;
'list')
# Display the currently running IP tables ruleset.
$IPTABLES --list -t nat -n
$IPTABLES --list -t mangle -n
$IPTABLES --list -t filter -n
if os.path.isfile(CLIENTS_FILE):
os.remove(CLIENTS_FILE)
# Make sure forwarding is enabled which it should be but just to be sure do it here
os.system("echo 1 > /proc/sys/net/ipv4/ip_forward")
# Setup the iptables
# Set white listed clients
f = open(CLIENTS_FILE, "w")
for client in WHITE_LIST:
os.system("iptables -A INPUT -s " + client + " -j ACCEPT")
f.write(client + "\n")
f.close()
# Redirect all web traffic to port 80 on the pineapple
os.system("iptables -t nat -A PREROUTING -s 172.16.42.0/24 -p tcp --dport 80 -j DNAT --to-destination 172.16.42.1:80")
# Accept dns
os.system("iptables -A INPUT -p tcp --dport 53 -j ACCEPT")
def handler(args=None):
cmd = None
param = None
if args is not None:
try:
cmd = args[1].lower()
except IndexError as e:
cmd = "help"
try:
param = args[2].lower()
except IndexError as e:
pass
else:
cmd = "start"
commands = {"start": "Start EvilPortal", "stop": "Stop EvilPortal", "help": "What you are reading"}
if cmd == "start":
start_evilportal()
elif cmd == "stop":
stop_evilportal()
elif cmd == "authorize":
authorize_client(param)
elif cmd == "help":
print "-"*20
print "Evil Portal"
print "-"*20
for command, description in commands.iteritems():
print command + ":\t" + description
if __name__ == '__main__':
handler(sys.argv)
exit 0
;;
*)
echo "USAGE: $0 {initialize|add <IP>|remove <IP>|purge|list}"
exit 0
esac

View File

@@ -7,6 +7,7 @@ abstract class Portal
protected $error;
protected $AUTHORIZED_CLIENTS_FILE = "/tmp/EVILPORTAL_CLIENTS.txt";
private $BASE_EP_COMMAND = 'module EvilPortal';
public function __construct($request)
{
@@ -24,44 +25,80 @@ abstract class Portal
}
}
/**
* Run a command in the background and don't wait for it to finish.
* @param $command: The command to run
*/
protected function execBackground($command)
{
exec("echo \"{$command}\" | at now");
}
/**
* Creates an iptables rule allowing the client to access the internet and writes them to the authorized clients.
* Override this method to add other authorization steps validation.
* @param $clientIP: The IP address of the client to authorize
* @return bool: True if the client was successfully authorized otherwise false.
*/
protected function authorizeClient($clientIP)
{
if (!$this->isClientAuthorized($clientIP)) {
exec("iptables -t nat -I PREROUTING -s {$clientIP} -j ACCEPT");
// exec("{$this->BASE_EP_COMMAND} add {$clientIP}");
file_put_contents($this->AUTHORIZED_CLIENTS_FILE, "{$clientIP}\n", FILE_APPEND);
$this->redirect();
} else {
return false;
}
return true;
}
/**
* Handle client authorization here.
* By default it just checks that the redirection target is in the request.
* Override this to perform your own validation.
*/
protected function handleAuthorization()
{
if (isset($this->request->target)) {
$this->authorizeClient($_SERVER['REMOTE_ADDR']);
$this->showSuccess();
$this->onSuccess();
$this->redirect();
} elseif ($this->isClientAuthorized($_SERVER['REMOTE_ADDR'])) {
$this->showSuccess();
$this->redirect();
} else {
$this->showError();
}
}
/**
* Where to redirect to on successful authorization.
*/
protected function redirect()
{
header("Location: {$this->request->target}", true, 302);
}
protected function showSuccess()
/**
* Override this to do something when the client is successfully authorized.
* By default it just notifies the Web UI.
*/
protected function onSuccess()
{
echo "You have been authorized successfully.";
$this->execBackground("notify New client authorized through EvilPortal!");
}
/**
* If an error occurs then do something here.
* Override to provide your own functionality.
*/
protected function showError()
{
echo "You have not been authorized.";
}
/**
* Checks if the client has been authorized.
* @param $clientIP: The IP of the client to check.
* @return bool|int: True if the client is authorized else false.
*/
protected function isClientAuthorized($clientIP)
{
$authorizeClients = file_get_contents($this->AUTHORIZED_CLIENTS_FILE);

View File

@@ -0,0 +1,4 @@
#!/bin/bash
# Commands in this file are ran when a portal is de-activated.
# You can use any interpreter you want to, the default is bash.

View File

@@ -0,0 +1,4 @@
#!/bin/bash
# Commands in this file are ran when a portal is activated and when Evil Portal startsup on boot.
# You can use any interpreter you want to, the default is bash.

View File

@@ -5,18 +5,27 @@ class MyPortal extends Portal
public function handleAuthorization()
{
// handle form input or other extra things there
// Call parent to handle basic authorization first
parent::handleAuthorization();
// Check for other form data here
}
public function showSuccess()
/**
* Override this to do something when the client is successfully authorized.
* By default it just notifies the Web UI.
*/
public function onSuccess()
{
// Calls default success message
parent::showSuccess();
parent::onSuccess();
}
/**
* If an error occurs then do something here.
* Override to provide your own functionality.
*/
public function showError()
{
// Calls default error message

View File

@@ -0,0 +1,45 @@
<?php
/**
* getClientMac
* Gets the mac address of a client by the IP address
* Returns the mac address as a string
* @param $clientIP : The clients IP address
* @return string
*/
function getClientMac($clientIP)
{
return trim(exec("grep " . escapeshellarg($clientIP) . " /tmp/dhcp.leases | awk '{print $2}'"));
}
/**
* getClientSSID
* Gets the SSID a client is associated by the IP address
* Returns the SSID as a string
* @param $clientIP : The clients IP address
* @return string
*/
function getClientSSID($clientIP)
{
// Get the clients mac address. We need this to get the SSID
$mac = getClientMac($clientIP);
// get the path to the log file
$pineAPLogPath = trim(file_get_contents('/etc/pineapple/pineap_log_location'));
// get the ssid
return trim(exec("grep " . $mac . " " . $pineAPLogPath . "pineap.log | grep 'Association' | awk -F ',' '{print $4}'"));
}
/**
* getClientHostName
* Gets the host name of the connected client by the IP address
* Returns the host name as a string
* @param $clientIP : The clients IP address
* @return string
*/
function getClientHostName($clientIP)
{
return trim(exec("grep " . escapeshellarg($clientIP) . " /tmp/dhcp.leases | awk '{print $4}'"));
}

View File

@@ -1,27 +1,33 @@
<?php
$destination = "http://". $_SERVER['HTTP_HOST'] . $_SERVER['HTTP_URI'] . "";
$destination = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['HTTP_URI'] . "";
require_once('helper.php');
?>
<HTML>
<HEAD>
<title>Evil Portal</title>
<script type="text/javascript">
function redirect() { setTimeout(function(){window.location = "/captiveportal/index.php";},100);}
</script>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1">
</HEAD>
<BODY>
<center>
<div style="text-align: center;">
<h1>Evil Portal</h1>
<p>This is the default Evil Portal page</p>
<p>This is the default Evil Portal page.</p>
<p>The SSID you are connected to is <?=getClientSSID($_SERVER['REMOTE_ADDR']);?></p>
<p>Your host name is <?=getClientHostName($_SERVER['REMOTE_ADDR']);?></p>
<p>Your MAC Address is <?=getClientMac($_SERVER['REMOTE_ADDR']);?></p>
<p>Your internal IP address is <?=$_SERVER['REMOTE_ADDR'];?></p>
<form method="POST" action="/captiveportal/index.php" onsubmit="redirect()">
<form method="POST" action="/captiveportal/index.php">
<input type="hidden" name="target" value="<?=$destination?>">
<button type="submit">Authorize</button>
</form>
</center>
</div>
</BODY>
</HTML>
</HTML>

View File

@@ -0,0 +1,4 @@
{
"name": null,
"type": "basic"
}

View File

@@ -0,0 +1,4 @@
#!/bin/bash
# Commands in this file are ran when a portal is de-activated.
# You can use any interpreter you want to, the default is bash.

View File

@@ -0,0 +1,4 @@
#!/bin/bash
# Commands in this file are ran when a portal is activated and when Evil Portal startsup on boot.
# You can use any interpreter you want to, the default is bash.

View File

@@ -0,0 +1,33 @@
<?php namespace evilportal;
class MyPortal extends Portal
{
public function handleAuthorization()
{
// handle form input or other extra things there
// Call parent to handle basic authorization first
parent::handleAuthorization();
}
/**
* Override this to do something when the client is successfully authorized.
* By default it just notifies the Web UI.
*/
public function onSuccess()
{
// Calls default success message
parent::onSuccess();
}
/**
* If an error occurs then do something here.
* Override to provide your own functionality.
*/
public function showError()
{
// Calls default error message
parent::showError();
}
}

View File

@@ -0,0 +1,35 @@
<?php
$destination = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['HTTP_URI'] . "";
require_once('helper.php');
?>
<HTML>
<HEAD>
<title>Evil Portal</title>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<script type="text/javascript">
function redirect() { setTimeout(function(){window.location = "/captiveportal/index.php";},100);}
</script>
</HEAD>
<BODY>
<div style="text-align: center;">
<h1>Evil Portal</h1>
<p>This is the default Evil Portal page.</p>
<p>The SSID you are connected to is <?=getClientSSID($_SERVER['REMOTE_ADDR']);?></p>
<p>Your host name is <?=getClientHostName($_SERVER['REMOTE_ADDR']);?></p>
<p>Your MAC Address is <?=getClientMac($_SERVER['REMOTE_ADDR']);?></p>
<p>Your internal IP address is <?=$_SERVER['REMOTE_ADDR'];?></p>
<form method="POST" action="/captiveportal/index.php" onsubmit="redirect()">
<input type="hidden" name="target" value="<?=$destination?>">
<button type="submit">Authorize</button>
</form>
</div>
</BODY>
</HTML>

View File

@@ -0,0 +1,45 @@
<?php
/**
* getClientMac
* Gets the mac address of a client by the IP address
* Returns the mac address as a string
* @param $clientIP : The clients IP address
* @return string
*/
function getClientMac($clientIP)
{
return trim(exec("grep " . escapeshellarg($clientIP) . " /tmp/dhcp.leases | awk '{print $2}'"));
}
/**
* getClientSSID
* Gets the SSID a client is associated by the IP address
* Returns the SSID as a string
* @param $clientIP : The clients IP address
* @return string
*/
function getClientSSID($clientIP)
{
// Get the clients mac address. We need this to get the SSID
$mac = getClientMac($clientIP);
// get the path to the log file
$pineAPLogPath = trim(file_get_contents('/etc/pineapple/pineap_log_location'));
// get the ssid
return trim(exec("grep " . $mac . " " . $pineAPLogPath . "pineap.log | grep 'Association' | awk -F ',' '{print $4}'"));
}
/**
* getClientHostName
* Gets the host name of the connected client by the IP address
* Returns the host name as a string
* @param $clientIP : The clients IP address
* @return string
*/
function getClientHostName($clientIP)
{
return trim(exec("grep " . escapeshellarg($clientIP) . " /tmp/dhcp.leases | awk '{print $4}'"));
}

View File

@@ -0,0 +1,81 @@
<?php
require_once('helper.php');
/**
*
* DO NOT MODIFY THIS FILE
* I highly recommend against modifying this file unless you know what you are doing!
*
* This file handles determining the destination file a client should see based on the conditions set in the json.
*
*/
// The value for this variable needs to be set when an new instance of a portal is created
// EvilPortal does this automatically when a targeted portal is created by running:
// sed -i 's/"portal_name_here"/"{portalName}"/g' index.php
$PORTAL_NAME = "portal_name_here";
// Get the information about the client and map
// it to the rule key specified in the {$PORTAL_NAME}.ep file
$MAPPED_RULES = [
"mac" => getClientMac($_SERVER['REMOTE_ADDR']),
"ssid" => getClientSSID($_SERVER['REMOTE_ADDR']),
"hostname" => getClientHostName($_SERVER['REMOTE_ADDR']),
"useragent" => $_SERVER['HTTP_USER_AGENT']
];
// Read the json
$jsonData = json_decode(file_get_contents("{$PORTAL_NAME}.ep"), true);
$routeData = $jsonData['targeted_rules'];
// This variable represents the page to include
$includePage = null;
// Check rules to find the page
foreach ($routeData['rule_order'] as $key) {
$includePage = handle_rule($routeData['rules'][$key], $MAPPED_RULES[$key]);
if ($includePage != null) {
include $includePage;
break;
}
}
// We have to display something.
// If the includePage variable is still null after checking the rules
// then include the default page.
if ($includePage == null) {
include $routeData['default'];
}
/**
* Checks if a given rule matches a given value
* @param $rules: The rules to check the client data against
* @param $client_data: The data to check if the rules match
* @return string: If a rule matches it returns the page to include, null otherwise
*/
function handle_rule($rules, $client_data) {
$return_value = null;
foreach ($rules as $key => $val) {
switch($key) {
case "exact": // exact matches
if (isset($val[$client_data])) {
$return_value = $val[$client_data];
break 2; // break out of the loop
}
break 1;
case "regex": // regex matches
foreach($val as $expression => $destination) {
if (preg_match($expression, $client_data)) {
$return_value = $destination;
break 1; // match was found. Exit this loop
}
if ($return_value != null)
break 2; // break out of the main loop
}
break 1;
}
}
return $return_value;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,34 @@
{
"name": null,
"type": "targeted",
"targeted_rules": {
"default": "default.php",
"rule_order": ["mac", "ssid", "hostname", "useragent"],
"rules": {
"mac": {
"exact": {
},
"regex": {
}
},
"ssid": {
"exact": {
},
"regex": {
}
},
"hostname": {
"exact": {
},
"regex": {
}
},
"useragent": {
"exact": {
},
"regex": {
}
}
}
}
}

View File

@@ -1,322 +1,671 @@
registerController("EvilPortalController", ['$api', '$scope', function ($api, $scope) {
getControls();
getPortals();
$scope.portals = [];
$scope.portalToDelete = null;
$scope.portalDeleteValidation = '';
$scope.messages = [];
$scope.newPortalName = '';
$scope.throbber = true;
$scope.running = false;
$scope.library = true;
$scope.whiteList = '';
$scope.whiteListInput = '';
$scope.accessList = '';
$scope.accessListInput = '';
$scope.workshopPortal = {name: "", files: [], storage: "internal"};
$scope.editPortalFile = {portalName: "", storage: "", file: "", code: ""};
$scope.handleControl = function (control) {
control.throbber = true;
switch (control.title) {
case "CaptivePortal":
$api.request({
module: "EvilPortal",
action: "startStop"
}, function (response) {
getControls();
control.throbber = false;
if (!response.control_success) {
$scope.sendMessage(control.title, response.control_message);
}
$scope.refreshLivePreview()
});
break;
case "Auto Start":
$api.request({
module: "EvilPortal",
action: "enableDisable"
}, function (response) {
getControls();
control.throbber = false;
if (!response.control_success) {
$scope.sendMessage(control.title, response.control_message);
}
});
break;
}
// status information about the module
$scope.evilPortal = {
"throbber": false,
"sdAvailable": false,
"running": false,
"startOnBoot": false,
"library": true
};
// controls that belong in the Controls pane
$scope.controls = [
{ "title": "Captive Portal", "visible": true, "throbber": false, "status": "Start"},
{"title": "Start On Boot", "visible": true, "throbber": false, "status": "Enable"}
];
// messages to be displayed in the Messages pane
$scope.messages = [];
$scope.whiteList = {"clients": "", "toManipulate": null};
$scope.accessList = {"clients": "", "toManipulate": null};
// all of the portals that could be found
$scope.portals = [];
// a model of a new portal to create
$scope.newPortal = {"type": "basic", "name": ""};
// deleting portal stuff
$scope.portalToDelete = null;
$scope.portalDeleteValidation = null;
// the portal workshop
$scope.workshop = {"portal": {}, "dirContents": null, "inRoot": true, "rootDirectory": null, "editFile": {"path": null, "isNewFile": true},
"onEnable": null, "onDisable": null, "concreteTargetedRules": null, "workingTargetedRules": null, "deleteFile": null
};
/**
* Reset the workshop object to a blank slate with initial values.
*/
$scope.resetWorkshop = function () {
$scope.workshop = {"portal": {}, "dirContents": null, "inRoot": true, "rootDirectory": null, "editFile": {"path": null, "isNewFile": true},
"onEnable": null, "onDisable": null, "concreteTargetedRules": null, "workingTargetedRules": null, "deleteFile": null
};
};
/**
* Push a message to the Evil Portal Messages Pane
* @param t: The Title of the message
* @param m: The message body
*/
$scope.sendMessage = function (t, m) {
// Add a new message to the top of the list
$scope.messages.unshift({title: t, msg: m});
// if there are 4 items in the list remove the 4th item
if ($scope.messages.length == 4) {
if ($scope.messages.length === 4) {
$scope.dismissMessage(3);
}
};
/**
* Remove a message from the Evil Portal Messages pane
* @param $index: The index of the message in the list to remove
*/
$scope.dismissMessage = function ($index) {
//var index = $scope.messages.indexOf(message);
$scope.messages.splice($index, 1);
};
function getControls() {
$scope.throbber = true;
$api.request({
module: "EvilPortal",
action: "getControlValues"
}, function (response) {
updateControls(response);
});
}
/**
* Preform an action for a given control
* This can be starting the captive portal or toggle on boot.
* @param control: The control to handle
*/
$scope.handleControl = function(control) {
control.throbber = true;
var actionToPreform = null;
switch(control.title) {
case "Captive Portal":
actionToPreform = "toggleCaptivePortal";
break;
function updateControls(response) {
var running;
var autostart;
if (response.running == false) {
running = "Start";
$scope.running = false;
} else {
running = "Stop";
$scope.running = true;
case "Start On Boot":
actionToPreform = "toggleOnBoot";
break;
}
if (response.autostart == false) {
autostart = "Enable";
} else {
autostart = "Disable";
}
$scope.controls = [
{
title: "CaptivePortal",
status: running,
visible: true,
throbber: false
},
{
title: "Auto Start",
status: autostart,
visible: true,
throbber: false
}];
$scope.throbber = false;
}
$scope.createNewPortal = function () {
if (actionToPreform !== null) {
$api.request({
module: "EvilPortal",
action: actionToPreform
}, function(response) {
if (!response.success) {
$scope.sendMessage(control.title, response.message);
}
getStatus();
});
}
};
/**
* Validates the information in the newPortal model and then makes an API request to create a new portal.
* @param storage: The storage medium to create the portal on (internal or sd)
*/
$scope.createNewPortal = function(storage) {
$api.request({
module: "EvilPortal",
action: "createNewPortal",
portalName: $scope.newPortalName
}, function (response) {
if (response.create_success) {
getPortals();
$scope.newPortalName = '';
} else {
$scope.sendMessage("Error Creating Portal", response.create_message);
name: $scope.newPortal.name,
type: $scope.newPortal.type,
storage: storage
}, function(response) {
if (!response.success) {
$scope.sendMessage('Error Creating Portal', response.message);
return;
}
});
};
$scope.deletePortalRequest = function(portal) {
$scope.portalToDelete = portal;
console.log(portal);
};
$scope.deletePortal = function (portal) {
console.log(portal.storage);
console.log(portal.title);
$scope.portalToDelete = null;
$scope.portalDeleteValidation = null
$api.request({
module: "EvilPortal",
action: "deletePortal",
storage: portal.storage,
name: portal.title
}, function (response) {
$scope.sendMessage("Delete Portal", response.message);
$scope.newPortal = {"type": "basic", "name": ""};
getPortals();
});
};
$scope.activatePortal = function (portal) {
/**
* Move a given portal between storage mediums if an SD card is present.
* @param portal: The portal to move
*/
$scope.movePortal = function(portal) {
if (!$scope.evilPortal.sdAvailable) {
$scope.sendMessage("No SD Card.", "An SD card must be present to preform this action.");
return;
}
$api.request({
module: "EvilPortal",
action: "movePortal",
name: portal.title,
storage: portal.storage
}, function(response) {
if (response.success) {
getPortals();
$scope.sendMessage("Moved Portal", response.message);
} else {
$scope.sendMessage("Error Moving " + portal.title, response.message);
}
});
};
/**
* Delete a portal from the wifi pineapple
* @param verified: Has the delete request been verified? If so then make the API request otherwise setup
* @param portal: The portal to delete
*/
$scope.deletePortal = function(verified, portal) {
if (!verified) { // if the request has not been verified then setup the shits
$scope.portalToDelete = portal;
return;
}
if ($scope.portalToDelete === null || $scope.portalToDelete.fullPath === null) {
$scope.sendMessage("Unable To Delete Portal", "No portal was set for deletion.");
return;
}
deleteFileOrDirectory($scope.portalToDelete.fullPath, function (response) {
if (!response.success) {
$scope.sendMessage("Error Deleting Portal", response.message); // push an error if deletion failed
} else {
$scope.sendMessage("Deleted Portal", "Successfully deleted " + $scope.portalToDelete.title + ".");
$scope.portalToDelete = null;
$scope.portalDeleteValidation = null;
getPortals(); // refresh the library
}
});
};
/**
* Activate a portal
* @param portal: The portal to activate
*/
$scope.activatePortal = function(portal) {
$api.request({
module: "EvilPortal",
action: "activatePortal",
storage: portal.storage,
name: portal.title
}, function (response) {
//$scope.sendMessage("Activate Portal", response.message);
getPortals();
name: portal.title,
storage: portal.storage
}, function(response) {
console.log(response);
if (response.success) {
getPortals();
$scope.sendMessage("Activated Portal", portal.title + " has been activated successfully.");
} else {
$scope.sendMessage("Error Activating " + portal.title, response.message);
}
});
};
$scope.deactivatePortal = function (portal) {
/**
* Deactivate a given portal if its active
* @param portal: The portal to deactivate
*/
$scope.deactivatePortal = function(portal) {
$api.request({
module: "EvilPortal",
action: "deactivatePortal",
storage: portal.storage,
name: portal.title
}, function (response) {
//$scope.sendMessage("Deactivate Portal", response.message);
getPortals();
});
};
$scope.editPortal = function (portal, file) {
$api.request({
module: "EvilPortal",
action: "getPortalCode",
storage: portal.storage,
name: portal.name,
portalFile: file
}, function (response) {
//$scope.sendMessage("Edit Portal", response.message);
$scope.editPortalFile.code = response.code;
$scope.editPortalFile.file = file;
$scope.editPortalFile.portalName = portal.name;
$scope.editPortalFile.storage = portal.storage;
});
};
$scope.savePortalCode = function (editFile) {
$api.request({
module: "EvilPortal",
action: "submitPortalCode",
storage: editFile.storage,
portalCode: editFile.code,
name: editFile.portalName,
fileName: editFile.file
}, function (response) {
$scope.sendMessage("Edit File", response.message);
});
};
$scope.getPortalFiles = function (portal) {
$api.request({
module: "EvilPortal",
action: "portalFiles",
storage: portal.storage,
name: portal.title
}, function (response) {
$scope.workshopPortal.name = portal.title;
$scope.workshopPortal.storage = portal.storage;
$scope.workshopPortal.files = response.portalFiles;
$scope.library = false;
});
};
function getPortals() {
$api.request({
module: "EvilPortal",
action: "portalList"
}, function (response) {
$scope.portals = [];
for (var i = 0; i < response.length; i++) {
$scope.portals.unshift({
title: response[i].title,
storage: response[i].location,
active: response[i].active
});
//console.log({title: response[i].title, storage: response[i].location, active: response[i].active});
name: portal.title,
storage: portal.storage
}, function(response) {
console.log(response);
if (response.success) {
getPortals();
$scope.sendMessage("Deactivated Portal", portal.title + " has been deactivated successfully.");
} else {
$scope.sendMessage("Error Deactivating " + portal.title, response.message);
}
});
}
$scope.refreshLivePreview = function () {
window.frames['livePreviewIframe'].src = "http://172.16.42.1";
};
$scope.getList = function (listToGet) {
/**
* Load portal contents and open it up in the work bench
* @param portal: The portal to get the contents of
*/
$scope.loadPortal = function (portal) {
getFileOrDirectoryContent(portal.fullPath, function(response) {
if (!response.success) {
$scope.sendMessage("Error Getting Contents", response.message);
return;
}
$scope.workshop.inRoot = true;
$scope.workshop.portal = portal;
$scope.workshop.dirContents = response.content;
$scope.workshop.rootDirectory = portal.fullPath;
$scope.evilPortal.library = false;
console.log(response.content);
});
};
/**
* Load toggle commands for the current portal in the work bench.
* These are the commands that are executed when a portal is enabled/disabled.
*/
$scope.loadToggleCommands = function() {
[".enable", ".disable"].forEach(getScript);
function getScript(scriptName) {
getFileOrDirectoryContent($scope.workshop.rootDirectory + "/" + scriptName, function (response) {
if (!response.success) {
$scope.sendMessage("Error Getting Contents", response.message);
return;
}
if (scriptName === ".enable")
$scope.workshop.onEnable = response.content.fileContent;
else
$scope.workshop.onDisable = response.content.fileContent;
});
}
};
/**
* Save toggle commands.
* @param cmdFile: The commands to save (enable, disable)
*/
$scope.saveToggleCommands = function(cmdFile) {
function sendData(f, content) {
writeToFile($scope.workshop.rootDirectory + "/" + f, content, false, function (response) {
if (!response.success)
$scope.sendMessage("Error write to file " + f, response.message);
});
}
switch(cmdFile) {
case "disable":
sendData(".disable", $scope.workshop.onDisable);
break;
case "enable":
sendData(".enable", $scope.workshop.onEnable);
break;
default:
sendData(".enable", $scope.workshop.onEnable);
sendData(".disable", $scope.workshop.onDisable);
break;
}
};
/**
* Load the rules for targeted portals
*/
$scope.loadTargetedRules = function() {
$api.request({
module: "EvilPortal",
action: "getList",
listName: listToGet
}, function (response) {
if (response.list_success) {
if (listToGet == "whiteList") {
$scope.whiteList = response.list_contents;
} else if (listToGet == "accessList") {
$scope.accessList = response.list_contents;
action: "getRules",
name: $scope.workshop.portal.title,
storage: $scope.workshop.portal.storage
}, function(response) {
if (response.success) {
$scope.workshop.concreteTargetedRules = response.data;
$scope.workshop.workingTargetedRules = {"rules": {}};
// welcome to the realm of loops. I will be your guide
// We have to turn each rule into a keyed set of rules with a rule index represented by var index
// this is because we need a constant key for each rule when editing on the web interface
// the index must be removed later before saving the results to the routes.json file
// if you have a better way to do this you are my hero. Email me n3rdcav3@gmail.com or fork the repo :)
// This first loop loops over each rule categories such as "mac", "ssid" and so on
for (var key in response.data['rules']) {
// we then create the a object with that key name in our workingData object
$scope.workshop.workingTargetedRules['rules'][key] = {};
// Now its time to loop over each category specifier such as "exact" and "regex"
for (var specifier in response.data['rules'][key]) {
var index = 0;
// We then create that specifier in our workingData
$scope.workshop.workingTargetedRules['rules'][key][specifier] = {};
// finally we loop over the specific rules defined in the specifier
for (var r in response.data['rules'][key][specifier]) {
var obj = {};
obj['key'] = r;
obj['destination'] = response.data['rules'][key][specifier][r];
$scope.workshop.workingTargetedRules['rules'][key][specifier][index] = obj;
}
// increment index
index++;
}
}
} else {
$scope.sendMessage("List Data Error", response.list_message);
console.log(response);
$scope.sendMessage("Error", "There was an issue getting the portal rules.");
}
});
};
$scope.addWhiteListClient = function () {
/**
* Remove a targeted rule
* @param rule
* @param specifier
* @param index
*/
$scope.removeTargetedRule = function(rule, specifier, index) {
delete $scope.workshop.workingTargetedRules['rules'][rule][specifier][index];
};
/**
* Create a new targeted rule
* @param rule
* @param specifier
*/
$scope.newTargetedRule = function(rule, specifier) {
// make sure the specifier is set
if ($scope.workshop.workingTargetedRules['rules'][rule][specifier] == undefined) {
$scope.workshop.workingTargetedRules['rules'][rule][specifier] = {};
}
var highest = 0;
// get the highest index
for (var i in $scope.workshop.workingTargetedRules['rules'][rule][specifier]) {
if (parseInt(i) >= highest) {
highest = i + 1;
}
}
$scope.workshop.workingTargetedRules['rules'][rule][specifier][highest] = {"": ""};
};
/**
* Build the targeted rules and sned them to the API for saving
*/
$scope.saveTargetedRules = function() {
// build the rules
for (var key in $scope.workshop.concreteTargetedRules.rules) {
for (var specifier in $scope.workshop.concreteTargetedRules.rules[key]) {
var obj = {};
for (var i in $scope.workshop.workingTargetedRules['rules'][key][specifier]) {
obj[$scope.workshop.workingTargetedRules['rules'][key][specifier][i]['key']] = $scope.workshop.workingTargetedRules['rules'][key][specifier][i]['destination'];
}
$scope.workshop.concreteTargetedRules['rules'][key][specifier] = obj;
}
}
console.log(JSON.stringify($scope.workshop.concreteTargetedRules));
$api.request({
module: "EvilPortal",
action: "addToList",
listName: "whiteList",
clientIP: $scope.whiteListInput
}, function (response) {
if (response.add_success) {
$scope.whiteListInput = '';
$scope.getList("whiteList");
} else {
$scope.sendMessage("White List", response.add_message);
console.log(response);
action: "saveRules",
name: $scope.workshop.portal.title,
storage: $scope.workshop.portal.storage,
rules: JSON.stringify($scope.workshop.concreteTargetedRules)
}, function(response) {
if (!response.success) {
$scope.sendMessage("Error", response.message);
}
});
};
$scope.removeWhiteListClient = function () {
/**
* check if a given object is empty.
* @param obj: The object to check
* @returns {boolean}: true if empty false if not empty
*/
$scope.isObjectEmpty = function(obj) {
return (Object.keys(obj).length === 0);
};
/**
* Load the contents of a given file.
* @param filePath: The path to the file to load
*/
$scope.loadFileContent = function(filePath) {
getFileOrDirectoryContent(filePath, function(response) {
if (!response.success) {
$scope.sendMessage("Error Getting Contents", response.message);
return;
}
$scope.workshop.editFile = {
"name": response.content.name,
"path": response.content.path,
"size": response.content.size,
"content": response.content.fileContent
};
});
};
/**
* Setup the workshop to create a new empty file.
*/
$scope.setupNewFile = function() {
var basePath = ($scope.workshop.portal.storage === "sd") ? "/sd/portals/" : "/root/portals/";
$scope.workshop.editFile.path = basePath + $scope.workshop.portal.title + "/";
$scope.workshop.editFile.isNewFile = true;
};
/**
* Write file content to the file system.
* @param editFile: A portal.editFile object
*/
$scope.saveFileContent = function(editFile) {
// new files wont have the filename in the path so make sure to set it here if needed.
if (!editFile.path.includes(editFile.name))
editFile.path = editFile.path + editFile.name;
console.log(editFile.path);
writeToFile(editFile.path, editFile.content, false, function(response) {
if (!response.success)
$scope.sendMessage("Error write to file " + editFile.name, response.message);
$scope.loadPortal($scope.workshop.portal); // refresh the portal
});
};
/**
* Delete a requested file.
*/
$scope.deleteFile = function() {
deleteFileOrDirectory($scope.workshop.deleteFile.path, function(response){
if (!response.success)
$scope.sendMessage("Error deleting file " + $scope.workshop.deleteFile.name, response.message);
$scope.loadPortal($scope.workshop.portal); // refresh the portal
});
};
/**
* Load either the white list or the authorized clients (access) list
* @param listName: The name of the list: whiteList or accessList (authorized clients)
*/
$scope.getList = function (listName) {
var whiteList = '/pineapple/modules/EvilPortal/data/allowed.txt';
var authorized = '/tmp/EVILPORTAL_CLIENTS.txt';
getFileOrDirectoryContent((listName === "whiteList") ? whiteList : authorized, function (response) {
switch (listName) {
case 'whiteList':
$scope.whiteList.clients = response.content.fileContent;
break;
case 'accessList':
$scope.accessList.clients = response.content.fileContent;
break;
}
})
};
/**
* Remove a client from either the white list (whiteList) the authorized clients list (accessList)
* @param listName: whiteList or accessList
*/
$scope.removeClientFromList = function(listName) {
var clientToRemove = (listName === 'whiteList') ? $scope.whiteList.toManipulate : $scope.accessList.toManipulate;
console.log(clientToRemove);
$api.request({
module: "EvilPortal",
action: "removeFromList",
listName: "whiteList",
clientIP: $scope.whiteListInput
}, function (response) {
if (response.remove_success) {
$scope.whiteListInput = '';
$scope.getList("whiteList");
} else {
$scope.sendMessage("White List", response.remove_message);
console.log(response);
action: "removeClientFromList",
clientIP: clientToRemove,
listName: listName
}, function(response) {
if (!response.success) {
$scope.sendMessage("Error", response.message);
return;
}
$scope.getList(listName);
switch (listName) {
case 'whiteList':
$scope.whiteList.toManipulate = null;
break;
case 'accessList':
$scope.accessList.toManipulate = null;
break;
}
});
};
/**
* Add a new client to the white list
*/
$scope.addWhiteListClient = function() {
writeToFile('/pineapple/modules/EvilPortal/data/allowed.txt', $scope.whiteList.toManipulate + "\n", true, function(response) {
$scope.getList('whiteList');
});
$scope.whiteList.toManipulate = null;
};
/**
* Authorize a new client
*/
$scope.authorizeClient = function () {
$api.request({
module: "EvilPortal",
action: "addToList",
listName: "accessList",
clientIP: $scope.accessListInput
action: "authorizeClient",
clientIP: $scope.accessList.toManipulate
}, function (response) {
if (response.add_success) {
$scope.accessListInput = '';
$scope.getList("accessList");
} else {
$scope.sendMessage("Access List", response.add_message);
console.log(response);
}
$scope.getList('accessList');
$scope.accessList.toManipulate = null;
});
};
$scope.revokeClient = function () {
/**
* Get a line clicked in a text area and set that line as the text for a text input
* @param textareaId: The id of the text area to grab from
* @param inputname: The name of the input field to write to
*/
$scope.getClickedClient = function(textareaId, inputname) {
var textarea = $('#' + textareaId);
var lineNumber = textarea.val().substr(0, textarea[0].selectionStart).split('\n').length;
var ssid = textarea.val().split('\n')[lineNumber-1].trim();
$("input[name='" + inputname + "']").val(ssid).trigger('input');
};
/**
* Write given content to a given file on the file system.
* @param filePath: The path to the file to write content to
* @param fileContent: The content to write to the file
* @param appendFile: Should the content be append to the file (true) or overwrite the file (false)
* @param callback: A callback function to handle the API response
*/
function writeToFile(filePath, fileContent, appendFile, callback) {
$api.request({
module: "EvilPortal",
action: "removeFromList",
listName: "accessList",
clientIP: $scope.accessListInput
}, function (response) {
if (response.remove_success) {
$scope.accessListInput = '';
$scope.getList("accessList");
} else {
$scope.sendMessage("Access List", response.remove_message);
console.log(response);
}
action: "writeFileContent",
filePath: filePath,
content: fileContent,
append: appendFile
}, function(response) {
callback(response);
});
}
/**
* Get the contents of a directory
* @param pathToObject: The full path to the file or directory to get the contents of
* @param callback: A function that handles the response from the API.
*/
function getFileOrDirectoryContent(pathToObject, callback) {
$api.request({
module: "EvilPortal",
action: "getFileContent",
filePath: pathToObject
}, function(response) {
callback(response);
});
}
/**
* Delete a file or directory from the pineapples filesystem.
* This is intended to be used for only deleting portals and portal related files but anything can be delete.
* @param fileOrDirectory: The path to the file to delete
* @param callback: The callback function to handle the API response
*/
function deleteFileOrDirectory(fileOrDirectory, callback) {
$api.request({
module: "EvilPortal",
action: "deleteFile",
filePath: fileOrDirectory
}, function(response) {
callback(response);
});
}
/**
* Update the control models so they reflect the proper information
*/
function updateControls() {
$scope.controls = [
{
"title": "Captive Portal",
"status": ($scope.evilPortal.running) ? "Stop" : "Start",
"visible": true,
"throbber": false
},
{
"title": "Start On Boot",
"status": ($scope.evilPortal.startOnBoot) ? "Disable": "Enable",
"visible": true,
"throbber": false
}
];
}
/**
* Get the status's for the controls in the Controls pane and other various information
*/
function getStatus() {
$scope.evilPortal.throbber = true;
$api.request({
module: "EvilPortal",
action: "status"
}, function (response) {
for (var key in response) {
if (response.hasOwnProperty(key) && $scope.evilPortal.hasOwnProperty(key)) {
$scope.evilPortal[key] = response[key];
}
}
$scope.evilPortal.throbber = false;
updateControls();
});
}
/**
* Get all of the portals on the Pineapple
*/
function getPortals() {
$scope.evilPortal.throbber = true;
$api.request({
module: "EvilPortal",
action: "listAvailablePortals"
}, function(response) {
if (!response.success) {
$scope.sendMessage("Error Listing Portals", "An error occurred while trying to get list of portals.");
return;
}
$scope.portals = [];
response.portals.forEach(function(item, index) {
$scope.portals.unshift({
title: item.title,
storage: item.storage,
active: item.active,
type: item.portalType,
fullPath: item.location
});
});
});
}
// The status for the Evil Portal module as well as current portals should be retrieved when the controller loads.
getStatus();
getPortals();
}]);

View File

@@ -1,6 +1,7 @@
<div class="row" ng-controller="EvilPortalController">
<div class="col-md-3">
<!-- Controls Panel -->
<div class="panel panel-default">
<div class="panel-heading">
<a href="javascript:;" data-toggle="collapse" data-target="#collapseControls" class="text-muted"><h4
@@ -24,6 +25,8 @@
</div>
</div>
</div>
<!-- Messages Panel -->
<div class="panel panel-default">
<div class="panel-heading">
<a href="javascript:;" data-toggle="collapse" data-target="#collapseMessages" class="text-muted"><h4
@@ -37,10 +40,11 @@
<table style="width:100%" ng-hide="(messages.length == 0)">
<tr ng-repeat="message in messages">
<td>
<hr/>
<h5><b>{{ message.title }}</b> <a ng-click="dismissMessage($index)" href="javascript:;"
class="pull-right">Dismiss</a></h5>
<fieldset>
<legend><h5><b>{{ message.title }}</b> <a ng-click="dismissMessage($index)" href="javascript:;"
class="pull-right">Dismiss</a></h5></legend>
<p class="text-muted"><i>{{ message.msg }}</i></p>
</fieldset>
</td>
</tr>
</table>
@@ -49,49 +53,65 @@
</div>
</div>
<!--<div ng-show="dependencies">-->
<div class="panel-group" id="accordion">
<div class="col-md-9">
<!-- Library panel -->
<div class="panel panel-default">
<div class="panel-heading">
<h5 class="panel-title"><a href="javascript:;" data-toggle="collapse" data-parent="#accordion"
data-target="#collapseLibrary" class="text-muted">Work Bench</a></h5>
data-target="#collapseLibrary" class="text-muted">Work Bench <b style="text-transform: capitalize"><i>{{ workshop.portal.title }}</i></b></a></h5>
</div>
<div id="collapseLibrary" class="panel-collapse collapse in">
<div class="panel-body">
<div class="input-group" ng-show="library">
<input type="text" class="form-control" placeholder="PortalName" name="portalName"
ng-model="newPortalName">
<span class="input-group-btn">
<button ng-disabled="newPortalName == ''" class="btn btn-default" type="button" ng-click="createNewPortal()">Create New Portal</button>
</span>
<div class="input-group" ng-show="evilPortal.library">
<span class="input-group-btn">
<select class="form-control ng-pristine ng-valid ng-touched" style="width:80px" ng-model="newPortal.type">
<option value="basic">Basic</option>
<option value="targeted">Targeted</option>
</select>
</span>
<input type="text" class="form-control" placeholder="Portal Name" name="portalName" ng-model="newPortal.name">
<span class="input-group-btn">
<button ng-disabled="newPortal.name == ''" class="btn btn-default" type="button" ng-click="createNewPortal('internal')">Create New Portal</button>
<button ng-disabled="newPortal.name == ''" class="btn btn-default" type="button" ng-click="createNewPortal('sd')" ng-show="evilPortal.sdAvailable">Create On SD Card</button>
</span>
</div>
<hr ng-show="library"/>
<div ng-show="portals.length > 0 && library == true">
<hr ng-show="evilPortal.library"/>
<div ng-show="portals.length > 0 && evilPortal.library">
<div class="table-responsive">
<table class="table table-striped" align="center">
<thead>
<th>Portal Name</th>
<th>Portal Type</th>
<th>Location</th>
<th ng-show="evilPortal.sdAvailable">Move</th>
<th>Activate</th>
<th>Delete</th>
</thead>
<tbody>
<tr ng-repeat="portal in portals">
<td><a href="javascript:;" ng-click="getPortalFiles(portal)"><b>{{ portal.title
}}</b></a></td>
<td class="text-muted"><i>{{ portal.storage }}</i></td>
<td ng-hide="portal.active"><a href="javascript:;"
ng-click="activatePortal(portal)">Activate</a>
<!-- Open portal work shop -->
<td><a href="javascript:;" ng-click="loadPortal(portal)" style="text-transform: capitalize"><b>{{ portal.title }}</b></a></td>
<!-- Portal type -->
<td class="text-muted" style="text-transform: capitalize"><i>{{ portal.type }}</i></td>
<!-- What storage device the portal is on -->
<td class="text-muted" style="text-transform: capitalize"><i>{{ portal.storage }}</i></td>
<!-- change storage -->
<td ng-show="evilPortal.sdAvailable">
<a ng-show="(portal.storage == 'internal') && !portal.active" href="javascript:;" ng-click="movePortal(portal, 'sd');">Move to SD</a>
<a ng-show="(portal.storage == 'sd') && !portal.active" href="javascript:;" ng-click="movePortal(portal, 'internal');">Move to Internal</a>
</td>
<td ng-show="portal.active"><a href="javascript:;"
ng-click="deactivatePortal(portal)">Deactivate</a>
<!-- activate/deactivate -->
<td>
<a ng-hide="portal.active" href="javascript:;" ng-click="activatePortal(portal)">Activate</a>
<a ng-show="portal.active" href="javascript:;" ng-click="deactivatePortal(portal)">Deactivate</a>
</td>
<td ng-hide="portal.active"><a href="javascript:;" data-toggle="modal"
data-target="#deleteModal"
ng-click="deletePortalRequest(portal)"
ng-hide="portal.storage == 'active'">Delete</a>
<!-- delete portal button -->
<td>
<a ng-hide="portal.active" data-toggle="modal" data-target="#deleteModal" ng-click="deletePortal(false, portal)">Delete</a>
</td>
</tr>
</tbody>
@@ -99,28 +119,43 @@
</div>
</div>
<div ng-hide="portals.length > 0 || library == false">
<div ng-hide="portals.length > 0 || !evilPortal.library">
<p class="text-muted text-center"><i>No Portals in Library to Display.</i></p>
</div>
<div ng-show="library == false">
<button type="submit" class="btn btn-default btn-sm" ng-click="library = true">Back To
Library
</button>
<hr />
<h3>Contents of {{ workshopPortal.name }}</h3>
<!-- Portal Editor -->
<div ng-hide="evilPortal.library">
<!-- Editor Buttons -->
<div class="row-fluid">
<span class="pull-left">
<button type="submit" class="btn btn-default btn-sm" ng-click="evilPortal.library = true; resetWorkshop()">Back To Library</button>
</span>
<span class="pull-right">
<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#fileModal" ng-click="setupNewFile()">New File</button>
<!-- Holding off on toggle commands until a future release. Still need to think through how they should work. -->
<!--<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#commandEditorModal" ng-click="loadToggleCommands()">Toggle Commands</button>-->
<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#ruleEditorModal" ng-show="workshop.portal.type == 'targeted'" ng-click="loadTargetedRules();">Target Rule Editor</button>
<button type="submit" class="btn btn-default btn-sm" ng-click="loadPortal(workshop.portal)">Refresh</button>
</span>
</div>
<br/>
<hr />
<!-- Portal Files -->
<div class="table-responsive">
<table class="table table-striped" align="center">
<thead>
<th>File Name</th>
<th>Edit</th>
<th>Delete</th>
</thead>
<tbody>
<tr ng-repeat="file in workshopPortal.files">
<td>{{ file }}</td>
<td><a href="javascript:;" ng-click="editPortal(workshopPortal, file)"
data-toggle="modal" data-target="#fileModal">Edit</a></td>
<tr ng-repeat="file in workshop.dirContents">
<td>{{ file.name }}</td>
<td><a href="javascript:;" ng-click="loadFileContent(file.path)" data-toggle="modal" data-target="#fileModal">Edit</a></td>
<td><a href="javascript:;" ng-click="workshop.deleteFile = file" data-toggle="modal" data-target="#deleteFileModal" ng-show="file.deletable">Delete</a></td>
</tr>
</tbody>
</table>
@@ -144,18 +179,18 @@
<i>This is a list of clients who are allowed to connect to the internet without ever viewing the captive portal.</i>
</p>
<p>
<textarea class="form-control" rows="15" ng-model="whiteList" readonly></textarea>
<textarea id="whiteListPool" class="form-control" rows="15" ng-mouseup="getClickedClient('whiteListPool', 'whiteListInput');" ng-model="whiteList.clients" readonly></textarea>
</p>
<div class="input-group">
<input type="text" class="form-control" placeholder="IP Address" name="ipaddress"
ng-model="whiteListInput">
<input type="text" class="form-control" placeholder="IP Address" name="whiteListInput"
ng-model="whiteList.toManipulate">
<span class="input-group-btn">
<button class="btn btn-default" type="button"
ng-click="addWhiteListClient()">Add</button>
<button class="btn btn-default" type="button"
ng-click="removeWhiteListClient()">Remove</button>
<button class="btn btn-default" type="button" ng-click="addWhiteListClient()">Add</button>
<button class="btn btn-default" type="button" ng-click="removeClientFromList('whiteList')">Remove</button>
</span>
</div>
<br/>
<button class="btn btn-default btn-sm" type="button" ng-click="getList('whiteList')">Refresh</button>
</div>
</div>
</div>
@@ -169,22 +204,24 @@
</div>
<div id="collapseAuthorizedClients" class="panel-collapse collapse">
<div class="panel-body">
<p class="text-muted" ng-show="running">
<i>This is a list of clients who have been authorized through the captive portal.</i>
</p>
<p ng-show="running">
<textarea class="form-control" rows="15" ng-model="accessList" readonly></textarea>
</p>
<div class="input-group" ng-show="running">
<input type="text" class="form-control" placeholder="IP Address" name="ipaddress"
ng-model="accessListInput">
<span class="input-group-btn">
<button class="btn btn-default" type="button"
ng-click="authorizeClient()">Authorize</button>
<button class="btn btn-default" type="button" ng-click="revokeClient()">Revoke</button>
</span>
<div ng-show="evilPortal.running">
<p class="text-muted">
<i>This is a list of clients who have been authorized through the captive portal.</i>
</p>
<p>
<textarea id='authorizedPool' class="form-control" rows="15" ng-mouseup="getClickedClient('authorizedPool', 'accessListInput');" ng-model="accessList.clients" readonly></textarea>
</p>
<div class="input-group">
<input type="text" class="form-control" placeholder="IP Address" name="accessListInput" ng-model="accessList.toManipulate">
<span class="input-group-btn">
<button class="btn btn-default" type="button" ng-click="authorizeClient()">Authorize</button>
<button class="btn btn-default" type="button" ng-click="removeClientFromList('accessList')">Revoke</button>
</span>
</div>
<br />
<button class="btn btn-default btn-sm" type="button" ng-click="getList('accessList')">Refresh</button>
</div>
<div ng-hide="running">
<div ng-hide="evilPortal.running">
<p class="text-muted text-center"><i>Evil Portal must be started first.</i></p>
</div>
</div>
@@ -200,14 +237,12 @@
</div>
<div id="collapsePreview" class="panel-collapse collapse">
<div class="panel-body">
<div ng-show="running">
<div ng-show="evilPortal.running">
<iframe src="http://172.16.42.1" style="width:100%;height:300px;border:none;"
id="livePreviewIframe"></iframe>
<button type="submit" ng-click="refreshLivePreview()" class="btn btn-default btn-sm">
Refresh
</button>
<button type="submit" ng-click="refreshLivePreview()" class="btn btn-default btn-sm">Refresh</button>
</div>
<div ng-hide="running">
<div ng-hide="evilPortal.running">
<p class="text-muted text-center"><i>Evil Portal must be started first.</i></p>
</div>
</div>
@@ -218,35 +253,72 @@
<div class="panel panel-default">
<div class="panel-heading">
<h5 class="panel-title"><a href="javascript:;" data-toggle="collapse" data-parent="#accordion"
data-target="#collapseChangelog" class="text-muted">Evil Portal Change
Log</a></h5>
data-target="#collapseChangelog" class="text-muted">Evil Portal Info</a></h5>
</div>
<div id="collapseChangelog" class="panel-collapse collapse">
<div class="panel-body">
<ul>
<li><b>2.1</b></li>
<fieldset>
<legend>Disclaimer</legend>
<p><b>Evil Portal and all of its components are intended for professional use only. No one associated with this project is an anyway liable for your actions.</b></p>
</fieldset>
<fieldset>
<legend>Help</legend>
<h5>Summary</h5>
<p>Evil Portal is a captive portal program that enables you to easily create a captive portal for whatever your needs are. There are two kinds of portals Basic and Targeted.
Basic portals are just a simple page that gets served to all clients. Targeted portals allow you to serve a unique page to different clients based on a given rule.</p>
<h5>Basic Portals</h5>
<p>A Basic Portal is a one-size serves all kind of portal. These portals are designed to be a single page that all clients will land on. This is the traditional way
that captive portals work and the way Evil Portal as been doing things from the beginning.</p>
<h5>Targeted Portals</h5>
<p>A Targeted Portal allows you to serve a different page on a per-client basis based on a condition. This can be something like their mac address, user-agent, etc...
Targeted Portals give you a whole new dynamic to what Evil Portal can do, if you want clients who are connected to the SSID "Coffee Shop" to see a "Coffee Shop" branded portal
while at the same time clients with Android phones seeing an Android branded portal then this is what you want.</p>
<h5>Useful Links</h5>
<a href="https://forums.hak5.org/index.php?/topic/37874-official-evilportal/">Hak5 Forum Thread</a>
<br />
<a href="https://github.com/frozenjava/EvilPortalNano">GitHub Project</a>
</fieldset>
<fieldset>
<legend>Change Log</legend>
<ul>
<li class="text-muted">Removed un-needed verbosity</li>
<li class="text-muted">Made tab key indent in the editor instead of change elements</li>
<li class="text-muted">Added confirmation dialogue box when deleting a portal</li>
<li class="text-muted">Created auto-start feature</li>
<li class="text-muted">Various other quality of life updates</li>
<li><b>3.0</b></li>
<ul>
<li class="text-muted">Added ability to route clients to different portals based upon some identifier [ssid, mac vendor, ip, etc...]</li>
<li class="text-muted">Updated the work bench so users can choose between targeted and non-targeted portals</li>
<li class="text-muted">Created easy-to-use interface for creating targeting rules</li>
<li class="text-muted">Created some consistency throughout the UI</li>
<li class="text-muted">Added ability to create portals on an SD card and move between SD and Internal storage easily</li>
<li class="text-muted">Made white listed and authorized clients IP addresses clickable like SSIDs in PineAP</li>
<li class="text-muted">Created helper functions getClientMac, getClientSSID, getClientHostName</li>
<li class="text-muted">Sending notification when a client goes through a portal.</li>
<li class="text-muted">Various quality of life improvements</li>
</ul>
</ul>
</ul>
<ul>
<li><b>2.0</b></li>
<ul>
<li class="text-muted">Captive Portal is now purely iptables (because F***
NoDogSplash)
</li>
<li><b>2.1</b></li>
<ul>
<li class="text-muted">Removed un-needed verbosity</li>
<li class="text-muted">Made tab key indent in the editor instead of change elements</li>
<li class="text-muted">Added confirmation dialogue box when deleting a portal</li>
<li class="text-muted">Created auto-start feature</li>
<li class="text-muted">Various other quality of life updates</li>
</ul>
</ul>
</ul>
<ul>
<li><b>1.0</b></li>
<ul>
<li class="text-muted">Initial Pineapple Nano Release</li>
<li><b>2.0</b></li>
<ul>
<li class="text-muted">Captive Portal is now purely iptables (because F***
NoDogSplash)
</li>
</ul>
</ul>
</ul>
<ul>
<li><b>1.0</b></li>
<ul>
<li class="text-muted">Initial Pineapple Nano Release</li>
</ul>
</ul>
</fieldset>
</div>
</div>
</div>
@@ -254,6 +326,7 @@
</div>
<!--</div>-->
<!-- Edit file modal -->
<div id="fileModal" class="modal fade" role="dialog">
<script type="text/javascript">
@@ -263,7 +336,7 @@
if (keyCode == 9) {
e.preventDefault();
var start = $(this).get(0).selectionStart;
var start = $(this).get(0).selection;
var end = $(this).get(0).selectionEnd;
// set textarea value to: text before caret + tab + text after caret
@@ -284,30 +357,27 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
ng-click="editPortalFile = {}">&times;</button>
<h4 class="modal-title">File Editor {{ editPortalFile.file }}</h4>
ng-click="workshop.editFile = {}">&times;</button>
<h4 class="modal-title" ng-show="!workshop.editFile.isNewFile">Editing File {{ workshop.editFile.name }}</h4>
<h4 class="modal-title" ng-show="workshop.editFile.isNewFile">Creating New File</h4>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="form-group">
<label class="control-label">File Name</label>
<input type="text" class="form-control" ng-model="editPortalFile.file"
placeholder="Notes.txt" disabled>
<input type="text" class="form-control" ng-model="workshop.editFile.name" placeholder="Notes.txt" ng-disabled="!workshop.editFile.isNewFile">
</div>
<div class="form-group">
<label class="control-label">File Contents</label> <a href="javascript:;"
ng-click="editPortalFile.code = ''">Clear</a>
<textarea class="form-control" rows="10" ng-model="editPortalFile.code"
<label class="control-label">File Contents</label> <a href="javascript:;" ng-click="workshop.editFile.content = null">Clear</a>
<textarea class="form-control" rows="10" ng-model="workshop.editFile.content"
placeholder="Write some text here" id="evilportalEditor"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" ng-click="savePortalCode(editPortalFile)" class="btn btn-success pull-left"
data-dismiss="modal">Save
</button>
<button type="button" class="btn btn-default pull-right" data-dismiss="modal"
ng-click="editPortalFile = {}">Cancel
<button type="button" ng-click="saveFileContent(workshop.editFile);" class="btn btn-success pull-right" data-dismiss="modal" ng-disabled="workshop.editFile.content == null">Save</button>
<button type="button" class="btn btn-default pull-left" data-dismiss="modal"
ng-click="workshop.editFile = {}">Cancel
</button>
</div>
</div>
@@ -315,7 +385,7 @@
</div>
<!-- Delete file Modal -->
<!-- Delete Portal Modal -->
<div id="deleteModal" class="modal fade" role="dialog">
<div class="modal-dialog">
@@ -323,7 +393,7 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" ng-click="portalToDelete = null; portalDeleteValidation = null">&times;</button>
<h4 class="modal-title">Delete Portal {{ portalToDelete.title }}</h4>
<h4 class="modal-title">Delete Portal <i>{{ portalToDelete.title }}</i>?</h4>
</div>
<div class="modal-body">
<p>You are about to delete {{ portalToDelete.title }} on {{ portalToDelete.storage }} storage. Once you do this it can not be undone.</p>
@@ -331,8 +401,121 @@
<input type="text" class="form-control" placeholder="PortalName" name="portalName" ng-model="portalDeleteValidation">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary pull-left" data-dismiss="modal" ng-click="portalToDelete = null; portalDeleteValidation = null">Cancel</button>
<button type="button" class="btn btn-danger pull-right" data-dismiss="modal" ng-click="deletePortal(portalToDelete)" ng-disabled="portalDeleteValidation != portalToDelete.title">Delete</button>
<button type="button" class="btn btn-default pull-left" data-dismiss="modal" ng-click="portalToDelete = null; portalDeleteValidation = null">Cancel</button>
<button type="button" class="btn btn-danger pull-right" data-dismiss="modal" ng-click="deletePortal(true, portalToDelete)" ng-disabled="portalDeleteValidation.toLowerCase() != portalToDelete.title.toLowerCase()">Delete</button>
</div>
</div>
</div>
</div>
<!-- Delete file Modal -->
<div id="deleteFileModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" ng-click="workshop.deleteFile = {}">&times;</button>
<h4 class="modal-title">Delete File <i><b>{{ workshop.deleteFile.name }}</b></i>?</h4>
</div>
<div class="modal-body">
<p>You are about to delete <b>{{ workshop.deleteFile.path }}</b>. Once you do this it can not be undone.</p>
<p><b>Are you absolutely sure you want to delete <i>{{ workshop.deleteFile.name }}</i>?</b></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default pull-left" data-dismiss="modal" ng-click="workshop.deleteFile = {}">Cancel</button>
<button type="button" class="btn btn-danger pull-right" data-dismiss="modal" ng-click="deleteFile()">Delete {{ workshop.deleteFile.name }}</button>
</div>
</div>
</div>
</div>
<!-- Rule Editor Modal -->
<div id="ruleEditorModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" ng-click="">&times;</button>
<h4 class="modal-title">Editing Rules for {{ workshop.portal.title }}</h4>
</div>
<div class="modal-body">
<div ng-repeat="(key, data) in workshop.concreteTargetedRules.rules">
<fieldset>
<legend style="text-transform: uppercase">{{ key }}</legend>
<div ng-repeat="(specifier, values) in data">
<h5 style="text-transform: capitalize">{{ specifier }} <a href="javascript:;" data-target="#" ng-click="newTargetedRule(key, specifier);">Add Rule</a></h5>
<div class="table-responsive" ng-hide="isObjectEmpty(workshop.workingTargetedRules.rules[key][specifier])">
<table class="table table-striped" align="center">
<thead>
<th>Rule Key</th>
<th>Destination</th>
<!--<th>Commit</th>-->
<th>Remove</th>
</thead>
<tbody>
<tr ng-repeat="(i, rules) in workshop.workingTargetedRules.rules[key][specifier]">
<td><input type="text" placeholder="Key Value" ng-model="rules.key"></td>
<td><input type="text" placeholder="Destination.php" ng-model="rules.destination"></td>
<!--<td><a href="javascript:;" data-target="#" ng-click="commitPortalRule(key, specifier, i, rules.key, rules.destination)">Commit</a></td>-->
<td><button ng-click="removeTargetedRule(key, specifier, i)" class="btn btn-sm btn-danger">Remove</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</fieldset>
<br />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default pull-left" data-dismiss="modal" ng-click="">Cancel</button>
<button type="button" class="btn btn-success pull-right" data-dismiss="modal" ng-click="saveTargetedRules()" >Save</button>
</div>
</div>
</div>
</div>
<!-- Toggled command editor -->
<div id="commandEditorModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Toggle Commands for {{ workshop.portal.title }}</h4>
</div>
<div class="modal-body">
<fieldset>
<legend>On Activation - <a ng-click="saveToggleCommands('enable')">Save</a></legend>
<div class="center-block">
<!-- For sake of getting EP 3.0 release, I'm not going to implement this just yet.
<span class="center-block">
<button type="submit" class="btn btn-link btn-sm">Enable PineAP</button>
<button type="submit" class="btn btn-link btn-sm">Enable Karma Associations</button>
<button type="submit" class="btn btn-link btn-sm">Enable Beacon Response</button>
</span>-->
</div>
<textarea style="width:100%;" rows="10" ng-model="workshop.onEnable"></textarea>
</fieldset>
<fieldset>
<legend>On Deactivation - <a ng-click="saveToggleCommands('disable')">Save</a></legend>
<div class="row-fluid">
<!-- For sake of getting EP 3.0 release, I'm not going to implement this just yet.
<span class="center-block">
<button type="submit" class="btn btn-link btn-sm">Disable PineAP</button>
<button type="submit" class="btn btn-link btn-sm">Disable Karma Associations</button>
<button type="submit" class="btn btn-link btn-sm">Disable Beacon Response</button>
</span>-->
</div>
<textarea style="width:100%;" rows="10" ng-model="workshop.onDisable"></textarea>
</fieldset>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default pull-left" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success pull-right" data-dismiss="modal" ng-click="saveToggleCommands('all')">Save All</button>
</div>
</div>
</div>

View File

@@ -1,10 +1,6 @@
{
"author": "newbi3",
"description": "An Evil Captive Portal.",
"devices": [
"nano",
"tetra"
],
"title": "Evil Portal",
"version": "2.1"
"title": "Evil Portal",
"version": "3.0",
"author": "newbi3",
"description": "An Evil Captive Portal."
}