mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 01:26:11 -08:00
chore:PHOLUS removal
This commit is contained in:
@@ -21,7 +21,6 @@ You can access the following files:
|
||||
| `notification_text.html` | The full HTML of the last email notification. |
|
||||
| `notification_json_final.json` | The json version of the last notification (e.g. used for webhooks - [sample JSON](https://github.com/jokob-sk/NetAlertX/blob/main/front/report_templates/webhook_json_sample.json)). |
|
||||
| `table_devices.json` | The current (at the time of the last update as mentioned above on this page) state of all of the available Devices detected by the app. |
|
||||
| `table_pholus_scan.json` | The latest state of the [pholus](https://github.com/jokob-sk/NetAlertX/tree/main/pholus) (A multicast DNS and DNS Service Discovery Security Assessment Tool) scan results. |
|
||||
| `table_plugins_events.json` | The list of the unprocessed (pending) notification events (plugins_events DB table). |
|
||||
| `table_plugins_history.json` | The list of notification events history. |
|
||||
| `table_plugins_objects.json` | The content of the plugins_objects table. Find more info on the [Plugin system here](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins)|
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
| Events | Used to collect connection/disconnection events. | ![Screen4][screen4] |
|
||||
| Online_History | Used to display the `Device presence` chart | ![Screen6][screen6] |
|
||||
| Parameters | Used to pass values between the frontend and backend. | ![Screen7][screen7] |
|
||||
| Pholus_Scan | Scan results of the Pholus python network penetration script. | ![Screen8][screen8] |
|
||||
| Plugins_Events | For capturing events exposed by a plugin via the `last_result.log` file. If unique then saved into the `Plugins_Objects` table. Entries are deleted once processed and stored in the `Plugins_History` and/or `Plugins_Objects` tables. | ![Screen10][screen10] |
|
||||
| Plugins_History | History of all entries from the `Plugins_Events` table | ![Screen11][screen11] |
|
||||
| Plugins_Language_Strings | Language strings collected from the plugin `config.json` files used for string resolution in the frontend. | ![Screen12][screen12] |
|
||||
@@ -29,7 +28,6 @@
|
||||
[screen4]: /docs/img/DATABASE/Events.png
|
||||
[screen6]: /docs/img/DATABASE/Online_History.png
|
||||
[screen7]: /docs/img/DATABASE/Parameters.png
|
||||
[screen8]: /docs/img/DATABASE/Pholus_Scan.png
|
||||
[screen10]: /docs/img/DATABASE/Plugins_Events.png
|
||||
[screen11]: /docs/img/DATABASE/Plugins_History.png
|
||||
[screen12]: /docs/img/DATABASE/Plugins_Language_Strings.png
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/components/graph_online_history.php';
|
||||
|
||||
// check permissions
|
||||
$dbPath = "../db/app.db";
|
||||
@@ -55,6 +56,7 @@
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
<script src="lib/AdminLTE/bower_components/chart.js/Chart.js?v=<?php include 'php/templates/version.php'; ?>"></script>
|
||||
<!-- The online history chart is rendered here -->
|
||||
<canvas id="OnlineChart" style="width:100%; height: 150px; margin-bottom: 15px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,40 +64,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/graph_online_history.js"></script>
|
||||
<script>
|
||||
$.get('api/table_online_history.json?nocache=' + Date.now(), function(res) {
|
||||
// Extracting data from the JSON response
|
||||
var timeStamps = [];
|
||||
var onlineCounts = [];
|
||||
var downCounts = [];
|
||||
var offlineCounts = [];
|
||||
var archivedCounts = [];
|
||||
|
||||
res.data.forEach(function(entry) {
|
||||
var dateObj = new Date(entry.Scan_Date);
|
||||
var formattedTime = dateObj.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit', hour12: false});
|
||||
|
||||
timeStamps.push(formattedTime);
|
||||
onlineCounts.push(entry.Online_Devices);
|
||||
downCounts.push(entry.Down_Devices);
|
||||
offlineCounts.push(entry.Offline_Devices);
|
||||
archivedCounts.push(entry.Archived_Devices);
|
||||
});
|
||||
|
||||
// Call your presenceOverTime function after data is ready
|
||||
presenceOverTime(
|
||||
timeStamps,
|
||||
onlineCounts,
|
||||
offlineCounts,
|
||||
archivedCounts,
|
||||
downCounts
|
||||
);
|
||||
}).fail(function() {
|
||||
// Handle any errors in fetching the data
|
||||
console.error('Error fetching online history data.');
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- datatable ------------------------------------------------------------- -->
|
||||
<div class="row">
|
||||
|
||||
42
front/php/components/graph_online_history.php
Executable file
42
front/php/components/graph_online_history.php
Executable file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
|
||||
?>
|
||||
|
||||
<script src="js/graph_online_history.js"></script>
|
||||
<script>
|
||||
$.get('api/table_online_history.json?nocache=' + Date.now(), function(res) {
|
||||
// Extracting data from the JSON response
|
||||
var timeStamps = [];
|
||||
var onlineCounts = [];
|
||||
var downCounts = [];
|
||||
var offlineCounts = [];
|
||||
var archivedCounts = [];
|
||||
|
||||
res.data.forEach(function(entry) {
|
||||
var dateObj = new Date(entry.Scan_Date);
|
||||
var formattedTime = dateObj.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit', hour12: false});
|
||||
|
||||
timeStamps.push(formattedTime);
|
||||
onlineCounts.push(entry.Online_Devices);
|
||||
downCounts.push(entry.Down_Devices);
|
||||
offlineCounts.push(entry.Offline_Devices);
|
||||
archivedCounts.push(entry.Archived_Devices);
|
||||
});
|
||||
|
||||
// Call your presenceOverTime function after data is ready
|
||||
presenceOverTime(
|
||||
timeStamps,
|
||||
onlineCounts,
|
||||
offlineCounts,
|
||||
archivedCounts,
|
||||
downCounts
|
||||
);
|
||||
}).fail(function() {
|
||||
// Handle any errors in fetching the data
|
||||
console.error('Error fetching online history data.');
|
||||
});
|
||||
</script>
|
||||
@@ -258,7 +258,7 @@ function cleanLog($logFile)
|
||||
|
||||
$path = "";
|
||||
|
||||
$allowedFiles = ['app.log', 'app_front.log', 'IP_changes.log', 'stdout.log', 'stderr.log', "pholus_lastrun.log", 'app.php_errors.log', 'execution_queue.log'];
|
||||
$allowedFiles = ['app.log', 'app_front.log', 'IP_changes.log', 'stdout.log', 'stderr.log', 'app.php_errors.log', 'execution_queue.log'];
|
||||
|
||||
if(in_array($logFile, $allowedFiles))
|
||||
{
|
||||
|
||||
@@ -578,7 +578,7 @@
|
||||
"REPORT_TITLE": "Report",
|
||||
"RandomMAC_hover": "Autodetected - indicates if the device randomizes it's MAC address.",
|
||||
"Reports_Sent_Log": "Sent Reports Log",
|
||||
"SCAN_SUBNETS_description": "Most on-network scanners (ARP-SCAN, NMAP, NSLOOKUP, DIG, PHOLUS) rely on scanning specific network interfaces and subnets. Check the <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">subnets documentation</a> for help on this setting, especially VLANs, what VLANs are supported, or how to figure out the network mask and your interface. <br/> <br/> An alternative to on-network scanners is to enable some other Device scanners/importers that don't rely on NetAlert<sup>X</sup> having access to the network (UNIFI, dhcp.leases, PiHole, etc.). <br/> <br/> Note: The scan time itself depends on the number of IP addresses to check, so set this up carefully with the appropriate network mask and interface.",
|
||||
"SCAN_SUBNETS_description": "Most on-network scanners (ARP-SCAN, NMAP, NSLOOKUP, DIG) rely on scanning specific network interfaces and subnets. Check the <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">subnets documentation</a> for help on this setting, especially VLANs, what VLANs are supported, or how to figure out the network mask and your interface. <br/> <br/> An alternative to on-network scanners is to enable some other Device scanners/importers that don't rely on NetAlert<sup>X</sup> having access to the network (UNIFI, dhcp.leases, PiHole, etc.). <br/> <br/> Note: The scan time itself depends on the number of IP addresses to check, so set this up carefully with the appropriate network mask and interface.",
|
||||
"SCAN_SUBNETS_name": "Networks to scan",
|
||||
"SYSTEM_TITLE": "System Information",
|
||||
"Setting_Override": "Override value",
|
||||
|
||||
@@ -631,7 +631,7 @@
|
||||
"REPORT_WEBHOOK_name": "Habilitar webhooks",
|
||||
"RandomMAC_hover": "Autodetectado - indica si el dispositivo aleatoriza su dirección MAC.",
|
||||
"Reports_Sent_Log": "Registro de informes enviados",
|
||||
"SCAN_SUBNETS_description": "La mayoría de los escáneres en red (ARP-SCAN, NMAP, NSLOOKUP, DIG, PHOLUS) se basan en el escaneo de interfaces de red y subredes específicas. Consulte la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">documentación sobre subredes</a> para obtener ayuda sobre esta configuración, especialmente VLANs, qué VLANs son compatibles, o cómo averiguar la máscara de red y su interfaz. <br/> <br/>Una alternativa a los escáneres en red es habilitar algunos otros escáneres/importadores de dispositivos que no dependen de que NetAlert<sup>X</sup> tenga acceso a la red (UNIFI, dhcp.leases, PiHole, etc.). <br/> <br/> Nota: El tiempo de escaneo en sí depende del número de direcciones IP a comprobar, así que configure esto cuidadosamente con la máscara de red y la interfaz adecuadas.",
|
||||
"SCAN_SUBNETS_description": "La mayoría de los escáneres en red (ARP-SCAN, NMAP, NSLOOKUP, DIG) se basan en el escaneo de interfaces de red y subredes específicas. Consulte la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">documentación sobre subredes</a> para obtener ayuda sobre esta configuración, especialmente VLANs, qué VLANs son compatibles, o cómo averiguar la máscara de red y su interfaz. <br/> <br/>Una alternativa a los escáneres en red es habilitar algunos otros escáneres/importadores de dispositivos que no dependen de que NetAlert<sup>X</sup> tenga acceso a la red (UNIFI, dhcp.leases, PiHole, etc.). <br/> <br/> Nota: El tiempo de escaneo en sí depende del número de direcciones IP a comprobar, así que configure esto cuidadosamente con la máscara de red y la interfaz adecuadas.",
|
||||
"SCAN_SUBNETS_name": "Subredes para escanear",
|
||||
"SMTP_FORCE_SSL_description": "Forzar SSL al conectarse a su servidor SMTP",
|
||||
"SMTP_FORCE_SSL_name": "Forzar SSL",
|
||||
|
||||
2
front/php/templates/language/it_it.json
Normal file → Executable file
2
front/php/templates/language/it_it.json
Normal file → Executable file
@@ -578,7 +578,7 @@
|
||||
"REPORT_TITLE": "Rapporto",
|
||||
"RandomMAC_hover": "Rilevato automaticamente: indica se il dispositivo genera il suo indirizzo MAC casualmente.",
|
||||
"Reports_Sent_Log": "Log rapporti inviati",
|
||||
"SCAN_SUBNETS_description": "La maggior parte degli scanner di rete (ARP-SCAN, NMAP, NSLOOKUP, DIG, PHOLUS) si basano sulla scansione di interfacce di rete e sottoreti specifiche. Consulta la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">documentazione sulle sottoreti</a> per assistenza su questa impostazione, in particolare VLAN, quali VLAN sono supportate o come individuare la maschera di rete e l'interfaccia. <br/> <br/> Un'alternativa agli scanner in rete è abilitare altri scanner/importatori di dispositivi che non si affidano a NetAlert<sup>X</sup> che hanno accesso alla rete (UNIFI, dhcp.leases , PiHole, ecc.). <br/> <br/> Nota: il tempo di scansione stesso dipende dal numero di indirizzi IP da controllare, quindi impostalo attentamente con la maschera di rete e l'interfaccia appropriate.",
|
||||
"SCAN_SUBNETS_description": "La maggior parte degli scanner di rete (ARP-SCAN, NMAP, NSLOOKUP, DIG) si basano sulla scansione di interfacce di rete e sottoreti specifiche. Consulta la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">documentazione sulle sottoreti</a> per assistenza su questa impostazione, in particolare VLAN, quali VLAN sono supportate o come individuare la maschera di rete e l'interfaccia. <br/> <br/> Un'alternativa agli scanner in rete è abilitare altri scanner/importatori di dispositivi che non si affidano a NetAlert<sup>X</sup> che hanno accesso alla rete (UNIFI, dhcp.leases , PiHole, ecc.). <br/> <br/> Nota: il tempo di scansione stesso dipende dal numero di indirizzi IP da controllare, quindi impostalo attentamente con la maschera di rete e l'interfaccia appropriate.",
|
||||
"SCAN_SUBNETS_name": "Reti da scansionare",
|
||||
"SYSTEM_TITLE": "Informazioni sistema",
|
||||
"Setting_Override": "Sovrascrivi valore",
|
||||
|
||||
@@ -578,7 +578,7 @@
|
||||
"REPORT_TITLE": "Rapport",
|
||||
"RandomMAC_hover": "Autodetektert - indikerer om enheten randomiserer MAC-adressen sin.",
|
||||
"Reports_Sent_Log": "Sendte rapport logger",
|
||||
"SCAN_SUBNETS_description": "De fleste skannere på nettet (ARP-Scan, NMAP, NSlookup, Dig, Pholus) er avhengige av å skanne spesifikke nettverksgrensesnitt og undernett. Sjekk <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">subnett dokumentasjonen</a> for hjelp på denne innstillingen, spesielt VLAN-er, hvilke VLAN-er som støttes, eller hvordan du kan finne ut nettverksmasken og grensesnittet ditt. <br/> <br/> Et alternativ til skannere på nettet er å aktivere noen andre enhetsskannere/importører som ikke er avhengige av Netalert<sup>X</sup> med tilgang til nettverket (UniFi, DHCP-Leaser, Pihole, osv.). <br/> <br/> Merk: Selve skanningstiden avhenger av antall IP -adresser som skal sjekkes, så sett dette opp nøye med riktig nettverksmaske og grensesnitt.",
|
||||
"SCAN_SUBNETS_description": "De fleste skannere på nettet (ARP-Scan, NMAP, NSlookup, Dig) er avhengige av å skanne spesifikke nettverksgrensesnitt og undernett. Sjekk <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">subnett dokumentasjonen</a> for hjelp på denne innstillingen, spesielt VLAN-er, hvilke VLAN-er som støttes, eller hvordan du kan finne ut nettverksmasken og grensesnittet ditt. <br/> <br/> Et alternativ til skannere på nettet er å aktivere noen andre enhetsskannere/importører som ikke er avhengige av Netalert<sup>X</sup> med tilgang til nettverket (UniFi, DHCP-Leaser, Pihole, osv.). <br/> <br/> Merk: Selve skanningstiden avhenger av antall IP -adresser som skal sjekkes, så sett dette opp nøye med riktig nettverksmaske og grensesnitt.",
|
||||
"SCAN_SUBNETS_name": "",
|
||||
"SYSTEM_TITLE": "Systeminformasjon",
|
||||
"Setting_Override": "Overstyr verdi",
|
||||
|
||||
@@ -578,7 +578,7 @@
|
||||
"REPORT_TITLE": "Zgłoszenie",
|
||||
"RandomMAC_hover": "Auto wykrywanie - oznacza czy urządzenie randomizuje swój adres MAC.",
|
||||
"Reports_Sent_Log": "Wyślij zgłoszenie logów",
|
||||
"SCAN_SUBNETS_description": "Większość skanerów sieciowych (ARP-SCAN, NMAP, NSLOOKUP, DIG, PHOLUS) opiera się na konkretnych interfejsach sieciowych oraz podsieci. Sprawdź <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\"> dokumentacji podsieci</a> jeżeli potrzebujesz pomocy w ustawieniach, a szczególnie z VLAN'ami, jakie VLAN'y są wspierane oraz jak rozgryźć maskę podsieci twojego interfejsu.<br/><br/> Alternatywą do skanerów sieciowych jest uruchomienie innego Skanera Urządzeń/Importera który nie polega by NetAlert<sup>X</sup> miał dostęp do sieci (UNIFI, dhcp.leases, PiHole, itp.).<br/><br/> Notatka: Czas skanu zależy od liczby adresów IP do sprawdzenia, więc ustaw go tak by skanował odpowiedni interfejs i maskę sieciową.",
|
||||
"SCAN_SUBNETS_description": "Większość skanerów sieciowych (ARP-SCAN, NMAP, NSLOOKUP, DIG) opiera się na konkretnych interfejsach sieciowych oraz podsieci. Sprawdź <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\"> dokumentacji podsieci</a> jeżeli potrzebujesz pomocy w ustawieniach, a szczególnie z VLAN'ami, jakie VLAN'y są wspierane oraz jak rozgryźć maskę podsieci twojego interfejsu.<br/><br/> Alternatywą do skanerów sieciowych jest uruchomienie innego Skanera Urządzeń/Importera który nie polega by NetAlert<sup>X</sup> miał dostęp do sieci (UNIFI, dhcp.leases, PiHole, itp.).<br/><br/> Notatka: Czas skanu zależy od liczby adresów IP do sprawdzenia, więc ustaw go tak by skanował odpowiedni interfejs i maskę sieciową.",
|
||||
"SCAN_SUBNETS_name": "",
|
||||
"SYSTEM_TITLE": "Informacje o Systemie",
|
||||
"Setting_Override": "Nadpisz wartość",
|
||||
|
||||
@@ -578,7 +578,7 @@
|
||||
"REPORT_TITLE": "Отчет",
|
||||
"RandomMAC_hover": "Автоматически обнаружено — указывает, рандомизирует ли устройство свой MAC-адрес.",
|
||||
"Reports_Sent_Log": "Отправленные уведомления",
|
||||
"SCAN_SUBNETS_description": "Большинство сетевых сканеров (ARP-SCAN, NMAP, NSLOOKUP, DIG, PHOLUS) полагаются на сканирование определенных сетевых интерфейсов и подсетей. Дополнительную информацию по этому параметру можно найти в <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">документации по подсетям</a>, особенно VLAN, какие VLAN поддерживаются или как разобраться в маске сети и своем интерфейсе. <br/> <br/> Альтернативой сетевым сканерам является включение некоторых других сканеров/импортеров устройств, которые не полагаются на NetAlert<sup>X</sup>, имеющий доступ к сети (UNIFI, dhcp.leases , PiHole и др.). <br/> <br/> Примечание. Само время сканирования зависит от количества проверяемых IP-адресов, поэтому тщательно настройте его, указав соответствующую маску сети и интерфейс.",
|
||||
"SCAN_SUBNETS_description": "Большинство сетевых сканеров (ARP-SCAN, NMAP, NSLOOKUP, DIG) полагаются на сканирование определенных сетевых интерфейсов и подсетей. Дополнительную информацию по этому параметру можно найти в <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">документации по подсетям</a>, особенно VLAN, какие VLAN поддерживаются или как разобраться в маске сети и своем интерфейсе. <br/> <br/> Альтернативой сетевым сканерам является включение некоторых других сканеров/импортеров устройств, которые не полагаются на NetAlert<sup>X</sup>, имеющий доступ к сети (UNIFI, dhcp.leases , PiHole и др.). <br/> <br/> Примечание. Само время сканирования зависит от количества проверяемых IP-адресов, поэтому тщательно настройте его, указав соответствующую маску сети и интерфейс.",
|
||||
"SCAN_SUBNETS_name": "Сети для сканирования",
|
||||
"SYSTEM_TITLE": "Системная информация",
|
||||
"Setting_Override": "Переопределить значение",
|
||||
|
||||
@@ -578,7 +578,7 @@
|
||||
"REPORT_TITLE": "报告",
|
||||
"RandomMAC_hover": "自动检测 - 表示设备是否随机化其 MAC 地址。",
|
||||
"Reports_Sent_Log": "已发送报告日志",
|
||||
"SCAN_SUBNETS_description": "大多数网络扫描器(ARP-SCAN、NMAP、NSLOOKUP、DIG、PHOLUS)依赖于扫描特定的网络接口和子网。查看<a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">子网文档</a>以获取有关此设置的帮助,尤其是 VLAN、支持哪些 VLAN,或者如何确定网络掩码和接口。<br/> <br/> 网络扫描器的替代方法是启用一些其他不依赖于 NetAlert<sup>X</sup> 访问网络的设备扫描器/导入器(UNIFI、dhcp.leases、PiHole 等)。<br/> <br/> 注意:扫描时间本身取决于要检查的 IP 地址数量,因此请使用适当的网络掩码和接口仔细设置。",
|
||||
"SCAN_SUBNETS_description": "大多数网络扫描器(ARP-SCAN、NMAP、NSLOOKUP、DIG)依赖于扫描特定的网络接口和子网。查看<a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">子网文档</a>以获取有关此设置的帮助,尤其是 VLAN、支持哪些 VLAN,或者如何确定网络掩码和接口。<br/> <br/> 网络扫描器的替代方法是启用一些其他不依赖于 NetAlert<sup>X</sup> 访问网络的设备扫描器/导入器(UNIFI、dhcp.leases、PiHole 等)。<br/> <br/> 注意:扫描时间本身取决于要检查的 IP 地址数量,因此请使用适当的网络掩码和接口仔细设置。",
|
||||
"SCAN_SUBNETS_name": "",
|
||||
"SYSTEM_TITLE": "系统信息",
|
||||
"Setting_Override": "覆盖值",
|
||||
|
||||
@@ -45,7 +45,6 @@ Device-detecting plugins insert values into the `CurrentScan` database table. T
|
||||
| `NTFPRCS` | ⚙ | Notification processing | | Yes | Template | [notification_processing](/front/plugins/notification_processing/)|
|
||||
| `NTFY` | ▶️ | NTFY notifications | | | Script | [_publisher_ntfy](/front/plugins/_publisher_ntfy/) |
|
||||
| `OMDSDN` | 📥 | OMADA TP-Link import | 🖧 🔄 | | Script | [omada_sdn_imp](/front/plugins/omada_sdn_imp/) |
|
||||
| `PHOLUS` | ♻ | Pholus name resolution | | | Script | [pholus_scan](/front/plugins/pholus_scan/) |
|
||||
| `PIHOLE` | 🔍/📥 | Pi-hole device import & sync | | | SQLite DB | [pihole_scan](/front/plugins/pihole_scan/) |
|
||||
| `PUSHSAFER` | ▶️ | Pushsafer notifications | | | Script | [_publisher_pushsafer](/front/plugins/_publisher_pushsafer/) |
|
||||
| `PUSHOVER` | ▶️ | Pushover notifications | | | Script | [_publisher_pushover](/front/plugins/_publisher_pushover/) |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Overview
|
||||
|
||||
Most on-network scanners (ARP-SCAN, NMAP, NSLOOKUP, DIG, PHOLUS) rely on scanning specific network interfaces and subnets. Check the [subnets documentation](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) for help on this setting, especially VLANs, what VLANs are supported, or how to figure out the network mask and your interface.
|
||||
Most on-network scanners (ARP-SCAN, NMAP, NSLOOKUP, DIG) rely on scanning specific network interfaces and subnets. Check the [subnets documentation](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) for help on this setting, especially VLANs, what VLANs are supported, or how to figure out the network mask and your interface.
|
||||
|
||||
An alternative to on-network scanners is to enable some other Device scanners/importers that don't rely on NetAlert<sup>X</sup> having access to the network (UNIFI, dhcp.leases, PiHole, etc.).
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ The database cleanup plugin (DBCLNP) helps maintain the system by removing outda
|
||||
- **`DAYS_TO_KEEP_EVENTS`**:
|
||||
Specifies the number of days to retain event logs. Event entries older than the given number of days will be automatically deleted during cleanup. Recommended value: `30` days.
|
||||
|
||||
- **`PHOLUS_DAYS_DATA`**:
|
||||
Deletes all data older than specified number of days for teh Pholus plugin used for name discovery. Recommended value: `30` days.
|
||||
|
||||
By fine-tuning these settings, you ensure that the database remains optimized, preventing performance degradation in the NetAlertX system.
|
||||
|
||||
|
||||
@@ -36,14 +36,13 @@ def main():
|
||||
HRS_TO_KEEP_NEWDEV = int(get_setting_value("HRS_TO_KEEP_NEWDEV"))
|
||||
HRS_TO_KEEP_OFFDEV = int(get_setting_value("HRS_TO_KEEP_OFFDEV"))
|
||||
DAYS_TO_KEEP_EVENTS = int(get_setting_value("DAYS_TO_KEEP_EVENTS"))
|
||||
PHOLUS_DAYS_DATA = get_setting_value("PHOLUS_DAYS_DATA")
|
||||
CLEAR_NEW_FLAG = get_setting_value("CLEAR_NEW_FLAG")
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] In script'])
|
||||
|
||||
|
||||
# Execute cleanup/upkeep
|
||||
cleanup_database(fullDbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, HRS_TO_KEEP_OFFDEV, PLUGINS_KEEP_HIST, CLEAR_NEW_FLAG)
|
||||
cleanup_database(fullDbPath, DAYS_TO_KEEP_EVENTS, HRS_TO_KEEP_NEWDEV, HRS_TO_KEEP_OFFDEV, PLUGINS_KEEP_HIST, CLEAR_NEW_FLAG)
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] Cleanup complete'])
|
||||
|
||||
@@ -52,7 +51,7 @@ def main():
|
||||
#===============================================================================
|
||||
# Cleanup / upkeep database
|
||||
#===============================================================================
|
||||
def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, HRS_TO_KEEP_OFFDEV, PLUGINS_KEEP_HIST, CLEAR_NEW_FLAG):
|
||||
def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, HRS_TO_KEEP_NEWDEV, HRS_TO_KEEP_OFFDEV, PLUGINS_KEEP_HIST, CLEAR_NEW_FLAG):
|
||||
"""
|
||||
Cleaning out old records from the tables that don't need to keep all data.
|
||||
"""
|
||||
@@ -163,29 +162,6 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP
|
||||
cursor.execute(query)
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Cleanup Pholus_Scan
|
||||
if PHOLUS_DAYS_DATA != "" and PHOLUS_DAYS_DATA != 0:
|
||||
mylog('verbose', [f'[{pluginName}] Pholus_Scan: Delete all older than ' + str(PHOLUS_DAYS_DATA) + ' days (PHOLUS_DAYS_DATA setting)'])
|
||||
# todo: improvement possibility: keep at least N per mac
|
||||
cursor.execute (f"""DELETE FROM Pholus_Scan
|
||||
WHERE Time <= date('now', '-{str(PHOLUS_DAYS_DATA)} day')""")
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# De-Dupe (de-duplicate - remove duplicate entries) from the Pholus_Scan table
|
||||
mylog('verbose', [f'[{pluginName}] Pholus_Scan: Delete all duplicates'])
|
||||
cursor.execute ("""DELETE FROM Pholus_Scan
|
||||
WHERE rowid > (
|
||||
SELECT MIN(rowid) FROM Pholus_Scan p2
|
||||
WHERE Pholus_Scan.MAC = p2.MAC
|
||||
AND Pholus_Scan.Value = p2.Value
|
||||
AND Pholus_Scan.Record_Type = p2.Record_Type
|
||||
);""")
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# De-dupe (de-duplicate) from the Plugins_Objects table
|
||||
# TODO This shouldn't be necessary - probably a concurrency bug somewhere in the code :(
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
## Overview
|
||||
|
||||
A plugin to resolve `(unknown)` device names. It uses the [Pholus](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/pholus_scan/pholus) sniffing tool.
|
||||
|
||||
### Usage
|
||||
|
||||
- Go to settings and find Pholus (Name discovery) in the list of settings.
|
||||
- Enable the plugin by changing the `RUN` parameter from disabled to one you prefer (`schedule`, `always_after_scan`, `on_new_device`).
|
||||
- Specify the `PHOLUS_RUN_TIMEOUT` (Will be divided by the number of subnets specified in the Arp-Scan (Network scan) plugin setting `SCAN_SUBNETS`)
|
||||
- SAVE
|
||||
- Wait for the next scan to finish
|
||||
|
||||
|
||||
@@ -1,517 +0,0 @@
|
||||
{
|
||||
"code_name": "pholus_scan",
|
||||
"unique_prefix": "PHOLUS",
|
||||
"plugin_type": "other",
|
||||
"enabled": true,
|
||||
"data_source": "script",
|
||||
"mapped_to_table": "Pholus_Scan",
|
||||
"data_filters": [
|
||||
{
|
||||
"compare_column": "Object_PrimaryID",
|
||||
"compare_operator": "==",
|
||||
"compare_field_id": "txtMacFilter",
|
||||
"compare_js_template": "'{value}'.toString()",
|
||||
"compare_use_quotes": true
|
||||
}
|
||||
],
|
||||
"show_ui": true,
|
||||
"localized": ["display_name", "description", "icon"],
|
||||
"display_name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Pholus (Name discovery)"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Pholus (Descubrimiento de nombre)"
|
||||
}
|
||||
],
|
||||
"icon": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "<i class=\"fa-solid fa-search\"></i>"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "<i class=\"fa-solid fa-search\"></i>"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "This plugin is to execute a Pholus (name discovery) on the local network"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Este plugin sirve para ejecutar un escaneo Pholus (descubrimiento de nombres) en la red local"
|
||||
}
|
||||
],
|
||||
"params": [
|
||||
{
|
||||
"name": "subnets",
|
||||
"type": "setting",
|
||||
"value": "SCAN_SUBNETS",
|
||||
"base64": true
|
||||
},
|
||||
{
|
||||
"name": "timeout",
|
||||
"type": "setting",
|
||||
"value": "PHOLUS_RUN_TIMEOUT"
|
||||
}
|
||||
],
|
||||
"settings": [
|
||||
{
|
||||
"function": "RUN",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{ "elementType": "select", "elementOptions": [], "transformers": [] }
|
||||
]
|
||||
},
|
||||
"default_value": "disabled",
|
||||
"options": [
|
||||
"disabled",
|
||||
"once",
|
||||
"schedule",
|
||||
"always_after_scan",
|
||||
"on_new_device"
|
||||
],
|
||||
"localized": ["name", "description"],
|
||||
"events": ["run"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "When to run"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Cuando ejecutar"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "<a href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/pholus_scan/pholus\" target=\"_blank\" >Pholus</a> is a sniffing tool to discover additional information about the devices on the network, including the device name. If enabled this will execute the scan before every network scan cycle until there are no <code>(unknown)</code> or <code>(name not found)</code> devices. Please be aware it can spam the network with unnecessary traffic. Depends on the <a onclick=\"toggleAllSettings()\" href=\"#SCAN_SUBNETS\"><code>SCAN_SUBNETS</code> setting</a>. For a scheduled or one-off scan, check the <a href=\"#PHOLUS_RUN\"><code>PHOLUS_RUN</code> setting</a>.Specify when your Name-discovery scan will run. Typical setting would be <code>on_new_device</code> or <code>schedule</code> and then you specify a cron-like schedule in the <a href=\"#PHOLUS_RUN_SCHD\"><code>PHOLUS_RUN_SCHD</code>setting</a>."
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "<a href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/pholus_scan/pholus\" target=\"_blank\" >Pholus</a> es una herramienta de rastreo para descubrir información adicional sobre los dispositivos en la red, incluido el nombre del dispositivo. Si está habilitado, ejecutará el escaneo antes de cada ciclo de escaneo de red hasta que no haya dispositivos <code>(unknown)</code> o <code>(name not found)</code>. Tenga en cuenta que puede enviar spam a la red con tráfico innecesario. Depende de la configuración de <a onclick=\"toggleAllSettings()\" href=\"#SCAN_SUBNETS\"><code>SCAN_SUBNETS</code></a>. Para un análisis programado o único, verifique la configuración de <a href=\"#PHOLUS_RUN\"><code>PHOLUS_RUN</code></a>."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "CMD",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{ "readonly": "true" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "python3 /app/front/plugins/pholus_scan/script.py userSubnets={subnets} timeoutSec={timeout}",
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Command"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Comando"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Command to run. This should not be changed"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Comando para ejecutar. Esto no debe ser cambiado"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "RUN_TIMEOUT",
|
||||
"type": {
|
||||
"dataType": "integer",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{ "type": "number" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": 300,
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Run timeout"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Tiempo límite de ejecución"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Network scan time in seconds. Pholus scan will always run this long. The longer it runs the more device names might be resolved. Will be divided by the number of subnets."
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Tiempo de escaneo de red en segundos. El escaneo de Pholus siempre durará este tiempo. Cuanto más tiempo se ejecute, más nombres de dispositivos se podrán resolver. Se dividirá por el número de subredes."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "RUN_SCHD",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{ "elementType": "input", "elementOptions": [], "transformers": [] }
|
||||
]
|
||||
},
|
||||
"default_value": "30 3 * * *",
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Schedule"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Schedule"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#PHOLUS_RUN\"><code>PHOLUS_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>30 3 * * *</code> will run the scan at 3:30 am. Will be run NEXT time the time passes. <br/>"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#PHOLUS_RUN\"><code>PHOLUS_RUN</code></a>. Asegúrese de ingresar la programación en el formato cron correcto (por ejemplo, validar en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, al ingresar <code>30 3 * * *</code> se ejecutará el escaneo a las 3:30 am. Se ejecutará la PRÓXIMA vez que pase el tiempo. <br/>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "DAYS_DATA",
|
||||
"type": {
|
||||
"dataType": "integer",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{ "type": "number" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": 30,
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Retention of data"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Retención de datos"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "How many days of Pholus scan entries should be kept (globally, not device specific!) Enter <code>0</code> to disable."
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Cuántos días de entradas de escaneo de Pholus deben conservarse (globalmente, ¡no específico del dispositivo!). Introduzca <code>0</code> para desactivar."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "WATCH",
|
||||
"type": {
|
||||
"dataType": "array",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementOptions": [{ "multiple": "true" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": ["Watched_Value1", "Watched_Value2"],
|
||||
"options": [
|
||||
"Watched_Value1",
|
||||
"Watched_Value2",
|
||||
"Watched_Value3",
|
||||
"Watched_Value4"
|
||||
],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Watched"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Watched"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Info</li><li><code>Watched_Value2</code> is Record type</li><li><code>Watched_Value3</code> is Info </li><li><code>Watched_Value4</code> is N/A </li></ul>"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Enviar una notificación si los valores seleccionados cambian. Utilice <code>CTRL + Clic</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> es Información</li><li><code>Watched_Value2</code> es Tipo de registro</li><li><code>Watched_Value3</code> es La información </li><li><code>Watched_Value4</code> es N/A </li></ul>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "REPORT_ON",
|
||||
"type": {
|
||||
"dataType": "array",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementOptions": [{ "multiple": "true" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": ["new"],
|
||||
"options": [
|
||||
"new",
|
||||
"watched-changed",
|
||||
"watched-not-changed",
|
||||
"missing-in-last-scan"
|
||||
],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Report on"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Informar sobre"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "When should notification be sent out."
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Cuándo debe enviarse una notificación."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_column_definitions": [
|
||||
{
|
||||
"column": "Index",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "none",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Index"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Object_PrimaryID",
|
||||
"mapped_to_column": "MAC",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "device_name_mac",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "MAC"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "MAC"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Object_SecondaryID",
|
||||
"mapped_to_column": "IP_v4_or_v6",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "IP"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "IP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"mapped_to_column": "Info",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Info"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Info"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Watched_Value2",
|
||||
"mapped_to_column": "Record_Type",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Type"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Tipo"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Watched_Value3",
|
||||
"mapped_to_column": "Value",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Info"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Info"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "DateTimeCreated",
|
||||
"mapped_to_column": "Time",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Created"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Creado"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "DateTimeChanged",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Changed"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Cambiado"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Status",
|
||||
"css_classes": "col-sm-1",
|
||||
"show": true,
|
||||
"type": "replace",
|
||||
"default_value": "",
|
||||
"options": [
|
||||
{
|
||||
"equals": "watched-not-changed",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-square-check'></i><div></div>"
|
||||
},
|
||||
{
|
||||
"equals": "watched-changed",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-triangle-exclamation'></i></div>"
|
||||
},
|
||||
{
|
||||
"equals": "new",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-circle-plus'></i></div>"
|
||||
},
|
||||
{
|
||||
"equals": "missing-in-last-scan",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-question'></i></div>"
|
||||
}
|
||||
],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Status"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Estado"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -1,39 +0,0 @@
|
||||
# INFO
|
||||
For anyone reading, `pholus.py` is not actively developed as this project only uses `pholus3.py`.
|
||||
|
||||
# Pholus
|
||||
A multicast DNS and DNS Service Discovery Security Assessment Tool
|
||||
It can perform recconnaisance, Denial of Service, Man in the Middle attacks
|
||||
|
||||
## Scan passively
|
||||
Scan passively (for amount of timeout)
|
||||
python pholus3.py eth0 -stimeout 60
|
||||
|
||||
## Discovery of available services
|
||||
Sends a DNS query for PTR records with the name "_services._dns-sd._udp.<Domain>";
|
||||
this yields a set of PTR records where the rdata of each PTR record is the two-label
|
||||
<Service> name plus the same domain, e.g., "_http._tcp.<Domain>".
|
||||
By sending such a query, we can automatically discover all the services advertised in the network.
|
||||
|
||||
python pholus3.py eth0 -sscan
|
||||
|
||||
#If you want to perform the scan both for IPv4 and IPv6:
|
||||
python pholus3.py eth0 -sscan -4 -6
|
||||
|
||||
#You can also spoof the souce address to perform this reconnaissance in a stealthy way.
|
||||
python pholus3.py eth0 -sscan -s4 192.168.2.30
|
||||
|
||||
## Send mdns request
|
||||
python pholus3.py eth0 --request
|
||||
|
||||
## Perform a scan using reverse mDNS by providing a subnet
|
||||
python pholus3.py eth0 -rdns_scanning 192.168.2.0/24
|
||||
|
||||
## Send automatically fake responses
|
||||
python pholus3.py eth0 -afre -stimeout 100
|
||||
|
||||
## further MiTM (and other) capabilities
|
||||
use --help to identify specific spoofing capabilities for MiTM purposes, eg -printer)
|
||||
|
||||
## Read a pcap file and pring mDNS info (no sudo/root required)
|
||||
python pholus3.py ../mdns_traffic.pcap --readpcap
|
||||
@@ -1,23 +0,0 @@
|
||||
__copyright__ = """Copyright (C) 2016 Antonios Atlasis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
This tool may be used for legal purposes only. Users take full responsibility
|
||||
for any actions performed using this tool. If these terms are not acceptable to
|
||||
you, then do not use this tool.
|
||||
|
||||
You are encouraged to send comments, improvements or suggestions to
|
||||
aatlasis@secfu.net
|
||||
"""
|
||||
@@ -1,975 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
from scapy.all import *
|
||||
import argparse
|
||||
import re
|
||||
import binascii
|
||||
import random
|
||||
import multiprocessing
|
||||
import logging
|
||||
import itertools
|
||||
from scapy.utils import PcapWriter
|
||||
|
||||
sys.setrecursionlimit(30000)
|
||||
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)#supress Scapy warnings`
|
||||
|
||||
#####################################################
|
||||
### DEFINE AN IPV4 RANGE FOR IN-ADDR.ARPA QUERIES ###
|
||||
#####################################################
|
||||
def ip_range(input_string):
|
||||
octets = input_string.split('.')
|
||||
octets.reverse()
|
||||
chunks = [map(int, octet.split('-')) for octet in octets]
|
||||
ranges = [range(c[0], c[1] + 1) if len(c) == 2 else c for c in chunks]
|
||||
for address in itertools.product(*ranges):
|
||||
address = address + ('in-addr','arpa')
|
||||
yield '.'.join(map(str, address))
|
||||
|
||||
######################################
|
||||
### OBTAIN THE SYSTEM IPV6 ADDRESS ###
|
||||
######################################
|
||||
def get_my_ipv6_addr(interface):
|
||||
myip=""
|
||||
try:
|
||||
for ifaces in scapy.arch.linux.in6_getifaddr(): #in6_getifaddr() #return a list of IPs - ifaces, etc
|
||||
if ifaces[2]==interface:
|
||||
if not myip:
|
||||
myip=ifaces[0]
|
||||
elif myip[0:6] == "fe80::":
|
||||
myip=ifaces[0]
|
||||
return myip
|
||||
except:
|
||||
print "The interface",interface,"does not exist. Please, try again."
|
||||
exit(0)
|
||||
|
||||
######################################
|
||||
### OBTAIN THE SYSTEM IPV4 ADDRESS ###
|
||||
######################################
|
||||
def get_my_ipv4_addr(interface):
|
||||
myip=""
|
||||
try:
|
||||
myip=scapy.arch.get_if_addr(interface)
|
||||
return myip
|
||||
except:
|
||||
print "The interface",interface,"does not exist. Please, try again."
|
||||
exit(0)
|
||||
|
||||
##########################
|
||||
##### SNIFFER CLASS #####
|
||||
##########################
|
||||
class Sniffer():
|
||||
def __init__ (self,filter,interface,sniffer_timeout,queue,dns,show_ttl,dos_ttl, conflict, ttl,d4, d6, target_mac, auto_fake_responses,source_IPv6, source_IPv4, target_mac1, target_mac2,source_mac,hlimit,workstation,printer,googlecast,airtv,flood,flooding_timeout,flooding_interval, v4, v6):
|
||||
self.filter = filter
|
||||
self.interface = interface
|
||||
self.sniffer_timeout=sniffer_timeout
|
||||
self.queue=queue
|
||||
self.dns=dns
|
||||
self.show_ttl=show_ttl
|
||||
self.dos_ttl=dos_ttl
|
||||
self.conflict=conflict
|
||||
self.ttl=ttl
|
||||
self.d4=d4
|
||||
self.d6=d6
|
||||
self.target_mac=target_mac
|
||||
self.auto_fake_responses=auto_fake_responses
|
||||
self.source_IPv6=source_IPv6
|
||||
self.source_IPv4=source_IPv4
|
||||
self.target_mac1=target_mac1
|
||||
self.target_mac2=target_mac2
|
||||
self.source_mac=source_mac
|
||||
self.hlimit=hlimit
|
||||
self.workstation=workstation
|
||||
self.printer=printer
|
||||
self.airtv=airtv
|
||||
self.googlecast=googlecast
|
||||
self.flood=flood
|
||||
self.flooding_interval=flooding_interval
|
||||
self.flooding_timeout=flooding_timeout
|
||||
self.v4=v4
|
||||
self.v6=v6
|
||||
sniff(filter=self.filter, iface=self.interface, prn=self.handler, store=0, timeout=self.sniffer_timeout)
|
||||
def handler(self,packets):
|
||||
ext_handler(packets,self.queue,self.dns,self.show_ttl,1,self.dos_ttl,self.conflict, self.ttl,self.interface,self.d4,self.d6,self.target_mac,self.auto_fake_responses,self.source_IPv6,self.source_IPv4,self.target_mac1,self.target_mac2,self.source_mac,self.hlimit,self.workstation,self.printer,self.googlecast,self.airtv,self.flood,self.flooding_timeout,self.flooding_interval,self.v4,self.v6)
|
||||
|
||||
##################################
|
||||
##### OFFLINE SNIFFER CLASS #####
|
||||
##################################
|
||||
class Sniffer_Offline():
|
||||
def __init__ (self,interface,queue,show_ttl,d4, d6, target_mac,auto_fake_responses,source_IPv6, source_IPv4, target_mac1, target_mac2,source_mac,hlimit):
|
||||
self.interface = interface
|
||||
self.queue=queue
|
||||
self.show_ttl=show_ttl
|
||||
self.d4=d4
|
||||
self.d6=d6
|
||||
self.target_mac=target_mac
|
||||
self.auto_fake_responses=auto_fake_responses
|
||||
self.source_IPv6=source_IPv6
|
||||
self.source_IPv4=source_IPv4
|
||||
self.target_mac1=target_mac1
|
||||
self.target_mac2=target_mac2
|
||||
self.source_mac=source_mac
|
||||
self.hlimit=hlimit
|
||||
sniff(filter="udp and (port 5353 or port 53)", offline=self.interface, prn=self.handler, timeout=1)
|
||||
def handler(self,packets):
|
||||
ext_handler(packets,self.queue,False,self.show_ttl,1,False,False,4500,self.interface,self.d4,self.d6,self.target_mac,False,self.source_IPv6,self.source_IPv4,self.target_mac1,self.target_mac2,self.source_mac,self.hlimit,False,False,False,False,10.0,0.1,True,False)
|
||||
|
||||
########################################################################
|
||||
### THE HANDLER THAT THE TWO SNIFFERS CALL - THIS MAKES THE MAIN JOB ###
|
||||
########################################################################
|
||||
def ext_handler(packets,queue,unidns,show_ttl,print_res,dos_ttl,conflict,ttl,interface,d4,d6,target_mac,auto_fake_responses,source_IPv6,source_IPv4,target_mac1,target_mac2,source_mac,hlimit,workstation,printer,googlecast,airtv,flood,flooding_timeout,flodding_interval,v4,v6):
|
||||
dns_type = {12: "PTR", 28: "AAAA", 13: "HINFO",33: "SRV", 1: "A", 255: "* (ANY)", 16: "TXT", 15: "MX", 6: "SOA", 256: "URI", 5: "CNAME",39: "DNAME"}
|
||||
Ether_src=packets.getlayer(Ether).src
|
||||
IP_src=None
|
||||
if packets.haslayer(IPv6):
|
||||
IP_src=packets.getlayer(IPv6).src
|
||||
elif packets.haslayer(IP):
|
||||
IP_src=packets.getlayer(IP).src
|
||||
res0= Ether_src + " " + IP_src
|
||||
#try:
|
||||
if packets.haslayer(DNS):
|
||||
dns=packets.getlayer(DNS)
|
||||
if (conflict or dos_ttl) and dns.ancount>0:
|
||||
DNSBlocks = [ ]
|
||||
DNSBlocks.append(dns.an)
|
||||
#new_DNS_packet=DNS(id=dns[DNS].id,qr=dns[DNS].qr,opcode=dns[DNS].opcode,aa=dns[DNS].aa,tc=dns[DNS].tc,rd=dns[DNS].rd,ra=dns[DNS].ra,z=dns[DNS].z,ad=dns[DNS].ad,cd=dns[DNS].cd,rcode=dns[DNS].rcode,qdcount=dns[DNS].qdcount,ancount=dns[DNS].ancount,nscount=dns[DNS].nscount,arcount=dns[DNS].arcount,qd=dns[DNS].qd)
|
||||
if conflict:
|
||||
new_DNS_packet=DNS(id=dns[DNS].id,qr=dns[DNS].qr,opcode=dns[DNS].opcode,aa=dns[DNS].aa,tc=dns[DNS].tc,rd=dns[DNS].rd,ra=dns[DNS].ra,z=dns[DNS].z,ad=dns[DNS].ad,cd=dns[DNS].cd,rcode=dns[DNS].rcode,qdcount=0,ancount=dns[DNS].ancount,nscount=0,arcount=0,qd=dns[DNS].qd)
|
||||
if target_mac:
|
||||
new_packet=Ether(src=source_mac,dst=target_mac)
|
||||
else:
|
||||
new_packet=Ether(src=source_mac,dst=packets[Ether].dst)
|
||||
if packets.haslayer(IPv6):
|
||||
if d6:
|
||||
new_packet=new_packet/IPv6(src=source_IPv6,dst=d6,hlim=packets[IPv6].hlim)
|
||||
else:
|
||||
new_packet=new_packet/IPv6(src=source_IPv6,dst=packets[IPv6].dst,hlim=packets[IPv6].hlim)
|
||||
else:
|
||||
if d4:
|
||||
new_packet=new_packet/IP(src=source_IPv4,dst=d4,ttl=packets[IP].ttl)
|
||||
else:
|
||||
new_packet=new_packet/IP(src=source_IPv4,dst=packets[IP].dst,ttl=packets[IP].ttl)
|
||||
for p in DNSBlocks:
|
||||
if isinstance(p,DNSRR):
|
||||
new_DNS_packet=new_DNS_packet/p
|
||||
elif dos_ttl:
|
||||
new_DNS_packet=DNS(id=dns[DNS].id,qr=dns[DNS].qr,opcode=dns[DNS].opcode,aa=dns[DNS].aa,tc=dns[DNS].tc,rd=dns[DNS].rd,ra=dns[DNS].ra,z=dns[DNS].z,ad=dns[DNS].ad,cd=dns[DNS].cd,rcode=dns[DNS].rcode,qdcount=0,ancount=dns[DNS].ancount,nscount=0,arcount=dns[DNS].arcount,qd=dns[DNS].qd)
|
||||
DNSBlocks.append(dns.ar)
|
||||
if target_mac:
|
||||
new_packet=Ether(src=source_mac,dst=target_mac)
|
||||
else:
|
||||
new_packet=Ether(src=packets[Ether].src,dst=packets[Ether].dst)
|
||||
if packets.haslayer(IPv6):
|
||||
if d6:
|
||||
new_packet=new_packet/IPv6(src=packets[IPv6].src,dst=d6,hlim=packets[IPv6].hlim)
|
||||
else:
|
||||
new_packet=new_packet/IPv6(src=packets[IPv6].src,dst=packets[IPv6].dst,hlim=packets[IPv6].hlim)
|
||||
else:
|
||||
if d4:
|
||||
new_packet=new_packet/IP(src=packets[IP].src,dst=d4,ttl=packets[IP].ttl)
|
||||
else:
|
||||
new_packet=new_packet/IP(src=packets[IP].src,dst=packets[IP].dst,ttl=packets[IP].ttl)
|
||||
for p in DNSBlocks:
|
||||
if isinstance(p,DNSRR):
|
||||
new_p=DNSRR()
|
||||
new_p.ttl=0
|
||||
new_p.rrname=p.rrname
|
||||
new_p.type=p.type
|
||||
new_p.rclass=p.rclass
|
||||
new_p.rdlen=p.rdlen
|
||||
new_p.rdata=p.rdata
|
||||
new_DNS_packet=new_DNS_packet/new_p
|
||||
if unidns:
|
||||
new_packet=new_packet/UDP(dport=53)/new_DNS_packet
|
||||
else:
|
||||
new_packet=new_packet/UDP(dport=5353,sport=5353)/new_DNS_packet
|
||||
for x in range(0,2):#Send each packet twice
|
||||
sendp(new_packet,iface=interface)
|
||||
elif auto_fake_responses or (not (dos_ttl or conflict)):
|
||||
## IF THIS IS A QUERY ##
|
||||
if dns.opcode==0:
|
||||
res0 = res0 + " QUERY"
|
||||
if dns.qdcount>0:
|
||||
DNSBlocks = [ ]
|
||||
DNSBlocks.append(dns.qd)
|
||||
for block in DNSBlocks:
|
||||
while isinstance(block,DNSQR):
|
||||
dnsqr=block.getlayer(DNSQR)
|
||||
### IF WE NEED TO AUTO RESPOND WITH A FAKE | DOS RESPONSE ###
|
||||
if auto_fake_responses:
|
||||
myttl=int(ttl)
|
||||
if (("in-addr.arpa" in dnsqr.qname) or ("ip6.arpa" in dnsqr.name)) and workstation:
|
||||
#ip=dnsqr.qname.split(".")
|
||||
#qname = ip[3]+"."+ip[2]+"."+ip[1]+"."+ip[0]
|
||||
#print "Fake IP =",qname
|
||||
if unidns:
|
||||
dns_packet=UDP(dport=53)/DNS(qr=1,aa=1,rd=0,ancount=1,arcount=0)/DNSRR(rrname=dnsqr.qname,ttl=myttl,rdata='mitsos.local',type="PTR")
|
||||
else:
|
||||
dns_packet=UDP(sport=5353,dport=5353)/DNS(qr=1,aa=1,rd=0,ancount=1,arcount=0)/DNSRR(rrname=dnsqr.qname,ttl=myttl,rdata='mitsos.local',type="PTR")
|
||||
elif ("_workstation._tcp." in dnsqr.qname) and workstation:
|
||||
qname=dnsqr.qname
|
||||
if qname.endswith('.'):
|
||||
qname=qname[:-1]
|
||||
if unidns:
|
||||
dns_packet=UDP(dport=53)/DNS(qr=1,aa=1,rd=0,ancount=3)/DNSRR(rrname="_workstation._tcp.local",ttl=myttl,rdata="mitsos._workstation._tcp.local",type="PTR",rclass=32769)/DNSRR(rrname=qname,ttl=myttl,type="TXT",rclass=32769)/DNSRR(rrname="mitsos.local",ttl=myttl,rdata=source_IPv4,type="A",rclass=32769)
|
||||
else:
|
||||
dns_packet=UDP(sport=5353,dport=5353)/DNS(qr=1,aa=1,rd=0,ancount=3)/DNSRR(rrname="_workstation._tcp.local",ttl=myttl,rdata="mitsos._workstation._tcp.local",type="PTR",rclass=32769)/DNSRR(rrname=qname,ttl=myttl,type="TXT",rclass=32769)/DNSRR(rrname="mitsos.local",ttl=myttl,rdata=source_IPv4,type="A",rclass=32769)
|
||||
elif (("_pdl-datastream._tcp." in dnsqr.qname) or ("_ipp._tcp." in dnsqr.qname)) and printer:
|
||||
qname=dnsqr.qname
|
||||
if qname.endswith('.'):
|
||||
qname=qname[:-1]
|
||||
#make the SRV additional record
|
||||
port=9100
|
||||
port='{0:016b}'.format(port)
|
||||
port="{0:0>4X}".format(int(port, 2))
|
||||
weight=0
|
||||
weight='{0:016b}'.format(weight)
|
||||
weight="{0:0>4X}".format(int(weight, 2))
|
||||
priority=0
|
||||
priority='{0:016b}'.format(priority)
|
||||
priority="{0:0>4X}".format(int(priority, 2))
|
||||
data=priority.decode("hex")+weight.decode("hex")+port.decode("hex")
|
||||
#for explanation, check http://stackoverflow.com/questions/26933016/rdata-field-of-a-dns-sd-ptr-packet-sent-via-scapy-results-in-an-unknown-extended
|
||||
sublabels = "HP10000.local".split(".") + [""]
|
||||
label_format = ""
|
||||
for s in sublabels:
|
||||
label_format = '%s%dp' % (label_format, len(s) + 1)
|
||||
label_data = struct.pack(label_format, *sublabels)
|
||||
srv_rrname=data+label_data
|
||||
txt_record=""
|
||||
rdata=['txtvers=1','qtotal=1','pdl=application/vnd.hp-PCL','ty=MyOfficejet100000','product=(Trexa gureue)','priority=0','adminur=http://'+source_IPv4]
|
||||
for r in rdata:
|
||||
length=hex(len(r))[2:]
|
||||
#check http://code.activestate.com/recipes/576617-converting-arbitrary-size-python-integers-to-packe/
|
||||
if len(r) > 255:
|
||||
s=struct.Struct('I')# CHECK IT
|
||||
else:
|
||||
s=struct.Struct('B')
|
||||
value=(len(r),)
|
||||
packed_data=s.pack(*value)
|
||||
txt_record=txt_record+packed_data+r
|
||||
pkt= DNSRR(rrname="MyOfficejet10000._pdl-datasream._tcp.local",type="TXT",ttl=myttl,rclass=32769,rdata=txt_record)
|
||||
pkt.rdlen-=1
|
||||
mylength=len(pkt.rrname)+12
|
||||
dnsrr_packet=str(pkt)[0:mylength]+str(pkt)[mylength+1::]
|
||||
pkt=dnsrr_packet
|
||||
if unidns:
|
||||
dns_packet=UDP(dport=53)/DNS(qr=1,aa=1,rd=0,ancount=1,arcount=3)/DNSRR(rrname=qname,ttl=myttl,rdata="MyOffice10000."+qname,type="PTR",rclass=32769)/DNSRR(rrname="HP10000.local",ttl=myttl,type="A",rclass=32769,rdata=source_IPv4)/DNSRR(rrname="MyOffice100000."+qname,ttl=myttl,type="SRV",rclass=32769,rdata=srv_rrname)/pkt
|
||||
else:
|
||||
dns_packet=UDP(sport=5353,dport=5353)/DNS(qr=1,aa=1,rd=0,ancount=1,arcount=3)/DNSRR(rrname=qname,ttl=myttl,rdata="MyOffice10000."+qname,type="PTR",rclass=32769)/DNSRR(rrname="HP10000.local",ttl=myttl,type="A",rclass=32769,rdata=source_IPv4)/DNSRR(rrname="MyOffice100000."+qname,ttl=myttl,type="SRV",rclass=32769,rdata=srv_rrname)/pkt
|
||||
elif ("_googlecast._tcp." in dnsqr.qname) and googlecast:
|
||||
qname=dnsqr.qname
|
||||
if qname.endswith('.'):
|
||||
qname=qname[:-1]
|
||||
#make the SRV additional record
|
||||
port=8009
|
||||
port='{0:016b}'.format(port)
|
||||
port="{0:0>4X}".format(int(port, 2))
|
||||
weight=0
|
||||
weight='{0:016b}'.format(weight)
|
||||
weight="{0:0>4X}".format(int(weight, 2))
|
||||
priority=0
|
||||
priority='{0:016b}'.format(priority)
|
||||
priority="{0:0>4X}".format(int(priority, 2))
|
||||
data=priority.decode("hex")+weight.decode("hex")+port.decode("hex")
|
||||
sublabels = "32799abf-18ea-631d-62ae-390515bb47c5.local".split(".") + [""]
|
||||
label_format = ""
|
||||
for s in sublabels:
|
||||
label_format = '%s%dp' % (label_format, len(s) + 1)
|
||||
label_data = struct.pack(label_format, *sublabels)
|
||||
srv_rrname=data+label_data
|
||||
txt_record=""
|
||||
rdata=['id=32799abf-18ea-631d-62ae-390515bb47c5','rm=','ve=05','md=Chromecast Ultra','ic=/setup/icon.png','fn=LivingRoom','ca=4101','st=0','bs=FA8FCA7EO948','rs=0']
|
||||
for r in rdata:
|
||||
length=hex(len(r))[2:]
|
||||
if len(r) > 255:
|
||||
s=struct.Struct('I')# CHECK IT
|
||||
else:
|
||||
s=struct.Struct('B')
|
||||
value=(len(r),)
|
||||
packed_data=s.pack(*value)
|
||||
txt_record=txt_record+packed_data+r
|
||||
pkt= DNSRR(rrname="Chromecast-Ultra-32799abf-18ea-631d-62ae-390515bb47c5."+qname,type="TXT",ttl=myttl,rclass=32769,rdata=txt_record)
|
||||
pkt.rdlen-=1
|
||||
mylength=len(pkt.rrname)+12
|
||||
dnsrr_packet=str(pkt)[0:mylength]+str(pkt)[mylength+1::]
|
||||
pkt=dnsrr_packet
|
||||
if unidns:
|
||||
dns_packet=UDP(dport=53)/DNS(qr=1,aa=1,rd=0,ancount=1,arcount=3)/DNSRR(rrname=qname,ttl=myttl,rdata="Chromecast-Ultra-32799abf-18ea-631d-62ae-390515bb47c5."+qname,type="PTR")/pkt/DNSRR(rrname="ChromecastUltra."+qname,ttl=myttl,type="SRV",rclass=32769,rdata=srv_rrname)/DNSRR(rrname="32799abf-18ea-631d-62ae-390515bb47c5.local",ttl=myttl,type="A",rclass=32769,rdata=source_IPv4)
|
||||
else:
|
||||
dns_packet=UDP(sport=5353,dport=5353)/DNS(qr=1,aa=1,rd=0,ancount=1,arcount=3)/DNSRR(rrname=qname,ttl=myttl,rdata="Chromecast-Ultra-32799abf-18ea-631d-62ae-390515bb47c5."+qname,type="PTR")/pkt/DNSRR(rrname="ChromecastUltra."+qname,ttl=myttl,type="SRV",rclass=32769,rdata=srv_rrname)/DNSRR(rrname="32799abf-18ea-631d-62ae-390515bb47c5.local",ttl=myttl,type="A",rclass=32769,rdata=source_IPv4)
|
||||
elif ("_airplay." in dnsqr.qname) and airtv:
|
||||
qname=dnsqr.qname
|
||||
if qname.endswith('.'):
|
||||
qname=qname[:-1]
|
||||
|
||||
txt_record=""
|
||||
rdata=['model=J33AP']
|
||||
for r in rdata:
|
||||
length=hex(len(r))[2:]
|
||||
if len(r) > 255:
|
||||
s=struct.Struct('I')# CHECK IT
|
||||
else:
|
||||
s=struct.Struct('B')
|
||||
value=(len(r),)
|
||||
packed_data=s.pack(*value)
|
||||
txt_record=txt_record+packed_data+r
|
||||
pkt= DNSRR(rrname="Apple TV-mitsos._device-info._tcp.local",type="TXT",ttl=myttl,rclass=32769,rdata=txt_record)
|
||||
pkt.rdlen-=1
|
||||
mylength=len(pkt.rrname)+12
|
||||
dnsrr_packet=str(pkt)[0:mylength]+str(pkt)[mylength+1::]
|
||||
pkt=dnsrr_packet
|
||||
|
||||
txt_record=""
|
||||
rdata=['deviceid=9C:20:7B:AD:6B:E4','features=0x5A7FFFF7,0xE','flags=0x44','model=AppleTV3,1','pk=ab69a89af6fe7ff1fd803f74c6681d786aff1e6bc52087e49cc8f22585916ccd','pi=a62a851e-d024-439f-9ee0-01ef1b23d6ca','srcvers=220.68','vv=2']
|
||||
for r in rdata:
|
||||
length=hex(len(r))[2:]
|
||||
if len(r) > 255:
|
||||
s=struct.Struct('I')# CHECK IT
|
||||
else:
|
||||
s=struct.Struct('B')
|
||||
value=(len(r),)
|
||||
packed_data=s.pack(*value)
|
||||
txt_record=txt_record+packed_data+r
|
||||
pkt2= DNSRR(rrname="Apple TV-mitsos._airplay._tcp.local",type="TXT",ttl=myttl,rclass=32769,rdata=txt_record)
|
||||
pkt2.rdlen-=1
|
||||
mylength=len(pkt2.rrname)+12
|
||||
dnsrr_packet=str(pkt2)[0:mylength]+str(pkt2)[mylength+1::]
|
||||
pkt2=dnsrr_packet
|
||||
|
||||
txt_record=""
|
||||
rdata=['cn=0,1,2,3','da=true','et=0,3,5','ft=0x5A7FFFF7,0xE','md=0,1,2','am=AppleTV3,1','pk=ab69a89af6fe7ff1fd803f74c6681d786aff1e6bc52087e49cc8f22585916ccd','sf=0x44','tp=UDP','vn=65537','vs=220.68','vv=2']
|
||||
for r in rdata:
|
||||
length=hex(len(r))[2:]
|
||||
if len(r) > 255:
|
||||
s=struct.Struct('I')# CHECK IT
|
||||
else:
|
||||
s=struct.Struct('B')
|
||||
value=(len(r),)
|
||||
packed_data=s.pack(*value)
|
||||
txt_record=txt_record+packed_data+r
|
||||
pkt3= DNSRR(rrname="9C207BAD6BE4@Apple TV-mitsos._raop._tcp.local",type="TXT",ttl=myttl,rclass=32769,rdata=txt_record)
|
||||
pkt3.rdlen-=1
|
||||
mylength=len(pkt3.rrname)+12
|
||||
dnsrr_packet=str(pkt3)[0:mylength]+str(pkt3)[mylength+1::]
|
||||
pkt3=dnsrr_packet
|
||||
|
||||
#make the SRV additional record
|
||||
port=7000
|
||||
port='{0:016b}'.format(port)
|
||||
port="{0:0>4X}".format(int(port, 2))
|
||||
weight=0
|
||||
weight='{0:016b}'.format(weight)
|
||||
weight="{0:0>4X}".format(int(weight, 2))
|
||||
priority=0
|
||||
priority='{0:016b}'.format(priority)
|
||||
priority="{0:0>4X}".format(int(priority, 2))
|
||||
data=priority.decode("hex")+weight.decode("hex")+port.decode("hex")
|
||||
sublabels = "Apple-TV.local".split(".") + [""]
|
||||
label_format = ""
|
||||
for s in sublabels:
|
||||
label_format = '%s%dp' % (label_format, len(s) + 1)
|
||||
label_data = struct.pack(label_format, *sublabels)
|
||||
srv_rrname=data+label_data
|
||||
|
||||
if unidns:
|
||||
dns_packet=UDP(dport=53)
|
||||
else:
|
||||
dns_packet=UDP(sport=5353,dport=5353)/DNS(qr=1,aa=1,rd=0,ancount=3,arcount=6)/DNSRR(rrname=qname,ttl=myttl,rdata="Apple TV-mitsos._device-info."+qname,type="PTR")/pkt/DNSRR(rrname="_raop._tcp.local",ttl=myttl,rdata="9C207BAD6BE4@Apple TV-mitsos._raop._tcp.local",type="PTR")/pkt2/pkt3/DNSRR(rrname="Apple TV-mitsos."+qname,ttl=myttl,type="SRV",rclass=32769,rdata=srv_rrname)/DNSRR(rrname="9C207BAD6BE4@Apple TV-mitsos._raop._tcp.local",ttl=myttl,type="SRV",rclass=32769,rdata=srv_rrname)/DNSRR(rrname="Apple-TV.local",ttl=myttl,rdata=source_IPv4,type="A",rclass=32769)/DNSRR(rrname="Apple-TV.local",ttl=myttl,rdata=source_IPv6,type="AAAA",rclass=32769)
|
||||
else:
|
||||
qname=dnsqr.qname
|
||||
if qname.endswith('.'):
|
||||
qname=qname[:-1]
|
||||
print "Query Name = ",qname," Type=",dnsqr.qtype
|
||||
if unidns:
|
||||
dns_packet=UDP(sport=5353,dport=5353)/DNS(qr=1,aa=1,rd=0,ancount=1)/DNSRR(rrname=qname,ttl=myttl,rdata=source_IPv4,type="A")
|
||||
else:
|
||||
dns_packet=UDP(sport=5353,dport=5353)/DNS(qr=1,aa=1,rd=0,ancount=1)/DNSRR(rrname=qname,ttl=myttl,rdata=source_IPv4,type="A")
|
||||
send_packets(v4,v6,source_mac,target_mac1,target_mac2,source_IPv4,d4,source_IPv6,d6,interface,hlimit,dns_packet,False,10.0,0.1)#CHANGE DEFAULT VALUES
|
||||
#CHANGE: SEND IT TWICE, IF NOT FLOOD
|
||||
#if not flood:
|
||||
# for x in range(0,10):#Send each packet twice
|
||||
# send_packets(v4,v6,source_mac,target_mac1,target_mac2,source_IPv4,d4,source_IPv6,d6,interface,hlimit,dns_packet,False,10.0,0.1)#CHANGE DEFAULT VALUES
|
||||
### END "IF WE NEED TO AUTO RESPOND WITH A FAKE RESPONSE
|
||||
### NEXT LINES ARE ONLY USED TO PRINT RESULTS ###
|
||||
if dnsqr.qclass==32769:
|
||||
res = res0 + " Question: "+dnsqr.qname + " " + dns_type[dnsqr.qtype] +" QU Class:IN"
|
||||
elif dnsqr.qclass==1:
|
||||
res = res0 + " Question: "+dnsqr.qname + " "+ dns_type[dnsqr.qtype] + " QM Class:IN"
|
||||
elif dnsqr.qclass==255:
|
||||
res = res0 + " Question: "+dnsqr.qname + " "+ dns_type[dnsqr.qtype] + " QM Class:ANY"
|
||||
else:
|
||||
print "DNSQR:"
|
||||
print "-----"
|
||||
print dnsqr.show()
|
||||
print "DEBUGGING IS NEEDED"
|
||||
exit(0)
|
||||
if print_res==1:
|
||||
print res
|
||||
queue.put(res)
|
||||
block = block.payload
|
||||
if dns.arcount>0:
|
||||
DNSBlocks = [ ]
|
||||
DNSBlocks.append(dns.ar)
|
||||
for block in DNSBlocks:
|
||||
if block.haslayer(DNSRROPT):
|
||||
while isinstance(block,DNSRROPT):#Somewhat equivalent: while not isinstance(an, NoPayload):
|
||||
dnsrropt=block.getlayer(DNSRROPT)
|
||||
#print "DNS OPT Resource Record"
|
||||
if dnsrropt.rrname == ".":
|
||||
rrname = "<Root>"
|
||||
else:
|
||||
rrname = dnsrropt.rrname
|
||||
if dnsrropt.type==41:
|
||||
ARtype="OPT"
|
||||
else:
|
||||
ARtype=str(dnsrropt.type)
|
||||
res = res0 + " Additional_Record: " + rrname + " " + ARtype
|
||||
if dnsrropt.haslayer(EDNS0TLV):
|
||||
edns0tlv=dnsrropt.getlayer(EDNS0TLV)
|
||||
if edns0tlv.optcode==4:
|
||||
optcode="Reserved"
|
||||
else:
|
||||
optcode=str(edns0tlv.optcode)
|
||||
res = res + " EDNS0TLV: " + optcode + " " + str(edns0tlv.optdata).encode("HEX")
|
||||
if print_res==1:
|
||||
print res
|
||||
queue.put(res)
|
||||
block = block.payload
|
||||
elif block.haslayer(DNSRR):
|
||||
while isinstance(block,DNSRR):#Somewhat equivalent: while not isinstance(an, NoPayload):
|
||||
dnsrr=block.getlayer(DNSRR)
|
||||
if dnsrr.rclass==32769:
|
||||
res = res0 + " DNS Resource Record: "+ dnsrr.rrname + " " + dns_type[dnsrr.type] +" QU Class:IN "+dnsrr.rdata
|
||||
elif dnsrr.rclass==1:
|
||||
res = res0 + " DNS Resource Record: "+dnsrr.rrname + " "+ dns_type[dnsrr.type] + " QM Class:IN "+dnsrr.rdata
|
||||
elif dnsrr.qclass==255:
|
||||
res = res0 + " Question: "+dnsrr.qname + " "+ dns_type[dnsrr.qtype] + " QM Class:ANY"
|
||||
else:
|
||||
print "DNSRR:"
|
||||
print "-----"
|
||||
print dnsrr.show()
|
||||
print "DEBUGGING IS NEEDED HERE"
|
||||
exit(0)
|
||||
if dnsrr.type==33:#SRV Record
|
||||
priority=str(dnsrr.rdata)[0].encode("HEX")+str(dnsrr.rdata)[1].encode("HEX")
|
||||
weight=str(dnsrr.rdata)[2].encode("HEX")+str(dnsrr.rdata)[3].encode("HEX")
|
||||
port_number=str(dnsrr.rdata)[4].encode("HEX")+str(dnsrr.rdata)[5].encode("HEX")
|
||||
res = res0 + " Additional_Record: "+dnsrr.rrname + " " + dns_type[dnsrr.type]+" " + str(dnsrr.rclass) + " priority="+str(int(priority,16))+" weight="+str(int(weight,16))+" port="+str(int(port_number,16))+" target="+str(dnsrr.rdata)[6::]
|
||||
else:
|
||||
rdata=dnsrr.rdata
|
||||
if "._tcp." not in rdata and "._udp." not in rdata:
|
||||
if rdata == "_dhnap.":
|
||||
rdata=dnsrr.rdata+"_tcp."
|
||||
res = res0 + " Additional_Record: "+dnsrr.rrname + " " + dns_type[dnsrr.type]+" " + str(dnsrr.rclass) + ' "' +rdata+'"'
|
||||
if show_ttl:
|
||||
res = res + " TTL:"+str(dnsrr.ttl)
|
||||
if print_res==1:
|
||||
print res
|
||||
queue.put(res)
|
||||
block = block.payload
|
||||
if dns.ancount>0:
|
||||
DNSBlocks = [ ]
|
||||
DNSBlocks.append(dns.an)
|
||||
for block in DNSBlocks:
|
||||
while isinstance(block,DNSRR):
|
||||
dnsrr=block.getlayer(DNSRR)
|
||||
if dnsrr.rclass==1:
|
||||
rclass="Class:IN"
|
||||
else:
|
||||
rclass="Class:"+str(dnsrr.rclass)
|
||||
rdata=dnsrr.rdata
|
||||
if dnsrr.type==33:#SRV Record
|
||||
priority=str(dnsrr.rdata)[0].encode("HEX")+str(dnsrr.rdata)[1].encode("HEX")
|
||||
weight=str(dnsrr.rdata)[2].encode("HEX")+str(dnsrr.rdata)[3].encode("HEX")
|
||||
port_number=str(dnsrr.rdata)[4].encode("HEX")+str(dnsrr.rdata)[5].encode("HEX")
|
||||
res = res0 + " Answer: "+dnsrr.rrname + " " + dns_type[dnsrr.type]+" " + rclass + " priority="+str(int(priority,16))+" weight="+str(int(weight,16))+" port="+str(int(port_number,16))+" target="+str(dnsrr.rdata)[6::]
|
||||
else:
|
||||
if "._tcp." not in rdata and "._udp." not in rdata:
|
||||
if rdata == "_dhnap.":
|
||||
rdata=dnsrr.rdata+"_tcp."
|
||||
res = res0 + " Answer: "+dnsrr.rrname + " " + dns_type[dnsrr.type]+" " + rclass + ' "' +rdata+'"'
|
||||
if show_ttl:
|
||||
res = res + " TTL:"+str(dnsrr.ttl)
|
||||
if print_res==1:
|
||||
print res
|
||||
queue.put(res)
|
||||
block = block.payload
|
||||
if dns.nscount>0:
|
||||
DNSBlocks = [ ]
|
||||
DNSBlocks.append(dns.ns)
|
||||
for block in DNSBlocks:
|
||||
while isinstance(block,DNSRR):
|
||||
dnsrr=block.getlayer(DNSRR)
|
||||
if dnsrr.rclass==1:
|
||||
rclass="Class:IN"
|
||||
else:
|
||||
rclass="Class:"+str(dnsrr.rclass)
|
||||
res = res0 + " Auth_NS: "+dnsrr.rrname + " " + dns_type[dnsrr.type]+" " + rclass + ' "' +dnsrr.rdata+'"'
|
||||
if show_ttl:
|
||||
res = res + " TTL:"+str(dnsrr.ttl)
|
||||
if print_res==1:
|
||||
print res
|
||||
queue.put(res)
|
||||
block = block.payload
|
||||
else:
|
||||
print "not a DNS Query", dns.summary()
|
||||
#except Exception,e:
|
||||
# print "Exception",str(e),packets.summary()
|
||||
|
||||
########################################
|
||||
########### REQUEST FUNCTION ###########
|
||||
########################################
|
||||
def requests(interface,v4,v6,source_mac,target_mac1,target_mac2,source_IPv4,source_IPv6,d4,d6,hlimit,unidns,domain,query,types_of_queries,add_domain,query_class,flood,flooding_interval,flooding_timeout):
|
||||
if add_domain:
|
||||
print "Sending mdns requests"
|
||||
domain_list = domain.split(",")
|
||||
query_list = query.split(",")
|
||||
if add_domain:
|
||||
the_query=query_list[0]+"."+domain_list[0]
|
||||
else:
|
||||
types_of_queries="ALL"#implies that first a generic scan has beem performed
|
||||
the_query=query_list[0]
|
||||
dns_query=DNSQR(qname=the_query,qtype=types_of_queries,qclass=int(query_class))
|
||||
for j in range(1,len(query_list)):
|
||||
if add_domain:
|
||||
the_query=query_list[j]+"."+domain_list[0]
|
||||
else:
|
||||
the_query=query_list[j]
|
||||
dns_query=dns_query/DNSQR(qname=the_query,qtype=types_of_queries,qclass=int(query_class))
|
||||
if add_domain:
|
||||
for i in range(1,len(domain_list)):
|
||||
for j in query_list:
|
||||
the_query=j+"."+domain_list[i]
|
||||
dns_query=dns_query/DNSQR(qname=the_query,qtype=types_of_queries,qclass=int(query_class))
|
||||
if unidns:
|
||||
dns_packet=UDP(dport=53)/DNS(qr=0,qd=dns_query)
|
||||
else:
|
||||
dns_packet=UDP(sport=5353,dport=5353)/DNS(qr=0,qd=dns_query)
|
||||
send_packets(v4,v6,source_mac,target_mac1,target_mac2,source_IPv4,d4,source_IPv6,d6,interface,hlimit,dns_packet,flood,flooding_timeout,flooding_interval)
|
||||
|
||||
########################################
|
||||
########################################
|
||||
########################################
|
||||
def send_packets(v4,v6,source_mac,target_mac1,target_mac2,source_IPv4,dst_ipv4,source_IPv6,dst_ipv6,interface,hlimit,payload,flood,flooding_timeout,flooding_interval):
|
||||
if v4 or not v6:
|
||||
packet=IP(src=source_IPv4,dst=dst_ipv4,ttl=hlimit,proto="udp")/payload
|
||||
if len(packet)>1500:
|
||||
frags=fragment(packet)
|
||||
packets=[]
|
||||
for frag in frags:
|
||||
pkt1=Ether(src=source_mac,dst=target_mac1)/frag
|
||||
packets.append(pkt1)
|
||||
if flood:
|
||||
counter=0.0
|
||||
print "Stop flooding after ",flooding_timeout," sec."
|
||||
while(counter<float(flooding_timeout)):
|
||||
for packt in packets:
|
||||
sendp(pakt,iface=interface)
|
||||
counter+=float(flooding_interval)
|
||||
time.sleep(float(flooding_interval))
|
||||
else:
|
||||
for pakt in packets:
|
||||
sendp(pakt,iface=interface)
|
||||
else:
|
||||
pkt1=Ether(src=source_mac,dst=target_mac1)/packet
|
||||
if flood:
|
||||
counter=0.0
|
||||
print "Stop flooding after ",flooding_timeout," sec."
|
||||
while(counter<float(flooding_timeout)):
|
||||
sendp(pkt1,iface=interface)
|
||||
counter+=float(flooding_interval)
|
||||
time.sleep(float(flooding_interval))
|
||||
else:
|
||||
sendp(pkt1,iface=interface)
|
||||
if v6:
|
||||
packet=IPv6(src=source_IPv6,dst=dst_ipv6,hlim=hlimit)/payload
|
||||
if len(packet)>1500:
|
||||
frags2=fragment6(IPv6(src=source_IPv6,dst=dst_ipv6,hlim=hlimit)/IPv6ExtHdrFragment()/payload,1480)
|
||||
packets=[]
|
||||
for frag2 in frags2:
|
||||
pkt2=Ether(src=source_mac,dst=target_mac2)/frag2
|
||||
packets.append(pkt2)
|
||||
if flood:
|
||||
counter=0.0
|
||||
print "Stop flooding after ",flooding_timeout," sec."
|
||||
while(counter<float(flooding_timeout)):
|
||||
for packt in packets:
|
||||
sendp(pakt,iface=interface)
|
||||
counter+=float(flooding_interval)
|
||||
time.sleep(float(flooding_interval))
|
||||
else:
|
||||
for packt in packets:
|
||||
sendp(pkt2,iface=interface)
|
||||
else:
|
||||
pkt2=Ether(src=source_mac,dst=target_mac2)/packet
|
||||
if flood:
|
||||
counter=0.0
|
||||
print "Stop flooding after ",flooding_timeout," sec."
|
||||
while(counter<float(flooding_timeout)):
|
||||
sendp(pkt2,iface=interface)
|
||||
counter+=float(flooding_interval)
|
||||
time.sleep(float(flooding_interval))
|
||||
else:
|
||||
pkt2=Ether(src=source_mac,dst=target_mac2)/packet
|
||||
sendp(pkt2,iface=interface)
|
||||
|
||||
########################################
|
||||
########################################
|
||||
########################################
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(version='0.1',description='Pholus: An mdns testing tool')
|
||||
parser.add_argument('interface', action="store", help="the network interface to use, or the file (if -rpcap switch is used).")
|
||||
parser.add_argument('-rpcap','--readpcap', action="store_true", dest="rpcap", default=False, help="Read packets from a pcap file")
|
||||
parser.add_argument('-dns', '--DNS', action="store_true", dest="dns", default=False, help="Use unicast DNS request; remember to define target address")
|
||||
parser.add_argument('-4', '--v4', action="store_true", dest="v4", default=False, help="Send mdns request/response using IPv4.")
|
||||
parser.add_argument('-6', '--v6', action="store_true", dest="v6", default=False, help="Send mdns request/response using IPv6.")
|
||||
parser.add_argument('-hl', '--hlimit', action="store", dest="hlimit", default=255, type=int, help="The TTL (for IPv4) or Hop Limit (for IPv6).")
|
||||
parser.add_argument('-rq', '--request', action="store_true", dest="request", default=False, help="Send mdns request.")
|
||||
parser.add_argument('-rp', '--response', action="store_true", dest="response", default=False, help="Send unsolicired mdns response.")
|
||||
parser.add_argument('-ll', '--link_local', action="store_true", dest="link_local", default=False, help="As IPv6 source address, use link local address instead of global address")
|
||||
parser.add_argument('-s6', '--source_addr_v6', action="store", dest="source6", default=False, help="The IPv6 address of the sender (if you want to spoof it).")
|
||||
parser.add_argument('-s4', '--source_addr_v4', action="store", dest="source4", default=False, help="The IPv4 address of the sender (if you want to spoof it).")
|
||||
parser.add_argument('-d6', '--dest_addr_v6', action="store", dest="d6", default="ff02::fb", help="The IPv6 destination address of the mdns packets")
|
||||
parser.add_argument('-d4', '--dest_addr_v4', action="store", dest="d4", default="224.0.0.251", help="The IPv4 destination address of the mdns packets")
|
||||
parser.add_argument('-qtype', '--gtype', action="store", dest="qtype", default="ALL", help="qtype of mDNS query (e.g. PTR, etc.)")
|
||||
parser.add_argument('-query', '--query', action="store", dest="query", default="_services._dns-sd._udp", help="Query/ies of the mDNS packet; it can be a comma-separated list")
|
||||
parser.add_argument('-qclass', '--query_class', action="store", dest="q_class", default=1, help="The class of the query (e.g. QU vs QM)")
|
||||
parser.add_argument('-qu', '--qu', action="store_true", dest="qu", default=False, help="Send a Uniqast (QU) question in Queries")
|
||||
parser.add_argument('-dns_response', '--dns_response', action="store", dest="dns_response", default="", help="Responses of the mDNS packet; it can be a comma-separated list")
|
||||
parser.add_argument('-sttl', '--show_ttl', action="store_true", dest="show_ttl", default=False, help="Display the DNS TTL information of the received packets.")
|
||||
parser.add_argument('-rm', '--random-mac', action="store_true", dest="random_mac", default=False, help="Randomise the source MAC address.")
|
||||
parser.add_argument('-tm', '--target_mac', action="store", dest="target_mac", default=False, help="The mac address of the target (optional).")
|
||||
parser.add_argument('-sm', '--source_mac', action="store", dest="source_mac", default=False, help="The mac address of the source, e.g. for spoofing purposes (optional).")
|
||||
parser.add_argument('-stimeout','--sniffer_timeout', action="store", dest="sniffer_timeout", default=5, help="The timeout (in seconds) when the integrated sniffer (IF used) will exit automatically.")
|
||||
parser.add_argument('-domain','--domain', action="store", dest="domain", default="local", help="The domain to query; default:local")
|
||||
parser.add_argument('-rdns_scanning', '--reverse_dns_scanning', action="store", dest="rdns_scanning", default=False, help="The IPv4 address ranges that we want to scan, e.g. 192.168-169.2-3.1-255")
|
||||
parser.add_argument('-sscan', '--service_scanning', action="store_true", dest="service_scan", default=False, help="A service scan, initiated using _services._dns-sd._udp.local")
|
||||
parser.add_argument('-ttl','--define_ttl', action="store", dest="ttl", default=4500, help="The TTL to be used in the DNS messages.")
|
||||
parser.add_argument('-dos_ttl', '--DoS_zeroizing_ttl', action="store_true", dest="dos_ttl", default=False, help="DoS hosts by zeroizing the TTL value of the DNS packets.")
|
||||
parser.add_argument('-conflict', '--send_conflicting_questions', action="store_true", dest="conflict", default=False, help="Send conflicting questions with the received ones.")
|
||||
parser.add_argument('-afre', '--auto_fake_responses', action="store_true", dest="auto_fake_responses", default=False, help="Send automatically fake resposnes")
|
||||
parser.add_argument('-printer', '--printer', action="store_true", dest="printer", default=False, help="fake responses for printer")
|
||||
parser.add_argument('-airplay', '--airplay', action="store_true", dest="airtv", default=False, help="fake responses for AirPlay TV")
|
||||
parser.add_argument('-workstation', '--workstation', action="store_true", dest="workstation", default=False, help="fake responses for workstation")
|
||||
parser.add_argument('-googlecast', '--googlecast', action="store_true", dest="googlecast", default=False, help="fake responses for googlecast")
|
||||
parser.add_argument('-fl','--flood', action="store_true", dest="flood", default=0, help="flood the targets")
|
||||
parser.add_argument('-flooding-interval','--interval-of-flooding', action="store", dest="flooding_interval", default=1, help="the interval between packets when flooding the targets")
|
||||
parser.add_argument('-ftimeout','--flooding_timeout', action="store", dest="flooding_timeout", default=200, help="The time (in seconds) to flood your target.")
|
||||
values = parser.parse_args()
|
||||
|
||||
if values.rpcap:
|
||||
print "Read packets from a pcap file"
|
||||
if not os.path.isfile(values.interface):
|
||||
print '[-] ' + values.interface + ' does not exist'
|
||||
exit(0)
|
||||
elif not os.access(values.interface, os.R_OK):
|
||||
print '[-] ' + values.interface + ' access is denied'
|
||||
exit(0)
|
||||
print "Press Ctrl-C to exit and print the results"
|
||||
q = multiprocessing.Queue()
|
||||
#def __init__ (self,interface,queue,show_ttl,d4, d6, target_mac,auto_fake_responses,source_IPv6, source_IPv4, target_mac1, target_mac2,source_mac,hlimit):
|
||||
pr = multiprocessing.Process(target=Sniffer_Offline, args=(values.interface,q,values.show_ttl,values.d4, values.d6, values.target_mac, values.auto_fake_responses,values.source6,values.source4,values.target_mac,values.target_mac,values.source_mac,values.hlimit))
|
||||
#pr.daemon = True
|
||||
pr.start()
|
||||
pr.join()
|
||||
results=[]
|
||||
while not q.empty():
|
||||
results.append(q.get())
|
||||
myset=set(results)
|
||||
results=list(myset)
|
||||
results.sort()
|
||||
print "\n*********************************************RESULTS*********************************************"
|
||||
for r in results:
|
||||
print r
|
||||
exit(0)
|
||||
else:
|
||||
#####################################LETS DO SOME CHECKS FIRST TO SEE IF WE CAN WORK####################
|
||||
if os.geteuid() != 0:
|
||||
print "You must be root to run this script."
|
||||
exit(1)
|
||||
conf.verb=0
|
||||
#########################################DEFINE SOURCE ADDRESSES########################################
|
||||
source_mac=None
|
||||
if values.random_mac:
|
||||
source_mac = ':'.join(map(lambda x: "%02x" % x, [ 0x00, 0x16, 0x3E, random.randint(0x00, 0x7F), random.randint(0x00, 0xFF), random.randint(0x00, 0xFF) ]))
|
||||
elif values.source_mac:
|
||||
source_mac=values.source_mac
|
||||
else:
|
||||
try:
|
||||
source_mac=get_if_hwaddr(values.interface)
|
||||
except:
|
||||
print "interface ",values.interface," does not exist"
|
||||
exit(0)
|
||||
|
||||
if values.qu:
|
||||
q_class=32769
|
||||
else:
|
||||
q_class=int(values.q_class)
|
||||
if not values.source6:
|
||||
source_IPv6=None
|
||||
source_IPv6=get_my_ipv6_addr(values.interface)#Selects global IPv6 address, if available
|
||||
if not source_IPv6 or values.random_mac or values.link_local: #If a random MAC is used, construct the corresponding IPv6 Link Local Address
|
||||
mac_value = int(source_mac.translate(None, ' .:-'), 16)
|
||||
# Split out the bytes that slot into the IPv6 address
|
||||
# XOR the most significant byte with 0x02, inverting the
|
||||
# Universal / Local bit
|
||||
#thanks to http://codereview.stackexchange.com/questions/90263/network-mac-address-conversion-to-ipv6-link-local-address/90264
|
||||
high2 = mac_value >> 32 & 0xffff ^ 0x0200
|
||||
high1 = mac_value >> 24 & 0xff
|
||||
low1 = mac_value >> 16 & 0xff
|
||||
low2 = mac_value & 0xffff
|
||||
source_IPv6 = 'fe80::{:04x}:{:02x}ff:fe{:02x}:{:04x}'.format(high2, high1, low1, low2)
|
||||
else:
|
||||
source_IPv6=values.source6
|
||||
if not values.source4:
|
||||
source_IPv4=get_my_ipv4_addr(values.interface)
|
||||
else:
|
||||
source_IPv4=values.source4
|
||||
print "source MAC address:",source_mac,"source IPv4 Address:",source_IPv4,"source IPv6 address:",source_IPv6
|
||||
#########################################################################################################
|
||||
if values.target_mac:
|
||||
target_mac1=values.target_mac
|
||||
target_mac2=values.target_mac
|
||||
else:
|
||||
target_mac2="33:33:00:00:00:02"
|
||||
target_mac1="01:00:5e:00:00:fb"
|
||||
############################################Sniffing requirements########################################
|
||||
q = multiprocessing.Queue()
|
||||
if values.dos_ttl or values.auto_fake_responses:
|
||||
if values.auto_fake_responses:
|
||||
print "Send fake responses to requests"
|
||||
if values.target_mac:
|
||||
myfilter = "not ether src " + source_mac + " and not ether dst " + values.target_mac +" and udp and port 5353"
|
||||
else:
|
||||
myfilter = "not ether src " + source_mac + " and udp and port 5353"
|
||||
elif values.target_mac:
|
||||
print "Performing implicit DoS by sending automated spoofed DNS Answers with TTL=0"
|
||||
myfilter = "not ether dst " + values.target_mac + " and udp and port 5353"
|
||||
else:
|
||||
print "Performing implicit DoS by sending automated spoofed DNS Answers with TTL=0"
|
||||
myfilter = "udp and port 5353"
|
||||
print "Sniffer filter is:",myfilter
|
||||
print "I will sniff for",values.sniffer_timeout,"seconds, unless interrupted by Ctrl-C"
|
||||
print "Press Ctrl-C to exit"
|
||||
try:
|
||||
Sniffer(myfilter, values.interface, float(values.sniffer_timeout),q,values.dns,values.show_ttl, values.dos_ttl, values.conflict, values.ttl,values.d4, values.d6, values.target_mac, values.auto_fake_responses,source_IPv6, source_IPv4, target_mac1, target_mac2,source_mac,values.hlimit,values.workstation,values.printer,values.googlecast,values.airtv,values.flood,values.flooding_timeout,values.flooding_interval,values.v4,values.v6)
|
||||
except KeyboardInterrupt:
|
||||
print "Exiting on user's request"
|
||||
exit(0)
|
||||
exit(0)
|
||||
myfilter = "not ether src " + source_mac + " and udp and port 5353"
|
||||
print "Sniffer filter is:",myfilter
|
||||
print "I will sniff for",values.sniffer_timeout,"seconds, unless interrupted by Ctrl-C"
|
||||
pr = multiprocessing.Process(target=Sniffer, args=(myfilter, values.interface, float(values.sniffer_timeout),q,values.dns,values.show_ttl, values.dos_ttl, values.conflict, values.ttl,values.d4,values.d6, values.target_mac, values.auto_fake_responses,source_IPv6, source_IPv4, target_mac1, target_mac2, source_mac,values.hlimit,values.workstation,values.printer,values.googlecast,values.airtv,values.flood,values.flooding_timeout,values.flooding_interval,values.v4,values.v6))
|
||||
pr.daemon = True
|
||||
pr.start()
|
||||
print "------------------------------------------------------------------------"
|
||||
time.sleep(1)#to make sure than sniffer has started before we proceed, otherwise you may miss some traffic
|
||||
##########################################################################################################
|
||||
if values.request:
|
||||
requests(values.interface,values.v4,values.v6,source_mac,target_mac1,target_mac2,source_IPv4,source_IPv6,values.d4,values.d6,values.hlimit,values.dns,values.domain,values.query,values.qtype,True,q_class,values.flood,values.flooding_interval,values.flooding_timeout)
|
||||
elif values.response:
|
||||
#qr=1=>Response, aa=1=>Server is an authority for the domain, rd=0=> Do not query recursively
|
||||
if values.dns:
|
||||
dns_packet=UDP(dport=53)/DNS(qr=1,aa=1,rd=0)
|
||||
#dns_packet=UDP(dport=53)/DNS(opcode=0,qr=1,aa=1,qdcount=0,ancount=len(responses),qd=dns_response)
|
||||
else:
|
||||
dns_packet=UDP(sport=5353,dport=5353)/DNS(qr=1,aa=1,rd=0)
|
||||
#dns_packet=UDP(sport=5353,dport=5353)/DNS(opcode=0,qr=1,aa=1,qdcount=0,ancount=len(responses),qd=dns_response)
|
||||
responses = values.dns_response.split(",")
|
||||
no_of_answers=0
|
||||
no_of_additional_records=0
|
||||
dnsar=None
|
||||
for r in responses:
|
||||
values_of_responses = r.split("/")
|
||||
dnsrr=DNSRR()
|
||||
port=0
|
||||
weight=0
|
||||
priority=0
|
||||
no_of_answers+=1
|
||||
this_is_a_dns_ar=False
|
||||
#rdata=None
|
||||
rdata=[]
|
||||
for dns_values in values_of_responses:
|
||||
#print dns_values
|
||||
value = dns_values.split("==")
|
||||
if value[0]=="Type":
|
||||
dnsrr.type=value[1]
|
||||
elif value[0]=="Name":
|
||||
dnsrr.rrname=value[1]
|
||||
elif value[0]=="Target":
|
||||
rdata.append(value[1])
|
||||
#rdata=value[1]
|
||||
elif value[0]=="TTL":
|
||||
dnsrr.ttl=int(value[1])
|
||||
elif value[0]=="Flush":
|
||||
if value[1]=="True":
|
||||
dnsrr.rclass=32769
|
||||
elif value[0]=="Priority":
|
||||
priority=int(value[1])
|
||||
elif value[0]=="Weight":
|
||||
weight=int(value[1])
|
||||
elif value[0]=="Port":
|
||||
port=int(value[1])
|
||||
elif value[0]=="AR":
|
||||
if value[1]=="True":
|
||||
no_of_additional_records+=1
|
||||
this_is_a_dns_ar=True
|
||||
if dnsrr.type==33:
|
||||
port='{0:016b}'.format(port)
|
||||
port="{0:0>4X}".format(int(port, 2))
|
||||
weight='{0:016b}'.format(weight)
|
||||
weight="{0:0>4X}".format(int(weight, 2))
|
||||
priority='{0:016b}'.format(priority)
|
||||
priority="{0:0>4X}".format(int(priority, 2))
|
||||
data=priority.decode("hex")+weight.decode("hex")+port.decode("hex")
|
||||
#http://stackoverflow.com/questions/26933016/rdata-field-of-a-dns-sd-ptr-packet-sent-via-scapy-results-in-an-unknown-extended
|
||||
sublabels = rdata[0].split(".") + [""]
|
||||
label_format = ""
|
||||
for s in sublabels:
|
||||
label_format = '%s%dp' % (label_format, len(s) + 1)
|
||||
label_data = struct.pack(label_format, *sublabels)
|
||||
dnsrr.rdata=data+label_data
|
||||
elif dnsrr.type==16:
|
||||
txt_record=""
|
||||
for r in rdata:
|
||||
length=hex(len(r))[2:]
|
||||
#check http://code.activestate.com/recipes/576617-converting-arbitrary-size-python-integers-to-packe/
|
||||
if len(r) > 255:
|
||||
s=struct.Struct('I')# CHECK IT
|
||||
else:
|
||||
s=struct.Struct('B')
|
||||
value=(len(r),)
|
||||
packed_data=s.pack(*value)
|
||||
txt_record=txt_record+packed_data+r
|
||||
dnsrr.rdata=txt_record
|
||||
#pkt= DNSRR(rrname="MyOfficejet10000._pdl-datasream._tcp.local",type="TXT",ttl=myttl,rclass=32769,rdata=txt_record)
|
||||
#pkt.rdlen-=1
|
||||
#mylength=len(pkt.rrname)+12
|
||||
#dnsrr_packet=str(pkt)[0:mylength]+str(pkt)[mylength+1::]
|
||||
#pkt=dnsrr_packet
|
||||
##################
|
||||
pkt=dnsrr
|
||||
pkt.rdlen-=1
|
||||
mylength=len(pkt.rrname)+12
|
||||
dnsrr_packet=str(pkt)[0:mylength]+str(pkt)[mylength+1::]
|
||||
##################
|
||||
#mylength=len(dnsrr.rrname)+12
|
||||
#dnsrr_packet=str(dnsrr)[0:mylength]+str(dnsrr)[mylength+1::]
|
||||
dnsrr=dnsrr_packet
|
||||
else:
|
||||
#print rdata
|
||||
dnsrr.rdata=rdata[0]
|
||||
if not this_is_a_dns_ar:
|
||||
dns_packet=dns_packet/dnsrr
|
||||
else:
|
||||
if not dnsar:
|
||||
dnsar=dnsrr
|
||||
else:
|
||||
dnsar=dnsar/dnsrr
|
||||
if dnsar:
|
||||
dns_packet=dns_packet/dnsar
|
||||
dns_packet[DNS].ancount=no_of_answers-no_of_additional_records
|
||||
dns_packet[DNS].arcount=no_of_additional_records
|
||||
send_packets(values.v4,values.v6,source_mac,target_mac1,target_mac2,source_IPv4,values.d4,source_IPv6,values.d6,values.interface,values.hlimit,dns_packet,values.flood,values.flooding_timeout,values.flooding_interval)
|
||||
elif values.rdns_scanning:
|
||||
dns_query=None
|
||||
for address in ip_range(values.rdns_scanning):
|
||||
the_query=address
|
||||
if not dns_query:
|
||||
dns_query=DNSQR(qname=the_query,qtype=values.qtype,qclass=values.q_class)
|
||||
else:
|
||||
dns_query=dns_query/DNSQR(qname=the_query,qtype=values.qtype,qclass=values.q_class)
|
||||
if values.dns:
|
||||
dns_packet=UDP(dport=53)/DNS(qr=0,qd=dns_query)
|
||||
else:
|
||||
dns_packet=UDP(sport=5353,dport=5353)/DNS(qr=0,qd=dns_query)
|
||||
send_packets(values.v4,values.v6,source_mac,target_mac1,target_mac2,source_IPv4,values.d4,source_IPv6,values.d6,values.interface,values.hlimit,dns_packet,values.flood,values.flooding_timeout,values.flooding_interval)
|
||||
elif values.service_scan:
|
||||
requests(values.interface,values.v4,values.v6,source_mac,target_mac1,target_mac2,source_IPv4,source_IPv6,values.d4,values.d6,values.hlimit,values.dns,values.domain,values.query,values.qtype,True,q_class,values.flood,values.flooding_interval,values.flooding_timeout)
|
||||
############################################################################################
|
||||
############################################################################################
|
||||
if pr:
|
||||
try:
|
||||
pr.join()
|
||||
except KeyboardInterrupt:
|
||||
print "Exiting on user's request"
|
||||
exit(0)
|
||||
|
||||
#### AFTER EXITING, PRINT THE RESULTS ####
|
||||
results=[]
|
||||
while not q.empty():
|
||||
results.append(q.get())
|
||||
if values.rdns_scanning:
|
||||
targets=[]
|
||||
q2 = multiprocessing.Queue()
|
||||
pr2 = multiprocessing.Process(target=Sniffer, args=(myfilter, values.interface, float(values.sniffer_timeout),q2,values.dns,values.show_ttl, values.dos_ttl,values.conflict, values.ttl,values.d4, values.d6, values.target_mac, values.auto_fake_responses,source_IPv6, source_IPv4, target_mac1, target_mac2,source_mac,values.hlimit,values.workstation,values.printer,values.googlecast,values.airtv,values.flood,values.flooding_timeout,values.flooding_interval,values.v4,values.v6))
|
||||
pr2.daemon = True
|
||||
pr2.start()
|
||||
time.sleep(1) #to make sure than sniffer has started before we proceed, otherwise you may miss some traffic
|
||||
for r in results:
|
||||
r2=r.split(" ")
|
||||
service=r2[7].strip('"')
|
||||
if service.endswith('local.'):
|
||||
service=service[:-6]
|
||||
if service.endswith('.'):
|
||||
service=service[:-1]
|
||||
if (r2[1],service) not in targets:
|
||||
targets.append((r2[1],service))
|
||||
requests(values.interface,values.v4,values.v6,source_mac,target_mac1,target_mac2,source_IPv4,source_IPv6,values.d4,values.d6,values.hlimit,values.dns,values.domain,service,values.qtype,True,q_class,values.flood,values.flooding_interval,values.flooding_timeout)
|
||||
if pr2:
|
||||
try:
|
||||
pr2.join()
|
||||
except KeyboardInterrupt:
|
||||
print "Exiting on user's request"
|
||||
while not q2.empty():
|
||||
results.append(q2.get())
|
||||
elif values.service_scan:
|
||||
targets=[]
|
||||
q2 = multiprocessing.Queue()
|
||||
pr2 = multiprocessing.Process(target=Sniffer, args=(myfilter, values.interface, float(values.sniffer_timeout),q2,values.dns,values.show_ttl, values.dos_ttl,values.conflict, values.ttl,values.d4, values.d6, values.target_mac, values.auto_fake_responses,source_IPv6, source_IPv4, target_mac1, target_mac2,source_mac,values.hlimit,values.workstation,values.printer,values.googlecast,values.airtv,values.flood,values.flooding_timeout,values.flooding_interval,values.v4,values.v6))
|
||||
pr2.daemon = True
|
||||
pr2.start()
|
||||
time.sleep(1) #to make sure than sniffer has started before we proceed, otherwise you may miss some traffic
|
||||
for r in results:
|
||||
r2=r.split(" ")
|
||||
service=r2[7].strip('"')[:-1]
|
||||
if (r2[1],service) not in targets:
|
||||
print (r2[1],service)
|
||||
targets.append((r2[1],service))
|
||||
requests(values.interface,values.v4,values.v6,source_mac,target_mac1,target_mac2,source_IPv4,source_IPv6,values.d4,values.d6,values.hlimit,values.dns,values.domain,service,values.qtype,True,q_class,values.flood,values.flooding_interval,values.flooding_timeout)
|
||||
if pr2:
|
||||
try:
|
||||
pr2.join()
|
||||
except KeyboardInterrupt:
|
||||
print "Exiting on user's request"
|
||||
while not q2.empty():
|
||||
results.append(q2.get())
|
||||
targets2=[]
|
||||
q3 = multiprocessing.Queue()
|
||||
pr3 = multiprocessing.Process(target=Sniffer, args=(myfilter, values.interface, float(values.sniffer_timeout),q3,values.dns,values.show_ttl, values.dos_ttl, values.conflict,values.ttl,values.d4, values.d6, values.target_mac, values.auto_fake_responses,source_IPv6, source_IPv4, target_mac1, target_mac2,source_mac,values.hlimit,values.workstation,values.printer,values.googlecast,values.airtv,values.flood,values.flooding_timeout,values.flooding_interval,values.v4,values.v6))
|
||||
pr3.daemon = True
|
||||
pr3.start()
|
||||
time.sleep(1) #to make sure than sniffer has started before we proceed, otherwise you may miss some traffic
|
||||
for r in results:
|
||||
r2=r.split(" ")
|
||||
service=r2[4]
|
||||
if service.endswith('local.'):
|
||||
service=service[:-6]
|
||||
if service.endswith('.'):
|
||||
service=service[:-1]
|
||||
if (r2[1],service) not in targets and (r2[1],service) not in targets2 and "_services._dns-sd._udp" not in service:
|
||||
targets2.append((r2[1],service))
|
||||
requests(values.interface,values.v4,values.v6,source_mac,target_mac1,target_mac2,source_IPv4,source_IPv6,values.d4,values.d6,values.hlimit,values.dns,values.domain,service,values.qtype,True,q_class,values.flood,values.flooding_interval,values.flooding_timeout)
|
||||
if pr3:
|
||||
try:
|
||||
pr3.join()
|
||||
except KeyboardInterrupt:
|
||||
print "Exiting on user's request"
|
||||
while not q3.empty():
|
||||
results.append(q3.get())
|
||||
print "\n*********************************************RESULTS*********************************************"
|
||||
for r in results:
|
||||
print r
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,193 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import argparse
|
||||
import sys
|
||||
import re
|
||||
import base64
|
||||
import subprocess
|
||||
from time import strftime
|
||||
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from logger import mylog
|
||||
from plugin_helper import Plugin_Object, Plugin_Objects
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
from const import logPath, applicationPath
|
||||
import conf
|
||||
from pytz import timezone
|
||||
|
||||
# Make sure the TIMEZONE for logging is correct
|
||||
conf.tz = timezone(get_setting_value('TIMEZONE'))
|
||||
|
||||
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
|
||||
LOG_FILE = os.path.join(CUR_PATH, 'script.log')
|
||||
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
|
||||
fullPholusPath = os.path.join(CUR_PATH, 'pholus/pholus3.py')
|
||||
|
||||
pluginName = 'PHOLUS'
|
||||
|
||||
def main():
|
||||
|
||||
# the script expects a parameter in the format of userSubnets=subnet1,subnet2,...
|
||||
parser = argparse.ArgumentParser(description='Import devices from settings')
|
||||
parser.add_argument('userSubnets', nargs='+', help="list of subnets with options")
|
||||
parser.add_argument('timeoutSec', nargs='+', help="timeout")
|
||||
values = parser.parse_args()
|
||||
|
||||
# Assuming Plugin_Objects is a class or function that reads data from the RESULT_FILE
|
||||
# and returns a list of objects called 'devices'.
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
# Print a message to indicate that the script is starting.
|
||||
mylog('verbose',[f'[{pluginName}] In script'])
|
||||
|
||||
# Assuming 'values' is a dictionary or object that contains a key 'userSubnets'
|
||||
# which holds a list of user-submitted subnets.
|
||||
# Printing the userSubnets list to check its content.
|
||||
mylog('verbose',[f'[{pluginName}] Subnets: ', values.userSubnets])
|
||||
mylog('verbose',[f'[{pluginName}] len Subnets: ', len(values.userSubnets)])
|
||||
|
||||
# Extract the base64-encoded subnet information from the first element of the userSubnets list.
|
||||
# The format of the element is assumed to be like 'userSubnets=b<base64-encoded-data>'.
|
||||
userSubnetsParamBase64 = values.userSubnets[0].split('userSubnets=b')[1]
|
||||
timeoutSec = values.timeoutSec[0].split('=')[1]
|
||||
|
||||
# Printing the extracted base64-encoded subnet information.
|
||||
mylog('verbose', [f'[{pluginName}] { userSubnetsParamBase64 }'])
|
||||
mylog('verbose', [f'[{pluginName}] { timeoutSec }'])
|
||||
|
||||
# Decode the base64-encoded subnet information to get the actual subnet information in ASCII format.
|
||||
userSubnetsParam = base64.b64decode(userSubnetsParamBase64).decode('ascii')
|
||||
|
||||
# Print the decoded subnet information.
|
||||
mylog('verbose', [f'[{pluginName}] userSubnetsParam { userSubnetsParam } '])
|
||||
|
||||
# Check if the decoded subnet information contains multiple subnets separated by commas.
|
||||
# If it does, split the string into a list of individual subnets.
|
||||
# Otherwise, create a list with a single element containing the subnet information.
|
||||
if ',' in userSubnetsParam:
|
||||
subnets_list = userSubnetsParam.split(',')
|
||||
else:
|
||||
subnets_list = [userSubnetsParam]
|
||||
|
||||
# Execute the ARP scanning process on the list of subnets (whether it's one or multiple subnets).
|
||||
# The function 'execute_arpscan' is assumed to be defined elsewhere in the code.
|
||||
all_entries = execute_pholus_scan(subnets_list, timeoutSec)
|
||||
|
||||
|
||||
for entry in all_entries:
|
||||
plugin_objects.add_object(
|
||||
# "Info", "Time", "MAC", "IP_v4_or_v6", "Record_Type", "Value"
|
||||
primaryId = entry[2],
|
||||
secondaryId = entry[3],
|
||||
watched1 = entry[0],
|
||||
watched2 = entry[4],
|
||||
watched3 = entry[5],
|
||||
watched4 = '',
|
||||
extra = entry[0],
|
||||
foreignKey = entry[2])
|
||||
|
||||
plugin_objects.write_result_file()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def execute_pholus_scan(userSubnets, timeoutSec):
|
||||
# output of possible multiple interfaces
|
||||
result_list = []
|
||||
|
||||
timeoutPerSubnet = float(timeoutSec) / len(userSubnets)
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] { timeoutPerSubnet } '])
|
||||
|
||||
# scan each interface
|
||||
for interface in userSubnets:
|
||||
|
||||
temp = interface.split("--interface=")
|
||||
|
||||
if len(temp) != 2:
|
||||
mylog('verbose', [f'[{pluginName}] Skip scan (need interface in format "192.168.1.0/24 --inteface=eth0"), got: ', interface])
|
||||
return
|
||||
|
||||
mask = temp[0].strip()
|
||||
interface = temp[1].strip()
|
||||
|
||||
pholus_output_list = execute_pholus_on_interface (interface, timeoutPerSubnet, mask)
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] { pholus_output_list } '])
|
||||
|
||||
|
||||
result_list += pholus_output_list
|
||||
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] Pholus output number of entries:', len(result_list)])
|
||||
mylog('verbose', [f'[{pluginName}] List:', result_list])
|
||||
|
||||
return result_list
|
||||
|
||||
def execute_pholus_on_interface(interface, timeoutSec, mask):
|
||||
|
||||
|
||||
# logging & updating app state
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] Scan: Pholus for ', str(timeoutSec), 's ('+ str(round(int(timeoutSec) / 60, 1)) +'min)'])
|
||||
mylog('verbose', [f'[{pluginName}] Pholus scan on [interface] ', interface, ' [mask] ' , mask])
|
||||
|
||||
# the scan always lasts 2x as long, so the desired user time from settings needs to be halved
|
||||
adjustedTimeout = str(round(int(timeoutSec) / 2, 0))
|
||||
|
||||
# python3 -m trace --trace /app/pholus/pholus3.py eth1 -rdns_scanning 192.168.1.0/24 -stimeout 600
|
||||
pholus_args = ['python3', fullPholusPath, interface, "-rdns_scanning", mask, "-stimeout", adjustedTimeout]
|
||||
|
||||
# Execute command
|
||||
output = ""
|
||||
|
||||
try:
|
||||
# try runnning a subprocess with a forced (timeout + 30 seconds) in case the subprocess hangs
|
||||
output = subprocess.check_output (pholus_args, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(timeoutSec + 30))
|
||||
except subprocess.CalledProcessError as e:
|
||||
# An error occured, handle it
|
||||
mylog('verbose', [f'[{pluginName}]', e.output])
|
||||
mylog('verbose', [f'[{pluginName}] ⚠ ERROR - Pholus Scan - check logs'])
|
||||
except subprocess.TimeoutExpired as timeErr:
|
||||
mylog('verbose', [f'[{pluginName}] Pholus TIMEOUT - the process forcefully terminated as timeout reached'])
|
||||
|
||||
if output == "": # check if the subprocess failed
|
||||
mylog('verbose', [f'[{pluginName}] Scan: Pholus FAIL - check logs'])
|
||||
else:
|
||||
mylog('verbose', [f'[{pluginName}] Scan: Pholus SUCCESS'])
|
||||
|
||||
# check the last run output
|
||||
f = open(logPath + '/pholus_lastrun.log', 'r+')
|
||||
newLines = f.read().split('\n')
|
||||
f.close()
|
||||
|
||||
# cleanup - select only lines containing a separator to filter out unnecessary data
|
||||
newLines = list(filter(lambda x: '|' in x, newLines))
|
||||
|
||||
# build SQL query parameters to insert into the DB
|
||||
params = []
|
||||
|
||||
for line in newLines:
|
||||
columns = line.split("|")
|
||||
if len(columns) == 4:
|
||||
# "Info", "Time", "MAC", "IP_v4_or_v6", "Record_Type", "Value"
|
||||
params.append( [interface + " " + mask, timeNowTZ() , columns[0].replace(" ", ""), columns[1].replace(" ", ""), columns[2].replace(" ", ""), columns[3]])
|
||||
|
||||
return params
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# BEGIN
|
||||
#===============================================================================
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
require 'php/components/graph_online_history.php';
|
||||
?>
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
@@ -125,41 +126,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/graph_online_history.js"></script>
|
||||
<script>
|
||||
$.get('api/table_online_history.json?nocache=' + Date.now(), function(res) {
|
||||
// Extracting data from the JSON response
|
||||
var timeStamps = [];
|
||||
var onlineCounts = [];
|
||||
var downCounts = [];
|
||||
var offlineCounts = [];
|
||||
var archivedCounts = [];
|
||||
|
||||
res.data.forEach(function(entry) {
|
||||
var dateObj = new Date(entry.Scan_Date);
|
||||
var formattedTime = dateObj.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit', hour12: false});
|
||||
|
||||
timeStamps.push(formattedTime);
|
||||
onlineCounts.push(entry.Online_Devices);
|
||||
downCounts.push(entry.Down_Devices);
|
||||
offlineCounts.push(entry.Offline_Devices);
|
||||
archivedCounts.push(entry.Archived_Devices);
|
||||
});
|
||||
|
||||
// Call your presenceOverTime function after data is ready
|
||||
presenceOverTime(
|
||||
timeStamps,
|
||||
onlineCounts,
|
||||
offlineCounts,
|
||||
archivedCounts,
|
||||
downCounts
|
||||
);
|
||||
}).fail(function() {
|
||||
// Handle any errors in fetching the data
|
||||
console.error('Error fetching online history data.');
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- /.row -->
|
||||
|
||||
<!-- Calendar -------------------------------------------------------------- -->
|
||||
|
||||
@@ -835,7 +835,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
|
||||
if(!canReadAndWriteConfig)
|
||||
{
|
||||
showMessage (getString("settings_readonly"), 10000, "modal_red");
|
||||
console.log(`app.conf seems to be read only (canRWConfig: ${canReadAndWriteConfig}`);
|
||||
console.log(`app.conf seems to be read only (canRWConfig: ${canReadAndWriteConfig})`);
|
||||
} else
|
||||
{
|
||||
// check if config file has been updated
|
||||
|
||||
@@ -476,10 +476,6 @@ class DB():
|
||||
self.sql.execute(""" DROP VIEW IF EXISTS Sessions_Devices;""")
|
||||
self.sql.execute("""CREATE VIEW Sessions_Devices AS SELECT * FROM Sessions LEFT JOIN "Devices" ON ses_MAC = devMac;""")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Settings table setup
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -505,24 +501,9 @@ class DB():
|
||||
""")
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Pholus_Scan table setup
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
# Create Pholus_Scan table if missing
|
||||
mylog('verbose', ["[upgradeDB] Re-creating Pholus_Scan table"])
|
||||
self.sql.execute("""CREATE TABLE IF NOT EXISTS "Pholus_Scan" (
|
||||
"Index" INTEGER,
|
||||
"Info" TEXT,
|
||||
"Time" TEXT,
|
||||
"MAC" TEXT,
|
||||
"IP_v4_or_v6" TEXT,
|
||||
"Record_Type" TEXT,
|
||||
"Value" TEXT,
|
||||
"Extra" TEXT,
|
||||
PRIMARY KEY("Index" AUTOINCREMENT)
|
||||
);
|
||||
""")
|
||||
mylog('verbose', ["[upgradeDB] Removing Pholus_Scan table"])
|
||||
self.sql.execute("""DROP TABLE IF EXISTS Pholus_Scan""")
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -4,7 +4,7 @@ import subprocess
|
||||
import conf
|
||||
import os
|
||||
import re
|
||||
from helper import timeNowTZ, get_setting, get_setting_value, list_to_where, resolve_device_name_dig, resolve_device_name_pholus, get_device_name_nbtlookup, get_device_name_nslookup, get_device_name_mdns, check_IP_format, sanitize_SQL_input
|
||||
from helper import timeNowTZ, get_setting, get_setting_value, list_to_where, resolve_device_name_dig, get_device_name_nbtlookup, get_device_name_nslookup, get_device_name_mdns, check_IP_format, sanitize_SQL_input
|
||||
from logger import mylog, print_log
|
||||
from const import vendorsPath, vendorsPathNewest, sql_generateGuid
|
||||
|
||||
@@ -501,7 +501,6 @@ def update_devices_names (db):
|
||||
foundmDNSLookup = 0
|
||||
foundNsLookup = 0
|
||||
foundNbtLookup = 0
|
||||
foundPholus = 0
|
||||
|
||||
# Gen unknown devices
|
||||
sql.execute ("SELECT * FROM Devices WHERE devName IN ('(unknown)','', '(name not found)') AND devLastIP <> '-'")
|
||||
@@ -515,15 +514,6 @@ def update_devices_names (db):
|
||||
# Devices without name
|
||||
mylog('verbose', f'[Update Device Name] Trying to resolve devices without name. Unknown devices count: {len(unknownDevices)}')
|
||||
|
||||
# get names from Pholus scan
|
||||
sql.execute ('SELECT * FROM Pholus_Scan where "Record_Type"="Answer"')
|
||||
pholusResults = list(sql.fetchall())
|
||||
db.commitDB()
|
||||
|
||||
# Number of entries from previous Pholus scans
|
||||
mylog('verbose', ['[Update Device Name] Pholus entries from prev scans: ', len(pholusResults)])
|
||||
|
||||
|
||||
for device in unknownDevices:
|
||||
newName = nameNotFound
|
||||
|
||||
@@ -548,27 +538,13 @@ def update_devices_names (db):
|
||||
if newName != nameNotFound:
|
||||
foundNsLookup += 1
|
||||
|
||||
# Resolve device name with NSLOOKUP plugin data
|
||||
# Resolve device name with NBTLOOKUP plugin data
|
||||
if newName == nameNotFound:
|
||||
newName = get_device_name_nbtlookup(db, device['devMac'], device['devLastIP'])
|
||||
|
||||
if newName != nameNotFound:
|
||||
foundNbtLookup += 1
|
||||
|
||||
|
||||
# Resolve with Pholus
|
||||
if newName == nameNotFound:
|
||||
|
||||
# Try MAC matching
|
||||
newName = resolve_device_name_pholus (device['devMac'], device['devLastIP'], pholusResults, nameNotFound, False)
|
||||
# Try IP matching
|
||||
if newName == nameNotFound:
|
||||
newName = resolve_device_name_pholus (device['devMac'], device['devLastIP'], pholusResults, nameNotFound, True)
|
||||
|
||||
# count
|
||||
if newName != nameNotFound:
|
||||
foundPholus += 1
|
||||
|
||||
# if still not found update name so we can distinguish the devices where we tried already
|
||||
if newName == nameNotFound :
|
||||
|
||||
@@ -579,11 +555,11 @@ def update_devices_names (db):
|
||||
if device['devName'] != nameNotFound:
|
||||
recordsNotFound.append (["(name not found)", device['devMac']])
|
||||
else:
|
||||
# name was found with DiG or Pholus
|
||||
# name was found
|
||||
recordsToUpdate.append ([newName, device['devMac']])
|
||||
|
||||
# Print log
|
||||
mylog('verbose', [f'[Update Device Name] Names Found (DiG/mDNS/NSLOOKUP/NBTSCAN/Pholus): {len(recordsToUpdate)} ({foundDig}/{foundmDNSLookup}/{foundNsLookup}/{foundNbtLookup}/{foundPholus})'] )
|
||||
mylog('verbose', [f'[Update Device Name] Names Found (DiG/mDNS/NSLOOKUP/NBTSCAN): {len(recordsToUpdate)} ({foundDig}/{foundmDNSLookup}/{foundNsLookup}/{foundNbtLookup})'] )
|
||||
mylog('verbose', [f'[Update Device Name] Names Not Found : {notFound}'] )
|
||||
|
||||
# update not found devices with (name not found)
|
||||
|
||||
@@ -695,89 +695,9 @@ def resolve_device_name_dig (pMAC, pIP):
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# DNS record (Pholus/Name resolution) cleanup methods
|
||||
# DNS record (Name resolution) cleanup methods
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Disclaimer - I'm interfacing with a script I didn't write (pholus3.py) so it's possible I'm missing types of answers
|
||||
# it's also possible the pholus3.py script can be adjusted to provide a better output to interface with it
|
||||
# Hit me with a PR if you know how! :)
|
||||
def resolve_device_name_pholus (pMAC, pIP, allRes, nameNotFound, match_IP = False):
|
||||
|
||||
pholusMatchesIndexes = []
|
||||
|
||||
result = nameNotFound
|
||||
|
||||
# Collect all Pholus entries with matching MAC and of type Answer
|
||||
index = 0
|
||||
for result in allRes:
|
||||
# limiting entries used for name resolution to the ones containing the current IP (v4 only)
|
||||
if ((match_IP and result["IP_v4_or_v6"] == pIP ) or ( result["MAC"] == pMAC )) and result["Record_Type"] == "Answer" and '._googlezone' not in result["Value"]:
|
||||
# found entries with a matching MAC address, let's collect indexes
|
||||
pholusMatchesIndexes.append(index)
|
||||
|
||||
index += 1
|
||||
|
||||
# return if nothing found
|
||||
if len(pholusMatchesIndexes) == 0:
|
||||
return nameNotFound
|
||||
|
||||
# we have some entries let's try to select the most useful one
|
||||
# Do I need to pre-order allRes to have the most valuable onse on the top?
|
||||
|
||||
for i in pholusMatchesIndexes:
|
||||
if not checkIPV4(allRes[i]['IP_v4_or_v6']):
|
||||
continue
|
||||
|
||||
value = allRes[i]["Value"]
|
||||
|
||||
# airplay matches contain a lot of information
|
||||
# Matches for example:
|
||||
# Brand Tv (50)._airplay._tcp.local. TXT Class:32769 "acl=0 deviceid=66:66:66:66:66:66 features=0x77777,0x38BCB46 rsf=0x3 fv=p20.T-FFFFFF-03.1 flags=0x204 model=XXXX manufacturer=Brand serialNumber=XXXXXXXXXXX protovers=1.1 srcvers=777.77.77 pi=FF:FF:FF:FF:FF:FF psi=00000000-0000-0000-0000-FFFFFFFFFF gid=00000000-0000-0000-0000-FFFFFFFFFF gcgl=0 pk=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
if '._airplay._tcp.local. TXT Class:32769' in value:
|
||||
return cleanDeviceName(value.split('._airplay._tcp.local. TXT Class:32769')[0], match_IP)
|
||||
|
||||
# second best - contains airplay
|
||||
# Matches for example:
|
||||
# _airplay._tcp.local. PTR Class:IN "Brand Tv (50)._airplay._tcp.local."
|
||||
if '_airplay._tcp.local. PTR Class:IN' in value and ('._googlecast') not in value:
|
||||
return cleanDeviceName(value.split('"')[1], match_IP)
|
||||
|
||||
# Contains PTR Class:32769
|
||||
# Matches for example:
|
||||
# 3.1.168.192.in-addr.arpa. PTR Class:32769 "MyPc.local."
|
||||
if 'PTR Class:32769' in value:
|
||||
return cleanDeviceName(value.split('"')[1], match_IP)
|
||||
|
||||
# Contains AAAA Class:IN
|
||||
# Matches for example:
|
||||
# DESKTOP-SOMEID.local. AAAA Class:IN "fe80::fe80:fe80:fe80:fe80"
|
||||
if 'AAAA Class:IN' in value:
|
||||
return cleanDeviceName(value.split('.local.')[0], match_IP)
|
||||
|
||||
# Contains _googlecast._tcp.local. PTR Class:IN
|
||||
# Matches for example:
|
||||
# _googlecast._tcp.local. PTR Class:IN "Nest-Audio-ff77ff77ff77ff77ff77ff77ff77ff77._googlecast._tcp.local."
|
||||
if '_googlecast._tcp.local. PTR Class:IN' in value and ('Google-Cast-Group') not in value:
|
||||
return cleanDeviceName(value.split('"')[1], match_IP)
|
||||
|
||||
# Contains A Class:32769
|
||||
# Matches for example:
|
||||
# Android.local. A Class:32769 "192.168.1.6"
|
||||
if ' A Class:32769' in value:
|
||||
return cleanDeviceName(value.split(' A Class:32769')[0], match_IP)
|
||||
|
||||
# Contains PTR Class:IN
|
||||
# Matches for example:
|
||||
# _esphomelib._tcp.local. PTR Class:IN "ceiling-light-1._esphomelib._tcp.local."
|
||||
if 'PTR Class:IN' in value and len(value.split('"')) > 1:
|
||||
return cleanDeviceName(value.split('"')[1], match_IP)
|
||||
|
||||
return nameNotFound
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
import dns.resolver
|
||||
|
||||
def cleanDeviceName(str, match_IP):
|
||||
|
||||
Reference in New Issue
Block a user