├── README.md
├── fix-executables.sh
└── src
├── etc
├── init.d
│ └── pineapd
└── uci-defaults
│ └── 93-pineap.sh
└── pineapple
├── api
├── API.php
├── APIModule.php
├── Authentication.php
├── DatabaseConnection.php
├── Module.php
├── Modules.php
├── Notifications.php
├── Setup.php
├── SystemModule.php
├── index.php
└── pineapple.php
├── changes
├── config.php
├── css
├── bootstrap.min.css
└── main.css
├── html
├── clone-modal.html
├── hook-modal.html
└── install-modal.html
├── img
├── browser_chrome.png
├── browser_ff.png
├── browser_ie.png
├── browser_opera.png
├── browser_safari.png
├── chevron_down.svg
├── chevron_down_gray.svg
├── chevron_up.svg
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── logo.png
├── logout.png
├── notify.png
└── throbber.gif
├── index.html
├── js
├── controllers.js
├── directives.js
├── filters.js
├── helpers.js
├── pineapple.js
├── services.js
└── vendor
│ ├── angular-cookies.min.js
│ ├── angular-route.min.js
│ ├── angular.min.js
│ ├── bootstrap.min.js
│ └── jquery.min.js
├── modules
├── Advanced
│ ├── api
│ │ └── module.php
│ ├── formatSD
│ │ ├── fdisk_options
│ │ └── format_sd
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Clients
│ ├── api
│ │ └── module.php
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Configuration
│ ├── api
│ │ ├── landingpage_index.php
│ │ └── module.php
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Dashboard
│ ├── api
│ │ └── module.php
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Filters
│ ├── api
│ │ └── module.php
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Help
│ ├── api
│ │ └── module.php
│ ├── files
│ │ ├── debug
│ │ └── dumpscan.php
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Logging
│ ├── api
│ │ └── module.php
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── ModuleManager
│ ├── api
│ │ └── module.php
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Networking
│ ├── api
│ │ ├── AccessPoint.php
│ │ ├── ClientMode.php
│ │ ├── Interfaces.php
│ │ └── module.php
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Notes
│ ├── api
│ │ └── module.php
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── PineAP
│ ├── api
│ │ ├── PineAPHelper.php
│ │ └── module.php
│ ├── executable
│ │ └── executable
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Recon
│ ├── api
│ │ ├── WebSocketsHandler.py
│ │ ├── dbhandler.py
│ │ ├── module.php
│ │ ├── reconpp.py
│ │ └── wsauth.py
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Reporting
│ ├── api
│ │ └── module.php
│ ├── files
│ │ ├── getlog.php
│ │ └── reporting
│ ├── js
│ │ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
├── Setup
│ ├── eula.txt
│ ├── js
│ │ └── module.js
│ ├── license.txt
│ └── module.html
└── Tracking
│ ├── api
│ └── module.php
│ ├── js
│ └── module.js
│ ├── module.html
│ ├── module.info
│ └── module_icon.svg
└── pineapple_version
/README.md:
--------------------------------------------------------------------------------
1 | # Wifi Pineapple Panel
2 |
3 | The idea of this project is to have a better panel for the pineapple NANO/TETRA
4 | To install it, you just have to copy the contents of the src folder to / of the pineapple and reboot.
5 |
6 |
7 | # Changes
8 |
9 | The following functionalities are modified looking for a better user experience
10 |
11 | ## General:
12 | - Compress PNG images (size -55K)
13 | - Compress SVG images (size -7K)
14 | - Update Bootstrap to 3.4.1 (size +2K)
15 | - Rebuild mobile view
16 | - Add Chevron icon to accordions (size +1K)
17 | - Change notification time from 6000 to 30000 (decrease RPM from 10 to 2)
18 | - Added more refresh buttons
19 | - Fixed several bugs found in the panel
20 | - Expose AngularJS Pineapple API in JS window
21 | - Add timeout and prevent duplicated request in API service
22 | - Refactor all indexedDB code
23 | - Removed use of php7-mod-sockets and php7-mod-openssl (size -200K)
24 | - Implemented use of uclient-fetch as a replacement for wget and file_get_contents(https)
25 |
26 | ## Dashboard
27 | - Change update time from 5000 to 10000 (decrease RPM from 12 to 6)
28 |
29 | ## Recon:
30 | - Code refactor in module.php
31 | - Add results counter in titles with badges
32 | - Fix column alignment
33 |
34 | ## Clients:
35 | - Add loading indicator
36 | - Change default text logic
37 |
38 | ## PineAP:
39 | - Configure used monitor interface (pineapd pineap_interface)
40 | - Configure used source interface (pineapd source_mac grabber)
41 | - Show pineapd service errors
42 |
43 | ## Logging:
44 | - Fire data loading on open accordion
45 | - Add PineAP Logs loading indicator
46 | - Save filters in cookies
47 |
48 | ## Network:
49 | - Add tabs
50 | - Add "Wireless raw config editor" section
51 | - Add "Info" section
52 | - Add "Interface actions" section
53 | - Decrease initial requests (from 8 to 3)
54 | - Use new feed for out.txt updates
55 |
56 | ## Advanced:
57 | - Add tabs
58 | - Add "Manual upgrade" section
59 | - Decrease initial requests (from 8 to 3)
60 | - Use Universal Wifi pineapple hardware cloner downloads as update feed
61 | - Add "Keep settings and retain the current configuration" checkbox
62 |
63 | ## Modules:
64 | - Refactor in Modules.php
65 | - Add support for injectJS in modules manifest
66 | - Use new modules feed: https://github.com/xchwarze/wifi-pineapple-community/tree/main/modules
67 |
68 |
69 | # Notes
70 |
71 | 1. For edit notification timer you can use this
72 | ```bash
73 | # sed -i 's/OLD-VALUE/NEW-VALUE/' FILE
74 | sed -i 's/30000/60000/' src/pineapple/js/controllers.js
75 | ```
76 |
77 | 2. To open the menu on hover uncomment this in src/pineapple/main.css
78 | ```css
79 | .sidebar:hover {
80 | margin-left: 0;
81 | }
82 | .menu-toggle {
83 | display: none !important;
84 | }
85 | ```
86 |
87 | 3. To develop locally you can point your panel to the pinapple replacing the src/pineapple/api/index.php with this
88 | ```php
89 | /tmp/pineap.conf
9 | autostart = $(uci get pineap.@config[0].autostart)
10 | karma = $(uci get pineap.@config[0].karma)
11 | beacon_interval = $(uci get pineap.@config[0].beacon_interval)
12 | beacon_response_interval = $(uci get pineap.@config[0].beacon_response_interval)
13 | beacon_responses = $(uci get pineap.@config[0].beacon_responses)
14 | capture_ssids = $(uci get pineap.@config[0].capture_ssids)
15 | broadcast_ssid_pool = $(uci get pineap.@config[0].broadcast_ssid_pool)
16 | logging = $(uci get pineap.@config[0].logging)
17 | mac_filter = $(uci get pineap.@config[0].mac_filter)
18 | ssid_filter = $(uci get pineap.@config[0].ssid_filter)
19 | ap_channel = $(uci get pineap.@config[0].ap_channel)
20 | pineap_mac = $(ifconfig -a $(uci get pineap.@config[0].pineap_source_interface) | grep HWaddr | awk '{print $5}')
21 | target_mac = $(uci get pineap.@config[0].target_mac)
22 | pineap_interface = $(uci get pineap.@config[0].pineap_interface)
23 | recon_db_path = $(uci get pineap.@config[0].recon_db_path)
24 | hostapd_db_path = $(uci get pineap.@config[0].hostapd_db_path)
25 | ssid_db_path = $(uci get pineap.@config[0].ssid_db_path)
26 | connect_notifications = $(uci get pineap.@config[0].connect_notifications)
27 | disconnect_notifications = $(uci get pineap.@config[0].disconnect_notifications)
28 | rts_enabled = off
29 | rts_scan_id = -1
30 | EOF
31 | }
32 |
33 | start() {
34 | mkdir -p /tmp/handshakes
35 | gen_config
36 |
37 | pineap_interface=$(uci -q get pineap.@config[0].pineap_interface)
38 | pineap_interface_original=${pineap_interface/mon/''}
39 |
40 | #ifconfig wlan1mon &>/dev/null || airmon-ng start wlan1 &>/dev/null
41 | ifconfig ${pineap_interface} &>/dev/null || airmon-ng start ${pineap_interface_original} &>/dev/null
42 | /usr/sbin/pineapd /tmp/pineap.conf &>/dev/null &
43 |
44 | sleep 1
45 | if [[ $(/usr/bin/pgrep pineapd) ]]; then
46 | echo "Status: OK"
47 | elif [[ ! $(ifconfig -a ${pineap_interface} | grep HWaddr | awk '{print $5}') ]]; then
48 | echo "Status: Monitor interface won't start! Try to run manually airmon-ng start ${pineap_interface_original}"
49 | else
50 | echo "There is a problem with pineapd. Try to run it manually"
51 | fi
52 | }
53 |
54 | stop() {
55 | killall -9 pineapd
56 | rm /var/run/pineapd.sock
57 | rm /var/run/pineapd.lock
58 | }
59 |
60 | restart() {
61 | stop
62 | start
63 | }
64 |
65 | boot() {
66 | gen_config
67 | /usr/sbin/pineapd /tmp/pineap.conf --initdb
68 | if [[ $(uci get pineap.@config[0].autostart) == '1' ]]; then
69 | start
70 | fi
71 | logger =========== PineAP ==============
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/src/etc/uci-defaults/93-pineap.sh:
--------------------------------------------------------------------------------
1 | # -- Set up PineAP configuration
2 |
3 | touch /etc/config/pineap
4 | uci add pineap config
5 | uci set pineap.@config[0].autostart=0
6 | uci set pineap.@config[0].karma='off'
7 | uci set pineap.@config[0].beacon_interval='NORMAL'
8 | uci set pineap.@config[0].beacon_response_interval='NORMAL'
9 | uci set pineap.@config[0].beacon_responses='off'
10 | uci set pineap.@config[0].capture_ssids='off'
11 | uci set pineap.@config[0].broadcast_ssid_pool='off'
12 | uci set pineap.@config[0].logging='off'
13 | uci set pineap.@config[0].mac_filter='black'
14 | uci set pineap.@config[0].ssid_filter='black'
15 | uci set pineap.@config[0].connect_notifications='off'
16 | uci set pineap.@config[0].disconnect_notifications='off'
17 | uci set pineap.@config[0].ap_channel=11
18 | uci set pineap.@config[0].pineap_interface='wlan1mon'
19 | uci set pineap.@config[0].pineap_source_interface='wlan0'
20 |
21 | uci set pineap.@config[0].pineap_mac='00:11:22:33:44:55'
22 | uci set pineap.@config[0].target_mac='FF:FF:FF:FF:FF:FF'
23 | uci set pineap.@config[0].recon_db_path='/tmp/recon.db'
24 | uci set pineap.@config[0].hostapd_db_path='/tmp/log.db'
25 | uci set pineap.@config[0].ssid_db_path='/etc/pineapple/pineapple.db'
26 |
27 | uci set pineap.@config[0].pineape_passthrough='off'
28 |
29 | uci commit pineap
30 |
31 | exit 0
32 |
--------------------------------------------------------------------------------
/src/pineapple/api/APIModule.php:
--------------------------------------------------------------------------------
1 | request = $request;
14 | }
15 |
16 | public function getResponse()
17 | {
18 | if (empty($this->error) && !empty($this->response)) {
19 | return $this->response;
20 | } elseif (empty($this->error) && empty($this->response)) {
21 | return ['error' => 'API returned empty response'];
22 | } else {
23 | return ['error' => $this->error];
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/pineapple/api/Authentication.php:
--------------------------------------------------------------------------------
1 | dbConnection = new DatabaseConnection(self::DATABASE);
15 | $this->dbConnection->exec("CREATE TABLE IF NOT EXISTS api_tokens (token VARCHAR NOT NULL, name VARCHAR NOT NULL);");
16 | }
17 |
18 | public function getApiTokens()
19 | {
20 | $this->response = array("tokens" => $this->dbConnection->query("SELECT token,name FROM api_tokens;"));
21 | }
22 |
23 | public function checkApiToken()
24 | {
25 | if (isset($this->request->token)) {
26 | $token = $this->request->token;
27 | $result = $this->dbConnection->query("SELECT token FROM api_tokens WHERE token='%s';", $token);
28 | if (!empty($result) && isset($result[0]["token"]) && $result[0]["token"] === $token) {
29 | $this->response = array("valid" => true);
30 | return;
31 | }
32 | }
33 | $this->response = array("valid" => false);
34 | }
35 |
36 | public function addApiToken()
37 | {
38 | if (isset($this->request->token) && isset($this->request->name)) {
39 | $token = $this->request->token;
40 | $name = $this->request->name;
41 | $this->dbConnection->exec("INSERT INTO api_tokens(token, name) VALUES('%s','%s');", $token, $name);
42 | $this->response = array("success" => true);
43 | return;
44 | }
45 | $this->error = "Missing token or name";
46 | }
47 |
48 | private function login()
49 | {
50 | if (isset($this->request->username) && isset($this->request->password)) {
51 | if ($this->verifyPassword($this->request->password)) {
52 | $_SESSION['logged_in'] = true;
53 | $this->response = array("logged_in" => true);
54 | if (!isset($this->request->time)) {
55 | return;
56 | }
57 | $epoch = intval($this->request->time);
58 | if ($epoch > 1) {
59 | exec('date -s @' . $epoch);
60 | }
61 | return;
62 | }
63 | }
64 |
65 | $this->response = array("logged_in" => false);
66 | }
67 |
68 | private function verifyPassword($password)
69 | {
70 | $shadowContents = file_get_contents('/etc/shadow');
71 | $rootArray = explode(':', explode('root:', $shadowContents)[1]);
72 | $rootPass = $rootArray[0];
73 | if (!empty($rootPass) && gettype($rootPass) === "string") {
74 | return hash_equals($rootPass, crypt($password, $rootPass));
75 | }
76 | return false;
77 | }
78 |
79 | private function logout()
80 | {
81 | $this->response = array("logged_in" => false);
82 | unset($_COOKIE['XSRF-TOKEN']);
83 | setcookie('XSRF-TOKEN', '', time()-3600);
84 | unset($_SESSION['XSRF-TOKEN']);
85 | unset($_SESSION['logged_in']);
86 | session_destroy();
87 | }
88 |
89 | private function checkAuth()
90 | {
91 | if (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true) {
92 | $this->response = array("authenticated" => true);
93 | } else {
94 | if (file_exists("/etc/pineapple/setupRequired")) {
95 | $this->response = array("error" => "Not Authenticated", "setupRequired" => true);
96 | } else {
97 | $this->response = array("error" => "Not Authenticated");
98 | }
99 | }
100 | }
101 |
102 | public function route()
103 | {
104 | switch ($this->request->action) {
105 | case 'login':
106 | $this->login();
107 | break;
108 |
109 | case 'logout':
110 | $this->logout();
111 | break;
112 |
113 | case 'checkAuth':
114 | $this->checkAuth();
115 | break;
116 |
117 | case 'checkApiToken':
118 | $this->checkApiToken();
119 | break;
120 |
121 | case 'addApiToken':
122 | $this->addApiToken();
123 | break;
124 |
125 | case 'getApiTokens':
126 | $this->getApiTokens();
127 | break;
128 |
129 | default:
130 | $this->error = "Unknown action";
131 | }
132 |
133 | session_write_close();
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/pineapple/api/DatabaseConnection.php:
--------------------------------------------------------------------------------
1 | error = [];
12 | $this->databaseFile = $databaseFile;
13 | try {
14 | $this->dbConnection = new \SQLite3($this->databaseFile);
15 | $this->dbConnection->busyTimeout(20000);
16 | } catch (\Exception $e) {
17 | $this->error["databaseConnectionError"] = $e->getMessage();
18 | }
19 | }
20 |
21 | public function strError()
22 | {
23 | foreach ($this->error as $errorType => $errorMessage) {
24 | switch ($errorType) {
25 | case 'databaseConnectionError':
26 | return "Could not connect to database: $errorMessage";
27 | case 'databaseExecutionError':
28 | case 'databaseQueryError':
29 | return "Could not execute query: $errorMessage";
30 | default:
31 | return "Unknown database error";
32 | }
33 | }
34 |
35 | return true;
36 | }
37 |
38 | public function getDatabaseFile()
39 | {
40 | return $this->databaseFile;
41 | }
42 |
43 | public function getDbConnection()
44 | {
45 | return $this->dbConnection;
46 | }
47 |
48 | public static function formatQuery(...$query)
49 | {
50 | $query = $query[0];
51 | $sqlQuery = $query[0];
52 | $sqlParameters = array_slice($query, 1);
53 | if (empty($sqlParameters)) {
54 | return $sqlQuery;
55 | }
56 | for ($i = 0; $i < count($sqlParameters); ++$i) {
57 | if (gettype($sqlParameters[$i]) === "string") {
58 | $escaped = \SQLite3::escapeString($sqlParameters[$i]);
59 | $sqlParameters[$i] = $escaped;
60 | }
61 | }
62 | return vsprintf($sqlQuery, $sqlParameters);
63 | }
64 |
65 | public function query(...$query)
66 | {
67 | $safeQuery = DatabaseConnection::formatQuery($query);
68 | $result = $this->dbConnection->query($safeQuery);
69 | if (!$result) {
70 | $this->error['databaseQueryError'] = $this->dbConnection->lastErrorMsg();
71 | return $this->error;
72 | }
73 | $resultArray = [];
74 | while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
75 | $resultArray[] = $row;
76 | }
77 | return $resultArray;
78 | }
79 |
80 | public function exec(...$query)
81 | {
82 | $safeQuery = DatabaseConnection::formatQuery($query);
83 | try {
84 | $result = $this->dbConnection->exec($safeQuery);
85 | } catch (\Exception $e) {
86 | $this->error['databaseExecutionError'] = $e;
87 | return $this->error;
88 | }
89 | return ['success' => $result];
90 | }
91 |
92 | public function __destruct()
93 | {
94 | if ($this->dbConnection) {
95 | $this->dbConnection->close();
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/pineapple/api/Module.php:
--------------------------------------------------------------------------------
1 | request = $request;
18 | $this->moduleClass = $moduleClass;
19 | $this->error = '';
20 | }
21 |
22 | public function getResponse()
23 | {
24 | if (empty($this->error) && !empty($this->response)) {
25 | return $this->response;
26 | } elseif (!empty($this->streamFunction)) {
27 | header('Content-Type: text/plain');
28 | $this->streamFunction->__invoke();
29 | return false;
30 | } elseif (empty($this->error) && empty($this->response)) {
31 | return ['error' => 'Module returned empty response'];
32 | } else {
33 | return ['error' => $this->error];
34 | }
35 | }
36 |
37 | public function execBackground($command)
38 | {
39 | return \helper\execBackground($command);
40 | }
41 |
42 | protected function isSDAvailable()
43 | {
44 | return \helper\isSDAvailable();
45 | }
46 |
47 | protected function sdReaderPresent() {
48 | return \helper\sdReaderPresent();
49 | }
50 |
51 | protected function sdCardPresent() {
52 | return \helper\sdCardPresent();
53 | }
54 |
55 | protected function checkRunning($processName)
56 | {
57 | return \helper\checkRunning($processName);
58 | }
59 |
60 | protected function checkRunningFull($processString) {
61 | return \helper\checkRunningFull($processString);
62 | }
63 |
64 | public function uciGet($uciString)
65 | {
66 | return \helper\uciGet($uciString);
67 | }
68 |
69 | public function uciSet($settingString, $value)
70 | {
71 | \helper\uciSet($settingString, $value);
72 | }
73 |
74 | public function uciAddList($settingString, $value)
75 | {
76 | \helper\uciAddList($settingString, $value);
77 | }
78 |
79 | protected function downloadFile($file)
80 | {
81 | return \helper\downloadFile($file);
82 | }
83 |
84 | protected function getFirmwareVersion()
85 | {
86 | return \helper\getFirmwareVersion();
87 | }
88 |
89 | protected function getDevice()
90 | {
91 | return \helper\getDevice();
92 | }
93 |
94 | protected function getBoard()
95 | {
96 | return \helper\getBoard();
97 | }
98 |
99 | protected function getDeviceConfig()
100 | {
101 | return \helper\getDeviceConfig();
102 | }
103 |
104 | protected function getMacFromInterface($interface)
105 | {
106 | return \helper\getMacFromInterface($interface);
107 | }
108 |
109 | protected function installDependency($dependencyName, $installToSD = false)
110 | {
111 | if ($installToSD && !$this->isSDAvailable()) {
112 | return false;
113 | }
114 |
115 | $destination = $installToSD ? '--dest sd' : '';
116 | $dependencyName = escapeshellarg($dependencyName);
117 | if (!$this->checkDependency($dependencyName)) {
118 | exec("opkg update");
119 | exec("opkg install {$dependencyName} {$destination}");
120 | }
121 |
122 | return $this->checkDependency($dependencyName);
123 | }
124 |
125 | protected function checkDependency($dependencyName)
126 | {
127 | return \helper\checkDependency($dependencyName);
128 | }
129 |
130 | protected function fileGetContentsSSL($url)
131 | {
132 | return \helper\fileGetContentsSSL($url);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/pineapple/api/Modules.php:
--------------------------------------------------------------------------------
1 | modules = [
11 | 'systemModules' => [],
12 | 'userModules' => []
13 | ];
14 | }
15 |
16 | public function getModules()
17 | {
18 | require_once('DatabaseConnection.php');
19 |
20 | $dir = scandir("../modules");
21 | if ($dir === false) {
22 | $this->error = "Unable to access modules directory";
23 | return $this->modules;
24 | }
25 |
26 | natcasesort($dir);
27 | foreach ($dir as $moduleFolder) {
28 | $modulePath = "../modules/{$moduleFolder}";
29 | if ($moduleFolder[0] === '.' || !file_exists("{$modulePath}/module.info")) {
30 | continue;
31 | }
32 |
33 | $moduleInfo = @json_decode(file_get_contents("{$modulePath}/module.info"));
34 | if (json_last_error() !== JSON_ERROR_NONE || isset($moduleInfo->cliOnly)) {
35 | continue;
36 | }
37 |
38 | $jsonModulePath = "/modules/${moduleFolder}";
39 | $module = [
40 | "name" => $moduleFolder,
41 | "title" => isset($moduleInfo->title) ? $moduleInfo->title : $moduleFolder,
42 | "icon" => null,
43 | "injectJS" => isset($moduleInfo->injectJS) ? "${jsonModulePath}/{$moduleInfo->injectJS}" : null,
44 | ];
45 |
46 | if (file_exists("$modulePath/module_icon.svg")) {
47 | $module["icon"] = "${jsonModulePath}/module_icon.svg";
48 | } elseif (file_exists("$modulePath/module_icon.png")) {
49 | $module["icon"] = "${jsonModulePath}/module_icon.png";
50 | }
51 |
52 | if (isset($moduleInfo->system)) {
53 | if (isset($moduleInfo->index)) {
54 | $this->modules['systemModules'][$moduleInfo->index] = $module;
55 | }
56 | } else {
57 | $this->modules['userModules'][] = $module;
58 | }
59 | }
60 |
61 | return $this->modules;
62 | }
63 |
64 | public function route()
65 | {
66 | switch ($this->request->action) {
67 | case "getModuleList":
68 | $this->getModules();
69 | $this->response = ['modules' => $this->modules];
70 | break;
71 | default:
72 | $this->error = "Unknown action: " . $this->request->action;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/pineapple/api/Notifications.php:
--------------------------------------------------------------------------------
1 | dbConnection = new DatabaseConnection(self::DATABASE);
15 | if (!empty($this->dbConnection->error)) {
16 | $this->error = $this->dbConnection->strError();
17 | return;
18 | }
19 | $this->notifications = [];
20 | $this->dbConnection->exec("CREATE TABLE IF NOT EXISTS notifications (message VARCHAR NOT NULL, time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);");
21 | if (!empty($this->dbConnection->error)) {
22 | $this->error = $this->dbConnection->strError();
23 | }
24 | }
25 |
26 | public function route()
27 | {
28 | switch ($this->request->action) {
29 | case 'listNotifications':
30 | $this->response = $this->getNotifications();
31 | break;
32 | case 'addNotification':
33 | $this->response = $this->addNotification($this->request->message);
34 | break;
35 | case 'clearNotifications':
36 | $this->response = $this->clearNotifications();
37 | break;
38 | default:
39 | $this->error = "Unknown action: " . $this->request->action;
40 | }
41 | }
42 |
43 | public function addNotification($message)
44 | {
45 | return $this->dbConnection->exec("INSERT INTO notifications (message) VALUES('%s');", $message);
46 | }
47 |
48 | public function getNotifications()
49 | {
50 | $result = $this->dbConnection->query("SELECT message,time from notifications ORDER BY time DESC;");
51 | $this->notifications = $result;
52 | return $this->notifications;
53 | }
54 |
55 | public function clearNotifications()
56 | {
57 | $result = $this->dbConnection->exec('DELETE FROM notifications;');
58 | unset($this->notifications);
59 | return $result;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/pineapple/api/SystemModule.php:
--------------------------------------------------------------------------------
1 | handleRequest();
9 |
--------------------------------------------------------------------------------
/src/pineapple/api/pineapple.php:
--------------------------------------------------------------------------------
1 | = 1;
21 | }
22 |
23 | function sdReaderPresent() {
24 | return file_exists('/sd');
25 | }
26 |
27 | function sdCardPresent() {
28 | return !file_exists('/sd/NO_SD');
29 | }
30 |
31 | function checkRunning($processName)
32 | {
33 | $processName = escapeshellarg($processName);
34 | exec("/usr/bin/pgrep {$processName}", $output);
35 | return count($output) > 0;
36 | }
37 |
38 | function checkRunningFull($processString) {
39 | $processString = escapeshellarg($processString);
40 | exec("/usr/bin/pgrep -f {$processString}", $output);
41 | return count($output) > 0;
42 | }
43 |
44 | function uciGet($uciString, $autoBool = true)
45 | {
46 | $uciString = escapeshellarg($uciString);
47 | $result = exec("uci get {$uciString}");
48 | if ($autoBool && ($result === "0" || $result === "1")) {
49 | return $result === "1";
50 | }
51 |
52 | return $result;
53 | }
54 |
55 | function uciSet($settingString, $value, $autoCommit = true)
56 | {
57 | $settingString = escapeshellarg($settingString);
58 | if (!empty($value)) {
59 | $value = escapeshellarg($value);
60 | }
61 |
62 | if ($value === "''" || $value === "") {
63 | $value = "'0'";
64 | }
65 |
66 | exec("uci set {$settingString}={$value}");
67 | if ($autoCommit) {
68 | exec("uci commit {$settingString}");
69 | }
70 | }
71 |
72 | function uciAddList($settingString, $value, $autoCommit = true)
73 | {
74 | $settingString = escapeshellarg($settingString);
75 | if (!empty($value)) {
76 | $value = escapeshellarg($value);
77 | }
78 |
79 | if ($value === "''" || $value === "") {
80 | $value = "'0'";
81 | }
82 |
83 | exec("uci add_list {$settingString}={$value}");
84 | if ($autoCommit) {
85 | exec("uci commit {$settingString}");
86 | }
87 | }
88 |
89 | function uciCommit()
90 | {
91 | exec("uci commit");
92 | }
93 |
94 | function downloadFile($file)
95 | {
96 | $token = hash('sha256', $file . time());
97 |
98 | require_once('DatabaseConnection.php');
99 | $database = new \pineapple\DatabaseConnection("/etc/pineapple/pineapple.db");
100 | $database->exec("CREATE TABLE IF NOT EXISTS downloads (token VARCHAR NOT NULL, file VARCHAR NOT NULL, time timestamp default (strftime('%s', 'now')));");
101 | $database->exec("INSERT INTO downloads (token, file) VALUES ('%s', '%s')", $token, $file);
102 |
103 | return $token;
104 | }
105 |
106 | function getFirmwareVersion()
107 | {
108 | return trim(file_get_contents('/pineapple/pineapple_version'));
109 | }
110 |
111 | function getDevice()
112 | {
113 | return \DeviceConfig::DEVICE_TYPE;
114 | }
115 |
116 | function getDeviceConfig()
117 | {
118 | return [
119 | 'deviceType' => \DeviceConfig::DEVICE_TYPE,
120 | 'useInternalStorage' => \DeviceConfig::USE_INTERNAL_STORAGE,
121 | 'useUSBStorage' => \DeviceConfig::USE_USB_STORAGE,
122 | 'showFirewallConfig' => \DeviceConfig::SHOW_FIREWALL_CONFIG,
123 | 'showScanType' => \DeviceConfig::SHOW_SCAN_TYPE,
124 | 'hideWlan0Client' => \DeviceConfig::HIDE_WLAN0_CLIENT,
125 | ];
126 | }
127 |
128 | function getMacFromInterface($interface)
129 | {
130 | $interface = escapeshellarg($interface);
131 | return trim(exec("ifconfig {$interface} | grep HWaddr | awk '{print $5}'"));
132 | }
133 |
134 | function getBoard()
135 | {
136 | $data = file_get_contents('/tmp/sysinfo/board_name');
137 | if (!empty($data)) {
138 | return str_replace(',', '_', trim($data));
139 | }
140 |
141 | return false;
142 | }
143 |
144 | function fileGetContentsSSL($url)
145 | {
146 | $url = escapeshellarg($url);
147 | return exec("uclient-fetch -q -T 10 -O - {$url}");
148 | }
149 |
--------------------------------------------------------------------------------
/src/pineapple/changes:
--------------------------------------------------------------------------------
1 |
2 | General
3 |
4 | Compress images
5 | Update dependencies
6 | Rebuild mobile view
7 | A completely updated panel with fixes and improvements
8 | Decrease requests for less cpu usage
9 | Add timeout and prevent duplicated request in API service
10 | Refactor all indexedDB code
11 | Use new modules feed
12 |
13 |
14 | Recon
15 |
16 | Add results counter in titles with badges
17 | Fix column alignment
18 |
19 |
20 | PineAP
21 |
22 | Configure used monitor and source interfaces
23 | Show pineapd service errors
24 |
25 |
26 | Network
27 |
28 | Add tabs
29 | Add "Wireless raw config editor" section
30 | Add "Info" section
31 | Add "Interface actions" section
32 | Use new feed for out.txt updates
33 | Fix Networking wifi detection
34 |
35 |
36 | Advanced
37 |
38 | Add tabs
39 | Add "Manual upgrade" section
40 | Use Universal Wifi pineapple hardware cloner downloads as update feed
41 |
42 |
43 |
44 |
45 | 🚀Wifi Pineapple Panel by DSR! - View full changelog in GitHub! 🚀
46 |
47 |
--------------------------------------------------------------------------------
/src/pineapple/config.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
Clone Enterprise Access Point
15 |
16 | Clone
17 |
18 |
19 |
Note: Cloning an Access Point may restart the wireless radios.
20 |
21 |
22 |
23 |
24 |
25 |
26 |
Capture Wireless Handshake
27 |
28 |
29 |
30 | Start Capture
31 | Stop Capture
32 |
33 |
34 |
35 | Deauth
36 |
37 |
38 |
39 |
40 |
Full handshake found for {{ content.bssid }}.
41 |
Partial handshake found for {{ content.bssid }}.
42 |
43 | Download PCAP
44 | Delete
45 |
46 |
47 |
48 | A handshake capture is already running for another BSSID ({{ currentBSSID }}).
49 | Stop Other Capture
50 |
51 |
52 |
53 |
54 |
55 |
58 |
63 |
64 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/src/pineapple/html/install-modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
Third Party Module
12 | This module is developed by {{content.module['author']}} , a third-party developer.
13 |
14 |
15 |
16 |
First Party Module
17 | This module is developed by Hak5 , a first-party developer.
18 |
19 |
20 |
Generally, module support can be found at forums.hak5.org.
21 |
22 |
23 |
24 | Using an SD card instead of internal storage is strongly recommended.
25 |
26 |
27 |
28 | You do not have enough free space to install this module. Please insert an SD card and ensure that it is formatted correctly.
29 |
30 |
31 |
32 |
33 |
34 |
35 | Cancel
36 | Install to SD Card
37 | Install Internally
38 | Install Update
39 |
40 |
41 |
42 |
43 | Downloading Module, please wait.
44 |
45 |
46 |
47 | Installing Module, please wait.
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/pineapple/img/browser_chrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/browser_chrome.png
--------------------------------------------------------------------------------
/src/pineapple/img/browser_ff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/browser_ff.png
--------------------------------------------------------------------------------
/src/pineapple/img/browser_ie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/browser_ie.png
--------------------------------------------------------------------------------
/src/pineapple/img/browser_opera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/browser_opera.png
--------------------------------------------------------------------------------
/src/pineapple/img/browser_safari.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/browser_safari.png
--------------------------------------------------------------------------------
/src/pineapple/img/chevron_down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/img/chevron_down_gray.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/img/chevron_up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/img/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/favicon-16x16.png
--------------------------------------------------------------------------------
/src/pineapple/img/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/favicon-32x32.png
--------------------------------------------------------------------------------
/src/pineapple/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/favicon.ico
--------------------------------------------------------------------------------
/src/pineapple/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/logo.png
--------------------------------------------------------------------------------
/src/pineapple/img/logout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/logout.png
--------------------------------------------------------------------------------
/src/pineapple/img/notify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/notify.png
--------------------------------------------------------------------------------
/src/pineapple/img/throbber.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/throbber.gif
--------------------------------------------------------------------------------
/src/pineapple/js/controllers.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | angular.module('pineapple')
3 | .controller('NavigationController', ['$scope', '$api', '$routeParams', function($scope, $api, $routeParams) {
4 | $scope.systemModules = [];
5 | $scope.userModules = [];
6 | $scope.selectedIndex = -1;
7 |
8 | $scope.getClass = function(moduleName) {
9 | var status = ($routeParams.moduleName === $(".sidebar-nav li[module="+ moduleName +"]").attr("module"));
10 | return status ? 'active' : '';
11 | };
12 |
13 | $scope.getModuleClass = function() {
14 | return $(".module-nav li.active").length ? 'active' : '';
15 | };
16 |
17 | $scope.getModuleList = (function () {
18 | $api.request({
19 | system: 'modules',
20 | action: 'getModuleList'
21 | }, function(data) {
22 | if (data.error === undefined){
23 | $scope.systemModules = data.modules.systemModules;
24 | $scope.userModules = data.modules.userModules;
25 | $scope.processModulesExtras();
26 | }
27 | });
28 | });
29 |
30 | $scope.processModulesExtras = (function () {
31 | angular.forEach($scope.userModules, function(value) {
32 | if (value.injectJS) {
33 | $('head').append( $('') );
34 | }
35 | });
36 | });
37 |
38 | $scope.getAlias = function(moduleName) {
39 | return moduleName.match(/(\b\S)?/g).join('').match(/(^\S|\S$)?/g).join('').toUpperCase();
40 | };
41 |
42 | $api.registerNavbar($scope.getModuleList);
43 | $scope.getModuleList();
44 | }])
45 |
46 |
47 | .controller('NotificationController', ['$scope', '$api', '$interval', function($scope, $api, $interval){
48 | $scope.notifications = [];
49 |
50 | $api.getNotifications(function(data){
51 | $scope.notifications = data;
52 | });
53 |
54 | $scope.clearNotifications = function(){
55 | $scope.notifications = [];
56 | $api.clearNotifications();
57 | };
58 |
59 | $scope.notificationInterval = $interval(function() {
60 | $api.getNotifications(function(data){
61 | $scope.notifications = data;
62 | });
63 | }, 30000);
64 |
65 | $scope.$on('$destroy', function() {
66 | $interval.cancel($scope.notificationInterval);
67 | });
68 | }])
69 |
70 |
71 | .controller('AuthenticationController', ['$scope', '$api', function($scope, $api){
72 | $scope.username = "root";
73 | $scope.password = "";
74 | $scope.message = "";
75 |
76 | $scope.login = function(){
77 | $api.login($scope.username, $scope.password, function(data){
78 | if (data.logged_in !== undefined && data.logged_in === false) {
79 | $scope.message = "Invalid username or password.";
80 | } else {
81 | window.location.reload();
82 | }
83 | });
84 | };
85 |
86 | $scope.logout = function(){
87 | $api.logout(function(){
88 | window.location.reload();
89 | });
90 | };
91 |
92 | $scope.haltPineapple = (function() {
93 | if (confirm("Are you sure you want to shutdown your WiFi Pineapple?")) {
94 | $api.request({
95 | module: "Configuration",
96 | action: "haltPineapple"
97 | }, function(response) {
98 | if (response.success !== undefined) {
99 | alert("Your WiFi Pineapple is now shutting down. Once the LED has turned off, it is safe to unplug.");
100 | }
101 | });
102 | }
103 | });
104 |
105 | $scope.rebootPineapple = (function() {
106 | if (confirm("Are you sure you want to reboot your WiFi Pineapple?")) {
107 | $api.request({
108 | module: "Configuration",
109 | action: "rebootPineapple"
110 | }, function(response) {
111 | if (response.success !== undefined) {
112 | alert("Your WiFi Pineapple is now rebooting. You may need to reconnect once it is done.");
113 | }
114 | });
115 | }
116 | });
117 | }]);
118 | })();
--------------------------------------------------------------------------------
/src/pineapple/js/filters.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | angular.module('pineapple')
3 | .filter('timesince', function () {
4 | return function (input) {
5 | var then = new Date(input * 1000);
6 | var now = new Date();
7 | var hoursSince = Math.round(Math.abs(now - then) / 1000 / 60 / 60);
8 | var minutesSince = Math.round(Math.abs(now - then) / 60 / 1000);
9 | var secondsSince = Math.round(Math.abs(now - then) / 1000);
10 | if (secondsSince >= 1 && secondsSince < 60) {
11 | return secondsSince + ' ' + (secondsSince === 1 ? 'second' : 'seconds') + ' ago';
12 | } else if (minutesSince >= 1 && minutesSince < 60) {
13 | return minutesSince + ' ' + (minutesSince === 1 ? 'minute' : 'minutes') + ' ago';
14 | } else if (hoursSince >= 1 && hoursSince < 24) {
15 | return hoursSince + ' ' + (hoursSince === 1 ? 'hour' : 'hours') + ' ago';
16 | } else {
17 | var hours = then.getHours();
18 | var minutes = then.getMinutes();
19 | var month = then.getMonth();
20 | var day = then.getDay();
21 | var year = then.getYear();
22 | return 'at ' + (year + 1990) + '-' + month + '-' + day + ' ' + (hours % 12 === 0 ? '12' : (hours % 12).toString()) + ':' + minutes + (hours > 12 ? ' PM' : ' AM');
23 | }
24 | }
25 | })
26 |
27 | .filter('timesincedate', function () {
28 | return function (input) {
29 | if (input === undefined) {
30 | return "";
31 | }
32 | var then = utcDate(input);
33 | var now = new Date();
34 | var hoursSince = Math.round(Math.abs(now - then) / 1000 / 60 / 60);
35 | var minutesSince = Math.round(Math.abs(now - then) / 60 / 1000);
36 | var secondsSince = Math.round(Math.abs(now - then) / 1000);
37 | if (secondsSince >= 1 && secondsSince < 60) {
38 | return secondsSince + ' ' + (secondsSince === 1 ? 'second' : 'seconds') + ' ago';
39 | } else if (minutesSince >= 1 && minutesSince < 60) {
40 | return minutesSince + ' ' + (minutesSince === 1 ? 'minute' : 'minutes') + ' ago';
41 | } else {
42 | var hours = ("0" + then.getHours()).slice(-2);
43 | var minutes = ("0" + then.getMinutes()).slice(-2);
44 | var month = ("0" + (then.getMonth() + 1)).slice(-2);
45 | var day = ("0" + then.getDate()).slice(-2);
46 | var year = then.getYear();
47 | return 'at ' + (year + 1900) + '-' + month + '-' + day + ' ' + hours + ':' + minutes;
48 | }
49 | }
50 | })
51 |
52 | .filter('timesinceepoch', function () {
53 | return function (input) {
54 | if (input === undefined) {
55 | return "";
56 | }
57 | var then = new Date(input * 1000);
58 | var now = new Date();
59 | var hoursSince = Math.round(Math.abs(now - then) / 1000 / 60 / 60);
60 | var minutesSince = Math.round(Math.abs(now - then) / 60 / 1000);
61 | var secondsSince = Math.round(Math.abs(now - then) / 1000);
62 | if (secondsSince >= 1 && secondsSince < 60) {
63 | return secondsSince + ' ' + (secondsSince === 1 ? 'second' : 'seconds') + ' ago';
64 | } else if (minutesSince >= 1 && minutesSince < 60) {
65 | return minutesSince + ' ' + (minutesSince === 1 ? 'minute' : 'minutes') + ' ago';
66 | } else {
67 | var hours = ("0" + then.getHours()).slice(-2);
68 | var minutes = ("0" + then.getMinutes()).slice(-2);
69 | var month = ("0" + then.getMonth()).slice(-2);
70 | var day = ("0" + then.getDay()).slice(-2);
71 | var year = then.getYear();
72 | return 'at ' + (year + 1900) + '-' + month + '-' + day + ' ' + hours + ':' + minutes;
73 | }
74 | }
75 | })
76 |
77 | .filter('utcToBrowser', function () {
78 | return function (input) {
79 | if (input === undefined) {
80 | return "";
81 | }
82 | var d = new Date(input + " UTC");
83 |
84 | var day = d.getDate();
85 | var month = d.getMonth();
86 | var year = d.getFullYear();
87 |
88 | return day + ' ' + month+1 + ' ' + year;
89 | }
90 | })
91 |
92 | .filter('rawHTML', ['$sce', function ($sce) {
93 | return function (input) {
94 | return $sce.trustAsHtml(input);
95 | }
96 | }])
97 |
98 | .filter('roundCeil', function () {
99 | return function (input) {
100 | return Math.ceil(input);
101 | }
102 | });
103 | })();
--------------------------------------------------------------------------------
/src/pineapple/js/helpers.js:
--------------------------------------------------------------------------------
1 | function registerController(name, controller) {
2 | angular.module('pineapple').controllerProvider.register(name, controller);
3 | }
4 |
5 | function resizeModuleContent() {
6 | var offset = 50;
7 | var height = ((window.innerHeight > 0) ? window.innerHeight : screen.height) - 1;
8 | height = height - offset;
9 | if (height < 1) height = 1;
10 | if (height > offset) {
11 | $(".module-content").css("min-height", (height) + "px");
12 | }
13 | }
14 |
15 | function convertMACAddress(mac) {
16 | var pattern = /([-: ])/igm;
17 | return mac.replace(pattern, ":");
18 | }
19 |
20 | function locallyAssigned(mac) {
21 | return (parseInt('0x' + mac.split(':')[0]) & 0x02) !== 0;
22 | }
23 |
24 | function annotateMacs() {
25 | var mac_rows = $('td, .autoselect').filter(
26 | function() {
27 | return /^[0-9a-f]{1,2}([.:-])[0-9a-f]{1,2}(?:\1[0-9a-f]{1,2}){4}$/i.test(this.textContent.trim());
28 | });
29 | mac_rows.filter(function() {
30 | return locallyAssigned(this.textContent.trim());
31 | }).prop('title', 'This MAC was likely locally assigned and was not assigned by the hardware vendor. This could be the result of MAC randomization, Spoofing, or a vendor that has not registered with the IEEE Registration Authority.').css('color', '#31708f');
32 | mac_rows.filter(function() {
33 | return !locallyAssigned(this.textContent.trim());
34 | }).prop('title', 'This MAC was likely globally assigned by the hardware vendor. It has probably not been randomized for privacy.');
35 | }
36 |
37 | function utcDate(timestampStr) {
38 | var a = timestampStr.split(' ');
39 | var dmy = a[0].split('-');
40 | var hms = a[1].split(':');
41 |
42 | return new Date(Date.UTC(dmy[0], dmy[1] - 1, dmy[2], hms[0], hms[1], hms[2]));
43 | }
44 |
45 | function selectElement(elem) {
46 | var selectRange = document.createRange();
47 | selectRange.selectNodeContents(elem);
48 | var selection = window.getSelection();
49 | selection.removeAllRanges();
50 | selection.addRange(selectRange);
51 | }
52 |
53 | function loadActiveTabData() {
54 | $('.tab-pane.active .controller').each(function(index) {
55 | var controller = angular.element( $(this)[0] ).scope();
56 | if (typeof controller.reloadData === "function") {
57 | controller.reloadData();
58 | }
59 | });
60 | }
61 |
62 | $('html').click(function(e){
63 | var elem = e.toElement;
64 | if (elem !== undefined && elem.classList.contains('autoselect')) {
65 | selectElement(elem);
66 | }
67 | });
68 |
69 | $(window).resize(function() {
70 | resizeModuleContent();
71 | });
72 |
73 | setInterval(annotateMacs, 1500);
74 |
--------------------------------------------------------------------------------
/src/pineapple/js/pineapple.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | var pineapple = angular.module('pineapple', ['ngRoute', 'ngCookies'])
3 |
4 | .config(['$routeProvider', '$controllerProvider', '$compileProvider', '$filterProvider', '$provide', function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
5 | pineapple.controllerProvider = $controllerProvider;
6 | pineapple.compileProvider = $compileProvider;
7 | pineapple.routeProvider = $routeProvider;
8 | pineapple.filterProvider = $filterProvider;
9 | pineapple.provide = $provide;
10 | }])
11 |
12 | .run(['$api', function($api){
13 | window.pineapple = $api;
14 |
15 | pineapple.routeProvider
16 | .when('/modules/:moduleName', {
17 | templateUrl: function(params) {
18 | return 'modules/'+ params.moduleName +'/module.html';
19 | },
20 | controller: function() {
21 | resizeModuleContent();
22 | },
23 | resolve: {
24 | jsLoader: ['$route', function($route) {
25 | return $.getScript('modules/'+ $route.current.params.moduleName +'/js/module.js');
26 | }]
27 | }
28 | })
29 | .otherwise({
30 | redirectTo: '/modules/Dashboard'
31 | });
32 | }])
33 | })();
34 |
--------------------------------------------------------------------------------
/src/pineapple/js/vendor/angular-cookies.min.js:
--------------------------------------------------------------------------------
1 | (function(n,c){'use strict';function l(b,a,g){var d=g.baseHref(),k=b[0];return function(b,e,f){var g,h;f=f||{};h=f.expires;g=c.isDefined(f.path)?f.path:d;c.isUndefined(e)&&(h="Thu, 01 Jan 1970 00:00:00 GMT",e="");c.isString(h)&&(h=new Date(h));e=encodeURIComponent(b)+"="+encodeURIComponent(e);e=e+(g?";path="+g:"")+(f.domain?";domain="+f.domain:"");e+=h?";expires="+h.toUTCString():"";e+=f.secure?";secure":"";f=e.length+1;4096 4096 bytes)!");k.cookie=e}}c.module("ngCookies",["ng"]).info({angularVersion:"1.6.10"}).provider("$cookies",[function(){var b=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(a,g){return{get:function(d){return a()[d]},getObject:function(d){return(d=this.get(d))?c.fromJson(d):d},getAll:function(){return a()},put:function(d,a,m){g(d,a,m?c.extend({},b,m):b)},putObject:function(d,b,a){this.put(d,c.toJson(b),a)},remove:function(a,k){g(a,void 0,k?c.extend({},b,k):b)}}}]}]);c.module("ngCookies").factory("$cookieStore",
3 | ["$cookies",function(b){return{get:function(a){return b.getObject(a)},put:function(a,c){b.putObject(a,c)},remove:function(a){b.remove(a)}}}]);l.$inject=["$document","$log","$browser"];c.module("ngCookies").provider("$$cookieWriter",function(){this.$get=l})})(window,window.angular);
4 |
--------------------------------------------------------------------------------
/src/pineapple/js/vendor/angular-route.min.js:
--------------------------------------------------------------------------------
1 | (function(J,d){'use strict';function A(d){k&&d.get("$route")}function B(t,u,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,f,b,c,m){function v(){l&&(g.cancel(l),l=null);n&&(n.$destroy(),n=null);p&&(l=g.leave(p),l.done(function(a){!1!==a&&(l=null)}),p=null)}function E(){var b=t.current&&t.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),c=t.current;p=m(b,function(b){g.enter(b,null,p||f).done(function(b){!1===b||!d.isDefined(w)||w&&!a.$eval(w)||u()});
2 | v()});n=c.scope=b;n.$emit("$viewContentLoaded");n.$eval(k)}else v()}var n,p,l,w=b.autoscroll,k=b.onload||"";a.$on("$routeChangeSuccess",E);E()}}}function C(d,k,g){return{restrict:"ECA",priority:-400,link:function(a,f){var b=g.current,c=b.locals;f.html(c.$template);var m=d(f.contents());if(b.controller){c.$scope=a;var v=k(b.controller,c);b.controllerAs&&(a[b.controllerAs]=v);f.data("$ngControllerController",v);f.children().data("$ngControllerController",v)}a[b.resolveAs||"$resolve"]=c;m(a)}}}var x,
3 | y,F,G,z=d.module("ngRoute",[]).info({angularVersion:"1.6.10"}).provider("$route",function(){function t(a,f){return d.extend(Object.create(a),f)}function u(a,d){var b=d.caseInsensitiveMatch,c={originalPath:a,regexp:a},g=c.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)(\*\?|[?*])?/g,function(a,b,d,c){a="?"===c||"*?"===c?"?":null;c="*"===c||"*?"===c?"*":null;g.push({name:d,optional:!!a});b=b||"";return""+(a?"":b)+"(?:"+(a?b:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([/$*])/g,
4 | "\\$1");c.regexp=new RegExp("^"+a+"$",b?"i":"");return c}x=d.isArray;y=d.isObject;F=d.isDefined;G=d.noop;var g={};this.when=function(a,f){var b;b=void 0;if(x(f)){b=b||[];for(var c=0,m=f.length;c /sys/bus/usb/drivers/usb/unbind > /dev/null 2>&1
12 | sleep 2
13 | echo '$DEVICE' > /sys/bus/usb/drivers/usb/bind > /dev/null 2>&1
14 | }
15 |
16 | touch /tmp/sd_format.progress
17 |
18 | umount /sd
19 | swapoff /dev/sdcard/sd2
20 |
21 | reset_sd
22 | sleep 5
23 |
24 | sleep 2
25 | cat /pineapple/modules/Advanced/formatSD/fdisk_options | fdisk /dev/sdcard/sd
26 | sleep 2
27 |
28 | umount /sd
29 | mkfs.ext4 -F /dev/sdcard/sd1
30 |
31 | sleep 2
32 |
33 | swapoff /dev/sdcard/sd2
34 | mkfs.ext4 -F /dev/sdcard/sd2
35 |
36 |
37 | mkswap /dev/sdcard/sd2
38 |
39 | mount /dev/sdcard/sd1 /sd
40 | swapon /dev/sdcard/sd2
41 |
42 | rm /tmp/sd_format.progress
--------------------------------------------------------------------------------
/src/pineapple/modules/Advanced/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Information on system resources and firmware.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Advanced",
10 | "version": "1.0",
11 | "index": 12
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Advanced/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Clients/api/module.php:
--------------------------------------------------------------------------------
1 | dbConnection = false;
11 |
12 | $dbLocation = $this->uciGet("pineap.@config[0].hostapd_db_path");
13 | if (file_exists($dbLocation)) {
14 | $this->dbConnection = new DatabaseConnection($dbLocation);
15 | }
16 | }
17 |
18 | public function route()
19 | {
20 | switch ($this->request->action) {
21 | case 'getClientData':
22 | $this->getClientData();
23 | break;
24 | case 'kickClient':
25 | $this->kickClient();
26 | break;
27 | }
28 | }
29 |
30 | private function getLeases() {
31 | $dhcpReport = array();
32 | $leases = explode("\n", @file_get_contents('/var/dhcp.leases'));
33 | if ($leases) {
34 | foreach ($leases as $lease) {
35 | $dhcpReport[explode(' ', $lease)[1]] = array_slice(explode(' ', $lease), 2, 2);
36 | }
37 | }
38 | return $dhcpReport;
39 | }
40 |
41 | private function getARPData() {
42 | $arpReport = array();
43 | exec('cat /proc/net/arp | awk \'{ if ($1 != "IP") {printf "%s %s\n", $1, $4;}}\'', $arpEntries);
44 | foreach ($arpEntries as $arpEntry) {
45 | $arpEntryArray = explode(' ', $arpEntry);
46 | $arpReport[$arpEntryArray[1]] = $arpEntryArray[0];
47 | }
48 | return $arpReport;
49 | }
50 |
51 | private function getSSIDData()
52 | {
53 | $ssidData = array();
54 | $clientRows = $this->dbConnection->query("SELECT DISTINCT mac,ssid FROM log WHERE log_type=1 ORDER BY updated_at ASC;");
55 | foreach ($clientRows as $row) {
56 | $ssidData[strtolower($row['mac'])] = $row['ssid'];
57 | }
58 | return $ssidData;
59 | }
60 |
61 | private function getStations() {
62 | $stationsReport = array();
63 | exec('
64 | iw dev wlan0 station dump |
65 | awk \'{ if ($1 == "Station") { printf "%s ", $2; } else if ($1 == "inactive") {print $3;} }\'
66 | ', $stations);
67 | foreach ($stations as $key => $station) {
68 | if (empty($station)) {
69 | continue;
70 | }
71 | $stationArray = explode(' ', $station);
72 | $stationsReport[$stationArray[0]] = $stationArray[1];
73 | }
74 | exec('
75 | iw dev wlan0-2 station dump |
76 | awk \'{ if ($1 == "Station") { printf "%s ", $2; } else if ($1 == "inactive") {print $3;} }\'
77 | ', $stations);
78 | foreach ($stations as $key => $station) {
79 | if (empty($station)) {
80 | continue;
81 | }
82 | $stationArray = explode(' ', $station);
83 | $stationsReport[$stationArray[0]] = $stationArray[1];
84 | }
85 | return $stationsReport;
86 | }
87 |
88 | private function getClientData()
89 | {
90 | $connectedClients = array();
91 | $stationData = $this->getStations();
92 | $dhcpData = $this->getLeases();
93 | $arpData = $this->getARPData();
94 | $ssidData = $this->getSSIDData();
95 | foreach ($stationData as $mac => $signal) {
96 | $connectedClients[] = [
97 | 'mac' => $mac,
98 | 'ip' => $arpData[$mac],
99 | 'ssid' => $ssidData[$mac],
100 | 'host' => $dhcpData[$mac][1],
101 | ];
102 | }
103 | $this->response = array(
104 | 'clients' => $connectedClients
105 | );
106 | }
107 |
108 | private function kickClient()
109 | {
110 | exec("hostapd_cli -i wlan0 deauthenticate {$this->request->mac}");
111 | exec("hostapd_cli -i wlan0 disassociate {$this->request->mac}");
112 | exec("hostapd_cli -i wlan0-2 deauthenticate {$this->request->mac}");
113 | exec("hostapd_cli -i wlan0-2 disassociate {$this->request->mac}");
114 | $this->response = array('success' => true);
115 | }
116 | }
--------------------------------------------------------------------------------
/src/pineapple/modules/Clients/js/module.js:
--------------------------------------------------------------------------------
1 | registerController("ClientsController", ['$api', '$scope', '$timeout', function($api, $scope, $timeout){
2 | $scope.clients = [];
3 | $scope.loading = false;
4 |
5 | $scope.getClientData = function(){
6 | $scope.loading = true;
7 | $api.request({
8 | module: "Clients",
9 | action: "getClientData"
10 | }, function(response) {
11 | $scope.loading = false;
12 | $scope.parseClients(response.clients);
13 | });
14 | };
15 |
16 | $scope.parseClients = function($clients) {
17 | $scope.clients = $clients;
18 | };
19 |
20 | $scope.kickClient = function(client){
21 | $api.request({
22 | module: "Clients",
23 | action: "kickClient",
24 | mac: client.mac
25 | }, function(){
26 | client['kicking'] = true;
27 | $timeout(function() {
28 | client['kicking'] = false;
29 | $scope.getClientData();
30 | }, 3000);
31 | });
32 | };
33 |
34 | $scope.getClientData();
35 | }]);
--------------------------------------------------------------------------------
/src/pineapple/modules/Clients/module.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Clients Refresh
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | MAC Address
15 | IP Address
16 | SSID
17 | Hostname
18 | Kick Client
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{ client['mac'] }}
26 |
27 |
28 |
29 | {{ client['ip'] == null ? "No IP" : client['ip'] }}
30 |
31 |
32 |
33 |
34 |
35 | {{ client['ssid'] == null ? 'No SSID' : client['ssid'] }}
36 |
37 |
38 |
39 |
40 | {{ client['host'] == null ? 'No Hostname' : client['host'] }}
41 |
42 |
43 |
44 | Kick
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | No clients found.
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Clients/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "View and manage connected clients.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Clients",
10 | "version": "1.0",
11 | "index": 3
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Clients/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Configuration/api/landingpage_index.php:
--------------------------------------------------------------------------------
1 | exec('CREATE TABLE IF NOT EXISTS user_agents (browser TEXT NOT NULL);');
11 | $statement = $sqlite->prepare('INSERT INTO user_agents (browser) VALUES(:browser);');
12 | $statement->bindValue(':browser', $browser, SQLITE3_TEXT);
13 | try {
14 | $ret = $statement->execute();
15 | } catch (Exception $e) {
16 | return false;
17 | }
18 | return $ret;
19 | }
20 |
21 | function identifyUserAgent($userAgent)
22 | {
23 | if (preg_match('/^Mozilla/', $userAgent)) {
24 | if (preg_match('/Chrome\/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/', $userAgent)) {
25 | increment_browser('chrome');
26 | } elseif (preg_match('/Safari\/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/', $userAgent)) {
27 | increment_browser('safari');
28 | } elseif (strpos($userAgent, 'MSIE ') || preg_match('/Trident\/[0-9]/', $userAgent)) {
29 | increment_browser('internet_explorer');
30 | } elseif (preg_match('/Firefox\/[0-9]{1,3}\.[0-9]{1,3}/', $userAgent)) {
31 | increment_browser('firefox');
32 | } else {
33 | increment_browser('other');
34 | }
35 | } elseif (preg_match('/^Opera\/[0-9]{1,3}\.[0-9]/', $userAgent)) {
36 | increment_browser('opera');
37 | } else {
38 | increment_browser('other');
39 | }
40 | }
41 |
42 | identifyUserAgent($_SERVER['HTTP_USER_AGENT']);
43 |
44 | require_once('/etc/pineapple/landingpage.php');
45 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Configuration/api/module.php:
--------------------------------------------------------------------------------
1 | request->action) {
8 | case 'getCurrentTimeZone':
9 | $this->getCurrentTimeZone();
10 | break;
11 | case 'getLandingPageData':
12 | $this->getLandingPageData();
13 | break;
14 | case 'saveLandingPage':
15 | $this->saveLandingPageData();
16 | break;
17 | case 'changePass':
18 | $this->changePass();
19 | break;
20 | case 'changeTimeZone':
21 | $this->changeTimeZone();
22 | break;
23 | case 'resetPineapple':
24 | $this->resetPineapple();
25 | break;
26 | case 'haltPineapple':
27 | $this->haltPineapple();
28 | break;
29 | case 'rebootPineapple':
30 | $this->rebootPineapple();
31 | break;
32 | case 'getLandingPageStatus':
33 | $this->getLandingPageStatus();
34 | break;
35 | case 'getAutoStartStatus':
36 | $this->getAutoStartStatus();
37 | break;
38 | case 'enableLandingPage':
39 | $this->enableLandingPage();
40 | break;
41 | case 'disableLandingPage':
42 | $this->disableLandingPage();
43 | break;
44 | case 'enableAutoStart':
45 | $this->enableAutoStart();
46 | break;
47 | case 'disableAutoStart':
48 | $this->disableAutoStart();
49 | break;
50 | case 'getButtonScript':
51 | $this->getButtonScript();
52 | break;
53 | case 'saveButtonScript':
54 | $this->saveButtonScript();
55 | break;
56 | case 'getDevice':
57 | $this->getDeviceName();
58 | break;
59 | case 'getDeviceConfig':
60 | $this->getDeviceConfigArray();
61 | break;
62 | }
63 | }
64 |
65 | private function haltPineapple()
66 | {
67 | $this->execBackground("sync && led all off && halt");
68 | $this->response = array("success" => true);
69 | }
70 |
71 | private function rebootPineapple()
72 | {
73 | $this->execBackground("reboot");
74 | $this->response = array("success" => true);
75 | }
76 |
77 | private function resetPineapple()
78 | {
79 | $this->execBackground("jffs2reset -y && reboot &");
80 | $this->response = array("success" => true);
81 | }
82 |
83 | private function getCurrentTimeZone()
84 | {
85 | $currentTimeZone = exec('date +%Z%z');
86 | $this->response = array("currentTimeZone" => $currentTimeZone);
87 | }
88 |
89 | private function changeTimeZone()
90 | {
91 | $timeZone = $this->request->timeZone;
92 | file_put_contents('/etc/TZ', $timeZone);
93 | $this->uciSet('system.@system[0].timezone', $timeZone);
94 | $this->response = array("success" => true);
95 | }
96 |
97 | private function getLandingPageData()
98 | {
99 | $landingPage = file_get_contents('/etc/pineapple/landingpage.php');
100 | $this->response = array("landingPage" => $landingPage);
101 | }
102 |
103 | private function getLandingPageStatus()
104 | {
105 | if (!empty(exec("iptables -L -vt nat | grep 'www to:.*:80'"))) {
106 | $this->response = array("enabled" => true);
107 | return;
108 | }
109 | $this->response = array("enabled" => false);
110 | }
111 |
112 | private function enableLandingPage()
113 | {
114 | exec('iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination $(uci get network.lan.ipaddr):80');
115 | exec('iptables -t nat -A POSTROUTING -j MASQUERADE');
116 | copy('/pineapple/modules/Configuration/api/landingpage_index.php', '/www/index.php');
117 | $this->response = array("success" => true);
118 | }
119 |
120 | private function disableLandingPage()
121 | {
122 | @unlink('/www/index.php');
123 | exec('iptables -t nat -D PREROUTING -p tcp --dport 80 -j DNAT --to-destination $(uci get network.lan.ipaddr):80');
124 | $this->response = array("success" => true);
125 | }
126 |
127 | private function getAutoStartStatus()
128 | {
129 | if($this->uciGet("landingpage.@settings[0].autostart") == 1) {
130 | $this->response = array("enabled" => true);
131 | } else {
132 | $this->response = array("enabled" => false);
133 | }
134 | }
135 |
136 | private function enableAutoStart()
137 | {
138 | $this->uciSet("landingpage.@settings[0].autostart", "1");
139 | $this->response = array("success" => true);
140 | }
141 |
142 | private function disableAutoStart()
143 | {
144 | $this->uciSet("landingpage.@settings[0].autostart", "0");
145 | $this->response = array("success" => true);
146 | }
147 |
148 | private function saveLandingPageData()
149 | {
150 | if (file_put_contents('/etc/pineapple/landingpage.php', $this->request->landingPageData) !== false) {
151 | $this->response = array("success" => true);
152 | } else {
153 | $this->error = "Error saving Landing Page.";
154 | }
155 | }
156 |
157 | private function getButtonScript()
158 | {
159 | if (file_exists('/etc/pineapple/button_script')) {
160 | $script = file_get_contents('/etc/pineapple/button_script');
161 | $this->response = array("buttonScript" => $script);
162 | } else {
163 | $this->error = "The button script does not exist.";
164 | }
165 | }
166 |
167 | private function saveButtonScript()
168 | {
169 | if (file_exists('/etc/pineapple/button_script')) {
170 | file_put_contents('/etc/pineapple/button_script', $this->request->buttonScript);
171 | $this->response = array("success" => true);
172 | } else {
173 | $this->error = "The button script does not exist.";
174 | }
175 | }
176 |
177 | private function getDeviceName()
178 | {
179 | $this->response = array("device" => $this->getDevice());
180 | }
181 |
182 | private function getDeviceConfigArray()
183 | {
184 | $this->response = array("config" => $this->getDeviceConfig());
185 | }
186 |
187 | protected function changePass()
188 | {
189 | if ($this->request->newPassword === $this->request->newPasswordRepeat) {
190 | if (parent::changePassword($this->request->oldPassword, $this->request->newPassword) === true) {
191 | $this->response = array("success" => true);
192 | return;
193 | }
194 | }
195 |
196 | $this->response = array("success" => false);
197 | }
198 | }
--------------------------------------------------------------------------------
/src/pineapple/modules/Configuration/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Manage general settings and the landing page.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Configuration",
10 | "version": "1.0",
11 | "index": 11
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Configuration/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Dashboard/api/module.php:
--------------------------------------------------------------------------------
1 | dbConnection = false;
12 | if (file_exists('/tmp/landingpage.db')) {
13 | $this->dbConnection = new DatabaseConnection('/tmp/landingpage.db');
14 | }
15 | }
16 |
17 | public function route()
18 | {
19 | switch ($this->request->action) {
20 | case 'getOverviewData':
21 | $this->getOverviewData();
22 | break;
23 |
24 | case 'getLandingPageData':
25 | $this->getLandingPageData();
26 | break;
27 |
28 | case 'getBulletins':
29 | $this->getBulletins();
30 | break;
31 | }
32 | }
33 |
34 | private function getOverviewData()
35 | {
36 | $this->response = [
37 | "cpu" => $this->getCpu(),
38 | "uptime" => $this->getUptime(),
39 | "clients" => $this->getClients()
40 | ];
41 | }
42 |
43 | private function getCpu()
44 | {
45 | $loads = sys_getloadavg();
46 | $load = round($loads[0]/2*100, 1);
47 |
48 | if ($load > 100) {
49 | return '100';
50 | }
51 |
52 | return $load;
53 | }
54 |
55 | private function getUptime()
56 | {
57 | $seconds = intval(explode('.', file_get_contents('/proc/uptime'))[0]);
58 | $days = floor($seconds / (24 * 60 * 60));
59 | $hours = floor(($seconds % (24 * 60 * 60)) / (60 * 60));
60 | if ($days > 0) {
61 | return $days . ($days == 1 ? " day, " : " days, ") . $hours . ($hours == 1 ? " hour" : " hours");
62 | }
63 | $minutes = floor(($seconds % (60 * 60)) / 60);
64 | return $hours . ($hours == 1 ? " hour, " : " hours, ") . $minutes . ($minutes == 1 ? " minute" : " minutes");
65 | }
66 |
67 | private function getClients()
68 | {
69 | return exec('iw dev wlan0 station dump | grep Station | wc -l');
70 | }
71 |
72 | private function getLandingPageData()
73 | {
74 | if ($this->dbConnection !== false) {
75 | $stats = [];
76 | $stats['Chrome'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'chrome\';'));
77 | $stats['Safari'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'safari\';'));
78 | $stats['Firefox'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'firefox\';'));
79 | $stats['Opera'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'opera\';'));
80 | $stats['Internet Explorer'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'internet_explorer\';'));
81 | $stats['Other'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'other\';'));
82 | $this->response = $stats;
83 | } else {
84 | $this->error = "A connection to the database is not established.";
85 | }
86 | }
87 |
88 |
89 | private function getBulletins()
90 | {
91 | $bulletinData = @$this->fileGetContentsSSL(self::REMOTE_URL . "/json/news.json");
92 | if ($bulletinData !== false) {
93 | $this->response = json_decode($bulletinData);
94 | if (json_last_error() === JSON_ERROR_NONE) {
95 | return;
96 | }
97 | }
98 |
99 | $this->error = "Error connecting to " . self::REMOTE_NAME . ". Please check your connection.";
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Dashboard/js/module.js:
--------------------------------------------------------------------------------
1 | registerController("DashboardOverviewController", ['$api', '$scope', '$interval', function($api, $scope, $interval) {
2 | $scope.cpu = "";
3 | $scope.uptime = "";
4 | $scope.clients = "";
5 | $scope.ssids = "";
6 | $scope.newssids = "";
7 |
8 | $scope.populateDashboard = (function() {
9 | $api.request({
10 | module: "Dashboard",
11 | action: "getOverviewData"
12 | }, function(response) {
13 | $scope.cpu = response.cpu;
14 | $scope.uptime = response.uptime;
15 | $scope.clients = response.clients;
16 | });
17 | $api.request({
18 | module: "PineAP",
19 | action: "countSSIDs"
20 | }, function(response) {
21 | $scope.ssids = response.SSIDs;
22 | $scope.newssids = response.newSSIDs;
23 | });
24 | });
25 |
26 | $scope.populateInterval = $interval(function(){
27 | $scope.populateDashboard();
28 | }, 10000);
29 |
30 | $scope.populateDashboard();
31 | $scope.$on('$destroy', function() {
32 | $interval.cancel($scope.populateInterval);
33 | });
34 | }]);
35 |
36 | registerController("DashboardLandingPageController", ['$api', '$scope', function($api, $scope){
37 | $scope.browsers = [];
38 |
39 | $api.request({
40 | module: "Dashboard",
41 | action: "getLandingPageData"
42 | }, function(response){
43 | if (response.error === undefined) {
44 | $scope.browsers = response;
45 | }
46 | });
47 | }]);
48 |
49 | registerController("DashboardBulletinsController", ['$api', '$scope', function($api, $scope){
50 | $scope.bulletins = [];
51 |
52 | $scope.getBulletins = function() {
53 | $scope.loading = true;
54 |
55 | $api.request({
56 | module: "Dashboard",
57 | action: "getBulletins"
58 | }, function(response){
59 | $scope.loading = false;
60 | if (response.error !== undefined) {
61 | $scope.error = response.error;
62 | } else {
63 | $scope.bulletins = response;
64 | $scope.error = false;
65 | }
66 | });
67 | }
68 | }]);
--------------------------------------------------------------------------------
/src/pineapple/modules/Dashboard/module.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ uptime }}
7 |
UPTIME
8 |
9 |
12 |
13 |
14 |
15 |
28 |
29 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
Landing Page Browser Stats
51 |
52 |
53 |
54 |
55 |
56 | Chrome
57 |
58 | {{ browsers['Chrome'] }}
59 |
60 |
61 |
62 |
63 | Firefox
64 |
65 | {{ browsers['Firefox'] }}
66 |
67 |
68 |
69 |
70 | Internet Explorer
71 |
72 | {{ browsers['Internet Explorer'] }}
73 |
74 |
75 |
76 |
77 | Opera
78 |
79 | {{ browsers['Opera'] }}
80 |
81 |
82 |
83 |
84 | Safari
85 |
86 | {{ browsers['Safari'] }}
87 |
88 |
89 |
90 | Other
91 |
92 | {{ browsers['Other'] }}
93 |
94 |
95 |
96 |
97 | No Landing Page Browser Stats Available
98 |
99 |
100 |
101 |
102 |
103 |
News
104 |
105 |
106 |
107 |
{{ bulletin.title }} {{ bulletin.date }}
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | Load project news!
119 |
120 |
121 |
122 | {{ error }}
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | Notifications
135 |
136 |
137 | Clear
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | {{ notification.message }}
146 | {{ notification.time | timesincedate }}
147 |
148 |
149 |
150 |
151 |
152 | No Notifications
153 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Dashboard/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "At-a-glance view of the WiFi Pineapple.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Dashboard",
10 | "version": "1.0",
11 | "index": 1
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Dashboard/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Filters/js/module.js:
--------------------------------------------------------------------------------
1 | registerController('clientFilterController', ['$api', '$scope', function($api, $scope) {
2 | $scope.mode = '';
3 | $scope.mac = '';
4 | $scope.clientFilters = '';
5 |
6 | $scope.clearAll = (function() {
7 | $api.request({
8 | module: "Filters",
9 | action: "removeClients",
10 | clients: $scope.clientFilters.split("\n")
11 | }, function(response) {
12 | $scope.clientFilters = response.clientFilters;
13 | });
14 | });
15 |
16 | $scope.toggleMode = (function() {
17 | if ($scope.mode === 'Allow') {
18 | $scope.mode = 'Deny';
19 | } else {
20 | $scope.mode = 'Allow';
21 | }
22 | $api.request({
23 | module: 'Filters',
24 | action: 'toggleClientMode',
25 | mode: $scope.mode
26 | });
27 | });
28 |
29 | $scope.addClient = (function() {
30 | $api.request({
31 | module: 'Filters',
32 | action: 'addClient',
33 | mac: convertMACAddress($scope.mac)
34 | }, function(response) {
35 | if (response.error === undefined) {
36 | $scope.clientFilters = response.clientFilters;
37 | $scope.mac = "";
38 | }
39 | });
40 | });
41 |
42 | $scope.removeClient = (function() {
43 | $api.request({
44 | module: 'Filters',
45 | action: 'removeClient',
46 | mac: convertMACAddress($scope.mac)
47 | }, function(response) {
48 | if (response.error === undefined) {
49 | $scope.clientFilters = response.clientFilters;
50 | $scope.mac = "";
51 | }
52 | });
53 | });
54 |
55 | $api.request({
56 | module: 'Filters',
57 | action: 'getClientData'
58 | }, function(response) {
59 | if (response.error === undefined) {
60 | $scope.mode = response.mode;
61 | $scope.clientFilters = response.clientFilters;
62 | }
63 | });
64 | }]);
65 |
66 | registerController('ssidFilterController', ['$api', '$scope', function($api, $scope) {
67 | $scope.mode = '';
68 | $scope.ssid = '';
69 | $scope.ssidFilters = '';
70 |
71 | $scope.clearAll = (function() {
72 | $api.request({
73 | module: "Filters",
74 | action: "removeSSIDs",
75 | ssids: $scope.ssidFilters.split("\n")
76 | }, function(response) {
77 | $scope.ssidFilters = response.ssidFilters;
78 | });
79 | });
80 |
81 | $scope.toggleMode = (function() {
82 | if ($scope.mode === 'Allow') {
83 | $scope.mode = 'Deny';
84 | } else {
85 | $scope.mode = 'Allow';
86 | }
87 | $api.request({
88 | module: 'Filters',
89 | action: 'toggleSSIDMode',
90 | mode: $scope.mode
91 | });
92 | });
93 |
94 | $scope.addSSID = (function() {
95 | $api.request({
96 | module: 'Filters',
97 | action: 'addSSID',
98 | ssid: $scope.ssid
99 | }, function(response) {
100 | if (response.error === undefined) {
101 | $scope.ssidFilters = response.ssidFilters;
102 | $scope.ssid = "";
103 | }
104 | });
105 | });
106 |
107 | $scope.removeSSID = (function() {
108 | $api.request({
109 | module: 'Filters',
110 | action: 'removeSSID',
111 | ssid: $scope.ssid
112 | }, function(response) {
113 | if (response.error === undefined) {
114 | $scope.ssidFilters = response.ssidFilters;
115 | $scope.ssid = "";
116 | }
117 | });
118 | });
119 |
120 | $api.request({
121 | module: 'Filters',
122 | action: 'getSSIDData'
123 | }, function(response) {
124 | if (response.error === undefined) {
125 | $scope.mode = response.mode;
126 | $scope.ssidFilters = response.ssidFilters;
127 | }
128 | });
129 | }]);
130 |
131 | function getClientLineNumber(textarea) {
132 | var lineNumber = textarea.value.substr(0, textarea.selectionStart).split("\n").length;
133 | var mac = textarea.value.split("\n")[lineNumber-1].trim();
134 | $("input[name='mac']").val(mac).trigger('input');
135 | }
136 |
137 | function getSSIDLineNumber(textarea) {
138 | var lineNumber = textarea.value.substr(0, textarea.selectionStart).split("\n").length;
139 | var ssid = textarea.value.split("\n")[lineNumber-1].trim();
140 | $("input[name='ssid']").val(ssid).trigger('input');
141 | }
--------------------------------------------------------------------------------
/src/pineapple/modules/Filters/module.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Client Filtering
7 |
8 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{ mode }} Listed MAC(s)
21 |
22 | Switch
23 |
24 |
25 |
26 |
27 |
29 |
30 |
31 |
32 |
33 | Add
34 | Remove
35 |
36 |
37 |
38 |
39 |
Client filters specify which devices, by MAC address, are either explicitly allowed to
40 | connect or
41 | explicitly denied from connecting. In Allow Mode only the listed MAC Addresses are allowed to
42 | connect. In Deny Mode, the listed MAC addresses will be prevented from connecting.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | SSID Filtering
53 |
54 |
57 |
61 |
62 |
63 |
64 |
65 |
66 | {{ mode }} Listed SSID(s)
67 |
68 | Switch
69 |
70 |
71 |
72 |
73 |
75 |
76 |
77 |
78 |
79 | Add
80 | Remove
81 |
82 |
83 |
84 |
85 |
SSID filters specify the network names to which the WiFi Pineapple will respond. In Allow Mode,
86 | devices will only be allowed to associate with the WiFi Pineapple for SSID names listed. In Deny
87 | Mode, devices will be prevented from associating with the WiFi Pineapple for the listed SSID
88 | names.
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Filters/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Filters",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Filters",
10 | "version": "1.0",
11 | "index": 5
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Filters/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Help/api/module.php:
--------------------------------------------------------------------------------
1 | request->action) {
8 | case 'generateDebugFile':
9 | $this->generateDebugFile();
10 | break;
11 |
12 | case 'downloadDebugFile':
13 | $this->downloadDebugFile();
14 | break;
15 |
16 | case 'getConsoleOutput':
17 | $this->getConsoleOutput();
18 | break;
19 | }
20 | }
21 |
22 | private function generateDebugFile()
23 | {
24 | @unlink('/tmp/debug.log');
25 | $this->execBackground("(/pineapple/modules/Help/files/debug 2>&1) > /tmp/debug_generation_output");
26 | $this->response = array("success" => true);
27 | }
28 |
29 | private function downloadDebugFile()
30 | {
31 | if (!file_exists('/tmp/debug.log')) {
32 | $this->error = "The debug file is missing.";
33 | return;
34 | }
35 | $this->response = array("success" => true, "downloadToken" => $this->downloadFile("/tmp/debug.log"));
36 | }
37 |
38 | private function getConsoleOutput()
39 | {
40 | $output = "";
41 | if (file_exists("/tmp/debug_generation_output")) {
42 | $output = file_get_contents("/tmp/debug_generation_output");
43 | }
44 | $this->response = array("output" => $output);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Help/files/debug:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Simple script to gather log information
3 | LOG=/tmp/debug.log.tmp
4 |
5 | touch $LOG
6 | echo "Retrieving /proc/cpuinfo"
7 | echo "============CPUINFO========" > $LOG
8 | cat /proc/cpuinfo >> $LOG
9 | echo "Retrieving firmware version"
10 | echo -n "Firmware Version: " >> $LOG && cat /pineapple/pineapple_version >> $LOG
11 | echo "Retrieving lsusb output"
12 | echo "=============LSUSB=========" >> $LOG
13 | lsusb >> $LOG
14 | echo "Retrieving disk usage information"
15 | echo "==============DF===========" >> $LOG
16 | df -h >> $LOG
17 | echo "Retrieving iw device list"
18 | echo "==============IW===========" >> $LOG
19 | iw dev >> $LOG
20 | echo "Retrieving ifconfig interface list"
21 | echo "===========IFCONFIG========" >> $LOG
22 | ifconfig -a >> $LOG
23 | echo "Retrieving iwconfig device list"
24 | echo "===========IWCONFIG========" >> $LOG
25 | (iwconfig 2>&1) >> $LOG
26 | echo "Retrieving dmesg log"
27 | echo "============DMESG==========" >> $LOG
28 | dmesg >> $LOG
29 | echo "Retrieving syslog"
30 | echo "============LOGREAD========" >> $LOG
31 | logread >> $LOG
32 | echo "Retrieving /etc/config/wireless"
33 | echo "===== WIRELESS CONFIG======" >> $LOG
34 | cat /etc/config/wireless >> $LOG
35 | echo "Performing site survey"
36 | echo "============SURVEY=========" >> $LOG
37 | echo -e "\tEnsuring pineapd is started"
38 | (/etc/init.d/pineapd start 2>&1) >> $LOG
39 | scan_type=0
40 | cat /pineapple/config.php | grep "'tetra'" && scan_type=2
41 | echo -e "\tRunning scan type $scan_type for 15 seconds"
42 | (/usr/bin/pineap /tmp/pineap.conf run_scan 15 $scan_type 2>&1) >> $LOG
43 | sleep 2
44 | scan_id="$(/usr/bin/pineap /tmp/pineap.conf get_status | grep scanID | awk '{print $2}' | sed 's/,//')"
45 | echo -e "\tNew scan id: $scan_id"
46 | echo -e "\tWaiting for scan to finish"
47 | while [ "$(/usr/bin/pineap /tmp/pineap.conf get_status | grep 'scanRunning' | awk '{print $2}' | sed 's/,//')" == "true" ]; do sleep 1; done
48 | echo -e "\tRetrieving scan results, appending to debug log"
49 | chmod a+x /pineapple/modules/Help/files/dumpscan.php
50 | /pineapple/modules/Help/files/dumpscan.php $scan_id >> $LOG
51 | echo "Renaming debug file"
52 | mv $LOG /tmp/debug.log
53 | echo "Completed Debug Filed Generation"
54 | logger "Completed Debug File Generation"
--------------------------------------------------------------------------------
/src/pineapple/modules/Help/files/dumpscan.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php-cgi -q
2 | error['databaseConnectionError'])) {
91 | exit($dbConnection->strError() . "\n");
92 | }
93 |
94 | $data = array();
95 | $data[$scanID] = array();
96 | $aps = $dbConnection->query("SELECT scan_id, ssid, bssid, encryption, hidden, channel, signal, wps, last_seen FROM aps WHERE scan_id='%d';", $scanID);
97 | foreach ($aps as $ap_row) {
98 | $data[$scanID]['aps'][$ap_row['bssid']] = array();
99 | $data[$scanID]['aps'][$ap_row['bssid']]['ssid'] = $ap_row['ssid'];
100 | $data[$scanID]['aps'][$ap_row['bssid']]['encryption'] = printEncryption($ap_row['encryption']);
101 | $data[$scanID]['aps'][$ap_row['bssid']]['hidden'] = $ap_row['hidden'];
102 | $data[$scanID]['aps'][$ap_row['bssid']]['channel'] = $ap_row['channel'];
103 | $data[$scanID]['aps'][$ap_row['bssid']]['signal'] = $ap_row['signal'];
104 | $data[$scanID]['aps'][$ap_row['bssid']]['wps'] = $ap_row['wps'];
105 | $data[$scanID]['aps'][$ap_row['bssid']]['last_seen'] = $ap_row['last_seen'];
106 | $data[$scanID]['aps'][$ap_row['bssid']]['clients'] = array();
107 | $clients = $dbConnection->query("SELECT scan_id, mac, bssid, last_seen FROM clients WHERE scan_id='%d' AND bssid='%s';", $ap_row['scan_id'], $ap_row['bssid']);
108 | foreach ($clients as $client_row) {
109 | $data[$scanID]['aps'][$ap_row['bssid']]['clients'][$client_row['mac']] = array();
110 | $data[$scanID]['aps'][$ap_row['bssid']]['clients'][$client_row['mac']]['bssid'] = $client_row['bssid'];
111 | $data[$scanID]['aps'][$ap_row['bssid']]['clients'][$client_row['mac']]['last_seen'] = $client_row['last_seen'];
112 | }
113 | }
114 |
115 | $data[$scanID]['outOfRangeClients'] = array();
116 | $clients = $dbConnection->query("
117 | SELECT t1.mac, t1.bssid, t1.last_seen FROM clients t1
118 | LEFT JOIN aps t2 ON
119 | t2.bssid = t1.bssid WHERE t2.bssid IS NULL AND
120 | t1.bssid != 'FF:FF:FF:FF:FF:FF' COLLATE NOCASE AND t1.scan_id='%d';
121 | ", $scanID);
122 |
123 | foreach ($clients as $client_row) {
124 | $data[$scanID]['outOfRangeClients'][$client_row['mac']] = array();
125 | $data[$scanID]['outOfRangeClients'][$client_row['mac']] = $client_row['bssid'];
126 | }
127 |
128 | $data[$scanID]['unassociatedClients'] = array();
129 | $clients = $dbConnection->query("SELECT mac FROM clients WHERE bssid='FF:FF:FF:FF:FF:FF' COLLATE NOCASE;");
130 |
131 | foreach ($clients as $client_row) {
132 | $data[$scanID]['unassociatedClients'][] = $client_row['mac'];
133 | }
134 |
135 | file_put_contents("php://stdout", json_encode($data, JSON_PRETTY_PRINT));
--------------------------------------------------------------------------------
/src/pineapple/modules/Help/js/module.js:
--------------------------------------------------------------------------------
1 | registerController("DebugController", ['$api', '$scope', '$timeout', '$interval', function($api, $scope, $timeout, $interval){
2 | $scope.loading = false;
3 | $scope.debugStarted = false;
4 | $scope.output = "";
5 | $scope.getOutputInterval = null;
6 |
7 | $scope.generateDebugFile = (function(){
8 | $api.request({
9 | module: "Help",
10 | action: "generateDebugFile"
11 | }, function(response) {
12 | if (response.success === true) {
13 | $scope.loading = true;
14 | $scope.debugStarted = true;
15 | $timeout($scope.downloadDebugFile, 15000);
16 | if ($scope.getOutputInterval === null) {
17 | $scope.getOutputInterval = $interval($scope.getOutput, 700);
18 | }
19 | }
20 | })
21 | });
22 |
23 | $scope.getOutput = (function() {
24 | $api.request({
25 | module: "Help",
26 | action: "getConsoleOutput"
27 | }, function(response) {
28 | $scope.output = response.output;
29 | var el = $("#output");
30 | if (el.length) {
31 | el.scrollTop(el[0].scrollHeight - el.height())
32 | }
33 | });
34 | });
35 |
36 | $scope.tryAgainSoon = (function(){
37 | $timeout($scope.downloadDebugFile, 2000);
38 | });
39 |
40 | $scope.downloadDebugFile = (function(){
41 | $api.request({
42 | module: "Help",
43 | action: "downloadDebugFile"
44 | }, function(response) {
45 | if (response.success === true) {
46 | $scope.loading = false;
47 | window.location = '/api/?download=' + response.downloadToken;
48 | $interval.cancel($scope.getOutputInterval);
49 | } else {
50 | $scope.tryAgainSoon();
51 | }
52 | })
53 | });
54 | }]);
55 | registerController("HelpController", ['$api', '$scope', function($api, $scope) {
56 | $scope.device = "";
57 |
58 | $api.onDeviceIdentified(function(device, scope) {
59 | scope.device = device;
60 | }, $scope);
61 | }]);
--------------------------------------------------------------------------------
/src/pineapple/modules/Help/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Information and debug log generation for the WiFi Pineapple.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Help",
10 | "version": "1.1",
11 | "index": 14
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Help/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Logging/api/module.php:
--------------------------------------------------------------------------------
1 | request->action) {
8 | case 'getSyslog':
9 | $this->getSyslog();
10 | break;
11 |
12 | case 'getDmesg':
13 | $this->getDmesg();
14 | break;
15 |
16 | case 'getReportingLog':
17 | $this->getReportingLog();
18 | break;
19 |
20 | case 'getPineapLog':
21 | $this->getPineapLog();
22 | break;
23 |
24 | case 'clearPineapLog':
25 | $this->clearPineapLog();
26 | break;
27 |
28 | case 'getPineapLogLocation':
29 | $this->getPineapLogLocation();
30 | break;
31 |
32 | case 'setPineapLogLocation':
33 | $this->setPineapLogLocation();
34 | break;
35 |
36 | case 'downloadPineapLog':
37 | $this->downloadPineapLog();
38 | break;
39 | }
40 | }
41 |
42 | private function downloadPineapLog()
43 | {
44 | $dbLocation = $this->uciGet("pineap.@config[0].hostapd_db_path");
45 | $db = new DatabaseConnection($dbLocation);
46 | $rows = $db->query("SELECT * FROM log ORDER BY updated_at ASC;");
47 | $logFile = fopen("/tmp/pineap.log", 'w');
48 | $count = "-";
49 | foreach ($rows as $row) {
50 | switch ($row['log_type']) {
51 | case 0:
52 | $type = "Probe Request";
53 | $count = $row['dups'];
54 | break;
55 | case 1:
56 | $type = "Association";
57 | break;
58 | case 2:
59 | $type = "De-association";
60 | break;
61 | default:
62 | $type = "";
63 | break;
64 | }
65 | fwrite($logFile, "${row['created_at']},\t${type},\t${row['mac']},\t${row['ssid']},\t${count}\n");
66 | }
67 | fclose($logFile);
68 | $this->response = array("download" => $this->downloadFile('/tmp/pineap.log'));
69 | }
70 |
71 | private function getSyslog()
72 | {
73 | exec("logread", $syslogOutput);
74 | $this->response = implode("\n", $syslogOutput);
75 | }
76 |
77 | private function getDmesg()
78 | {
79 | exec("dmesg", $dmesgOutput);
80 | $this->response = implode("\n", $dmesgOutput);
81 | }
82 |
83 | private function getReportingLog()
84 | {
85 | touch('/tmp/reporting.log');
86 | $this->streamFunction = function () {
87 | $fp = fopen('/tmp/reporting.log', 'r');
88 | while (($buf = fgets($fp)) !== false) {
89 | echo $buf;
90 | }
91 | fclose($fp);
92 | };
93 | }
94 |
95 | private function getPineapLog()
96 | {
97 | $dbLocation = $this->uciGet("pineap.@config[0].hostapd_db_path");
98 | $db = new DatabaseConnection($dbLocation);
99 | $rows = $db->query("SELECT * FROM log ORDER BY updated_at DESC;");
100 | $this->response = array("pineap_log" => $rows);
101 | }
102 |
103 | private function clearPineapLog()
104 | {
105 | $dbLocation = $this->uciGet("pineap.@config[0].hostapd_db_path");
106 | $db = new DatabaseConnection($dbLocation);
107 | $db->exec("DELETE FROM log;");
108 | $this->response = array('success' => true);
109 | }
110 |
111 | private function getPineapLogLocation()
112 | {
113 | $dbBasePath = dirname($this->uciGet("pineap.@config[0].hostapd_db_path"));
114 | $this->response = array('location' => $dbBasePath . "/");
115 | }
116 |
117 | private function setPineapLogLocation()
118 | {
119 | $dbLocation = dirname($this->request->location . '/fake_file');
120 | $this->uciSet("pineap.@config[0].hostapd_db_path", $dbLocation . '/log.db');
121 | $this->response = array('success' => true);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Logging/js/module.js:
--------------------------------------------------------------------------------
1 | registerController('PineAPLogController', ['$api', '$scope', '$timeout', '$cookies', function($api, $scope, $timeout, $cookies) {
2 | $scope.log = [];
3 | $scope.mac = '';
4 | $scope.ssid = '';
5 | $scope.logLocation = '';
6 | $scope.locationModified = false;
7 | $scope.orderByName = 'log_time';
8 | $scope.reverseSort = true;
9 | $scope.loadingPineapLog = false;
10 |
11 | $scope.checkboxOptions = {
12 | probes: $cookies.get('probesLog') !== undefined ? $cookies.get('probesLog') === 'true' : true,
13 | associations: $cookies.get('associationsLog') !== undefined ? $cookies.get('associationsLog') === 'true' : true,
14 | removeDuplicates: $cookies.get('removeDuplicatesLog') !== undefined ? $cookies.get('removeDuplicatesLog') === 'true' : false
15 | };
16 |
17 | $scope.refreshLog = (function() {
18 | $scope.log = [];
19 | $scope.loadingPineapLog = true;
20 | $api.request({
21 | module: 'Logging',
22 | action: 'getPineapLog'
23 | }, function(response) {
24 | $scope.loadingPineapLog = false;
25 | if (response.error === undefined) {
26 | $scope.log = response.pineap_log;
27 | $scope.applyFilter();
28 | annotateMacs();
29 | }
30 | });
31 | });
32 |
33 | $scope.downloadLog = (function() {
34 | $api.request({
35 | module: 'Logging',
36 | action: 'downloadPineapLog'
37 | }, function(response) {
38 | if (response.error === undefined) {
39 | window.location = '/api/?download=' + response.download;
40 | }
41 | });
42 | });
43 |
44 | $scope.getPineapLogLocation = (function () {
45 | $api.request({
46 | module: 'Logging',
47 | action: 'getPineapLogLocation'
48 | }, function(response) {
49 | if (response.error === undefined) {
50 | $scope.logLocation = response.location;
51 | }
52 | });
53 | });
54 |
55 | $scope.setPineapLogLocation = (function () {
56 | $api.request({
57 | module: 'Logging',
58 | action: 'setPineapLogLocation',
59 | location: $scope.logLocation
60 | }, function(response) {
61 | if (response.error === undefined) {
62 | $scope.locationModified = true;
63 | $timeout(function() {
64 | $scope.locationModified = false;
65 | }, 3000);
66 | }
67 | });
68 | });
69 |
70 | $scope.checkMatch = (function(text, filter) {
71 | if (filter.trim() === '') {
72 | return true;
73 | }
74 | if (text.toLowerCase().indexOf(filter.toLowerCase()) !== -1) {
75 | return true;
76 | }
77 | try {
78 | var re = new RegExp(filter);
79 | if (text.match(re) !== null) {
80 | return true;
81 | }
82 | }
83 | catch (err) {}
84 | return false;
85 | });
86 |
87 | $scope.applyFilter = (function() {
88 | $cookies.put('probesLog', $scope.checkboxOptions.probes);
89 | $cookies.put('associationsLog', $scope.checkboxOptions.associations);
90 | $cookies.put('removeDuplicatesLog', $scope.checkboxOptions.removeDuplicates);
91 |
92 | var hashArray = [];
93 | $.each($scope.log, function(i, value){
94 | if (value.log_time !== '') {
95 | value.hidden = false;
96 | if ($scope.checkboxOptions.removeDuplicates) {
97 | var index = value.ssid + value.log_type + value.mac;
98 | if (hashArray[index] === undefined) {
99 | hashArray[index] = true;
100 | } else {
101 | value.hidden = true;
102 | return true;
103 | }
104 | }
105 |
106 | if (!$scope.checkboxOptions.probes && value.log_type === 0) {
107 | value.hidden = true;
108 | }
109 |
110 | if (!$scope.checkboxOptions.associations && (value.log_type === 1 || value.log_type === 2)) {
111 | value.hidden = true;
112 | }
113 |
114 | if (!$scope.checkMatch(value.mac, $scope.mac)) {
115 | value.hidden = true;
116 | } else if (!$scope.checkMatch(value.ssid, $scope.ssid)) {
117 | value.hidden = true;
118 | }
119 | }
120 | });
121 | });
122 |
123 | $scope.clearFilter = (function() {
124 | $scope.checkboxOptions = {
125 | probes: true,
126 | associations: true,
127 | removeDuplicates: false
128 | };
129 | $scope.mac = '';
130 | $scope.ssid = '';
131 |
132 | $scope.applyFilter();
133 | });
134 |
135 | $scope.clearLog = (function() {
136 | $api.request({
137 | module: 'Logging',
138 | action: 'clearPineapLog'
139 | }, function(response) {
140 | if (response.error === undefined) {
141 | $scope.log = [];
142 | }
143 | });
144 | });
145 |
146 | $scope.getPineapLogLocation();
147 | $scope.refreshLog();
148 | }]);
149 |
150 | registerController('SyslogController', ['$api', '$scope', function($api, $scope) {
151 | $scope.refreshLog = (function(force) {
152 | $scope.syslog = 'Loading...';
153 | $api.request({
154 | module: 'Logging',
155 | action: 'getSyslog'
156 | }, function(response) {
157 | if (response.error === undefined) {
158 | $scope.syslog = response;
159 | }
160 | })
161 | });
162 | }]);
163 |
164 | registerController('DmesgController', ['$api', '$scope', function($api, $scope) {
165 | $scope.refreshLog = (function() {
166 | $scope.dmesg = 'Loading...';
167 | $api.request({
168 | module: 'Logging',
169 | action: 'getDmesg'
170 | }, function(response) {
171 | if (response.error === undefined) {
172 | $scope.dmesg = response;
173 | }
174 | })
175 | });
176 | }]);
177 |
178 | registerController('ReportingLogController', ['$api', '$scope', function($api, $scope) {
179 | $scope.refreshLog = (function() {
180 | $scope.reportingLog = 'Loading...';
181 | $api.request({
182 | module: 'Logging',
183 | action: 'getReportingLog'
184 | }, function(response) {
185 | if (response.error === undefined) {
186 | $scope.reportingLog = response;
187 | }
188 | })
189 | });
190 | }]);
191 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Logging/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Manage and view various logs.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Logging",
10 | "version": "1.1",
11 | "index": 8
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Logging/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/ModuleManager/api/module.php:
--------------------------------------------------------------------------------
1 | request->action) {
8 | case 'getAvailableModules':
9 | $this->getAvailableModules();
10 | break;
11 |
12 | case 'getInstalledModules':
13 | $this->getInstalledModules();
14 | break;
15 |
16 | case 'installModule':
17 | $this->installModule();
18 | break;
19 |
20 | case 'downloadModule':
21 | $this->downloadModule();
22 | break;
23 |
24 | case 'checkDestination':
25 | $this->checkDestination();
26 | break;
27 |
28 | case 'removeModule':
29 | $this->removeModule();
30 | break;
31 |
32 | case 'downloadStatus':
33 | $this->downloadStatus();
34 | break;
35 |
36 | case 'installStatus':
37 | $this->installStatus();
38 | break;
39 |
40 | case 'restoreSDcardModules':
41 | if ($this->sdReaderPresent()) {
42 | $this->restoreSDcardModules();
43 | }
44 | break;
45 | }
46 | }
47 |
48 | private function getAvailableModules()
49 | {
50 | $moduleData = @$this->fileGetContentsSSL(self::REMOTE_URL . "/modules/build/modules.json");
51 | if ($moduleData !== false) {
52 | $moduleData = json_decode($moduleData);
53 | if (json_last_error() === JSON_ERROR_NONE) {
54 | $this->response = array('availableModules' => $moduleData);
55 | }
56 | } else {
57 | $this->error = 'Error connecting to ' . self::REMOTE_NAME . '. Please check your connection.';
58 | }
59 | }
60 |
61 | private function getInstalledModules()
62 | {
63 | $modules = array();
64 | $modulesDirectories = scandir('/pineapple/modules');
65 | foreach ($modulesDirectories as $moduleDirectory) {
66 | if ($moduleDirectory[0] === ".") {
67 | continue;
68 | }
69 |
70 | $path = "/pineapple/modules/{$moduleDirectory}/module.info";
71 | if (file_exists($path)) {
72 | $moduleData = json_decode(file_get_contents($path));
73 | if (json_last_error() !== JSON_ERROR_NONE) {
74 | continue;
75 | }
76 |
77 | $module = array();
78 | $module['title'] = $moduleData->title;
79 | $module['author'] = $moduleData->author;
80 | $module['version'] = $moduleData->version;
81 | $module['description'] = $moduleData->description;
82 | $module['size'] = exec("du -sh /pineapple/modules/$moduleDirectory/ | awk '{print $1;}'");
83 | if (isset($moduleData->system)) {
84 | $module['type'] = "System";
85 | } elseif (isset($moduleData->cliOnly)) {
86 | $module['type'] = "CLI";
87 | } else {
88 | $module['type'] = "GUI";
89 | }
90 |
91 | $modules[$moduleDirectory] = $module;
92 | }
93 | }
94 | $this->response = array("installedModules" => $modules);
95 | }
96 |
97 | private function downloadModule()
98 | {
99 | @unlink('/tmp/moduleDownloaded');
100 |
101 | if ($this->request->destination === 'sd') {
102 | @mkdir('/sd/tmp/');
103 | $dest = '/sd/tmp/';
104 | } else {
105 | $dest = '/tmp/';
106 | }
107 |
108 | $module = "{$this->request->moduleName}.tar.gz";
109 | $this->execBackground("uclient-fetch -q -T 10 -O {$dest}{$module} '" . self::REMOTE_URL . "/modules/build/{$module}' && touch /tmp/moduleDownloaded");
110 | $this->response = array('success' => true);
111 | }
112 |
113 | private function downloadStatus()
114 | {
115 | if (file_exists('/tmp/moduleDownloaded')) {
116 | if ($this->request->destination === 'sd') {
117 | $dest = '/sd/tmp/';
118 | } else {
119 | $dest = '/tmp/';
120 | }
121 |
122 | if (hash_file('sha256', "{$dest}{$this->request->moduleName}.tar.gz") == $this->request->checksum) {
123 | $this->response = array('success' => true);
124 | return;
125 | }
126 | }
127 | $this->response = array('success' => false);
128 | }
129 |
130 | private function installModule()
131 | {
132 | @unlink('/tmp/moduleInstalled');
133 | $this->removeModule();
134 |
135 | if ($this->request->destination === 'sd') {
136 | @mkdir('/sd/modules/');
137 | $dest = '/sd/tmp/';
138 | $installDest = '/sd/modules/';
139 | exec("ln -s /sd/modules/{$this->request->moduleName} /pineapple/modules/{$this->request->moduleName}");
140 | } else {
141 | $dest = '/tmp/';
142 | $installDest = '/pineapple/modules/';
143 | }
144 |
145 | $module = "{$this->request->moduleName}.tar.gz";
146 | $this->execBackground("tar -xzvC {$installDest} -f {$dest}{$module} && rm {$dest}{$module} && touch /tmp/moduleInstalled");
147 | $this->response = array('success' => true);
148 | }
149 |
150 | private function installStatus()
151 | {
152 | $this->response = array('success' => file_exists('/tmp/moduleInstalled'));
153 | }
154 |
155 | private function checkDestination()
156 | {
157 | $config = $this->getDeviceConfig();
158 | $this->response = array(
159 | 'module' => $this->request->name,
160 | 'internal' => (disk_free_space('/') > ($this->request->size + 150000) && $config['useInternalStorage']),
161 | 'sd' => ($this->isSDAvailable() && $config['useUSBStorage']),
162 | );
163 | }
164 |
165 | private function removeModule()
166 | {
167 | $path = "/pineapple/modules/{$this->request->moduleName}";
168 | if (is_link($path)) {
169 | @unlink($path);
170 | exec("rm -rf /sd/modules/{$this->request->moduleName}");
171 | } else {
172 | exec("rm -rf {$path}");
173 | }
174 |
175 | $this->response = array('success' => true);
176 | }
177 |
178 | private function restoreSDcardModules()
179 | {
180 | $restored = false;
181 | $sdcardModules = @scandir('/sd/modules/');
182 | foreach ($sdcardModules as $module) {
183 | if ($module[0] != '.' && !file_exists("/pineapple/modules/{$module}")) {
184 | $restored = true;
185 | exec("ln -s /sd/modules/{$module} /pineapple/modules/{$module}");
186 | }
187 | }
188 | $this->response = array("restored" => $restored);
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/pineapple/modules/ModuleManager/js/module.js:
--------------------------------------------------------------------------------
1 | registerController("ModuleManagerController", ['$api', '$scope', '$timeout', '$interval', '$templateCache', '$rootScope', function($api, $scope, $timeout, $interval, $templateCache, $rootScope){
2 | $rootScope.availableModules = [];
3 | $rootScope.installedModules = [];
4 | $scope.installedModule = "";
5 | $scope.removedModule = "";
6 | $scope.gotAvailableModules = false;
7 | $scope.connectionError = false;
8 | $scope.selectedModule = false;
9 | $scope.downloading = false;
10 | $scope.installing = false;
11 | $scope.linking = false;
12 |
13 | $scope.getAvailableModules = (function() {
14 | $scope.loading = true;
15 | $api.request({
16 | module: "ModuleManager",
17 | action: "getAvailableModules"
18 | }, function(response) {
19 | $scope.loading = false;
20 | if (response.error === undefined) {
21 | $rootScope.availableModules = response.availableModules;
22 | $scope.compareModuleLists();
23 | $scope.gotAvailableModules = true;
24 | $scope.connectionError = false;
25 | } else {
26 | $scope.connectionError = response.error;
27 | }
28 | });
29 | });
30 |
31 | $scope.getInstalledModules = (function() {
32 | $api.request({
33 | module: "ModuleManager",
34 | action: "getInstalledModules"
35 | }, function(response) {
36 | $rootScope.installedModules = response.installedModules;
37 | if ($scope.gotAvailableModules) {
38 | $scope.compareModuleLists();
39 | }
40 | });
41 | });
42 |
43 | $scope.compareModuleLists = (function() {
44 | angular.forEach($rootScope.availableModules, function(module, moduleName){
45 | if ($rootScope.installedModules[moduleName] === undefined){
46 | module['installable'] = true;
47 | } else if ($rootScope.availableModules[moduleName].version <= $rootScope.installedModules[moduleName].version) {
48 | module['installed'] = true;
49 | }
50 | });
51 | });
52 |
53 | $scope.removeModule = (function(name) {
54 | $api.request({
55 | module: 'ModuleManager',
56 | action: 'removeModule',
57 | moduleName: name
58 | }, function(response) {
59 | if (response.success === true) {
60 | $scope.getInstalledModules();
61 | $scope.removedModule = true;
62 | $api.reloadNavbar();
63 | $timeout(function(){
64 | $scope.removedModule = false;
65 | }, 2000);
66 | }
67 | });
68 | });
69 |
70 | $scope.restoreSDcardModules = (function() {
71 | $api.request({
72 | module: 'ModuleManager',
73 | action: 'restoreSDcardModules'
74 | }, function(response) {
75 | if (response.restored === true) {
76 | $scope.restoreSDcardModules();
77 | } else {
78 | $api.reloadNavbar();
79 | $scope.getInstalledModules();
80 | $scope.linking = false;
81 | }
82 | });
83 | });
84 |
85 | $scope.getInstalledModules();
86 | }]);
87 |
--------------------------------------------------------------------------------
/src/pineapple/modules/ModuleManager/module.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Successfully Installed Module
6 |
Successfully Removed Module
7 |
8 |
9 |
Get Modules
10 |
11 |
12 |
13 |
14 | {{ connectionError }}
15 |
16 |
17 |
18 |
19 |
Available Modules Refresh
20 |
21 |
22 |
23 |
24 |
25 | Module
26 | Version
27 | Description
28 | Author
29 | Size
30 | Type
31 | Action
32 |
33 |
34 |
35 |
36 |
37 | {{ module['title'] }}
38 |
39 |
40 | {{ module['version'] }}
41 |
42 |
43 | {{ module['description'] }}
44 |
45 |
46 | {{ module['author'] }}
47 |
48 |
49 | {{ (module['size']/1024).toFixed(2) }}K
50 |
51 |
52 | {{ module['type'] }}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
Installed Modules
77 |
78 |
79 |
80 |
81 |
82 | Module
83 | Version
84 | Description
85 | Size
86 | Author
87 | Type
88 | Action
89 |
90 |
91 |
92 |
93 |
94 | {{ module['title'] }}
95 |
96 |
97 | {{ module['version'] }}
98 |
99 |
100 | {{ module['description'] }}
101 |
102 |
103 | {{ module['size'] }}
104 |
105 |
106 | {{ module['author'] }}
107 |
108 |
109 | {{ module['type'] }}
110 |
111 |
112 | Remove
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/src/pineapple/modules/ModuleManager/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Download new community modules, and system module updates.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Modules",
10 | "version": "1.2"
11 | }
12 |
--------------------------------------------------------------------------------
/src/pineapple/modules/ModuleManager/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Networking/api/AccessPoint.php:
--------------------------------------------------------------------------------
1 | selectedChannel);
12 |
13 | uciSet('wireless.@wifi-iface[0].ssid', $apConfig->openSSID, false);
14 | uciSet('wireless.@wifi-iface[0].disabled', $apConfig->disableOpenAP, false);
15 | uciSet('wireless.@wifi-iface[0].hidden', $apConfig->hideOpenAP, false);
16 | uciSet('wireless.@wifi-iface[0].maxassoc', $apConfig->maxClients, false);
17 |
18 | uciSet('wireless.@wifi-iface[1].ssid', $apConfig->managementSSID, false);
19 | uciSet('wireless.@wifi-iface[1].key', $apConfig->managementKey, false);
20 | uciSet('wireless.@wifi-iface[1].disabled', $apConfig->disableManagementAP, false);
21 | uciSet('wireless.@wifi-iface[1].hidden', $apConfig->hideManagementAP, false);
22 |
23 | uciCommit();
24 | execBackground('wifi');
25 |
26 | return ["success" => true];
27 | }
28 |
29 | public function getAPConfig($getChannelInfo = true)
30 | {
31 | $channels = [];
32 | if ($getChannelInfo) {
33 | exec("iwinfo phy0 freqlist", $output);
34 | preg_match_all("/\(Channel (\d+)\)$/m", implode("\n", $output), $channelList);
35 |
36 | // Remove radar detection channels
37 | foreach ($channelList[1] as $channel) {
38 | if ((int)$channel < 52 || (int)$channel > 140) {
39 | $channels[] = $channel;
40 | }
41 | }
42 | }
43 |
44 | return [
45 | "selectedChannel" => uciGet("wireless.radio0.channel"),
46 | "availableChannels" => $channels,
47 |
48 | "openSSID" => uciGet("wireless.@wifi-iface[0].ssid"),
49 | "maxClients" => uciGet("wireless.@wifi-iface[0].maxassoc", false),
50 | "disableOpenAP" => uciGet("wireless.@wifi-iface[0].disabled"),
51 | "hideOpenAP" => uciGet("wireless.@wifi-iface[0].hidden"),
52 |
53 | "managementSSID" => uciGet("wireless.@wifi-iface[1].ssid"),
54 | "managementKey" => uciGet("wireless.@wifi-iface[1].key"),
55 | "disableManagementAP" => uciGet("wireless.@wifi-iface[1].disabled"),
56 | "hideManagementAP" => uciGet("wireless.@wifi-iface[1].hidden")
57 | ];
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Networking/api/ClientMode.php:
--------------------------------------------------------------------------------
1 | /dev/null");
16 | }
17 |
18 | if (uciGet("wireless.@wifi-iface[{$uciID}].network") === 'wwan') {
19 | uciSet("wireless.@wifi-iface[{$uciID}].network", 'lan');
20 | exec("wifi up $radio");
21 | sleep(2);
22 | }
23 |
24 | exec("iwinfo {$interface} scan", $apScan);
25 | if ($apScan[0] === 'No scan results') {
26 | return null;
27 | }
28 |
29 | $returnArray = [];
30 | $apArray = preg_split("/^Cell/m", implode("\n", $apScan));
31 | foreach ($apArray as $apData) {
32 | $apData = explode("\n", $apData);
33 | $ssid = substr(trim($apData[1]), 8, -1);
34 | if (!$ssid || mb_detect_encoding($ssid, "auto") === false || $ssid === "unknown") {
35 | continue;
36 | }
37 |
38 | $channelString = explode(" ", trim($apData[2]));
39 | $signalString = explode(" ", trim($apData[3]));
40 | $security = substr(trim($apData[4]), 12);
41 |
42 | $returnArray[] = [
43 | 'mac' => substr($apData[0], -17),
44 | 'ssid' => $ssid,
45 | 'channel' => intval(substr($channelString[1], 9)),
46 | 'signal' => substr($signalString[0], 8),
47 | 'quality' => substr($signalString[1], 9),
48 | 'security' => ($security === "none") ? "Open" : $security,
49 | ];
50 | }
51 |
52 | return $returnArray;
53 | }
54 |
55 | public function connectToAP($uciID, $ap, $key, $radioID)
56 | {
57 | exec('[ ! -z "$(wifi config)" ] && wifi config >> /etc/config/wireless');
58 |
59 | switch ($ap->security) {
60 | case 'Open':
61 | $encryption = "none";
62 | break;
63 | case 'WPA (TKIP)':
64 | case 'WPA PSK (TKIP)':
65 | $encryption = "psk+tkip";
66 | break;
67 | case 'WPA (CCMP)':
68 | case 'WPA PSK (CCMP)':
69 | $encryption = "psk+ccmp";
70 | break;
71 | case 'WPA (TKIP, CCMP)':
72 | case 'WPA PSK (TKIP, CCMP)':
73 | $encryption = "psk+tkip+ccmp";
74 | break;
75 | case 'WPA2 (TKIP)':
76 | case 'WPA2 PSK (TKIP)':
77 | $encryption = "psk2+tkip";
78 | break;
79 | case 'WPA2 (CCMP)':
80 | case 'WPA2 PSK (CCMP)':
81 | $encryption = "psk2+ccmp";
82 | break;
83 | case 'WPA2 (TKIP, CCMP)':
84 | case 'WPA2 PSK (TKIP, CCMP)':
85 | $encryption = "psk2+ccmp+tkip";
86 | break;
87 | case 'mixed WPA/WPA2 (TKIP)':
88 | case 'mixed WPA/WPA2 PSK (TKIP)':
89 | $encryption = "psk-mixed+tkip";
90 | break;
91 | case 'mixed WPA/WPA2 (CCMP)':
92 | case 'mixed WPA/WPA2 PSK (CCMP)':
93 | $encryption = "psk-mixed+ccmp";
94 | break;
95 | case 'mixed WPA/WPA2 (TKIP, CCMP)':
96 | case 'mixed WPA/WPA2 PSK (TKIP, CCMP)':
97 | $encryption = "psk-mixed+ccmp+tkip";
98 | break;
99 | default:
100 | $encryption = "";
101 | }
102 |
103 | uciSet("wireless.@wifi-iface[{$uciID}].network", 'wwan', false);
104 | uciSet("wireless.@wifi-iface[{$uciID}].mode", 'sta', false);
105 | uciSet("wireless.@wifi-iface[{$uciID}].ssid", $ap->ssid, false);
106 | uciSet("wireless.@wifi-iface[{$uciID}].encryption", $encryption, false);
107 | uciSet("wireless.@wifi-iface[{$uciID}].key", $key, false);
108 | uciCommit();
109 |
110 | if ($radioID === false) {
111 | execBackground("wifi");
112 | } else {
113 | execBackground("wifi reload {$radioID}");
114 | execBackground("wifi up {$radioID}");
115 | }
116 |
117 | return ["success" => true];
118 | }
119 |
120 | public function checkConnection()
121 | {
122 | $connection = exec('iwconfig 2>&1 | grep ESSID:\"');
123 | if (trim($connection)) {
124 | $interface = explode(" ", $connection)[0];
125 |
126 | $ssidString = substr($connection, strpos($connection, 'ESSID:'));
127 | $ssid = substr($ssidString, 7, -1);
128 | $ip = exec("ifconfig " . escapeshellarg($interface) . " | grep -m 1 inet | awk '{print \$2}' | awk -F':' '{print \$2}'");
129 |
130 | return ["connected" => true, "interface" => $interface, "ssid" => $ssid, "ip" => $ip];
131 | }
132 |
133 | return ["connected" => false];
134 | }
135 |
136 | public function disconnect($uciID, $radioID)
137 | {
138 | uciSet("wireless.@wifi-iface[{$uciID}].network", 'lan', false);
139 | uciSet("wireless.@wifi-iface[{$uciID}].ssid", '', false);
140 | uciSet("wireless.@wifi-iface[{$uciID}].encryption", 'none', false);
141 | uciSet("wireless.@wifi-iface[{$uciID}].key", '', false);
142 | uciCommit();
143 |
144 | if ($radioID === false) {
145 | execBackground("wifi");
146 | } else {
147 | execBackground("wifi reload {$radioID}");
148 | execBackground("wifi up {$radioID}");
149 | }
150 |
151 | return ["success" => true];
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Networking/api/Interfaces.php:
--------------------------------------------------------------------------------
1 | $radioConfig) {
36 | if (isset($radioConfig->interfaces[0]->config->ifname) &&
37 | $radioConfig->interfaces[0]->config->ifname === $interface) {
38 | return $radio;
39 | }
40 | }
41 |
42 | return false;
43 | }
44 |
45 | public function setMac($random, $interface, $newMac, $forceReload)
46 | {
47 | $uciID = $this->getUciID($interface);
48 | $interface = escapeshellarg($interface);
49 |
50 | if ($random) {
51 | $mac = exec("ifconfig {$interface} down && macchanger -A {$interface} | grep New | awk '{print \$3}'");
52 | } else {
53 | $requestMac = escapeshellarg($newMac);
54 | $mac = exec("ifconfig {$interface} down && macchanger -m {$requestMac} {$interface} | grep New | awk '{print \$3}'");
55 | }
56 |
57 | uciSet("wireless.@wifi-iface[{$uciID}].macaddr", $mac);
58 | if ($forceReload) {
59 | exec("wifi");
60 | } else {
61 | exec("ifconfig {$interface} up");
62 | }
63 |
64 | return ["success" => true, "uci" => $uciID];
65 | }
66 |
67 | public function resetMac($interface)
68 | {
69 | $uciID = $this->getUciID($interface);
70 | exec("uci set wireless.@wifi-iface[{$uciID}].macaddr=''");
71 | exec("wifi");
72 | return ["success" => true];
73 | }
74 |
75 | public function resetWirelessConfig()
76 | {
77 | execBackground("wifi config > /etc/config/wireless && wifi");
78 | return ["success" => true];
79 | }
80 |
81 | public function getInterfaceList()
82 | {
83 | exec("ifconfig -a | grep encap:Ethernet | awk '{print \$1\",\"\$5}'", $interfaceArray);
84 | return $interfaceArray;
85 | }
86 |
87 | public function getClientInterfaces()
88 | {
89 | $clientInterfaces = [];
90 | exec("ifconfig -a | grep wlan | awk '{print \$1}'", $interfaceArray);
91 | foreach ($interfaceArray as $interface) {
92 | if (\DeviceConfig::HIDE_WLAN0_CLIENT && substr($interface, 0, 5) === "wlan0") {
93 | continue;
94 | }
95 | $clientInterfaces[] = $interface;
96 | }
97 |
98 | return array_reverse($clientInterfaces);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Networking/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Make changes to Routing, Access Points, MAC Addresses and the Hostname.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Networking",
10 | "version": "1.1",
11 | "index": 10
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Networking/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Notes/api/module.php:
--------------------------------------------------------------------------------
1 | dbConnection = new DatabaseConnection(self::DATABASE);
13 | if (!empty($this->dbConnection->error)) {
14 | $this->error = $this->dbConnection->strError();
15 | return;
16 | }
17 | $this->dbConnection->exec("CREATE TABLE IF NOT EXISTS notes (type INT, key TEXT UNIQUE NOT NULL, name TEXT, note TEXT);");
18 | if (!empty($this->dbConnection->error)) {
19 | $this->error = $this->dbConnection->strError();
20 | }
21 | }
22 |
23 | public function route()
24 | {
25 | switch ($this->request->action) {
26 | case 'setName':
27 | $this->response = $this->setName($this->request->type, $this->request->key, $this->request->name);
28 | break;
29 | case 'setNote':
30 | $this->response = $this->setNote($this->request->type, $this->request->key, $this->request->name, $this->request->note);
31 | break;
32 | case 'getNotes':
33 | $this->response = $this->getNotes();
34 | break;
35 | case 'getNote':
36 | $this->response = $this->getNote($this->request->key);
37 | break;
38 | case 'deleteNote':
39 | $this->response = $this->deleteNote($this->request->key);
40 | break;
41 | case 'downloadNotes':
42 | $this->response = $this->downloadNotes();
43 | break;
44 | case 'getKeys':
45 | $this->response = $this->getKeys();
46 | break;
47 | }
48 | }
49 |
50 | public function setName($type, $key, $name)
51 | {
52 | return $this->dbConnection->exec("INSERT OR REPLACE INTO notes (type, key, name) VALUES('%d', '%s', '%s');", $type, $key, $name);
53 | }
54 |
55 | public function setNote($type, $key, $name, $note)
56 | {
57 | if (empty($name) && empty($note)) {
58 | return $this->deleteNote($key);
59 | } else {
60 | return $this->dbConnection->exec("INSERT OR REPLACE INTO notes (type, key, name, note) VALUES ('%d', '%s', '%s', '%s');", $type, $key, $name, $note);
61 | }
62 | }
63 |
64 | public function getNotes()
65 | {
66 | $macs = $this->dbConnection->query("SELECT type, key, name, note FROM notes WHERE type=0;");
67 | $ssids = $this->dbConnection->query("SELECT type, key, name, note FROM notes WHERE type=1;");
68 | return array("macs" => $macs, "ssids" => $ssids);
69 | }
70 |
71 | public function getNote($key)
72 | {
73 | return array("note" => $this->dbConnection->query("SELECT type, key, name, note FROM notes WHERE key='%s';", $key));
74 | }
75 |
76 | public function deleteNote($key)
77 | {
78 | if (!isset($key)) {
79 | return array("success" => false);
80 | }
81 | $this->dbConnection->exec("DELETE FROM notes WHERE key='%s';", $key);
82 | return array("success" => true);
83 | }
84 |
85 | public function downloadNotes()
86 | {
87 | $noteData = $this->dbConnection->query('SELECT * FROM notes;');
88 | foreach ($noteData as $idx => $note) {
89 | if ($note['type'] == 0) {
90 | $note['type'] = 'MAC';
91 | } else if ($note['type'] == 1) {
92 | $note['type'] = 'SSID';
93 | }
94 | $noteData[$idx] = $note;
95 | }
96 | $fileName = '/tmp/notes.json';
97 | file_put_contents($fileName, json_encode($noteData, JSON_PRETTY_PRINT));
98 | return array("download" => $this->downloadFile($fileName));
99 | }
100 |
101 | public function getKeys()
102 | {
103 | $keys = array();
104 | $res = $this->dbConnection->query("SELECT key FROM notes;");
105 | foreach ($res as $idx => $key) {
106 | $keys[] = $key['key'];
107 | }
108 | return array("keys" => $keys);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Notes/js/module.js:
--------------------------------------------------------------------------------
1 | registerController("NotesController", ['$api', '$scope', function($api, $scope){
2 | $scope.macs = [];
3 | $scope.ssids = [];
4 | $scope.error = null;
5 |
6 | $scope.getNotes = function() {
7 | $api.request({
8 | module: "Notes",
9 | action: "getNotes"
10 | }, function(response) {
11 | if (response.error !== undefined) {
12 | $scope.error = response.error;
13 | } else {
14 | $scope.macs = response.macs;
15 | $scope.ssids = response.ssids;
16 | }
17 | });
18 | };
19 |
20 | $scope.deleteNote = function($event) {
21 | var key = $event.target.getAttribute('key');
22 | $api.request({
23 | module: "Notes",
24 | action: "deleteNote",
25 | key: key
26 | }, function() {
27 | $scope.getNotes();
28 | });
29 | };
30 |
31 | $scope.downloadNotes = function() {
32 | $scope.getNotes();
33 | $api.request({
34 | module: "Notes",
35 | action: "downloadNotes"
36 | }, function(response) {
37 | if (response.error === undefined) {
38 | window.location = '/api?download=' + response.download;
39 | }
40 | });
41 | };
42 |
43 | $scope.getNotes();
44 | }]);
--------------------------------------------------------------------------------
/src/pineapple/modules/Notes/module.html:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 | MAC Notes
19 |
20 | Download
21 | Refresh
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Name
30 | MAC Address
31 | Note
32 | Delete
33 |
34 |
35 |
36 |
37 |
38 |
39 | {{ mac.name }}
40 |
41 |
42 |
43 |
44 |
45 | {{ mac.key }}
46 |
47 |
48 |
49 |
50 | {{ mac.note }}
51 |
52 |
53 |
54 | Delete Note
55 |
56 |
57 |
58 |
59 |
60 |
61 | No MAC notes found.
62 |
63 |
64 |
65 |
66 |
67 | SSID Notes
68 |
69 | Download
70 | Refresh
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Name
79 | SSID
80 | Note
81 | Delete
82 |
83 |
84 |
85 |
86 |
87 |
88 | {{ ssid.name }}
89 |
90 |
91 |
92 |
93 |
94 | {{ ssid.key }}
95 |
96 |
97 |
98 |
99 | {{ ssid.note }}
100 |
101 |
102 |
103 | Delete Note
104 |
105 |
106 |
107 |
108 |
109 |
110 | No SSID notes found.
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Notes/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Easily take notes on different clients and APs.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Notes",
10 | "version": "1.0",
11 | "index": 13
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Notes/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/PineAP/executable/executable:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Simple wrapper script for PineAP
3 |
4 | ps | grep [p]ineapd -q && {
5 | [[ "$1" == "stop" ]] && {
6 | echo [*] Stopping PineAP
7 | /etc/init.d/pineapd stop &> /dev/null
8 | echo [*] PineAP successfully stopped
9 | exit 0
10 | } || {
11 | echo -e [*] Executing /usr/bin/pineap /tmp/pineap.conf "$@" "\n"
12 | /usr/bin/pineap /tmp/pineap.conf "$@"
13 | }
14 | } || {
15 | [[ "$1" == "start" ]] && {
16 | echo "[*] Starting PineAP, please wait."
17 | /etc/init.d/pineapd start &> /dev/null
18 | echo "[*] PineAP started successfully"
19 | } || {
20 | echo "PineAPd is not running. Start it with 'module PineAP start'"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/pineapple/modules/PineAP/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "PineAP is an effective, modular rogue access point suite.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "PineAP",
10 | "version": "1.9",
11 | "index": 6
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/PineAP/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Recon/api/WebSocketsHandler.py:
--------------------------------------------------------------------------------
1 | import struct
2 | import SocketServer
3 | import time
4 | from base64 import b64encode
5 | from hashlib import sha1
6 | from mimetools import Message
7 | from StringIO import StringIO
8 | import wsauth
9 |
10 | # This class is taken and modified from https://gist.github.com/jkp/3136208
11 | class WebSocketsHandler(SocketServer.StreamRequestHandler):
12 | magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
13 |
14 | def setup(self):
15 | SocketServer.StreamRequestHandler.setup(self)
16 | self.server.websockets.append(self)
17 | self.handshake_done = False
18 |
19 | def handle(self):
20 | while self.server.running:
21 | if not self.handshake_done:
22 | self.handshake()
23 | else:
24 | time.sleep(5)
25 |
26 | def send_message(self, message):
27 | self.request.send(chr(129))
28 | length = len(message)
29 | if length <= 125:
30 | self.request.send(chr(length))
31 | elif length >= 126 and length <= 65535:
32 | self.request.send(chr(126))
33 | self.request.send(struct.pack(">H", length))
34 | else:
35 | self.request.send(chr(127))
36 | self.request.send(struct.pack(">Q", length))
37 | self.request.send(message)
38 |
39 | def handshake(self):
40 | data = self.request.recv(1024).strip()
41 | headers = Message(StringIO(data.split('\r\n', 1)[1]))
42 | try:
43 | uri = data.split('\r\n')[0].split(' ')[1]
44 | authtoken = dict(p.split('=') for p in uri.split('/')[-1].split('?')[1].split('&'))['authtoken']
45 | if not wsauth.verify_token(authtoken):
46 | print "[!] Invalid auth token: {}".format(authtoken)
47 | return
48 | else:
49 | print "[+] Valid auth token: {}".format(authtoken)
50 | except Exception, e:
51 | print "Error checking token: {}".format(e)
52 | if headers.get("Upgrade", None) != "websocket":
53 | return
54 | key = headers['Sec-WebSocket-Key']
55 | digest = b64encode(sha1(key + self.magic).hexdigest().decode('hex'))
56 | response = 'HTTP/1.1 101 Switching Protocols\r\n'
57 | response += 'Upgrade: websocket\r\n'
58 | response += 'Connection: Upgrade\r\n'
59 | response += 'Sec-WebSocket-Accept: %s\r\n\r\n' % digest
60 | self.handshake_done = self.request.send(response)
61 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Recon/api/dbhandler.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import sqlite3
3 | from sqlite3 import Error
4 | import time
5 | import json
6 | import subprocess
7 | import sys
8 | import thread
9 |
10 | EncryptionFields = {
11 | "WPA": 0x01,
12 | "WPA2": 0x02,
13 | "WEP": 0x04,
14 | "WPA_PAIRWISE_WEP40": 0x08,
15 | "WPA_PAIRWISE_WEP104": 0x10,
16 | "WPA_PAIRWISE_TKIP": 0x20,
17 | "WPA_PAIRWISE_CCMP": 0x40,
18 | "WPA2_PAIRWISE_WEP40": 0x80,
19 | "WPA2_PAIRWISE_WEP104": 0x100,
20 | "WPA2_PAIRWISE_TKIP": 0x200,
21 | "WPA2_PAIRWISE_CCMP": 0x400,
22 | "WPA_AKM_PSK": 0x800,
23 | "WPA_AKM_ENTERPRISE": 0x1000,
24 | "WPA_AKM_ENTERPRISE_FT": 0x2000,
25 | "WPA2_AKM_PSK": 0x4000,
26 | "WPA2_AKM_ENTERPRISE": 0x8000,
27 | "WPA2_AKM_ENTERPRISE_FT": 0x10000,
28 | "WPA_GROUP_WEP40": 0x20000,
29 | "WPA_GROUP_WEP104": 0x40000,
30 | "WPA_GROUP_TKIP": 0x80000,
31 | "WPA_GROUP_CCMP": 0x100000,
32 | "WPA2_GROUP_WEP40": 0x200000,
33 | "WPA2_GROUP_WEP104": 0x400000,
34 | "WPA2_GROUP_TKIP": 0x800000,
35 | "WPA2_GROUP_CCMP": 0x1000000
36 | }
37 |
38 |
39 |
40 | class DBHandler(threading.Thread):
41 | def __init__(self, group=None, target=None, name=None, args=(), kwargs=None):
42 | threading.Thread.__init__(self, group=group, target=target, name=name)
43 | self.args = args
44 | self.kwargs = kwargs
45 |
46 | self.database = self.args[1]
47 | self.scanID = int(self.args[2])
48 |
49 | self.accessPoints = {}
50 | self.realAPs = []
51 | self.unassociatedClients = []
52 | self.outOfRangeClients = {}
53 | return
54 |
55 | def createAPArray(self):
56 | self.accessPoints = {}
57 | cursor = self.conn.cursor()
58 | cursor.execute("SELECT ssid,bssid,encryption,channel,signal,wps,last_seen from aps WHERE scan_id = " + str(self.scanID) + ";")
59 | apRows = cursor.fetchall()
60 |
61 | for row in apRows:
62 | ap = {}
63 | ap["bssid"] = row[1]
64 | ap["ssid"] = row[0]
65 | ap["channel"] = row[3]
66 | ap["encryption"] = self.printEncryption(row[2])
67 | ap["wps"] = row[5]
68 | ap["lastSeen"] = row[6]
69 | ap["power"] = row[4]
70 | ap["clients"] = []
71 | self.accessPoints[row[1]] = ap
72 |
73 | self.realAPs = []
74 | for key, value in self.accessPoints.iteritems():
75 | self.realAPs.append(value)
76 |
77 | def createClientsArray(self):
78 | self.unassociatedClients = []
79 | self.outOfRangeClients = {}
80 | cursor = self.conn.cursor()
81 | cursor.execute("SELECT mac,bssid,last_seen from clients WHERE scan_id = " + str(self.scanID) + ";")
82 |
83 | clientRows = cursor.fetchall()
84 |
85 | for row in clientRows:
86 | if row[1] == "FF:FF:FF:FF:FF:FF":
87 | self.unassociatedClients.append({'mac': row[0], 'lastSeen': row[2]})
88 | elif row[1] in self.accessPoints:
89 | cliObj = {"mac": row[0], "lastSeen": row[2]}
90 | if cliObj not in self.accessPoints[row[1]]["clients"]:
91 | self.accessPoints[row[1]]["clients"].append(cliObj)
92 | else:
93 | self.outOfRangeClients[row[0]] = {"bssid": row[1], "lastSeen": row[2]}
94 |
95 | def printEncryption(self, encryptionType):
96 | retStr = ""
97 | if (encryptionType == 0):
98 | return "Open"
99 | if (encryptionType & EncryptionFields["WEP"]):
100 | return "WEP"
101 | elif ((encryptionType & EncryptionFields["WPA"]) and (encryptionType & EncryptionFields["WPA2"])):
102 | retStr += "WPA Mixed "
103 | elif (encryptionType & EncryptionFields["WPA"]):
104 | retStr += "WPA "
105 | elif (encryptionType & EncryptionFields["WPA2"]):
106 | retStr += "WPA2 "
107 |
108 | if ((encryptionType & EncryptionFields["WPA2_AKM_PSK"]) or (encryptionType & EncryptionFields["WPA_AKM_PSK"])):
109 | retStr += "PSK "
110 | elif ((encryptionType & EncryptionFields["WPA2_AKM_ENTERPRISE"]) or (encryptionType & EncryptionFields["WPA_AKM_ENTERPRISE"])):
111 | retStr += "Enterprise "
112 | elif ((encryptionType & EncryptionFields["WPA2_AKM_ENTERPRISE_FT"]) or (encryptionType & EncryptionFields["WPA_AKM_ENTERPRISE_FT"])):
113 | retStr += "Enterprise FT "
114 |
115 | retStr += "("
116 |
117 | if ((encryptionType & EncryptionFields["WPA2_PAIRWISE_CCMP"]) or (encryptionType & EncryptionFields["WPA_PAIRWISE_CCMP"])):
118 | retStr += "CCMP "
119 |
120 | if ((encryptionType & EncryptionFields["WPA2_PAIRWISE_TKIP"]) or (encryptionType & EncryptionFields["WPA_PAIRWISE_TKIP"])):
121 | retStr += "TKIP "
122 |
123 | # Fix the code below - these never trigger. Make sure to set "return WEP" to retStr += WEP
124 | if ((encryptionType & EncryptionFields["WPA2_PAIRWISE_WEP40"]) or (encryptionType & EncryptionFields["WPA_PAIRWISE_WEP40"])):
125 | retStr += "WEP40 "
126 |
127 | if ((encryptionType & EncryptionFields["WPA2_PAIRWISE_WEP104"]) or (encryptionType & EncryptionFields["WPA_PAIRWISE_WEP104"])):
128 | retStr += "WEP104 "
129 |
130 | retStr = retStr[:-1]
131 | retStr += ")"
132 | return retStr
133 |
134 |
135 | def run(self):
136 | try:
137 | self.conn = sqlite3.connect(self.database)
138 | except Error as e:
139 | print(e)
140 | return
141 |
142 | while True:
143 | if "scanID" not in subprocess.check_output(['/usr/bin/pineap', '/tmp/pineap.conf', 'get_status']):
144 | for websocket in self.args[0]:
145 | try:
146 | websocket.send_message(json.dumps({"scan_complete": True}))
147 | except Exception:
148 | pass
149 | thread.interrupt_main()
150 |
151 | self.createAPArray()
152 | self.createClientsArray()
153 | returnDictionary = {}
154 | returnDictionary["ap_list"] = self.realAPs
155 | returnDictionary["unassociated_clients"] = self.unassociatedClients
156 | returnDictionary["out_of_range_clients"] = self.outOfRangeClients
157 | returnDictionary["scan_complete"] = False
158 |
159 | for websocket in self.args[0]:
160 | try:
161 | websocket.send_message(json.dumps(returnDictionary))
162 | except Exception, e:
163 | pass
164 |
165 | time.sleep(3)
--------------------------------------------------------------------------------
/src/pineapple/modules/Recon/api/reconpp.py:
--------------------------------------------------------------------------------
1 | import WebSocketsHandler
2 | import dbhandler
3 | import SocketServer
4 | import fcntl
5 | import sys
6 | import os
7 | import wsauth
8 |
9 | LOCKFILE = '/tmp/reconpp.lock'
10 |
11 | def acquire_lockfile():
12 | with open(LOCKFILE, 'w') as f:
13 | try:
14 | fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
15 | except Exception:
16 | return False
17 | return True
18 |
19 | def release_lockfile():
20 | os.unlink(LOCKFILE)
21 |
22 | def main():
23 | if len(sys.argv) < 3 or len(sys.argv) > 3:
24 | print("Usage: reconpp.py ")
25 | sys.exit(1)
26 |
27 | if not acquire_lockfile():
28 | print("Failed to acquire lock file. Is reconpp already running?")
29 | sys.exit(1)
30 |
31 | wsauth.write_token()
32 |
33 | websockets = []
34 | running = True
35 |
36 | database = sys.argv[1]
37 | scanID = sys.argv[2]
38 |
39 | dbHandler = dbhandler.DBHandler(args=(websockets, database, scanID))
40 | dbHandler.setDaemon(True)
41 |
42 | SocketServer.ThreadingTCPServer.allow_reuse_address = 1
43 | server = SocketServer.ThreadingTCPServer(("", 1337), WebSocketsHandler.WebSocketsHandler)
44 | server.running = running
45 | server.websockets = websockets
46 |
47 | try:
48 | dbHandler.start()
49 | server.serve_forever()
50 | dbHandler.join()
51 | except KeyboardInterrupt:
52 | server.running = False
53 | server.server_close()
54 | finally:
55 | release_lockfile()
56 | wsauth.remove_token()
57 |
58 |
59 | if __name__ == '__main__':
60 | main()
61 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Recon/api/wsauth.py:
--------------------------------------------------------------------------------
1 | import random
2 | import string
3 | import os
4 |
5 | TOKENLEN = 64
6 | TOKENFILE = '/tmp/reconpp.token'
7 |
8 | def gen_token(n):
9 | return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(n))
10 |
11 | def write_token():
12 | if os.path.isfile(TOKENFILE):
13 | return
14 | with open(TOKENFILE, 'w') as f:
15 | f.write(gen_token(TOKENLEN))
16 |
17 | def read_token():
18 | with open(TOKENFILE) as f:
19 | return f.read()
20 |
21 | def remove_token():
22 | os.unlink(TOKENFILE)
23 |
24 | def verify_token(token):
25 | return token == read_token()
26 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Recon/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Paint a picture of the WiFi landscape.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Recon",
10 | "version": "1.3",
11 | "index": 2
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Recon/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Reporting/files/getlog.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php-cgi
2 | 1) {
9 | if ($argv[1] === "--probes") {
10 | $probesOnly = true;
11 | }
12 | }
13 |
14 | $logDBPath = exec("uci get pineap.@config[0].hostapd_db_path");
15 | if (!file_exists($logDBPath)) {
16 | exit("File ${logDBPath} does not exist\n");
17 | }
18 | $dbConnection = new DatabaseConnection($logDBPath);
19 | if ($dbConnection === NULL) {
20 | exit("Unable to create database connection\n");
21 | }
22 | if (isset($dbConnection->error['databaseConnectionError'])) {
23 | exit($dbConnection->strError() . "\n");
24 | }
25 |
26 | $sql = "SELECT * FROM log ORDER BY updated_at DESC;";
27 | if ($probesOnly) {
28 | $sql = "SELECT * FROM log WHERE log_type=0 ORDER BY updated_at DESC;";
29 | }
30 | $log = $dbConnection->query($sql);
31 |
32 | $clearlog = exec('uci get reporting.@settings[0].clear_log');
33 | if ($clearlog == '1') {
34 | $dbConnection->exec('DELETE FROM log;');
35 | }
36 | echo json_encode($log, JSON_PRETTY_PRINT);
37 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Reporting/files/reporting:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | tmpdir="/tmp"
4 | force_email_flag=$1
5 |
6 | checkInternet() {
7 | ping -c1 -W3 -q 8.8.8.8 &> /dev/null && {
8 | echo "1"
9 | } || {
10 | echo "0"
11 | }
12 | }
13 |
14 | checkSDCard() {
15 | [[ $(mount | grep /sd) == "" ]] && {
16 | echo "1"
17 | } || {
18 | echo "0"
19 | }
20 | }
21 |
22 | sendEmail() {
23 | tar_gz=$1
24 | to=$(uci get reporting.@ssmtp[0].to)
25 | from=$(uci get reporting.@ssmtp[0].from)
26 | email_body="To:$to\nFrom:$from\nSubject:WiFi Pineapple Report: $(date)\n\nPlease see the attached file."
27 |
28 | date=$(date +%Y-%m-%d-%H:%M)
29 |
30 | echo -e $email_body | (cat - && cat $tar_gz | uuencode "WiFi_Pineapple_Report_$(date +%Y-%m-%d-%H-%M).tar.gz") | ssmtp $to &> /dev/null && {
31 | echo "$date: Successfully sent email to $to" >> /tmp/reporting.log
32 | } || {
33 | echo "$date: Failed to send email to $to" >> /tmp/reporting.log
34 | }
35 | }
36 |
37 | getPineAPReport() {
38 | [[ "$(uci get reporting.@settings[0].log)" == "1" ]] && {
39 | /pineapple/modules/Reporting/files/getlog.php > $tmpdir/report/pineap.log
40 | }
41 | }
42 |
43 | getClientReport() {
44 | [[ "$(uci get reporting.@settings[0].client)" == "1" ]] && {
45 | /pineapple/modules/Reporting/files/getlog.php --probes > $tmpdir/report/probing_clients.csv
46 | }
47 | }
48 |
49 | getSiteReport() {
50 | [[ "$(uci get reporting.@settings[0].survey)" == "1" ]] && {
51 | /etc/init.d/pineapd start
52 | sleep 3
53 | duration=$(uci get reporting.@settings[0].duration)
54 | scan_type=0
55 | cat /pineapple/config.php | grep "'tetra'" && scan_type=2
56 | echo -e "\tRunning scan type $scan_type for 15 seconds"
57 | (/usr/bin/pineap /tmp/pineap.conf run_scan $duration $scan_type 2>&1) >> /tmp/reporting.log
58 | sleep 2
59 | scan_id="$(/usr/bin/pineap /tmp/pineap.conf get_status | grep scanID | awk '{print $2}' | sed 's/,//')"
60 | echo -e "\tNew scan id: $scan_id"
61 | echo -e "\tWaiting for scan to finish"
62 | while [ "$(/usr/bin/pineap /tmp/pineap.conf get_status | grep 'scanRunning' | awk '{print $2}' | sed 's/,//')" == "true" ]; do sleep 1; done
63 | echo -e "\tRetrieving scan results, appending to debug log"
64 | chmod a+x /pineapple/modules/Help/files/dumpscan.php
65 | /pineapple/modules/Help/files/dumpscan.php $scan_id > $tmpdir/report/site_survey
66 | }
67 | }
68 |
69 | getTrackedClients() {
70 | [[ "$(uci get reporting.@settings[0].tracking)" == "1" ]] && {
71 | cp /tmp/tracking.report $tmpdir/report/tracked_clients &> /dev/null
72 | echo "" > /tmp/tracking.report
73 | }
74 | }
75 |
76 | generateReport() {
77 | rm -rf $tmpdir/report &> /dev/null
78 | mkdir -p $tmpdir/report &> /dev/null
79 |
80 | archive_name="WiFi_Pineapple_Report_$(date +%Y-%m-%d-%H-%M).tar.gz"
81 |
82 | echo getPineAPReport
83 | getPineAPReport
84 | echo getClientReport
85 | getClientReport
86 | echo getTrackedclients
87 | getTrackedClients
88 | echo getSiteReport
89 | getSiteReport
90 |
91 | echo tar
92 | tar -C $tmpdir -pczhf $tmpdir/$archive_name report
93 |
94 | [[ "$(uci get reporting.@settings[0].send_email)" == "1" ]] || [[ "$force_email_flag" == "force_email" ]] && {
95 | [[ "$(checkInternet)" == "1" ]] && {
96 | sendEmail $tmpdir/$archive_name
97 | } || {
98 | echo "$(date +%Y-%m-%d-%H:%M): Failed to email report - no internet connection available" >> /tmp/reporting.log
99 | }
100 | }
101 |
102 | [[ $(checkSDCard) == "1" ]] && {
103 | [[ "$(uci get reporting.@settings[0].save_report)" != "1" ]] && {
104 | rm -rf $tmpdir/$archive_name
105 | } || {
106 | mkdir -p /sd/wifipineapple_reports &> /dev/null
107 | mv $tmpdir/$archive_name /sd/wifipineapple_reports/$archive_name
108 | echo "$(date +%Y-%m-%d-%H:%M): Report saved to SD card" >> /tmp/reporting.log
109 | }
110 | } || {
111 | echo "$(date +%Y-%m-%d-%H:%M): Failed to save to SD card - no SD card found" >> /tmp/reporting.log
112 | }
113 |
114 | rm -rf $tmpdir/report &> /dev/null
115 | }
116 |
117 | tmpdir="/tmp"
118 | [[ $(checkSDCard) == "1" ]] && {
119 | tmpdir="/sd/tmp"
120 | }
121 |
122 | generateReport
--------------------------------------------------------------------------------
/src/pineapple/modules/Reporting/js/module.js:
--------------------------------------------------------------------------------
1 | registerController('ReportConfigurationController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
2 | $scope.configSaved = false;
3 | $scope.sdDisabled = false;
4 | $scope.config = {
5 | generateReport: false,
6 | storeReport: false,
7 | sendReport: false,
8 | interval: 1
9 | };
10 |
11 | $scope.saveConfiguration = (function() {
12 | $api.request({
13 | module: 'Reporting',
14 | action: 'setReportConfiguration',
15 | config: $scope.config
16 | }, function(response) {
17 | if (response.error === undefined) {
18 | $scope.configSaved = true;
19 | $timeout(function() {
20 | $scope.configSaved = false;
21 | }, 2000);
22 | }
23 | });
24 | });
25 |
26 | $scope.getConfiguration = (function() {
27 | $api.request({
28 | module: 'Reporting',
29 | action: 'getReportConfiguration'
30 | }, function(response) {
31 | $scope.config = response.config;
32 | $scope.sdDisabled = response.sdDisabled;
33 | });
34 | });
35 |
36 | $scope.getConfiguration();
37 | }]);
38 |
39 | registerController('ReportContentController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
40 | $scope.configSaved = false;
41 | $scope.config = {
42 | pineAPLog: false,
43 | clearLog: false,
44 | siteSurvey: false,
45 | client: false,
46 | tracking: false,
47 | siteSurveyDuration: 15
48 | };
49 | $scope.error = "";
50 |
51 | $scope.saveConfiguration = (function() {
52 | $api.request({
53 | module: 'Reporting',
54 | action: 'setReportContents',
55 | config: $scope.config
56 | }, function(response) {
57 | if (response.error === undefined) {
58 | $scope.configSaved = true;
59 | $timeout(function() {
60 | $scope.configSaved = false;
61 | }, 2000);
62 | }
63 | });
64 | });
65 |
66 | $api.request({
67 | module: 'Reporting',
68 | action: 'getReportContents'
69 | }, function(response) {
70 | $scope.config = response.config
71 | });
72 | }]);
73 |
74 | registerController('EmailConfigurationController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
75 | $scope.configSaved = false;
76 | $scope.testing = false;
77 | $scope.config = {
78 | from : "",
79 | to : "",
80 | server: "",
81 | port: "",
82 | domain: "",
83 | username: "",
84 | password: "",
85 | tls: true,
86 | starttls: true
87 | };
88 |
89 | $scope.saveConfiguration = (function() {
90 | $api.request({
91 | module: 'Reporting',
92 | action: 'setEmailConfiguration',
93 | config: $scope.config
94 | }, function(response) {
95 | if (response.error === undefined) {
96 | $scope.configSaved = true;
97 | $timeout(function() {
98 | $scope.configSaved = false;
99 | }, 2000);
100 | }
101 | });
102 | });
103 |
104 | $scope.testConfiguration = (function() {
105 | $scope.saveConfiguration();
106 |
107 | if ($scope.config['from'] === "" || $scope.config['to'] === "" || $scope.config['server'] === "" ||
108 | $scope.config['port'] === "" || $scope.config['domain'] === "" || $scope.config['username'] === "") {
109 | $scope.error = "You have not provided a correct configuration. Please check all fields and try again.";
110 | $timeout(function () {
111 | $scope.error = "";
112 | }, 2000);
113 | } else {
114 | $api.request({
115 | module: 'Reporting',
116 | action: 'testReportConfiguration'
117 | }, function (response) {
118 | if (response.error === undefined) {
119 | $scope.testing = true;
120 | $timeout(function () {
121 | $scope.testing = false;
122 | }, 2000);
123 | }
124 | });
125 | }
126 | });
127 |
128 | $api.request({
129 | module: 'Reporting',
130 | action: 'getEmailConfiguration'
131 | }, function(response) {
132 | $scope.config = response.config;
133 | });
134 | }]);
135 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Reporting/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Generate reports at specified intervals.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Reporting",
10 | "version": "1.1",
11 | "index": 9
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Reporting/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Tracking/api/module.php:
--------------------------------------------------------------------------------
1 | dbConnection = false;
13 | if (file_exists(self::DATABASE)) {
14 | $this->dbConnection = new DatabaseConnection(self::DATABASE);
15 | }
16 | }
17 |
18 | public function route()
19 | {
20 | switch ($this->request->action) {
21 | case 'getScript':
22 | $this->getScript();
23 | break;
24 |
25 | case 'saveScript':
26 | $this->saveScript();
27 | break;
28 |
29 | case 'getTrackingList':
30 | $this->getTrackingList();
31 | break;
32 |
33 | case 'addMac':
34 | $this->addMac();
35 | break;
36 |
37 | case 'removeMac':
38 | $this->removeMac();
39 | break;
40 |
41 | case 'clearMacs':
42 | $this->clearMacs();
43 | break;
44 | }
45 | }
46 |
47 | private function getScript()
48 | {
49 | $trackingScript = file_get_contents("/etc/pineapple/tracking_script_user");
50 | $this->response = ["trackingScript" => $trackingScript];
51 | }
52 |
53 | private function saveScript()
54 | {
55 | if (isset($this->request->trackingScript)) {
56 | file_put_contents("/etc/pineapple/tracking_script_user", $this->request->trackingScript);
57 | }
58 | $this->response = ["success" => true];
59 | }
60 |
61 | private function getTrackingList()
62 | {
63 | $trackingList = "";
64 | $result = $this->dbConnection->query("SELECT mac FROM tracking;");
65 |
66 | foreach ($result as $row) {
67 | $trackingList .= $row['mac'] . "\n";
68 | }
69 | $this->response = ["trackingList" => $trackingList];
70 | }
71 |
72 | private function addMac()
73 | {
74 | if (isset($this->request->mac) && !empty($this->request->mac)) {
75 | $mac = strtoupper($this->request->mac);
76 | if(preg_match('^[a-fA-F0-9:]{17}|[a-fA-F0-9]{12}^', $mac)) {
77 | $this->dbConnection->exec("INSERT INTO tracking (mac) VALUES ('%s');", $mac);
78 | $this->getTrackingList();
79 | } else {
80 | $this->error = "Please enter a valid MAC Address";
81 | }
82 | }
83 | }
84 |
85 | private function removeMac()
86 | {
87 | if (isset($this->request->mac) && !empty($this->request->mac)) {
88 | $mac = strtoupper($this->request->mac);
89 | if(preg_match('^[a-fA-F0-9:]{17}|[a-fA-F0-9]{12}^', $mac)) {
90 | $this->dbConnection->exec("DELETE FROM tracking WHERE mac='%s' COLLATE NOCASE;", $mac);
91 | $this->getTrackingList();
92 | } else {
93 | $this->error = "Please enter a valid MAC Address";
94 | }
95 | }
96 | }
97 |
98 | private function clearMacs()
99 | {
100 | $this->dbConnection->exec("DELETE FROM tracking;");
101 | $this->getTrackingList();
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Tracking/js/module.js:
--------------------------------------------------------------------------------
1 | registerController("TrackingListController", ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
2 | $scope.trackingList = "";
3 | $scope.mac = "";
4 | $scope.error = "";
5 |
6 | $api.request({
7 | module: 'Tracking',
8 | action: 'getTrackingList'
9 | }, function(response) {
10 | if (response.error === undefined) {
11 | $scope.trackingList = response.trackingList.toUpperCase();
12 | }
13 | });
14 |
15 | $scope.addMac = (function() {
16 | $api.request({
17 | module: 'Tracking',
18 | action: 'addMac',
19 | mac: convertMACAddress($scope.mac)
20 | }, function(response) {
21 | if (response.error === undefined) {
22 | $scope.trackingList = response.trackingList;
23 | $scope.mac = "";
24 | $scope.error = "";
25 | } else {
26 | $scope.error = response.error;
27 | $timeout(function() {
28 | $scope.error = "";
29 | }, 2000);
30 | }
31 | });
32 | });
33 |
34 | $scope.removeMac = (function() {
35 | $api.request({
36 | module: 'Tracking',
37 | action: 'removeMac',
38 | mac: convertMACAddress($scope.mac)
39 | }, function(response) {
40 | if (response.error === undefined) {
41 | $scope.trackingList = response.trackingList;
42 | $scope.mac = "";
43 | } else {
44 | $scope.error = response.error;
45 | $timeout(function() {
46 | $scope.error = "";
47 | }, 2000);
48 | }
49 | });
50 | });
51 |
52 | $scope.clearMacs = (function() {
53 | $api.request({
54 | module: 'Tracking',
55 | action: 'clearMacs'
56 | }, function(response) {
57 | if (response.error === undefined) {
58 | $scope.trackingList = response.trackingList;
59 | } else {
60 | $scope.error = response.error;
61 | }
62 | });
63 | });
64 | }]);
65 |
66 | registerController("TrackingScriptController", ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
67 | $scope.trackingScript = "";
68 | $scope.scriptSaved = false;
69 |
70 | $api.request({
71 | module: 'Tracking',
72 | action: 'getScript'
73 | }, function(response) {
74 | if (response.error === undefined) {
75 | $scope.trackingScript = response.trackingScript;
76 | }
77 | });
78 |
79 | $scope.saveScript = (function() {
80 | $api.request({
81 | module: 'Tracking',
82 | action: 'saveScript',
83 | trackingScript: $scope.trackingScript
84 | }, function(response) {
85 | if (response.success === true) {
86 | $scope.scriptSaved = true;
87 | $timeout(function(){
88 | $scope.scriptSaved = false;
89 | }, 2000);
90 | }
91 | });
92 | });
93 | }]);
94 |
95 | function getLineNumber(textarea) {
96 | var lineNumber = textarea.value.substr(0, textarea.selectionStart).split("\n").length;
97 | var mac = textarea.value.split("\n")[lineNumber-1].trim();
98 | $("input[name='mac']").val(mac).trigger('input');
99 | }
--------------------------------------------------------------------------------
/src/pineapple/modules/Tracking/module.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Client Tracking List
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Add
26 | Remove
27 |
28 |
29 |
30 |
31 | {{ error }}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
Tracking Script
41 |
42 |
43 |
44 |
45 |
46 |
Script saved successfully
47 |
Save Script
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Tracking/module.info:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Hak5",
3 | "description": "Continuously scan for specified clients by MAC Addresses.",
4 | "devices": [
5 | "nano",
6 | "tetra"
7 | ],
8 | "system": true,
9 | "title": "Tracking",
10 | "version": "1.0",
11 | "index": 4
12 | }
13 |
--------------------------------------------------------------------------------
/src/pineapple/modules/Tracking/module_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pineapple/pineapple_version:
--------------------------------------------------------------------------------
1 | 2.9.2
2 |
--------------------------------------------------------------------------------