mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
cf14f3f663 | |||
948fe89ce6 | |||
8d1a5babe8 | |||
042d5ba765 | |||
58857058a4 | |||
5e6443ae58 | |||
0e06d3bd76 | |||
64f7c6e1e5 | |||
8442ce93be | |||
730fa7dc8e | |||
0959140098 | |||
7c4764bff8 | |||
e179165850 | |||
0140a1fc97 | |||
cb0c6c6e44 | |||
0ceeb94111 | |||
6bf50a36dd | |||
fb0fda4d0d | |||
51f86d0286 | |||
614e844a51 | |||
fd16b2c94d | |||
a4d5930477 | |||
f9b2a76665 | |||
80279ca02b | |||
c7a06a4e43 | |||
9b969375b7 | |||
61eff05227 | |||
21151dd9ef | |||
cd3eb0963b | |||
a41a8a277f | |||
4b7a3bc138 | |||
6fa6ca8a67 | |||
6b5631373a | |||
de5cd8705f | |||
a317528aeb | |||
1ec6931740 | |||
45138c7fae | |||
dcd91268a1 | |||
27c8a113de | |||
9ea6728a26 | |||
af4e4d9a35 |
13
.github/ISSUE_TEMPLATE.yml
vendored
13
.github/ISSUE_TEMPLATE.yml
vendored
@ -33,12 +33,19 @@ body:
|
|||||||
label: Version
|
label: Version
|
||||||
description: What version of our software are you running?
|
description: What version of our software are you running?
|
||||||
options:
|
options:
|
||||||
- 2.9.3
|
- 2.9.4-2
|
||||||
- 2.9.3-2
|
|
||||||
- 2.9.4
|
|
||||||
default: 0
|
default: 0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: 3rd-party-hardware
|
||||||
|
attributes:
|
||||||
|
label: 3rd Party Hardware
|
||||||
|
description: Are you using any 3rd party hardware? By selecting "Yes", you agree that you have tested the issue without the 3rd party hardware. And acknowledge that the issue may be related to the 3rd party hardware, for which we cannot provide full support.
|
||||||
|
options:
|
||||||
|
- "Yes"
|
||||||
|
- "No"
|
||||||
|
default: 1
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: logs
|
id: logs
|
||||||
attributes:
|
attributes:
|
||||||
|
13
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
13
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -33,12 +33,19 @@ body:
|
|||||||
label: Version
|
label: Version
|
||||||
description: What version of our software are you running?
|
description: What version of our software are you running?
|
||||||
options:
|
options:
|
||||||
- 2.9.3
|
- 2.9.4-2
|
||||||
- 2.9.3-2
|
|
||||||
- 2.9.4
|
|
||||||
default: 0
|
default: 0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: 3rd-party-hardware
|
||||||
|
attributes:
|
||||||
|
label: 3rd Party Hardware
|
||||||
|
description: Are you using any 3rd party hardware? By selecting "Yes", you agree that you have tested the issue without the 3rd party hardware. And acknowledge that the issue may be related to the 3rd party hardware, for which we cannot provide full support.
|
||||||
|
options:
|
||||||
|
- "Yes"
|
||||||
|
- "No"
|
||||||
|
default: 1
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: logs
|
id: logs
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = '2.9.4-1'
|
__version__ = '2.9.5.2'
|
||||||
|
@ -208,7 +208,8 @@ def pwnagotchi_cli():
|
|||||||
ssid = input("SSID (Name): ")
|
ssid = input("SSID (Name): ")
|
||||||
bssid = input("BSSID (MAC): ")
|
bssid = input("BSSID (MAC): ")
|
||||||
f.write(f"\t\"{ssid}\",\n")
|
f.write(f"\t\"{ssid}\",\n")
|
||||||
f.write(f"\t\"{bssid}\",\n")
|
if bssid != "":
|
||||||
|
f.write(f"\t\"{bssid}\",\n")
|
||||||
f.write("]\n")
|
f.write("]\n")
|
||||||
# set bluetooth tether
|
# set bluetooth tether
|
||||||
pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n"
|
pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n"
|
||||||
|
@ -54,10 +54,13 @@ main.plugins.memtemp.enabled = false
|
|||||||
main.plugins.memtemp.scale = "celsius"
|
main.plugins.memtemp.scale = "celsius"
|
||||||
main.plugins.memtemp.orientation = "horizontal"
|
main.plugins.memtemp.orientation = "horizontal"
|
||||||
|
|
||||||
main.plugins.onlinehashcrack.enabled = false
|
main.plugins.ohcapi.enabled = false
|
||||||
main.plugins.onlinehashcrack.email = ""
|
main.plugins.ohcapi.api_key = "sk_your_api_key_here"
|
||||||
main.plugins.onlinehashcrack.dashboard = ""
|
main.plugins.ohcapi.receive_email = "yes"
|
||||||
main.plugins.onlinehashcrack.single_files = false
|
|
||||||
|
main.plugins.pwndroid.enabled = false
|
||||||
|
main.plugins.pwndroid.display = false # show coords on display
|
||||||
|
main.plugins.pwndroid.display_alitude = false # show altitude on display
|
||||||
|
|
||||||
main.plugins.pisugarx.enabled = false
|
main.plugins.pisugarx.enabled = false
|
||||||
main.plugins.pisugarx.rotation = false
|
main.plugins.pisugarx.rotation = false
|
||||||
@ -158,8 +161,9 @@ ui.faces.position_y = 34
|
|||||||
|
|
||||||
ui.web.enabled = true
|
ui.web.enabled = true
|
||||||
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
|
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
|
||||||
ui.web.username = "changeme"
|
ui.web.auth = false
|
||||||
ui.web.password = "changeme"
|
ui.web.username = "changeme" # if auth is true
|
||||||
|
ui.web.password = "changeme" # if auth is true
|
||||||
ui.web.origin = ""
|
ui.web.origin = ""
|
||||||
ui.web.port = 8080
|
ui.web.port = 8080
|
||||||
ui.web.on_frame = ""
|
ui.web.on_frame = ""
|
||||||
|
@ -16,8 +16,10 @@ def freq_to_channel(freq: float) -> int:
|
|||||||
return 14
|
return 14
|
||||||
# 5 GHz Wi-Fi channels
|
# 5 GHz Wi-Fi channels
|
||||||
elif 5150 <= freq <= 5850: # 5 GHz Wi-Fi
|
elif 5150 <= freq <= 5850: # 5 GHz Wi-Fi
|
||||||
if freq < 5270: # Channels 36-48
|
if 5150 <= freq <= 5350: # Channels 36-64
|
||||||
return int(((freq - 5180) / 20) + 36)
|
return int(((freq - 5180) / 20) + 36)
|
||||||
|
elif 5470 <= freq <= 5725: # Channels 100-144
|
||||||
|
return int(((freq - 5500) / 20) + 100)
|
||||||
else: # Channels 149-165
|
else: # Channels 149-165
|
||||||
return int(((freq - 5745) / 20) + 149)
|
return int(((freq - 5745) / 20) + 149)
|
||||||
# 6 GHz Wi-Fi channels
|
# 6 GHz Wi-Fi channels
|
||||||
|
@ -374,7 +374,7 @@ class auto_tune(plugins.Plugin):
|
|||||||
try:
|
try:
|
||||||
defaults = {'show_hidden': False,
|
defaults = {'show_hidden': False,
|
||||||
'reset_history': True,
|
'reset_history': True,
|
||||||
'extra_channels': 3,
|
'extra_channels': 15,
|
||||||
}
|
}
|
||||||
|
|
||||||
for d in defaults:
|
for d in defaults:
|
||||||
|
@ -208,7 +208,7 @@ class AutoUpdate(plugins.Plugin):
|
|||||||
|
|
||||||
to_install = []
|
to_install = []
|
||||||
to_check = [
|
to_check = [
|
||||||
('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
|
('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
|
||||||
('jayofelony/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
|
('jayofelony/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
|
||||||
('jayofelony/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
|
('jayofelony/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
|
||||||
]
|
]
|
||||||
|
@ -20,6 +20,8 @@ class BTTether(plugins.Plugin):
|
|||||||
logging.info("[BT-Tether] plugin loaded.")
|
logging.info("[BT-Tether] plugin loaded.")
|
||||||
|
|
||||||
def on_config_changed(self, config):
|
def on_config_changed(self, config):
|
||||||
|
if any(self.options[key] == '' for key in ['phone', 'phone-name', 'ip', 'mac']):
|
||||||
|
self.ready = False
|
||||||
ip = self.options['ip']
|
ip = self.options['ip']
|
||||||
mac = self.options['mac']
|
mac = self.options['mac']
|
||||||
phone_name = self.options['phone-name'] + ' Network'
|
phone_name = self.options['phone-name'] + ' Network'
|
||||||
@ -39,7 +41,8 @@ class BTTether(plugins.Plugin):
|
|||||||
'bluetooth.type', 'panu',
|
'bluetooth.type', 'panu',
|
||||||
'bluetooth.bdaddr', f'{mac}',
|
'bluetooth.bdaddr', f'{mac}',
|
||||||
'ipv4.method', 'manual',
|
'ipv4.method', 'manual',
|
||||||
'ipv4.addresses', f'{address}',
|
'ipv4.dns', '8.8.8.8 1.1.1.1',
|
||||||
|
'ipv4.addresses', f'{address}/24',
|
||||||
'ipv4.gateway', f'{gateway}',
|
'ipv4.gateway', f'{gateway}',
|
||||||
'ipv4.route-metric', '100'
|
'ipv4.route-metric', '100'
|
||||||
], check=True)
|
], check=True)
|
||||||
@ -47,9 +50,12 @@ class BTTether(plugins.Plugin):
|
|||||||
subprocess.run(['nmcli', 'connection', 'up', f'{phone_name}'], check=True)
|
subprocess.run(['nmcli', 'connection', 'up', f'{phone_name}'], check=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"[BT-Tether] Failed to connect to device: {e}")
|
logging.error(f"[BT-Tether] Failed to connect to device: {e}")
|
||||||
|
logging.error(f"[BT-Tether] Failed to connect to device: have you enabled bluetooth tethering on your phone?")
|
||||||
self.ready = True
|
self.ready = True
|
||||||
|
|
||||||
def on_ready(self, agent):
|
def on_ready(self, agent):
|
||||||
|
if any(self.options[key] == '' for key in ['phone', 'phone-name', 'ip', 'mac']):
|
||||||
|
self.ready = False
|
||||||
self.ready = True
|
self.ready = True
|
||||||
|
|
||||||
def on_ui_setup(self, ui):
|
def on_ui_setup(self, ui):
|
||||||
@ -59,16 +65,19 @@ class BTTether(plugins.Plugin):
|
|||||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||||
|
|
||||||
def on_ui_update(self, ui):
|
def on_ui_update(self, ui):
|
||||||
phone_name = self.options['phone-name'] + ' Network'
|
if self.ready:
|
||||||
if (subprocess.run(['bluetoothctl', 'info'], capture_output=True, text=True)).stdout.find('Connected: yes') != -1:
|
phone_name = self.options['phone-name'] + ' Network'
|
||||||
self.status = 'C'
|
if (subprocess.run(['bluetoothctl', 'info'], capture_output=True, text=True)).stdout.find('Connected: yes') != -1:
|
||||||
else:
|
self.status = 'C'
|
||||||
self.status = '-'
|
else:
|
||||||
try:
|
self.status = '-'
|
||||||
subprocess.run(['nmcli', 'connection', 'up', f'{phone_name}'], check=True)
|
try:
|
||||||
except Exception as e:
|
subprocess.run(['nmcli', 'connection', 'up', f'{phone_name}'], check=True)
|
||||||
logging.error(f"[BT-Tether] Failed to connect to device: {e}")
|
except Exception as e:
|
||||||
ui.set('bluetooth', self.status)
|
logging.debug(f"[BT-Tether] Failed to connect to device: {e}")
|
||||||
|
logging.error(f"[BT-Tether] Failed to connect to device: have you enabled bluetooth tethering on your phone?")
|
||||||
|
ui.set('bluetooth', self.status)
|
||||||
|
return
|
||||||
|
|
||||||
def on_unload(self, ui):
|
def on_unload(self, ui):
|
||||||
phone_name = self.options['phone-name'] + ' Network'
|
phone_name = self.options['phone-name'] + ' Network'
|
||||||
|
@ -44,7 +44,7 @@ def parse_pcap(filename):
|
|||||||
|
|
||||||
class Grid(plugins.Plugin):
|
class Grid(plugins.Plugin):
|
||||||
__author__ = 'evilsocket@gmail.com'
|
__author__ = 'evilsocket@gmail.com'
|
||||||
__version__ = '1.0.1'
|
__version__ = '1.1.0'
|
||||||
__license__ = 'GPL3'
|
__license__ = 'GPL3'
|
||||||
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
|
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
|
||||||
'networks to opwngrid.xyz '
|
'networks to opwngrid.xyz '
|
||||||
@ -69,6 +69,11 @@ class Grid(plugins.Plugin):
|
|||||||
def on_loaded(self):
|
def on_loaded(self):
|
||||||
logging.info("grid plugin loaded.")
|
logging.info("grid plugin loaded.")
|
||||||
|
|
||||||
|
def on_webhook(self, path, request):
|
||||||
|
from flask import make_response, redirect
|
||||||
|
response = make_response(redirect("https://opwngrid.xyz", code=302))
|
||||||
|
return response
|
||||||
|
|
||||||
def set_reported(self, reported, net_id):
|
def set_reported(self, reported, net_id):
|
||||||
if net_id not in reported:
|
if net_id not in reported:
|
||||||
reported.append(net_id)
|
reported.append(net_id)
|
||||||
|
233
pwnagotchi/plugins/default/ohcapi.py
Normal file
233
pwnagotchi/plugins/default/ohcapi.py
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from threading import Lock
|
||||||
|
from pwnagotchi.utils import StatusFile
|
||||||
|
import pwnagotchi.plugins as plugins
|
||||||
|
from json.decoder import JSONDecodeError
|
||||||
|
|
||||||
|
class ohcapi(plugins.Plugin):
|
||||||
|
__author__ = 'Rohan Dayaram'
|
||||||
|
__version__ = '1.1.0'
|
||||||
|
__license__ = 'GPL3'
|
||||||
|
__description__ = 'Uploads WPA/WPA2 handshakes to OnlineHashCrack.com using the new API (V2), no dashboard.'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.ready = False
|
||||||
|
self.lock = Lock()
|
||||||
|
try:
|
||||||
|
self.report = StatusFile('/root/handshakes/.ohc_uploads', data_format='json')
|
||||||
|
except JSONDecodeError:
|
||||||
|
os.remove('/root/.ohc_newapi_uploads')
|
||||||
|
self.report = StatusFile('/root/handshakes/.ohc_uploads', data_format='json')
|
||||||
|
self.skip = list()
|
||||||
|
self.last_run = 0 # Track last time periodic tasks were run
|
||||||
|
self.internet_active = False # Track whether internet is currently available
|
||||||
|
|
||||||
|
def on_loaded(self):
|
||||||
|
"""
|
||||||
|
Called when the plugin is loaded.
|
||||||
|
"""
|
||||||
|
required_fields = ['api_key']
|
||||||
|
missing = [field for field in required_fields if field not in self.options or not self.options[field]]
|
||||||
|
if missing:
|
||||||
|
logging.error(f"OHC NewAPI: Missing required config fields: {missing}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'receive_email' not in self.options:
|
||||||
|
self.options['receive_email'] = 'yes' # default
|
||||||
|
|
||||||
|
if 'sleep' not in self.options:
|
||||||
|
self.options['sleep'] = 60*60 # default to 1 hour
|
||||||
|
|
||||||
|
self.ready = True
|
||||||
|
logging.info("OHC NewAPI: Plugin loaded and ready.")
|
||||||
|
|
||||||
|
def on_webhook(self, path, request):
|
||||||
|
from flask import make_response, redirect
|
||||||
|
response = make_response(redirect("https://www.onlinehashcrack.com", code=302))
|
||||||
|
return response
|
||||||
|
|
||||||
|
def on_internet_available(self, agent):
|
||||||
|
"""
|
||||||
|
Called once when the internet becomes available.
|
||||||
|
Run upload/download tasks immediately.
|
||||||
|
"""
|
||||||
|
if not self.ready or self.lock.locked():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.internet_active = True
|
||||||
|
self._run_tasks(agent) # Run immediately when internet is detected
|
||||||
|
self.last_run = time.time() # Record the time of this run
|
||||||
|
|
||||||
|
def on_ui_update(self, ui):
|
||||||
|
"""
|
||||||
|
Called periodically by the UI. We will use this event to run tasks every 60 seconds if internet is still available.
|
||||||
|
"""
|
||||||
|
if not self.ready:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Attempt to get agent from ui
|
||||||
|
agent = getattr(ui, '_agent', None)
|
||||||
|
if agent is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if the internet is still available by pinging Google
|
||||||
|
try:
|
||||||
|
response = requests.get('https://www.google.com', timeout=5)
|
||||||
|
except requests.ConnectionError:
|
||||||
|
self.internet_active = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
self.internet_active = True
|
||||||
|
else:
|
||||||
|
self.internet_active = False
|
||||||
|
return
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - self.last_run >= self.options['sleep']:
|
||||||
|
self._run_tasks(agent)
|
||||||
|
self.last_run = current_time
|
||||||
|
|
||||||
|
def _extract_essid_bssid_from_hash(self, hash_line):
|
||||||
|
parts = hash_line.strip().split('*')
|
||||||
|
essid = 'unknown_ESSID'
|
||||||
|
bssid = '00:00:00:00:00:00'
|
||||||
|
|
||||||
|
if len(parts) > 5:
|
||||||
|
essid_hex = parts[5]
|
||||||
|
try:
|
||||||
|
essid = bytes.fromhex(essid_hex).decode('utf-8', errors='replace')
|
||||||
|
except:
|
||||||
|
essid = 'unknown_ESSID'
|
||||||
|
|
||||||
|
if len(parts) > 3:
|
||||||
|
apmac = parts[3]
|
||||||
|
if len(apmac) == 12:
|
||||||
|
bssid = ':'.join(apmac[i:i+2] for i in range(0, 12, 2))
|
||||||
|
|
||||||
|
if essid == 'unknown_ESSID' or bssid == '00:00:00:00:00:00':
|
||||||
|
logging.debug(f"OHC NewAPI: Failed to extract ESSID/BSSID from hash -> {hash_line}")
|
||||||
|
|
||||||
|
return essid, bssid
|
||||||
|
|
||||||
|
def _run_tasks(self, agent):
|
||||||
|
"""
|
||||||
|
Encapsulates the logic of extracting, uploading, and updating tasks.
|
||||||
|
"""
|
||||||
|
with self.lock:
|
||||||
|
display = agent.view()
|
||||||
|
config = agent.config()
|
||||||
|
reported = self.report.data_field_or('reported', default=[])
|
||||||
|
processed_stations = self.report.data_field_or('processed_stations', default=[])
|
||||||
|
handshake_dir = config['bettercap']['handshakes']
|
||||||
|
|
||||||
|
# Find .pcap files
|
||||||
|
handshake_filenames = os.listdir(handshake_dir)
|
||||||
|
handshake_paths = [os.path.join(handshake_dir, filename)
|
||||||
|
for filename in handshake_filenames if filename.endswith('.pcap')]
|
||||||
|
|
||||||
|
# If the corresponding .22000 file exists, skip re-upload
|
||||||
|
handshake_paths = [p for p in handshake_paths if not os.path.exists(p.replace('.pcap', '.22000'))]
|
||||||
|
|
||||||
|
# Filter out already reported and skipped .pcap files
|
||||||
|
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
||||||
|
|
||||||
|
if handshake_new:
|
||||||
|
logging.info(f"OHC NewAPI: Processing {len(handshake_new)} new PCAP handshakes.")
|
||||||
|
|
||||||
|
all_hashes = []
|
||||||
|
successfully_extracted = []
|
||||||
|
essid_bssid_map = {}
|
||||||
|
|
||||||
|
for idx, pcap_path in enumerate(handshake_new):
|
||||||
|
hashes = self._extract_hashes_from_handshake(pcap_path)
|
||||||
|
if hashes:
|
||||||
|
# Extract ESSID and BSSID from the first hash line
|
||||||
|
essid, bssid = self._extract_essid_bssid_from_hash(hashes[0])
|
||||||
|
if (essid, bssid) in processed_stations:
|
||||||
|
logging.debug(f"OHC NewAPI: Station {essid}/{bssid} already processed, skipping {pcap_path}.")
|
||||||
|
self.skip.append(pcap_path)
|
||||||
|
continue
|
||||||
|
|
||||||
|
all_hashes.extend(hashes)
|
||||||
|
successfully_extracted.append(pcap_path)
|
||||||
|
essid_bssid_map[pcap_path] = (essid, bssid)
|
||||||
|
else:
|
||||||
|
logging.debug(f"OHC NewAPI: No hashes extracted from {pcap_path}, skipping.")
|
||||||
|
self.skip.append(pcap_path)
|
||||||
|
|
||||||
|
# Now upload all extracted hashes
|
||||||
|
if all_hashes:
|
||||||
|
batches = [all_hashes[i:i+50] for i in range(0, len(all_hashes), 50)]
|
||||||
|
upload_success = True
|
||||||
|
for batch_idx, batch in enumerate(batches):
|
||||||
|
display.on_uploading(f"onlinehashcrack.com ({(batch_idx+1)*50}/{len(all_hashes)})")
|
||||||
|
if not self._add_tasks(batch):
|
||||||
|
upload_success = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if upload_success:
|
||||||
|
# Mark all successfully extracted pcaps as reported
|
||||||
|
for pcap_path in successfully_extracted:
|
||||||
|
reported.append(pcap_path)
|
||||||
|
essid, bssid = essid_bssid_map[pcap_path]
|
||||||
|
processed_stations.append((essid, bssid))
|
||||||
|
self.report.update(data={'reported': reported, 'processed_stations': processed_stations})
|
||||||
|
logging.debug("OHC NewAPI: Successfully reported all new handshakes.")
|
||||||
|
else:
|
||||||
|
# Upload failed, skip these pcaps for future attempts
|
||||||
|
for pcap_path in successfully_extracted:
|
||||||
|
self.skip.append(pcap_path)
|
||||||
|
logging.debug("OHC NewAPI: Failed to upload tasks, added to skip list.")
|
||||||
|
else:
|
||||||
|
logging.debug("OHC NewAPI: No hashes were extracted from the new pcaps. Nothing to upload.")
|
||||||
|
|
||||||
|
display.on_normal()
|
||||||
|
else:
|
||||||
|
logging.debug("OHC NewAPI: No new PCAP files to process.")
|
||||||
|
|
||||||
|
def _add_tasks(self, hashes, timeout=30):
|
||||||
|
clean_hashes = [h.strip() for h in hashes if h.strip()]
|
||||||
|
if not clean_hashes:
|
||||||
|
return True # No hashes to add is success
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'api_key': self.options['api_key'],
|
||||||
|
'agree_terms': "yes",
|
||||||
|
'action': 'add_tasks',
|
||||||
|
'algo_mode': 22000,
|
||||||
|
'hashes': clean_hashes,
|
||||||
|
'receive_email': self.options['receive_email']
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = requests.post('https://api.onlinehashcrack.com/v2',
|
||||||
|
json=payload,
|
||||||
|
timeout=timeout)
|
||||||
|
result.raise_for_status()
|
||||||
|
data = result.json()
|
||||||
|
logging.info(f"OHC NewAPI: Add tasks response: {data}")
|
||||||
|
return True
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logging.debug(f"OHC NewAPI: Exception while adding tasks -> {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _extract_hashes_from_handshake(self, pcap_path):
|
||||||
|
hashes = []
|
||||||
|
hcxpcapngtool = '/usr/bin/hcxpcapngtool'
|
||||||
|
hccapx_path = pcap_path.replace('.pcap', '.22000')
|
||||||
|
hcxpcapngtool_cmd = f"{hcxpcapngtool} -o {hccapx_path} {pcap_path}"
|
||||||
|
os.popen(hcxpcapngtool_cmd).read()
|
||||||
|
if os.path.exists(hccapx_path) and os.path.getsize(hccapx_path) > 0:
|
||||||
|
logging.debug(f"OHC NewAPI: Extracted hashes from {pcap_path}")
|
||||||
|
with open(hccapx_path, 'r') as hccapx_file:
|
||||||
|
hashes = hccapx_file.readlines()
|
||||||
|
else:
|
||||||
|
logging.debug(f"OHC NewAPI: Failed to extract hashes from {pcap_path}")
|
||||||
|
if os.path.exists(hccapx_path):
|
||||||
|
os.remove(hccapx_path)
|
||||||
|
return hashes
|
@ -1,147 +0,0 @@
|
|||||||
import os
|
|
||||||
import csv
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
import requests
|
|
||||||
from datetime import datetime
|
|
||||||
from threading import Lock
|
|
||||||
from pwnagotchi.utils import StatusFile, remove_whitelisted
|
|
||||||
import pwnagotchi.plugins as plugins
|
|
||||||
from json.decoder import JSONDecodeError
|
|
||||||
|
|
||||||
|
|
||||||
class OnlineHashCrack(plugins.Plugin):
|
|
||||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
|
||||||
__version__ = '2.1.0'
|
|
||||||
__license__ = 'GPL3'
|
|
||||||
__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.ready = False
|
|
||||||
try:
|
|
||||||
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
|
|
||||||
except JSONDecodeError:
|
|
||||||
os.remove('/root/.ohc_uploads')
|
|
||||||
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
|
|
||||||
self.skip = list()
|
|
||||||
self.lock = Lock()
|
|
||||||
self.options = dict()
|
|
||||||
|
|
||||||
def on_loaded(self):
|
|
||||||
"""
|
|
||||||
Gets called when the plugin gets loaded
|
|
||||||
"""
|
|
||||||
if 'email' not in self.options or ('email' in self.options and not self.options['email']):
|
|
||||||
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.ready = True
|
|
||||||
logging.info("OHC: OnlineHashCrack plugin loaded.")
|
|
||||||
|
|
||||||
def _upload_to_ohc(self, path, timeout=30):
|
|
||||||
"""
|
|
||||||
Uploads the file to onlinehashcrack.com
|
|
||||||
"""
|
|
||||||
with open(path, 'rb') as file_to_upload:
|
|
||||||
data = {'email': self.options['email']}
|
|
||||||
payload = {'file': file_to_upload}
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = requests.post('https://api.onlinehashcrack.com',
|
|
||||||
data=data,
|
|
||||||
files=payload,
|
|
||||||
timeout=timeout)
|
|
||||||
if 'already been sent' in result.text:
|
|
||||||
logging.debug(f"{path} was already uploaded.")
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
logging.debug(f"OHC: Got an exception while uploading {path} -> {e}")
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def _download_cracked(self, save_file, timeout=120):
|
|
||||||
"""
|
|
||||||
Downloads the cracked passwords and saves them
|
|
||||||
|
|
||||||
returns the number of downloaded passwords
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
s = requests.Session()
|
|
||||||
dashboard = s.get(self.options['dashboard'], timeout=timeout)
|
|
||||||
result = s.get('https://www.onlinehashcrack.com/wpa-exportcsv', timeout=timeout)
|
|
||||||
result.raise_for_status()
|
|
||||||
with open(save_file, 'wb') as output_file:
|
|
||||||
output_file.write(result.content)
|
|
||||||
except requests.exceptions.RequestException as req_e:
|
|
||||||
raise req_e
|
|
||||||
except OSError as os_e:
|
|
||||||
raise os_e
|
|
||||||
|
|
||||||
def on_webhook(self, path, request):
|
|
||||||
import requests
|
|
||||||
from flask import redirect
|
|
||||||
s = requests.Session()
|
|
||||||
s.get('https://www.onlinehashcrack.com/dashboard')
|
|
||||||
r = s.post('https://www.onlinehashcrack.com/dashboard', data={'emailTasks': self.options['email'], 'submit': ''})
|
|
||||||
return redirect(r.url, code=302)
|
|
||||||
|
|
||||||
def on_internet_available(self, agent):
|
|
||||||
"""
|
|
||||||
Called in manual mode when there's internet connectivity
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not self.ready or self.lock.locked():
|
|
||||||
return
|
|
||||||
|
|
||||||
with self.lock:
|
|
||||||
display = agent.view()
|
|
||||||
config = agent.config()
|
|
||||||
reported = self.report.data_field_or('reported', default=list())
|
|
||||||
handshake_dir = config['bettercap']['handshakes']
|
|
||||||
handshake_filenames = os.listdir(handshake_dir)
|
|
||||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
|
|
||||||
filename.endswith('.pcap')]
|
|
||||||
# pull out whitelisted APs
|
|
||||||
handshake_paths = remove_whitelisted(handshake_paths, config['main']['whitelist'])
|
|
||||||
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
|
||||||
if handshake_new:
|
|
||||||
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com")
|
|
||||||
for idx, handshake in enumerate(handshake_new):
|
|
||||||
display.on_uploading(f"onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._upload_to_ohc(handshake)
|
|
||||||
if handshake not in reported:
|
|
||||||
reported.append(handshake)
|
|
||||||
self.report.update(data={'reported': reported})
|
|
||||||
logging.debug(f"OHC: Successfully uploaded {handshake}")
|
|
||||||
except requests.exceptions.RequestException as req_e:
|
|
||||||
self.skip.append(handshake)
|
|
||||||
logging.debug("OHC: %s", req_e)
|
|
||||||
continue
|
|
||||||
except OSError as os_e:
|
|
||||||
self.skip.append(handshake)
|
|
||||||
logging.debug("OHC: %s", os_e)
|
|
||||||
continue
|
|
||||||
|
|
||||||
display.on_normal()
|
|
||||||
|
|
||||||
if 'dashboard' in self.options and self.options['dashboard']:
|
|
||||||
cracked_file = os.path.join(handshake_dir, 'onlinehashcrack.cracked')
|
|
||||||
if os.path.exists(cracked_file):
|
|
||||||
last_check = datetime.fromtimestamp(os.path.getmtime(cracked_file))
|
|
||||||
if last_check is not None and ((datetime.now() - last_check).seconds / (60 * 60)) < 1:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self._download_cracked(cracked_file)
|
|
||||||
logging.info("OHC: Downloaded cracked passwords.")
|
|
||||||
except requests.exceptions.RequestException as req_e:
|
|
||||||
logging.debug("OHC: %s", req_e)
|
|
||||||
except OSError as os_e:
|
|
||||||
logging.debug("OHC: %s", os_e)
|
|
||||||
if 'single_files' in self.options and self.options['single_files']:
|
|
||||||
with open(cracked_file, 'r') as cracked_list:
|
|
||||||
for row in csv.DictReader(cracked_list):
|
|
||||||
if row['password']:
|
|
||||||
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
|
|
||||||
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap')):
|
|
||||||
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
|
|
||||||
f.write(row['password'])
|
|
@ -106,7 +106,7 @@ def _send_to_wigle(lines, api_key, donate=True, timeout=30):
|
|||||||
|
|
||||||
class Wigle(plugins.Plugin):
|
class Wigle(plugins.Plugin):
|
||||||
__author__ = "Dadav and updated by Jayofelony"
|
__author__ = "Dadav and updated by Jayofelony"
|
||||||
__version__ = "3.0.1"
|
__version__ = "3.1.0"
|
||||||
__license__ = "GPL3"
|
__license__ = "GPL3"
|
||||||
__description__ = "This plugin automatically uploads collected WiFi to wigle.net"
|
__description__ = "This plugin automatically uploads collected WiFi to wigle.net"
|
||||||
|
|
||||||
@ -128,6 +128,11 @@ class Wigle(plugins.Plugin):
|
|||||||
self.ready = True
|
self.ready = True
|
||||||
logging.info("WIGLE: ready")
|
logging.info("WIGLE: ready")
|
||||||
|
|
||||||
|
def on_webhook(self, path, request):
|
||||||
|
from flask import make_response, redirect
|
||||||
|
response = make_response(redirect("https://www.wigle.net/", code=302))
|
||||||
|
return response
|
||||||
|
|
||||||
def on_internet_available(self, agent):
|
def on_internet_available(self, agent):
|
||||||
"""
|
"""
|
||||||
Called when there's internet connectivity
|
Called when there's internet connectivity
|
||||||
|
@ -161,8 +161,7 @@ class WpaSec(plugins.Plugin):
|
|||||||
|
|
||||||
def on_unload(self, ui):
|
def on_unload(self, ui):
|
||||||
with ui._lock:
|
with ui._lock:
|
||||||
ui.remove_element('ssid')
|
ui.remove_element('pass')
|
||||||
ui.remove_element('password')
|
|
||||||
|
|
||||||
def on_ui_update(self, ui):
|
def on_ui_update(self, ui):
|
||||||
if 'show_pwd' in self.options and self.options['show_pwd'] and 'download_results' in self.options and self.options['download_results']:
|
if 'show_pwd' in self.options and self.options['show_pwd'] and 'download_results' in self.options and self.options['download_results']:
|
||||||
|
@ -64,11 +64,14 @@ class Handler:
|
|||||||
def with_auth(self, f):
|
def with_auth(self, f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
auth = request.authorization
|
if not self._config['auth']:
|
||||||
if not auth or not auth.username or not auth.password or not self._check_creds(auth.username,
|
return f(*args, **kwargs)
|
||||||
auth.password):
|
else:
|
||||||
return Response('Unauthorized', 401, {'WWW-Authenticate': 'Basic realm="Unauthorized"'})
|
auth = request.authorization
|
||||||
return f(*args, **kwargs)
|
if not auth or not auth.username or not auth.password or not self._check_creds(auth.username,
|
||||||
|
auth.password):
|
||||||
|
return Response('Unauthorized', 401, {'WWW-Authenticate': 'Basic realm="Unauthorized"'})
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
158
pwndroid_privacy_policy
Normal file
158
pwndroid_privacy_policy
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
Privacy Policy for PwnDroid
|
||||||
|
Privacy Policy
|
||||||
|
|
||||||
|
Last updated: January 05, 2025
|
||||||
|
|
||||||
|
This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.
|
||||||
|
|
||||||
|
We use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy. This Privacy Policy has been created with the help of the Privacy Policy Generator.
|
||||||
|
Interpretation and Definitions
|
||||||
|
Interpretation
|
||||||
|
|
||||||
|
The words of which the initial letter is capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural.
|
||||||
|
Definitions
|
||||||
|
|
||||||
|
For the purposes of this Privacy Policy:
|
||||||
|
|
||||||
|
Account means a unique account created for You to access our Service or parts of our Service.
|
||||||
|
|
||||||
|
Affiliate means an entity that controls, is controlled by or is under common control with a party, where "control" means ownership of 50% or more of the shares, equity interest or other securities entitled to vote for election of directors or other managing authority.
|
||||||
|
|
||||||
|
Application refers to PwnDroid, the software program provided by the Company.
|
||||||
|
|
||||||
|
Company (referred to as either "the Company", "We", "Us" or "Our" in this Agreement) refers to PwnDroid.
|
||||||
|
|
||||||
|
Country refers to: Netherlands
|
||||||
|
|
||||||
|
Device means any device that can access the Service such as a computer, a cellphone or a digital tablet.
|
||||||
|
|
||||||
|
Personal Data is any information that relates to an identified or identifiable individual.
|
||||||
|
|
||||||
|
Service refers to the Application.
|
||||||
|
|
||||||
|
Service Provider means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used.
|
||||||
|
|
||||||
|
Usage Data refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit).
|
||||||
|
|
||||||
|
You means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable.
|
||||||
|
|
||||||
|
Collecting and Using Your Personal Data
|
||||||
|
Types of Data Collected
|
||||||
|
Personal Data
|
||||||
|
|
||||||
|
While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to:
|
||||||
|
|
||||||
|
Usage Data
|
||||||
|
|
||||||
|
Usage Data
|
||||||
|
|
||||||
|
Usage Data is collected automatically when using the Service.
|
||||||
|
|
||||||
|
Usage Data may include information such as Your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data.
|
||||||
|
|
||||||
|
When You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data.
|
||||||
|
|
||||||
|
We may also collect information that Your browser sends whenever You visit our Service or when You access the Service by or through a mobile device.
|
||||||
|
Information Collected while Using the Application
|
||||||
|
|
||||||
|
While using Our Application, in order to provide features of Our Application, We may collect, with Your prior permission:
|
||||||
|
|
||||||
|
Information regarding your location
|
||||||
|
|
||||||
|
We use this information to provide features of Our Service, to improve and customize Our Service. The information will not be uploaded to the Company's servers and/or a Service Provider's server or it will only be simply stored on Your device.
|
||||||
|
|
||||||
|
You can enable or disable access to this information at any time, through Your Device settings.
|
||||||
|
Use of Your Personal Data
|
||||||
|
|
||||||
|
The Company may use Personal Data for the following purposes:
|
||||||
|
|
||||||
|
To provide and maintain our Service, including to monitor the usage of our Service.
|
||||||
|
|
||||||
|
To manage Your Account: to manage Your registration as a user of the Service. The Personal Data You provide can give You access to different functionalities of the Service that are available to You as a registered user.
|
||||||
|
|
||||||
|
For the performance of a contract: the development, compliance and undertaking of the purchase contract for the products, items or services You have purchased or of any other contract with Us through the Service.
|
||||||
|
|
||||||
|
To contact You: To contact You by email, telephone calls, SMS, or other equivalent forms of electronic communication, such as a mobile application's push notifications regarding updates or informative communications related to the functionalities, products or contracted services, including the security updates, when necessary or reasonable for their implementation.
|
||||||
|
|
||||||
|
To provide You with news, special offers and general information about other goods, services and events which we offer that are similar to those that you have already purchased or enquired about unless You have opted not to receive such information.
|
||||||
|
|
||||||
|
To manage Your requests: To attend and manage Your requests to Us.
|
||||||
|
|
||||||
|
For business transfers: We may use Your information to evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which Personal Data held by Us about our Service users is among the assets transferred.
|
||||||
|
|
||||||
|
For other purposes: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, products, services, marketing and your experience.
|
||||||
|
|
||||||
|
We may share Your personal information in the following situations:
|
||||||
|
|
||||||
|
With Service Providers: We may share Your personal information with Service Providers to monitor and analyze the use of our Service, to contact You.
|
||||||
|
For business transfers: We may share or transfer Your personal information in connection with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a portion of Our business to another company.
|
||||||
|
With Affiliates: We may share Your information with Our affiliates, in which case we will require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other subsidiaries, joint venture partners or other companies that We control or that are under common control with Us.
|
||||||
|
With business partners: We may share Your information with Our business partners to offer You certain products, services or promotions.
|
||||||
|
With other users: when You share personal information or otherwise interact in the public areas with other users, such information may be viewed by all users and may be publicly distributed outside.
|
||||||
|
With Your consent: We may disclose Your personal information for any other purpose with Your consent.
|
||||||
|
|
||||||
|
Retention of Your Personal Data
|
||||||
|
|
||||||
|
The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if we are required to retain your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.
|
||||||
|
|
||||||
|
The Company will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period of time, except when this data is used to strengthen the security or to improve the functionality of Our Service, or We are legally obligated to retain this data for longer time periods.
|
||||||
|
Transfer of Your Personal Data
|
||||||
|
|
||||||
|
Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from Your jurisdiction.
|
||||||
|
|
||||||
|
Your consent to this Privacy Policy followed by Your submission of such information represents Your agreement to that transfer.
|
||||||
|
|
||||||
|
The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information.
|
||||||
|
Delete Your Personal Data
|
||||||
|
|
||||||
|
You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You.
|
||||||
|
|
||||||
|
Our Service may give You the ability to delete certain information about You from within the Service.
|
||||||
|
|
||||||
|
You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any personal information that You have provided to Us.
|
||||||
|
|
||||||
|
Please note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so.
|
||||||
|
Disclosure of Your Personal Data
|
||||||
|
Business Transactions
|
||||||
|
|
||||||
|
If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy.
|
||||||
|
Law enforcement
|
||||||
|
|
||||||
|
Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).
|
||||||
|
Other legal requirements
|
||||||
|
|
||||||
|
The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:
|
||||||
|
|
||||||
|
Comply with a legal obligation
|
||||||
|
Protect and defend the rights or property of the Company
|
||||||
|
Prevent or investigate possible wrongdoing in connection with the Service
|
||||||
|
Protect the personal safety of Users of the Service or the public
|
||||||
|
Protect against legal liability
|
||||||
|
|
||||||
|
Security of Your Personal Data
|
||||||
|
|
||||||
|
The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially acceptable means to protect Your Personal Data, We cannot guarantee its absolute security.
|
||||||
|
Children's Privacy
|
||||||
|
|
||||||
|
Our Service does not address anyone under the age of 13. We do not knowingly collect personally identifiable information from anyone under the age of 13. If You are a parent or guardian and You are aware that Your child has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data from anyone under the age of 13 without verification of parental consent, We take steps to remove that information from Our servers.
|
||||||
|
|
||||||
|
If We need to rely on consent as a legal basis for processing Your information and Your country requires consent from a parent, We may require Your parent's consent before We collect and use that information.
|
||||||
|
Links to Other Websites
|
||||||
|
|
||||||
|
Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party's site. We strongly advise You to review the Privacy Policy of every site You visit.
|
||||||
|
|
||||||
|
We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.
|
||||||
|
Changes to this Privacy Policy
|
||||||
|
|
||||||
|
We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page.
|
||||||
|
|
||||||
|
We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the "Last updated" date at the top of this Privacy Policy.
|
||||||
|
|
||||||
|
You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.
|
||||||
|
Contact Us
|
||||||
|
|
||||||
|
If you have any questions about this Privacy Policy, You can contact us:
|
||||||
|
|
||||||
|
By email: oudshoorn.jeroen@gmail.com
|
||||||
|
|
||||||
|
Generated using Privacy Policies Generator
|
Reference in New Issue
Block a user