diff --git a/README.md b/README.md index fc6cb45..eaa2ba7 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,15 @@ Some hints: - To find out more about the WiFi Pineapple API itself, set `debug = True` when instantiating your `Pineapple()`s and it will log the HTTP requests it makes - Read the WiFi Pineapple php source located on your pineapple at `/pineapple/modules/$modulename/api/module.php` as well as the corresponding python files in this project +## Notes +This is currently undergoing a rewrite and some methods may appear to have duplicate functionality. This is being done to keep backward compatibility. Method names will mirror those shipped with Pineapple Firmware and associated Modules. Planning on adding some additional helpers. This README will be updated over time. Feel free to let me know if you want a specific feature. + ## Examples: ##### Instantiate a Pineapple object:
 API_TOKEN = "xxxxxxxxxx..."
-from pineapple.pineapple import Pineapple
-fruit = Pineapple(API_TOKEN)
+from pineapple import *
+fruit = pineapple.Pineapple(API_TOKEN)
 
##### Add a notification:
@@ -27,5 +30,23 @@ fruit.getModule("pineap").enable()
 
 fruit.getModule("pineap").deauth('6e:74:64:69:63:6b', ['73:65:62:6b:69:6e', '6e:65:73:67:69:61'], 5, 1)
 
- +##### Get SSID Pool +
+p = fruit.getModule("pineap").getSSIDPool()
+
+Returns a dict. The pool is on the key "ssidPool" separated by newlines. To get a quick list, do the following: +
+ssids = p['ssidPool'].split('\n')
+
+##### Download Scans +Some support for download files via token was added to api and recon +
+fruit.getModule("recon").downloadResults(1)
+
+Will return a dict with key "download" and a unique download token for the results of Scan ID 1. So knowing that, +you can call api.download now to get the results +
+myScan = fruit.api.download(fruit.getModule("recon").downloadResults(1)['download'])
+
+myScan will be the raw file text. In the case of a recon scan, it is json. So you can make it more usable with json.loads(myScan) *To generate API tokens, use the [API Tokens](https://github.com/735tesla/Pineapple-API-Tokens-Module/) module* diff --git a/pineapple/advanced.py b/pineapple/advanced.py index f5afccf..9c7efdb 100644 --- a/pineapple/advanced.py +++ b/pineapple/advanced.py @@ -2,6 +2,29 @@ class Advanced(Module): def __init__(self, api): + """ + Methods this module should have: + + getResources +dropCaches +getUSB +getFstab +saveFstab +getCSS +saveCSS +formatSDCard +formatSDCardStatus +checkForUpgrade +downloadUpgrade +getDownloadStatus +performUpgrade +getCurrentVersion +checkApiToken +addApiToken +getApiTokens +revokeApiToken + """ + super(Advanced, self).__init__(api, 'Advanced') def getResources(self): return self.request('getResources') diff --git a/pineapple/api.py b/pineapple/api.py index 7264448..1188027 100644 --- a/pineapple/api.py +++ b/pineapple/api.py @@ -19,7 +19,25 @@ def request(self, type, module, action, args = None): payload = json.dumps(requestObject) resp = requests.post(self.url, data=payload, headers=self.headers) try: - return json.loads(resp.text) + # Update for latest firmware: + # The latest firmware puts 6 junk chars in the beginning of the json stream + # Filtering that out. + if resp.text[0:6] == ")]}',\n": + return json.loads(resp.text[6:]) + else: + return json.loads(resp.text) + except ValueError as e: print("Error decoding: %".format(repr(resp.text))) print e + + def download(self, dFile): + """ + Download a file by token. Just returning text instead of json as a just in case. + Easy to handle by just grabbing the response object and hitting it with json.loads + -- This was added to be able to download recon scans but I'm sure someone else might have another use for it + """ + requestObject = {'apiToken': self.apiToken, 'download': dFile } + resp = requests.get(self.url, requestObject) + return resp.text + diff --git a/pineapple/clients.py b/pineapple/clients.py index 312e253..e6c3984 100644 --- a/pineapple/clients.py +++ b/pineapple/clients.py @@ -3,7 +3,16 @@ class Clients(Module): def __init__(self, api): super(Clients, self).__init__(api, 'Clients') + def getClientData(self): + """ + Return List of Clients + """ return self.request('getClientData') + def kickClient(self, mac): + """ + Kick a client by mac address + """ return self.request('kickClient', {'mac': mac}) + diff --git a/pineapple/filters.py b/pineapple/filters.py index 788f062..e76c7d1 100644 --- a/pineapple/filters.py +++ b/pineapple/filters.py @@ -3,21 +3,81 @@ class Filters(Module): def __init__(self, api): super(Filters, self).__init__(api, 'Filters') + + def getSSIDMode(self): + """ + Return SSID Mode + Allow or Deny + """ + return self.request('getSSIDData') + + def getClientMode(self): + """ + Return Client Mode + Allow or Deny + """ + return self.request('getClientMode') + + def getSSIDFilters(self): + """ + Return SSID Filters from DB + """ + return self.request('getSSIDFilters') + + def getClientFilters(self): + """ + Return Client Filters from DB + """ + return self.request('getClientFilters') + def getClientData(self): + """ + Return array of mode and clientFilters + """ return self.request('getClientData') + def getSSIDData(self): + """ + Return array of mode and ssidFilters + """ return self.request('getSSIDData') - def toggleClientMode(self, allow): - mode = 'Allow' if enabled else 'Disallow' + + def toggleClientMode(self, mode): + """ + Toggle Client Mode + Allow or Deny are valid values. + Case Sensitive + """ return self.request('toggleClientMode', {'mode': mode}) + def toggleSSIDMode(self, allow): - mode = 'Allow' if enabled else 'Disallow' + """ + Toggle SSID Mode + Allow or Deny are valid values + Case Sensisitve + """ return self.request('toggleSSIDMode', {'mode': mode}) + def addClient(self, mac): + """ + Add Client by Mac to Filters + """ return self.request('addClient', {'mac': mac}) + def removeClient(self, mac): + """ + Remove Client by Mac from Filters + """ return self.request('removeClient', {'mac': mac}) + def addSSID(self, ssid): + """ + Add SSID to Filters + """ return self.request('addSSID', {'ssid': ssid}) + def removeSSID(self, ssid): + """ + Remove SSID from Filters + """ return self.request('removeSSID', {'ssid': ssid}) diff --git a/pineapple/logging.py b/pineapple/logging.py index e43b914..f1b6b65 100644 --- a/pineapple/logging.py +++ b/pineapple/logging.py @@ -1,15 +1,59 @@ from module import Module class Logging(Module): + def __init__(self, api): super(Logging, self).__init__(api, 'Logging') + def getSyslog(self): + """ + Return raw syslog + """ return self.request('getSyslog') + def getDmesg(self): + """ + Return raw dmesg + """ return self.request('getDmesg') + def getReportingLog(self): + """ + Return raw reporting log + """ return self.request('getReportingLog') + def getPineapLog(self): + """ + Return PineAP Log in JSON Format + """ return self.request('getPineapLog') + def clearPineapLog(self): + """ + Clear PineAP Log + """ return self.request('clearPineapLog') + + def getPineapLogLocation(self): + """ + Return the path where PineAP Log is written + + default: /tmp/ + """ + return self.request('getPineapLogLocation') + + def setPineapLogLocation(self, location): + """ + Update the path where PineAP Log is written + + default: /tmp/ + """ + return self.request('setPineapLogLocation', { 'location': location }) + + def downloadPineapLog(self): + """ + Return the download token for PineAP Log + """ + return self.request('downloadPineapLog') + diff --git a/pineapple/networking.py b/pineapple/networking.py index 00a3448..bb4d1d5 100644 --- a/pineapple/networking.py +++ b/pineapple/networking.py @@ -4,6 +4,9 @@ class Networking(Module): def __init__(self, api): super(Networking, self).__init__(api, 'Networking') def getRoutingTable(self): + """ + Return Routing Table + """ return self.request('getRoutingTable') def restartDNS(self): return self.request('restartDNS') @@ -16,6 +19,9 @@ def setHostname(self, hostname): def resetWirelessConfig(self): return self.request('resetWirelessConfig') def getInterfaceList(self): + """ + Return List of Interfaces + """ return self.request('getInterfaceList') def saveAPConfig(self, apConfig): return self.request('saveAPConfig', {'apConfig': apConfig}) @@ -26,15 +32,21 @@ def getMacData(self): def setMac(self, mac): return self.request('setMac', {'mac': mac}) def setRandomMac(self): + """ + Set a random mac + """ return self.request('setRandomMac') def resetMac(self): return self.request('resetMac') def scanForNetworks(self, interface): return self.request('scanForNetworks', {'interface': interface}) def getClientInterfaces(self): + """ + Return list of Client Interfaces + """ return self.request('getClientInterfaces') def connectToAP(self, interface, security, password = ''): - return self.request('connectToAP', {'interface': interface, security: security, key: password}) + return self.request('connectToAP', {'interface': interface, 'security': security, 'key': password}) def checkConnection(self): return self.request('checkConnection') def disconnect(self, interface): diff --git a/pineapple/pineap.py b/pineapple/pineap.py index f0e8af0..f27753f 100644 --- a/pineapple/pineap.py +++ b/pineapple/pineap.py @@ -4,26 +4,115 @@ class Pineap(Module): def __init__(self, api): super(Pineap, self).__init__(api, 'PineAP') + + """ + List of Methods this module needs: + + getPool +clearPool +addSSID +addSSIDs +removeSSID +getPoolLocation +setPoolLocation +setPineAPSettings +getPineAPSettings +setEnterpriseSettings +getEnterpriseSettings +detectEnterpriseCertificate +generateEnterpriseCertificate +clearEnterpriseCertificate +clearEnterpriseDB +getEnterpriseData +startHandshakeCapture +stopHandshakeCapture +getHandshake +getAllHandshakes +checkCaptureStatus +downloadHandshake +downloadAllHandshakes +clearAllHandshakes +deleteHandshake +deauth +enable +disable +enableAutoStart +disableAutoStart +downloadPineAPPool +loadProbes +inject +countSSIDs +downloadJTRHashes +downloadHashcatHashes + + """ + def getSSIDPool(self): + """ + Return raw SSID Pool. + + Note: It's just a \n terminated list + """ return self.request('getPool') + def clearPool(self): + """ + Clear the SSID Pool + """ return self.request('clearPool') + def addSSID(self, ssid): + """ + Add SSID to Pool + """ return self.request('addSSID', {'ssid': ssid}) + def removeSSID(self, ssid): + """ + Remove SSID from Pool + """ return self.request('removeSSID', {'ssid': ssid}) + def setPineAPSettings(self, settings): + """ + Set PineAP Settings. + Hand settings as a Python Dictionary + """ self.request('setPineAPSettings', {'settings': json.dumps(settings)}) + def getPineAPSettings(self): + """ + Get Current PineAP Settings + """ return self.request('getPineAPSettings') + def deauth(self, sta, clients, multiplier, channel): + """ + Deauth a Client from a Station X times on Y Channel + + Args in order: Station, Client, Multiplier, Channel + + """ multiplier = 10 if multiplier > 10 else multiplier - return self.request('deauth', {'sta': sta, 'clients': clients, 'multiplier': multiplier, channel: channel}) + return self.request('deauth', {'sta': sta, 'clients': clients, 'multiplier': multiplier, 'channel': channel}) + def enable(self): + """ + Enable PineAP + """ return self.request('enable') + def disable(self): + """ + Disable PineAP + """ return self.request('disable') + def saveSettignsAsDefault(self, config = None): + """ + Hand a config as Python Dictionary. + Once it updates, save as default + """ if config: resp = self.setPineAPSettings(config) if (resp['error']): diff --git a/pineapple/recon.py b/pineapple/recon.py index 1420ee6..54dbeed 100644 --- a/pineapple/recon.py +++ b/pineapple/recon.py @@ -3,7 +3,66 @@ class Recon(Module): def __init__(self, api): super(Recon, self).__init__(api, 'Recon') + def getScanStatus(self, scanId, scanType): + """ This method is deprecated but left in for compatibility with older firmware """ return self.request('getScanStatus', {'scan': {'scanID': scanId, 'scanType': scanType}}) + def startScan(self, scanType, scanDuration): + """ This method is deprecated but left in for compatibility with older firmware """ return self.request('startScan', {'scanType': scanType, 'scanDuration': scanDuration}) + + def startNormalScan(self, scanType, scanDuration): + """ Start a Normal Scan + Scan Type: + 0 - 2.4 Ghz + 1 - 5 Ghz + 2 - Both + """ + return self.request('startNormalScan', {'scanType': scanType, 'scanDuration': scanDuration}) + + def startLiveScan(self, scanType, scanDuration): + """ Start a Live Scan + Scan Type: + 0 - 2.4 Ghz + 1 - 5 Ghz + 2 - Both + """ + return self.request('startLiveScan', { 'scanType': scanType, 'scanDuration': scanDuration}) + + def checkPineAPDaemon(self): + """ Errors on return but added to mirror recon api """ + return self.request('checkPineAPDaemon') + + def startPineAPDaemon(self): + """ Start Pine AP Daemon """ + return self.request('startPineAPDaemon') + + def getScans(self): + """ Return a list of Scans """ + return self.request('getScans') + + def downloadResults(self, scanId): + """ Download Scan Results by ID + Basically a limited stub right now, + Pending rewrite of Pineapple module to download data + """ + return self.request('downloadResults', { 'scanId': scanId }) + + def removeScan(self, scanId): + """ Delete a scan by ID """ + return self.request('removeScan', { 'scanId': scanId}) + + def getCurrentScanID(self): + """ Return ID of Current Scan """ + return self.request('getCurrentScanID') + + def stopScan(self): + """ Stop currently running scan + Note: This is not by scan ID but rather just currently running scan + """ + return self.request('stopScan') + + def loadResults(self, scanId): + return self.request('loadResults', { 'scanId': scanId }) + diff --git a/pineapple/tracking.py b/pineapple/tracking.py index 9340da7..2c91cd4 100644 --- a/pineapple/tracking.py +++ b/pineapple/tracking.py @@ -3,15 +3,54 @@ class Tracking(Module): def __init__(self, api): super(Tracking, self).__init__(api, 'Tracking') + def getScript(self): + """ + Return contents of user tracking script. + + Default Script contents: + + #!/bin/bash + + echo $MAC + echo $TYPE # 0 PROBE; 1 ASSOCIATION + echo $SSID + """ return self.request('getScript') + def setScript(self, script): + """ + Set script content for user tracking + + Default Script contents: + + #!/bin/bash + echo $MAC + echo $TYPE # 0 PROBE; 1 ASSOCIATION + echo $SSID + """ return self.request('saveScript', {'trackingScript': script}) + def getTrackingList(self): + """ + Return list of macs being tracked + """ return self.request('getTrackingList') + def addMac(self, mac): + """ + Add mac to Tracking List + """ return self.request('addMac', {'mac': mac}) + def removeMac(self, mac): + """ + Remove mac from tracking list + """ return self.request('removeMac', {'mac': mac}) + def clearMacs(self): + """ + Clear all macs from Tracking + """ return self.request('clearMacs')