3 var local_uri_prefix = "";
4 if (typeof(KISMET_URI_PREFIX) !== 'undefined')
5 local_uri_prefix = KISMET_URI_PREFIX;
7 // Crypt set from packet_ieee80211.h
8 export const crypt_none = 0;
9 export const crypt_unknown = 1;
10 export const crypt_wep = (1 << 1);
11 export const crypt_layer3 = (1 << 2);
12 export const crypt_wep40 = (1 << 3);
13 export const crypt_wep104 = (1 << 4);
14 export const crypt_tkip = (1 << 5);
15 export const crypt_wpa = (1 << 6);
16 export const crypt_psk = (1 << 7);
17 export const crypt_aes_ocb = (1 << 8);
18 export const crypt_aes_ccm = (1 << 9);
19 export const crypt_wpa_migmode = (1 << 10);
20 export const crypt_eap = (1 << 11);
21 export const crypt_leap = (1 << 12);
22 export const crypt_ttls = (1 << 13);
23 export const crypt_tls = (1 << 14);
24 export const crypt_peap = (1 << 15);
25 export const crypt_sae = (1 << 16);
26 export const crypt_wpa_owe = (1 << 17);
28 export const crypt_protectmask = 0xFFFFF;
29 export const crypt_isakmp = (1 << 20);
30 export const crypt_pptp = (1 << 21);
31 export const crypt_fortress = (1 << 22);
32 export const crypt_keyguard = (1 << 23);
33 export const crypt_unknown_protected = (1 << 24);
34 export const crypt_unknown_nonwep = (1 << 25);
35 export const crypt_wps = (1 << 26);
36 export const crypt_version_wpa = (1 << 27);
37 export const crypt_version_wpa2 = (1 << 28);
38 export const crypt_version_wpa3 = (1 << 29);
40 export const crypt_l3_mask = 0x300004;
41 export const crypt_l2_mask = 0xFBFA;
43 // Some hex and ascii manipulation
44 function hexstr_to_bytes(hex) {
48 for (let i = 0; i < hex.length - 1; i += 2) {
49 bytes.push(parseInt(hex.substr(i, 2), 16));
59 if (typeof(b) === 'undefined' || b.length == 0)
60 return "..".repeat(8);
62 return b.reduce((output, elem) =>
63 (output + ('0' + elem.toString(16)).slice(-2) + ""), '') + "..".repeat(8 - b.length);
66 function asciidump(b) {
69 if (typeof(b) === 'undefined' || b.length == 0)
72 for (var i = 0; i < b.length; i++) {
73 if (b[i] >= 32 && b[i] <= 127) {
74 var c = String.fromCharCode(b[i]);
89 ret = ret + ".".repeat(8 - b.length);
94 function pretty_hexdump(b) {
98 if (typeof(b) === 'undefined')
101 for (var i = 0; i < b.length; i += 8) {
102 groups.push(b.slice(i, i + 8));
108 // Print 2 groups of 8, followed by ascii
109 for (var i = 0; i < groups.length; i += 2) {
110 var hex_str = hexdump(groups[i]) + " " + hexdump(groups[i + 1]);
111 var ascii_str = asciidump(groups[i]) + " " + asciidump(groups[i + 1]);
113 ret_groups.push(hex_str + ' ' + ascii_str);
119 export const CryptToHumanReadable = (cryptset) => {
122 if (cryptset == crypt_none)
123 return "None / Open";
125 if (cryptset == crypt_unknown)
128 if (cryptset & crypt_wps)
131 if ((cryptset & crypt_protectmask) == crypt_wep) {
133 return ret.join(" ");
136 if (cryptset == crypt_wpa_owe)
139 if (cryptset & crypt_wpa_owe)
144 if (cryptset & crypt_version_wpa2)
147 if (cryptset & crypt_version_wpa3)
150 if (cryptset & crypt_wpa)
153 if ((cryptset & crypt_version_wpa3) && (cryptset & crypt_psk) && (cryptset & crypt_sae))
154 ret.push("WPA3-TRANSITION");
156 if (cryptset & crypt_psk)
157 ret.push(WPAVER + "-PSK");
159 if (cryptset & crypt_sae)
160 ret.push(WPAVER + "-SAE");
162 if (cryptset & crypt_eap)
163 ret.push(WPAVER + "-EAP");
165 if (cryptset & crypt_peap)
166 ret.push(WPAVER + "-PEAP");
168 if (cryptset & crypt_leap)
169 ret.push("EAP-LEAP");
171 if (cryptset & crypt_ttls)
172 ret.push("EAP-TTLS");
174 if (cryptset & crypt_tls)
177 if (cryptset & crypt_wpa_migmode)
178 ret.push("WPA-MIGRATION");
180 if (cryptset & crypt_wep40)
183 if (cryptset & crypt_wep104)
186 if (cryptset & crypt_tkip)
189 if (cryptset & crypt_aes_ocb)
192 if (cryptset & crypt_aes_ccm)
195 if (cryptset & crypt_layer3)
198 if (cryptset & crypt_isakmp)
199 ret.push("Layer3-ISA-KMP");
201 if (cryptset & crypt_pptp)
202 ret.push("Layer3-PPTP");
204 if (cryptset & crypt_fortress)
205 ret.push("Fortress");
207 if (cryptset & crypt_keyguard)
208 ret.push("Keyguard");
210 if (cryptset & crypt_unknown_protected)
213 if (cryptset & crypt_unknown_nonwep)
214 ret.push("Unknown-Non-WEP");
216 return ret.join(" ");
219 kismet_ui.AddDeviceView("Wi-Fi Access Points", "phydot11_accesspoints", -10000, "Wi-Fi");
221 kismet_ui.AddChannelList("IEEE802.11", "Wi-Fi (802.11)", function(in_freq) {
225 in_freq = parseInt(in_freq / 1000);
229 else if (in_freq < 2400)
230 return `${in_freq}MHz`;
231 else if (in_freq == 5935)
233 else if (in_freq < 2484)
234 return (in_freq - 2407) / 5;
235 else if (in_freq >= 4910 && in_freq <= 4980)
236 return (in_freq - 4000) / 5;
237 else if (in_freq < 5950)
238 return (in_freq - 5000) / 5;
239 else if (in_freq <= 45000) /* DMG band lower limit */
240 return (in_freq - 5950) / 5;
241 else if (in_freq >= 58320 && in_freq <= 70200)
242 return (in_freq - 56160) / 2160;
247 /* Highlight WPA handshakes */
248 kismet_ui.AddDeviceRowHighlight({
249 name: "WPA Handshake",
250 description: "Network contains a complete WPA handshake",
252 defaultcolor: "#F00",
255 'dot11.device/dot11.device.wpa_handshake_list'
257 selector: function(data) {
259 for (const dev in data['dot11.device.wpa_handshake_list']) {
262 for (const p of data['dot11.device.wpa_handshake_list'][dev]) {
263 pmask = pmask | (1 << p['dot11.eapol.message_num']);
265 if ((pmask & 0x06) == 0x06 || (pmask & 0x0C) == 0x0C)
278 /* Highlight WPA RSN PMKID */
279 kismet_ui.AddDeviceRowHighlight({
281 description: "Network contains a RSN PMKID packet",
283 defaultcolor: "#F55",
286 'dot11.device/dot11.device.pmkid_packet'
288 selector: function(data) {
290 return 'dot11.device.pmkid_packet' in data && data['dot11.device.pmkid_packet'] != 0;
297 kismet_ui.AddDeviceRowHighlight({
298 name: "Wi-Fi Device",
299 description: "Highlight all Wi-Fi devices",
301 defaultcolor: "#99ff99",
302 defaultenable: false,
304 'kismet.device.base.phyname',
306 selector: function(data) {
307 return ('kismet.device.base.phyname' in data && data['kismet.device.base.phyname'] === 'IEEE802.11');
311 kismet_ui.AddDeviceColumn('wifi_clients', {
313 field: 'dot11.device/dot11.device.num_associated_clients',
314 description: 'Related Wi-Fi devices (associated and bridged)',
316 sClass: "dt-body-right",
319 kismet_ui.AddDeviceColumn('wifi_last_bssid', {
321 field: 'dot11.device/dot11.device.last_bssid',
322 description: 'Last associated BSSID',
326 renderfunc: function(d, t, r, m) {
334 return kismet.censorMAC(d);
338 kismet_ui.AddDeviceColumn('wifi_bss_uptime', {
340 field: 'dot11.device/dot11.device.bss_timestamp',
341 description: 'Estimated device uptime (from BSS timestamp)',
345 visible: false, // Off by default
346 renderfunc: function(d, t, r, m) {
347 return kismet_ui_base.renderUsecTime(d, t, r, m);
351 // Hidden column to fetch qbss state
352 kismet_ui.AddDeviceColumn('column_qbss_hidden', {
353 sTitle: 'qbss_available',
354 field: 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.dot11e_qbss',
355 name: 'qbss_available',
362 kismet_ui.AddDeviceColumn('wifi_qbss_usage', {
363 sTitle: 'QBSS Chan Usage',
364 // field: 'dot11.device/dot11.device.bss_timestamp',
365 field: 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.dot11e_channel_utilization_perc',
366 description: '802.11e QBSS channel utilization',
372 renderfunc: function(d, t, r, m) {
375 if (r['dot11.advertisedssid.dot11e_qbss'] == 1) {
379 perc = Number.parseFloat(d).toPrecision(4) + "%";
382 return '<div class="percentage-border"><span class="percentage-text">' + perc + '</span><div class="percentage-fill" style="width:' + d + '%"></div></div>';
386 kismet_ui.AddDeviceColumn('wifi_qbss_clients', {
388 field: 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.dot11e_qbss_stations',
389 description: '802.11e QBSS user count',
392 sClass: "dt-body-right",
393 renderfunc: function(d, t, r, m) {
394 if (r['dot11.advertisedssid.dot11e_qbss'] == 1) {
402 /* Custom device details for dot11 data */
403 kismet_ui.AddDeviceDetail("dot11", "Wi-Fi (802.11)", 0, {
404 filter: function(data) {
406 return (data['kismet.device.base.phyname'] === "IEEE802.11");
411 draw: function(data, target, options, storage) {
412 target.devicedata(data, {
413 "id": "dot11DeviceData",
416 field: 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.ssid',
417 title: "Last Beaconed SSID (AP)",
419 draw: function(opts) {
420 if (typeof(opts['value']) === 'undefined')
421 return '<i>None</i>';
422 if (opts['value'].replace(/\s/g, '').length == 0)
423 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
424 return kismet.censorString(opts['value']);
426 help: "If present, the last SSID (network name) advertised by a device as an access point beacon or as an access point issuing a probe response",
429 field: "dot11.device/dot11.device.last_probed_ssid_record/dot11.probedssid.ssid",
431 title: "Last Probed SSID (Client)",
432 empty: "<i>None</i>",
433 help: "If present, the last SSID (network name) probed for by a device as a client looking for a network.",
434 draw: function(opts) {
435 if (typeof(opts['value']) === 'undefined')
436 return '<i>None</i>';
437 if (opts['value'].replace(/\s/g, '').length == 0)
438 return '<i>Empty (' + opts['value'].length + ' spaces)</i>'
439 return kismet.censorString(opts['value']);
443 field: "dot11.device/dot11.device.last_bssid",
446 filter: function(opts) {
448 return opts['value'].replace(/0:/g, '').length != 0;
453 help: "If present, the BSSID (MAC address) of the last network this device was part of. Each Wi-Fi access point, even those with the same SSID, has a unique BSSID.",
454 draw: function(opts) {
455 let mac = kismet.censorMAC(opts['value']);
460 $('<span>').html(mac)
464 'class': 'copyuri pseudolink fa fa-copy',
465 'style': 'padding-left: 5px;',
466 'data-clipboard-text': `${mac}`,
475 field: "dot11.device/dot11.device.bss_timestamp",
478 draw: function(opts) {
479 if (opts['value'] == 0)
482 const data_sec = opts['value'] / 1000000;
484 const days = Math.floor(data_sec / 86400);
485 const hours = Math.floor((data_sec / 3600) % 24);
486 const minutes = Math.floor((data_sec / 60) % 60);
487 const seconds = Math.floor(data_sec % 60);
492 ret = ret + days + "d ";
493 if (hours > 0 || days > 0)
494 ret = ret + hours + "h ";
495 if (minutes > 0 || hours > 0 || days > 0)
496 ret = ret + minutes + "m ";
497 ret = ret + seconds + "s";
501 help: "Access points contain a high-precision timestamp which can be used to estimate how long the access point has been running. Typically access points start this value at zero on boot, but it may be set to an arbitrary number and is not always accurate.",
505 field: "dot11_fingerprint_group",
507 groupTitle: "Fingerprints",
508 id: "dot11_fingerprint_group",
511 field: "dot11.device/dot11.device.beacon_fingerprint",
514 empty: "<i>None</i>",
515 help: "Kismet uses attributes included in beacons to build a fingerprint of a device. This fingerprint is used to identify spoofed devices, whitelist devices, and to attempt to provide attestation about devices. The beacon fingerprint is only available when a beacon is seen from an access point.",
521 field: "dot11_packet_group",
523 groupTitle: "Packets",
524 id: "dot11_packet_group",
528 field: "graph_field_dot11",
531 render: function(opts) {
536 style: 'width: 50%; height: 200px; padding-bottom: 5px; float: left;',
538 .append('<div><b><center>Overall Packets</center></b></div>')
547 style: 'width: 50%; height: 200px; padding-bottom: 5px; float: right;',
549 .append('<div><b><center>Data Packets</center></b></div>')
559 draw: function(opts) {
561 let overalllegend = ['Management', 'Data'];
563 opts['data']['kismet.device.base.packets.llc'],
564 opts['data']['kismet.device.base.packets.data'],
567 'rgba(46, 99, 162, 1)',
568 'rgba(96, 149, 212, 1)',
569 'rgba(136, 189, 252, 1)',
573 labels: overalllegend,
577 backgroundColor: colors,
583 if ('dot11overalldonut' in window[storage]) {
584 window[storage].dot11overalldonut.data.datasets[0].data = overalldata;
585 window[storage].dot11overalldonut.update();
587 window[storage].dot11overalldonut =
588 new Chart($('#overalldonut', opts['container']), {
593 maintainAspectRatio: false,
608 window[storage].dot11overalldonut.render();
611 const datalegend = ['Data', 'Retry', 'Frag'];
613 opts['data']['kismet.device.base.packets.data'],
614 opts['data']['dot11.device']['dot11.device.num_retries'],
615 opts['data']['dot11.device']['dot11.device.num_fragments'],
618 const databarChartData = {
623 backgroundColor: colors,
629 if ('dot11datadonut' in window[storage]) {
630 window[storage].dot11datadonut.data.datasets[0].data = datadata;
631 window[storage].dot11datadonut.update();
633 window[storage].dot11datadonut =
634 new Chart($('#datadonut', opts['container']), {
636 data: databarChartData,
639 maintainAspectRatio: false,
654 window[storage].dot11datadonut.render();
660 field: "kismet.device.base.packets.total",
662 title: "Total Packets",
663 help: "Total packet count seen of all packet types",
666 field: "kismet.device.base.packets.llc",
668 title: "LLC/Management",
669 help: "LLC and Management packets define Wi-Fi networks. They include packets like beacons, probe requests and responses, and other packets. Access points will almost always have significantly more management packets than any other type.",
672 field: "kismet.device.base.packets.data",
674 title: "Data Packets",
675 help: "Wi-Fi data packets encode the actual data being sent by the device.",
678 field: "kismet.device.base.packets.error",
680 title: "Error/Invalid Packets",
681 help: "Invalid Wi-Fi packets are packets which have become corrupted in the air or which are otherwise invalid. Typically these packets are discarded instead of tracked because the validity of their contents cannot be verified, so this will often be 0.",
684 field: "dot11.device/dot11.device.num_fragments",
686 title: "Fragmented Packets",
687 help: "The data being sent over Wi-Fi can be fragmented into smaller packets. Typically this is not desirable because it increases the packet load and can add latency to TCP connections.",
690 field: "dot11.device/dot11.device.num_retries",
692 title: "Retried Packets",
693 help: "If a Wi-Fi data packet cannot be transmitted (due to weak signal, interference, or collisions with other packets transmitted at the same time), the Wi-Fi layer will automatically attempt to retransmit it a number of times. In busy environments, a retransmit rate of 50% or higher is not unusual.",
696 field: "dot11.device/dot11.device.datasize",
698 title: "Data (size)",
699 draw: kismet_ui.RenderHumanSize,
700 help: "The amount of data transmitted by this device",
703 field: "dot11.device/dot11.device.datasize_retry",
705 title: "Retried Data",
706 draw: kismet_ui.RenderHumanSize,
707 help: "The amount of data re-transmitted by this device, due to lost packets and automatic retry.",
713 field: "dot11_extras",
715 help: "Some devices advertise additional information about capabilities via additional tag fields when joining a network.",
716 filter: function(opts) {
718 if (opts['data']['dot11.device']['dot11.device.min_tx_power'] != 0)
721 if (opts['data']['dot11.device']['dot11.device.max_tx_power'] != 0)
724 if (opts['data']['dot11.device']['dot11.device.supported_channels'].length > 0)
733 groupTitle: "Additional Capabilities",
736 field: "dot11.device/dot11.device.min_tx_power",
738 help: "Some devices advertise their minimum transmit power in association requests. This data is in the IE 33 field. This data could be manipulated by hostile devices, but can be informational for normal devices.",
742 field: "dot11.device/dot11.device.max_tx_power",
744 help: "Some devices advertise their maximum transmit power in association requests. This data is in the IE 33 field. This data could be manipulated by hostile devices, but can be informational for normal devices.",
748 field: "dot11.device/dot11.device.supported_channels",
749 title: "Supported Channels",
750 help: "Some devices advertise the 5GHz channels they support while joining a network. Supported 2.4GHz channels are not included in this list. This data is in the IE 36 field. This data can be manipulated by hostile devices, but can be informational for normal deices.",
751 filter: function(opts) {
753 return (opts['data']['dot11.device']['dot11.device.supported_channels'].length);
758 draw: function(opts) {
760 return opts['data']['dot11.device']['dot11.device.supported_channels'].join(',');
770 field: "dot11.device/dot11.device.wpa_handshake_list",
771 id: "wpa_handshake_title",
772 filter: function(opts) {
774 return (Object.keys(opts['data']['dot11.device']['dot11.device.wpa_handshake_list']).length);
779 groupTitle: "WPA Handshakes",
782 field: "dot11.device/dot11.device.wpa_handshake_list",
784 help: "When a client joins a WPA network, it performs a "handshake" of four packets to establish the connection and the unique per-session key. To decrypt WPA or derive the PSK, at least two specific packets of this handshake are required. Kismet provides a simplified pcap file of the handshake packets seen, which can be used with other tools to derive the PSK or decrypt the packet stream.",
785 filter: function(opts) {
787 return (Object.keys(opts['data']['dot11.device']['dot11.device.wpa_handshake_list']).length);
799 draw: function(opts) {
800 return kismet.censorMAC(opts['index']);
807 draw: function(opts) {
809 for (const p of kismet.ObjectByString(opts['data'], opts['basekey'])) {
810 hs = hs | (1 << p['dot11.eapol.message_num']);
815 for (const p of [1, 2, 3, 4]) {
825 field: "wpa_handshake_download",
826 id: "handshake_download",
827 title: "Handshake PCAP",
828 draw: function(opts) {
830 for (const p of kismet.ObjectByString(opts['data'], opts['basekey'])) {
831 hs = hs | (1 << p['dot11.eapol.message_num']);
834 // We need packets 1&2 or 2&3 to be able to crack the handshake
837 warning = '<br><i style="color: red;">While handshake packets have been seen, a complete 4-way handshake has not been observed. You may still be able to utilize the partial handshake.</i>';
840 const key = opts['data']['kismet.device.base.key'];
841 const url = `<a href="phy/phy80211/by-key/${key}/device/${opts['index']}/pcap/handshake.pcap">` +
842 '<i class="fa fa-download"></i> Download Pcap File</a>' +
851 field: "dot11.device/dot11.device.pmkid_packet",
853 help: "Some access points disclose the RSN PMKID during the first part of the authentication process. This can be used to attack the PSK via tools like Aircrack-NG or Hashcat. If a RSN PMKID packet is seen, Kismet can provide a pcap file.",
856 groupTitle: "WPA RSN PMKID",
860 field: "pmkid_download",
861 id: "pmkid_download",
862 title: "WPA PMKID PCAP",
863 draw: function(opts) {
864 const key = opts['data']['kismet.device.base.key'];
865 const url = '<a href="phy/phy80211/by-key/' + key + '/pcap/handshake-pmkid.pcap">' +
866 '<i class="fa fa-download"></i> Download Pcap File</a>';
875 field: "dot11.device/dot11.device.probed_ssid_map",
876 id: "probed_ssid_header",
877 filter: function(opts) {
879 return (Object.keys(opts['data']['dot11.device']['dot11.device.probed_ssid_map']).length >= 1);
884 title: '<b class="k_padding_title">Probed SSIDs</b>',
885 help: "Wi-Fi clients will send out probe requests for networks they are trying to join. Probe requests can either be broadcast requests, requesting any network in the area respond, or specific requests, requesting a single SSID the client has used previously. Different clients may behave differently, and modern clients will typically only send generic broadcast probes.",
889 field: "dot11.device/dot11.device.probed_ssid_map",
892 filter: function(opts) {
894 return (Object.keys(opts['data']['dot11.device']['dot11.device.probed_ssid_map']).length >= 1);
902 iterateTitle: function(opts) {
903 const lastprobe = opts['value'][opts['index']];
904 let lastpssid = lastprobe['dot11.probedssid.ssid'];
905 const key = "probessid" + opts['index'];
907 if (lastpssid === '')
908 lastpssid = "<i>Broadcast</i>";
910 if (lastpssid.replace(/\s/g, '').length == 0)
911 lastpssid = '<i>Empty (' + lastpssid.length + ' spaces)</i>'
913 return '<a id="' + key + '" class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Probed SSID ' + kismet.censorString(lastpssid) + '</a>';
916 draw: function(opts) {
917 $('.expander', opts['cell']).simpleexpand();
922 field: "dot11.probedssid.ssid",
923 title: "Probed SSID",
924 empty: "<i>Broadcast</i>",
925 draw: function(opts) {
926 if (typeof(opts['value']) === 'undefined')
927 return '<i>None</i>';
928 if (opts['value'].replace(/\s/g, '').length == 0)
929 return 'Empty (' + opts['value'].length + ' spaces)'
930 return kismet.censorString(opts['value']);
934 field: "dot11.probedssid.wpa_mfp_required",
936 help: "Management Frame Protection (MFP) attempts to mitigate denial of service attacks by authenticating management packets. It can be part of the 802.11w Wi-Fi standard, or proprietary Cisco extensions.",
937 draw: function(opts) {
939 return "Required (802.11w)";
941 if (opts['base']['dot11.probedssid.wpa_mfp_supported'])
942 return "Supported (802.11w)";
944 return "Unavailable";
948 field: "dot11.probedssid.first_time",
951 draw: kismet_ui.RenderTrimmedTime,
954 field: "dot11.probedssid.last_time",
957 draw: kismet_ui.RenderTrimmedTime,
961 // Location is its own group
962 groupTitle: "Avg. Location",
963 id: "avg.dot11.probedssid.location",
966 groupField: "dot11.probedssid.location",
970 // Don't show location if we don't know it
971 filter: function(opts) {
972 return (kismet.ObjectByString(opts['base'], "dot11.probedssid.location/kismet.common.location.avg_loc/kismet.common.location.fix") >= 2);
977 field: "kismet.common.location.avg_loc/kismet.common.location.geopoint",
980 draw: function(opts) {
982 if (opts['value'][1] == 0 || opts['value'][0] == 0)
983 return "<i>Unknown</i>";
985 return kismet.censorLocation(opts['value'][1]) + ", " + kismet.censorLocation(opts['value'][0]);
987 return "<i>Unknown</i>";
992 field: "kismet.common.location.avg_loc/kismet.common.location.alt",
995 filter: function(opts) {
996 return (kismet.ObjectByString(opts['base'], "kismet.common.location.avg_loc/kismet.common.location.fix") >= 3);
998 draw: function(opts) {
1000 return kismet_ui.renderHeightDistance(opts['value']);
1002 return "<i>Unknown</i>";
1010 field: "dot11.probedssid.dot11r_mobility",
1011 title: "802.11r Mobility",
1013 help: "The 802.11r standard allows for fast roaming between access points on the same network. Typically this is found on enterprise-level access points, on a network where multiple APs service the same area.",
1014 draw: function(opts) { return "Enabled"; }
1017 field: "dot11.probedssid.dot11r_mobility_domain_id",
1018 title: "Mobility Domain",
1020 help: "The 802.11r standard allows for fast roaming between access points on the same network."
1023 field: "dot11.probedssid.wps_manuf",
1024 title: "WPS Manufacturer",
1025 filterOnEmpty: true,
1026 help: "Clients which support Wi-Fi Protected Setup (WPS) may include the device manufacturer in the WPS advertisements. WPS is not recommended due to security flaws."
1029 field: "dot11.probedssid.wps_device_name",
1030 title: "WPS Device",
1031 filterOnEmpty: true,
1032 help: "Clients which support Wi-Fi Protected Setup (WPS) may include the device name in the WPS advertisements. WPS is not recommended due to security flaws.",
1035 field: "dot11.probedssid.wps_model_name",
1037 filterOnEmpty: true,
1038 help: "Clients which support Wi-Fi Protected Setup (WPS) may include the specific device model name in the WPS advertisements. WPS is not recommended due to security flaws.",
1041 field: "dot11.probedssid.wps_model_number",
1042 title: "WPS Model #",
1043 filterOnEmpty: true,
1044 help: "Clients which support Wi-Fi Protected Setup (WPS) may include the specific model number in the WPS advertisements. WPS is not recommended due to security flaws.",
1047 field: "dot11.probedssid.wps_serial_number",
1048 title: "WPS Serial #",
1049 filterOnEmpty: true,
1050 help: "Clients which support Wi-Fi Protected Setup (WPS) may include the device serial number in the WPS advertisements. This information is not always valid or useful. WPS is not recommended due to security flaws.",
1058 field: "dot11.device/dot11.device.advertised_ssid_map",
1059 id: "advertised_ssid_header",
1060 filter: function(opts) {
1062 return (Object.keys(opts['data']['dot11.device']['dot11.device.advertised_ssid_map']).length >= 1);
1067 title: '<b class="k_padding_title">Advertised SSIDs</b>',
1068 help: "A single BSSID may advertise multiple SSIDs, either changing its network name over time or combining multiple SSIDs into a single BSSID radio address. Most modern Wi-Fi access points which support multiple SSIDs will generate a dynamic MAC address for each SSID. Advertised SSIDs have been seen transmitted from the access point in a beacon.",
1072 field: "dot11.device/dot11.device.advertised_ssid_map",
1073 id: "advertised_ssid",
1075 filter: function(opts) {
1077 return (Object.keys(opts['data']['dot11.device']['dot11.device.advertised_ssid_map']).length >= 1);
1084 iterateTitle: function(opts) {
1085 const lastssid = opts['value'][opts['index']]['dot11.advertisedssid.ssid'];
1086 const lastowessid = opts['value'][opts['index']]['dot11.advertisedssid.owe_ssid'];
1088 if (lastssid === '') {
1089 if ('dot11.advertisedssid.owe_ssid' in opts['value'][opts['index']] && lastowessid !== '') {
1090 return "SSID: " + kismet.censorString(lastowessid) + " <i>(OWE)</i>";
1093 return "SSID: <i>Unknown</i>";
1096 return "SSID: " + kismet.censorString(lastssid);
1100 field: "dot11.advertisedssid.ssid",
1102 draw: function(opts) {
1103 if (opts['value'].replace(/\s/g, '').length == 0) {
1104 if ('dot11.advertisedssid.owe_ssid' in opts['base']) {
1105 return "<i>SSID advertised as OWE</i>";
1107 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
1111 return kismet.censorString(opts['value']);
1113 help: "Advertised SSIDs can be any data, up to 32 characters. Some access points attempt to cloak the SSID by sending blank spaces or an empty string; these SSIDs can be discovered when a client connects to the network.",
1116 field: "dot11.advertisedssid.owe_ssid",
1119 filterOnEmpty: true,
1120 draw: function(opts) {
1121 return kismet.censorString(opts['value']);
1123 help: "Opportunistic Wireless Encryption (OWE) advertises the original SSID on an alternate BSSID.",
1126 field: "dot11.advertisedssid.owe_bssid",
1129 filterOnEmpty: true,
1130 help: "Opportunistic Wireless Encryption (OWE) advertises the original SSID with a reference to the linked BSSID.",
1131 draw: function(opts) {
1132 $.get(local_uri_prefix + "devices/by-mac/" + opts['value'] + "/devices.json")
1134 opts['container'].html(kismet.censorMAC(opts['value']));
1136 .done(function(clidata) {
1137 clidata = kismet.sanitizeObject(clidata);
1139 for (const cl of clidata) {
1140 if (cl['kismet.device.base.phyname'] === 'IEEE802.11') {
1141 opts['container'].html(kismet.censorMAC(opts['value']) + ' <a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + cl['kismet.device.base.key'] + '\')">View AP Details</a>');
1146 opts['container'].html(kismet.censorMAC(opts['value']));
1151 field: "dot11.advertisedssid.dot11s.meshid",
1154 filterOnEmpty: true,
1155 help: "802.11s mesh id, present only in meshing networks.",
1158 field: "dot11.advertisedssid.crypt_set",
1160 title: "Encryption",
1161 draw: function(opts) {
1162 return CryptToHumanReadable(opts['value']);
1164 help: "Encryption at the Wi-Fi layer (open, WEP, and WPA) is defined by the beacon sent by the access point advertising the network. Layer 3 encryption (such as VPNs) is added later and is not advertised as part of the network itself.",
1167 field: "dot11.advertisedssid.wpa_mfp_required",
1170 help: "Management Frame Protection (MFP) attempts to mitigate denial of service attacks by authenticating management packets. It can be part of the Wi-Fi 802.11w standard or a custom Cisco extension.",
1171 draw: function(opts) {
1173 return "Required (802.11w)";
1175 if (opts['base']['dot11.advertisedssid.wpa_mfp_supported'])
1176 return "Supported (802.11w)";
1178 if (opts['base']['dot11.advertisedssid.cisco_client_mfp'])
1179 return "Supported (Cisco)";
1181 return "Unavailable";
1185 field: "dot11.advertisedssid.channel",
1188 help: "Wi-Fi networks on 2.4GHz (channels 1 through 14) are required to include a channel in the advertisement because channel overlap makes it impossible to determine the exact channel the access point is transmitting on. Networks on 5GHz channels are typically not required to include the channel.",
1191 field: "dot11.advertisedssid.ht_mode",
1194 help: "802.11n and 802.11AC networks operate on expanded channels; HT40, HT80 HT160, or HT80+80 (found only on 802.11ac wave2 gear).",
1198 field: "dot11.advertisedssid.ht_center_1",
1201 help: "802.11AC networks operate on expanded channels. This is the frequency of the center of the expanded channel.",
1203 draw: function(opts) {
1204 return opts['value'] + " (Channel " + (opts['value'] - 5000) / 5 + ")";
1208 field: "dot11.advertisedssid.ht_center_2",
1211 help: "802.11AC networks operate on expanded channels. This is the frequency of the center of the expanded secondary channel. Secondary channels are only found on 802.11AC wave-2 80+80 gear.",
1213 draw: function(opts) {
1214 return opts['value'] + " (Channel " + (opts['value'] - 5000) / 5 + ")";
1218 field: "dot11.advertisedssid.beacon_info",
1220 title: "Beacon Info",
1221 filterOnEmpty: true,
1222 draw: function(opts) {
1223 return kismet.censorString(opts['value']);
1225 help: "Some access points, such as those made by Cisco, can include arbitrary custom info in beacons. Typically this is used by the network administrators to map where access points are deployed.",
1228 field: "dot11.advertisedssid.dot11s.gateway",
1230 title: "Mesh Gateway",
1231 help: "An 802.11s mesh device in gateway mode bridges Wi-Fi traffic to another network, such as a wired Ethernet connection.",
1232 filterOnEmpty: true,
1233 draw: function(opts) {
1234 if (opts['value'] == 1)
1236 return "Disabled (not a gateway)";
1240 field: "dot11.advertisedssid.dot11s.forwarding",
1242 title: "Mesh Forwarding",
1243 help: "An 802.11s mesh device may forward packets to another mesh device for delivery.",
1244 filterOnEmpty: true,
1245 draw: function(opts) {
1246 if (opts['value'] == 1)
1247 return "Forwarding";
1252 field: "dot11.advertisedssid.dot11s.num_peerings",
1254 title: "Mesh Peers",
1255 help: "Number of mesh peers for this device in an 802.11s network.",
1256 filterOnEmpty: true,
1259 field: "dot11.advertisedssid.dot11e_qbss_stations",
1261 title: "Connected Stations",
1262 help: "Access points which provide 802.11e / QBSS report the number of stations observed on the channel as part of the channel quality of service.",
1263 filter: function(opts) {
1265 return (opts['base']['dot11.advertisedssid.dot11e_qbss'] == 1);
1272 field: "dot11.advertisedssid.dot11e_channel_utilization_perc",
1274 title: "Channel Utilization",
1275 help: "Access points which provide 802.11e / QBSS calculate the estimated channel saturation as part of the channel quality of service.",
1276 draw: function(opts) {
1279 if (opts['value'] == 0) {
1282 perc = Number.parseFloat(opts['value']).toPrecision(4) + "%";
1285 return '<div class="percentage-border"><span class="percentage-text">' + perc + '</span><div class="percentage-fill" style="width:' + opts['value'] + '%"></div></div>';
1287 filter: function(opts) {
1289 return (opts['base']['dot11.advertisedssid.dot11e_qbss'] == 1);
1296 field: "dot11.advertisedssid.ccx_txpower",
1298 title: "Cisco CCX TxPower",
1300 help: "Cisco access points may advertise their transmit power in a Cisco CCX IE tag. Typically this is found on enterprise-level access points, where multiple APs service the same area.",
1301 draw: function(opts) {
1302 return opts['value'] + "dBm";
1306 field: "dot11.advertisedssid.dot11r_mobility",
1308 title: "802.11r Mobility",
1310 help: "The 802.11r standard allows for fast roaming between access points on the same network. Typically this is found on enterprise-level access points, on a network where multiple APs service the same area.",
1311 draw: function(_opts) { return "Enabled"; }
1314 field: "dot11.advertisedssid.dot11r_mobility_domain_id",
1316 title: "Mobility Domain",
1318 help: "The 802.11r standard allows for fast roaming between access points on the same network."
1321 field: "dot11.advertisedssid.first_time",
1323 title: "First Seen",
1324 draw: kismet_ui.RenderTrimmedTime,
1327 field: "dot11.advertisedssid.last_time",
1330 draw: kismet_ui.RenderTrimmedTime,
1334 // Location is its own group
1335 groupTitle: "Avg. Location",
1336 id: "avg.dot11.advertisedssid.location",
1339 groupField: "dot11.advertisedssid.location",
1343 // Don't show location if we don't know it
1344 filter: function(opts) {
1345 return (kismet.ObjectByString(opts['base'], "dot11.advertisedssid.location/kismet.common.location.avg_loc/kismet.common.location.fix") >= 2);
1350 field: "kismet.common.location.avg_loc/kismet.common.location.geopoint",
1353 draw: function(opts) {
1355 if (opts['value'][1] == 0 || opts['value'][0] == 0)
1356 return "<i>Unknown</i>";
1358 return kismet.censorLocation(opts['value'][1]) + ", " + kismet.censorLocation(opts['value'][0]);
1360 return "<i>Unknown</i>";
1365 field: "kismet.common.location.avg_loc/kismet.common.location.alt",
1368 filter: function(opts) {
1369 return (kismet.ObjectByString(opts['base'], "kismet.common.location.avg_loc/kismet.common.location.fix") >= 3);
1371 draw: function(opts) {
1373 return kismet_ui.renderHeightDistance(opts['value']);
1375 return "<i>Unknown</i>";
1383 field: "dot11.advertisedssid.beaconrate",
1385 title: "Beacon Rate",
1386 draw: function(opts) {
1387 return opts['value'] + '/sec';
1389 help: "Wi-Fi typically beacons at 10 packets per second; normally there is no reason for an access point to change this rate, but it may be changed in some situations where a large number of SSIDs are hosted on a single access point.",
1392 field: "dot11.advertisedssid.maxrate",
1395 draw: function(opts) {
1396 return opts['value'] + ' MBit/s';
1398 help: "The maximum basic transmission rate supported by this access point",
1401 field: "dot11.advertisedssid.dot11d_country",
1403 title: "802.11d Country",
1404 filterOnEmpty: true,
1405 help: "The 802.11d standard required access points to identify their operating country code and signal levels. This caused clients connecting to those access points to adopt the same regulatory requirements. 802.11d has been phased out and is not found on most modern access points but may still be seen on older hardware.",
1408 field: "dot11.advertisedssid.wps_manuf",
1410 title: "WPS Manufacturer",
1411 filterOnEmpty: true,
1412 help: "Access points which advertise Wi-Fi Protected Setup (WPS) may include the device manufacturer in the WPS advertisements. WPS is not recommended due to security flaws."
1415 field: "dot11.advertisedssid.wps_device_name",
1417 title: "WPS Device",
1418 filterOnEmpty: true,
1419 draw: function(opts) {
1420 return kismet.censorString(opts['value']);
1422 help: "Access points which advertise Wi-Fi Protected Setup (WPS) may include the device name in the WPS advertisements. WPS is not recommended due to security flaws.",
1425 field: "dot11.advertisedssid.wps_model_name",
1428 filterOnEmpty: true,
1429 help: "Access points which advertise Wi-Fi Protected Setup (WPS) may include the specific device model name in the WPS advertisements. WPS is not recommended due to security flaws.",
1432 field: "dot11.advertisedssid.wps_model_number",
1434 title: "WPS Model #",
1435 filterOnEmpty: true,
1436 help: "Access points which advertise Wi-Fi Protected Setup (WPS) may include the specific model number in the WPS advertisements. WPS is not recommended due to security flaws.",
1439 field: "dot11.advertisedssid.wps_serial_number",
1441 title: "WPS Serial #",
1442 filterOnEmpty: true,
1443 draw: function(opts) {
1444 return kismet.censorString(opts['value']);
1446 help: "Access points which advertise Wi-Fi Protected Setup (WPS) may include the device serial number in the WPS advertisements. This information is not always valid or useful. WPS is not recommended due to security flaws.",
1450 field: "dot11.advertisedssid.ie_tag_content",
1452 filterOnEmpty: true,
1453 id: "dot11_ssid_ietags",
1454 title: '<b class="k_padding_title">IE tags</b>',
1455 help: "IE tags in beacons define the network and the access point attributes; because dot11_keep_ietags is true, Kismet tracks these here.",
1459 field: "dot11.advertisedssid.ie_tag_content",
1461 id: "advertised_ietags",
1462 filterOnEmpty: true,
1465 render: function(_opts) {
1466 return '<table id="tagdump" border="0" />';
1469 draw: function(opts) {
1470 $('table#tagdump', opts['container']).empty();
1471 for (const ie in opts['value']) {
1472 const tag = opts['value'][ie];
1476 class: 'alternating'
1485 .html("<b>" + tag['dot11.ietag.number'] + "</b>")
1494 if (tag['dot11.ietag.oui'] != 0) {
1495 let oui = ("000000" + tag['dot11.ietag.oui'].toString(16)).substr(-6).replace(/(..)/g, '$1:').slice(0, -1);
1497 if (tag['dot11.ietag.oui_manuf'].length != 0)
1498 oui = oui + " (" + tag['dot11.ietag.oui_manuf'] + ")";
1500 $('#tagno', pretty_tag).append(
1506 if (tag['dot11.ietag.subtag'] >= 0) {
1507 $('#tagno', pretty_tag).append(
1509 .text("Subtag " + tag['dot11.ietag.subtag'])
1513 const hexdumps = pretty_hexdump(hexstr_to_bytes(tag['dot11.ietag.data']));
1515 for (const i in hexdumps) {
1516 $('#hexdump', pretty_tag).append(
1525 $('table#tagdump', opts['container']).append(pretty_tag);
1536 field: "dot11.device/dot11.device.responded_ssid_map",
1537 id: "responded_ssid_header",
1538 filter: function(opts) {
1540 return (Object.keys(opts['data']['dot11.device']['dot11.device.responded_ssid_map']).length >= 1);
1545 title: '<b class="k_padding_title">Responded SSIDs</b>',
1546 help: "A single BSSID may advertise multiple SSIDs, either changing its network name over time or combining multiple SSIDs into a single BSSID radio address. Most modern Wi-Fi access points which support multiple SSIDs will generate a dynamic MAC address for each SSID. SSIDs in the responded categoy have been seen as a probe response from the access point; typically an access point should only respond for SSIDs it also advertises.",
1550 field: "dot11.device/dot11.device.responded_ssid_map",
1551 id: "responded_ssid",
1553 filter: function(opts) {
1555 return (Object.keys(opts['data']['dot11.device']['dot11.device.responded_ssid_map']).length >= 1);
1562 iterateTitle: function(opts) {
1563 const lastssid = opts['value'][opts['index']]['dot11.advertisedssid.ssid'];
1564 const lastowessid = opts['value'][opts['index']]['dot11.advertisedssid.owe_ssid'];
1566 if (lastssid === '') {
1567 if ('dot11.advertisedssid.owe_ssid' in opts['value'][opts['index']] && lastowessid !== '') {
1568 return "SSID: " + kismet.censorString(lastowessid) + " <i>(OWE)</i>";
1571 return "SSID: <i>Unknown</i>";
1574 return "SSID: " + kismet.censorString(lastssid);
1578 field: "dot11.advertisedssid.ssid",
1580 draw: function(opts) {
1581 if (opts['value'].replace(/\s/g, '').length == 0) {
1582 if ('dot11.advertisedssid.owe_ssid' in opts['base']) {
1583 return "<i>SSID advertised as OWE</i>";
1585 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
1589 return kismet.censorString(opts['value']);
1591 help: "Advertised SSIDs can be any data, up to 32 characters. Some access points attempt to cloak the SSID by sending blank spaces or an empty string; these SSIDs can be discovered when a client connects to the network.",
1594 field: "dot11.advertisedssid.owe_ssid",
1597 filterOnEmpty: true,
1598 draw: function(opts) {
1599 return kismet.censorString(opts['value']);
1601 help: "Opportunistic Wireless Encryption (OWE) advertises the original SSID on an alternate BSSID.",
1604 field: "dot11.advertisedssid.owe_bssid",
1607 filterOnEmpty: true,
1608 help: "Opportunistic Wireless Encryption (OWE) advertises the original SSID with a reference to the linked BSSID.",
1609 draw: function(opts) {
1610 $.get(local_uri_prefix + "devices/by-mac/" + opts['value'] + "/devices.json")
1612 opts['container'].html(opts['value']);
1614 .done(function(clidata) {
1615 clidata = kismet.sanitizeObject(clidata);
1617 for (const cl of clidata) {
1618 if (cl['kismet.device.base.phyname'] === 'IEEE802.11') {
1619 opts['container'].html(kismet.censorMAC(opts['value']) + ' <a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + cl['kismet.device.base.key'] + '\')">View AP Details</a>');
1624 opts['container'].html(kismet.censorMAC(opts['value']));
1629 field: "dot11.advertisedssid.crypt_set",
1631 title: "Encryption",
1632 draw: function(opts) {
1633 return CryptToHumanReadable(opts['value']);
1635 help: "Encryption at the Wi-Fi layer (open, WEP, and WPA) is defined by the beacon sent by the access point advertising the network. Layer 3 encryption (such as VPNs) is added later and is not advertised as part of the network itself.",
1638 field: "dot11.advertisedssid.wpa_mfp_required",
1641 help: "Management Frame Protection (MFP) attempts to mitigate denial of service attacks by authenticating management packets. It can be part of the Wi-Fi 802.11w standard or a custom Cisco extension.",
1642 draw: function(opts) {
1644 return "Required (802.11w)";
1646 if (opts['base']['dot11.advertisedssid.wpa_mfp_supported'])
1647 return "Supported (802.11w)";
1649 if (opts['base']['dot11.advertisedssid.cisco_client_mfp'])
1650 return "Supported (Cisco)";
1652 return "Unavailable";
1656 field: "dot11.advertisedssid.ht_mode",
1659 help: "802.11n and 802.11AC networks operate on expanded channels; HT40, HT80 HT160, or HT80+80 (found only on 802.11ac wave2 gear).",
1663 field: "dot11.advertisedssid.ht_center_1",
1666 help: "802.11AC networks operate on expanded channels. This is the frequency of the center of the expanded channel.",
1668 draw: function(opts) {
1669 return opts['value'] + " (Channel " + (opts['value'] - 5000) / 5 + ")";
1673 field: "dot11.advertisedssid.ht_center_2",
1676 help: "802.11AC networks operate on expanded channels. This is the frequency of the center of the expanded secondary channel. Secondary channels are only found on 802.11AC wave-2 80+80 gear.",
1678 draw: function(opts) {
1679 return opts['value'] + " (Channel " + (opts['value'] - 5000) / 5 + ")";
1683 field: "dot11.advertisedssid.ccx_txpower",
1685 title: "Cisco CCX TxPower",
1687 help: "Cisco access points may advertise their transmit power in a Cisco CCX IE tag. Typically this is found on enterprise-level access points, where multiple APs service the same area.",
1688 draw: function(opts) {
1689 return opts['value'] + "dBm";
1693 field: "dot11.advertisedssid.dot11r_mobility",
1695 title: "802.11r Mobility",
1697 help: "The 802.11r standard allows for fast roaming between access points on the same network. Typically this is found on enterprise-level access points, on a network where multiple APs service the same area.",
1698 draw: function(_opts) { return "Enabled"; }
1701 field: "dot11.advertisedssid.dot11r_mobility_domain_id",
1703 title: "Mobility Domain",
1705 help: "The 802.11r standard allows for fast roaming between access points on the same network."
1708 field: "dot11.advertisedssid.first_time",
1710 title: "First Seen",
1711 draw: kismet_ui.RenderTrimmedTime,
1714 field: "dot11.advertisedssid.last_time",
1717 draw: kismet_ui.RenderTrimmedTime,
1721 // Location is its own group
1722 groupTitle: "Avg. Location",
1723 id: "avg.dot11.advertisedssid.location",
1726 groupField: "dot11.advertisedssid.location",
1730 // Don't show location if we don't know it
1731 filter: function(opts) {
1732 return (kismet.ObjectByString(opts['base'], "dot11.advertisedssid.location/kismet.common.location.avg_loc/kismet.common.location.fix") >= 2);
1737 field: "kismet.common.location.avg_loc/kismet.common.location.geopoint",
1740 draw: function(opts) {
1742 if (opts['value'][1] == 0 || opts['value'][0] == 0)
1743 return "<i>Unknown</i>";
1745 return kismet.censorLocation(opts['value'][1]) + ", " + kismet.censorLocation(opts['value'][0]);
1747 return "<i>Unknown</i>";
1752 field: "kismet.common.location.avg_loc/kismet.common.location.alt",
1755 filter: function(opts) {
1756 return (kismet.ObjectByString(opts['base'], "kismet.common.location.avg_loc/kismet.common.location.fix") >= 3);
1758 draw: function(opts) {
1760 return kismet_ui.renderHeightDistance(opts['value']);
1762 return "<i>Unknown</i>";
1771 field: "dot11.advertisedssid.maxrate",
1774 draw: function(opts) {
1775 return opts['value'] + ' MBit/s';
1777 help: "The maximum basic transmission rate supported by this access point",
1780 field: "dot11.advertisedssid.dot11d_country",
1782 title: "802.11d Country",
1783 filterOnEmpty: true,
1784 help: "The 802.11d standard required access points to identify their operating country code and signal levels. This caused clients connecting to those access points to adopt the same regulatory requirements. 802.11d has been phased out and is not found on most modern access points but may still be seen on older hardware.",
1787 field: "dot11.advertisedssid.wps_manuf",
1789 title: "WPS Manufacturer",
1790 filterOnEmpty: true,
1791 help: "Access points which advertise Wi-Fi Protected Setup (WPS) may include the device manufacturer in the WPS advertisements. WPS is not recommended due to security flaws."
1794 field: "dot11.advertisedssid.wps_device_name",
1796 title: "WPS Device",
1797 filterOnEmpty: true,
1798 help: "Access points which advertise Wi-Fi Protected Setup (WPS) may include the device name in the WPS advertisements. WPS is not recommended due to security flaws.",
1801 field: "dot11.advertisedssid.wps_model_name",
1804 filterOnEmpty: true,
1805 help: "Access points which advertise Wi-Fi Protected Setup (WPS) may include the specific device model name in the WPS advertisements. WPS is not recommended due to security flaws.",
1808 field: "dot11.advertisedssid.wps_model_number",
1810 title: "WPS Model #",
1811 filterOnEmpty: true,
1812 help: "Access points which advertise Wi-Fi Protected Setup (WPS) may include the specific model number in the WPS advertisements. WPS is not recommended due to security flaws.",
1815 field: "dot11.advertisedssid.wps_serial_number",
1817 title: "WPS Serial #",
1818 filterOnEmpty: true,
1819 draw: function(opts) {
1820 return kismet.censorString(opts['value']);
1822 help: "Access points which advertise Wi-Fi Protected Setup (WPS) may include the device serial number in the WPS advertisements. This information is not always valid or useful. WPS is not recommended due to security flaws.",
1826 field: "dot11.advertisedssid.ie_tag_content",
1828 filterOnEmpty: true,
1829 id: "dot11_ssid_ietags",
1830 title: '<b class="k_padding_title">IE tags</b>',
1831 help: "IE tags in beacons define the network and the access point attributes; because dot11_keep_ietags is true, Kismet tracks these here.",
1835 field: "dot11.advertisedssid.ie_tag_content",
1837 id: "advertised_ietags",
1838 filterOnEmpty: true,
1841 render: function(_opts) {
1842 return '<table id="tagdump" border="0" />';
1845 draw: function(opts) {
1846 $('table#tagdump', opts['container']).empty();
1847 for (const ie in opts['value']) {
1848 const tag = opts['value'][ie];
1852 class: 'alternating'
1861 .html("<b>" + tag['dot11.ietag.number'] + "</b>")
1870 if (tag['dot11.ietag.oui'] != 0) {
1871 let oui = ("000000" + tag['dot11.ietag.oui'].toString(16)).substr(-6).replace(/(..)/g, '$1:').slice(0, -1);
1873 if (tag['dot11.ietag.oui_manuf'].length != 0)
1874 oui = oui + " (" + tag['dot11.ietag.oui_manuf'] + ")";
1876 $('#tagno', pretty_tag).append(
1882 if (tag['dot11.ietag.subtag'] >= 0) {
1883 $('#tagno', pretty_tag).append(
1885 .text("Subtag " + tag['dot11.ietag.subtag'])
1889 const hexdumps = pretty_hexdump(hexstr_to_bytes(tag['dot11.ietag.data']));
1891 for (const i in hexdumps) {
1892 $('#hexdump', pretty_tag).append(
1901 $('table#tagdump', opts['container']).append(pretty_tag);
1911 field: "dot11_bssts_similar",
1912 id: "bssts_similar_header",
1913 help: "Wi-Fi access points advertise a high-precision timestamp in beacons. Multiple devices with extremely similar timestamps are typically part of the same physical access point advertising multiple BSSIDs.",
1914 filter: function(opts) {
1916 return (Object.keys(opts['data']['kismet.device.base.related_devices']['dot11_bssts_similar']).length >= 1);
1921 title: '<b class="k_padding_title">Shared Hardware (Uptime)</b>'
1925 field: "kismet.device.base.related_devices/dot11_bssts_similar",
1926 id: "bssts_similar",
1928 filter: function(opts) {
1930 return (Object.keys(opts['data']['kismet.device.base.related_devices']['dot11_bssts_similar']).length >= 1);
1937 iterateTitle: function(opts) {
1938 const key = kismet.ObjectByString(opts['data'], opts['basekey']);
1940 return '<a id="' + key + '" class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Shared with ' + opts['data'] + '</a>';
1943 return '<a class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Shared with ' + opts['data'] + '</a>';
1945 draw: function(opts) {
1946 $('.expander', opts['cell']).simpleexpand();
1948 const key = kismet.ObjectByString(opts['data'], opts['basekey']);
1949 const alink = $('a#' + key, opts['cell']);
1950 $.get(local_uri_prefix + "devices/by-key/" + key + "/device.json")
1951 .done(function(data) {
1952 data = kismet.sanitizeObject(data);
1958 mac = kismet.censorMAC(data['kismet.device.base.macaddr']);
1964 ssid = kismet.CensorString(data['dot11.device']['dot11.device.last_beaconed_ssid_record']['dot11.advertisedssid.ssid']);
1969 if (ssid == "" || typeof(data) === 'undefined')
1970 ssid = "<i>n/a</i>";
1972 alink.html("Related to " + mac + " (" + ssid + ")");
1978 field: "dot11.client.bssid_key",
1979 title: "Access Point",
1980 draw: function(opts) {
1981 if (opts['key'] === '') {
1982 return "<i>No records for access point</i>";
1984 return '<a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + opts['base'] + '\')">View AP Details</a>';
1992 field: "dot11_wps_uuid_identical",
1993 id: "wps_uuid_identical_header",
1994 help: "Some devices change MAC addresses but retain the WPS UUID unique identifier. These devices have been detected using the same unique ID, which is extremely unlikely to randomly collide.",
1995 filter: function(opts) {
1997 return (Object.keys(opts['data']['kismet.device.base.related_devices']['dot11_uuid_e']).length >= 1);
2002 title: '<b class="k_padding_title">Shared Hardware (WPS UUID)</b>'
2006 field: "kismet.device.base.related_devices/dot11_uuid_e",
2007 id: "wps_uuid_identical",
2009 filter: function(opts) {
2011 return (Object.keys(opts['data']['kismet.device.base.related_devices']['dot11_uuid_e']).length >= 1);
2018 iterateTitle: function(opts) {
2019 const key = kismet.ObjectByString(opts['data'], opts['basekey']);
2021 return '<a id="' + key + '" class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Same WPS UUID as ' + opts['data'] + '</a>';
2024 return '<a class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Same WPS UUID as ' + opts['data'] + '</a>';
2026 draw: function(opts) {
2027 $('.expander', opts['cell']).simpleexpand();
2029 const key = kismet.ObjectByString(opts['data'], opts['basekey']);
2030 const alink = $('a#' + key, opts['cell']);
2031 $.get(local_uri_prefix + "devices/by-key/" + key + "/device.json")
2032 .done(function(data) {
2033 data = kismet.sanitizeObject(data);
2035 let mac = "<i>unknown</i>";
2038 mac = kismet.censorMAC(data['kismet.device.base.macaddr']);
2043 alink.html("Related to " + mac);
2049 field: "dot11.client.bssid_key",
2050 title: "Access Point",
2051 draw: function(opts) {
2052 if (opts['key'] === '') {
2053 return "<i>No records for access point</i>";
2055 return '<a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + opts['base'] + '\')">View AP Details</a>';
2063 field: "dot11.device/dot11.device.client_map",
2064 id: "client_behavior_header",
2065 help: "A Wi-Fi device may be a client of multiple networks over time, but can only be actively associated with a single access point at once. Clients typically are able to roam between access points with the same name (SSID).",
2066 filter: function(opts) {
2068 return (Object.keys(opts['data']['dot11.device']['dot11.device.client_map']).length >= 1);
2073 title: '<b class="k_padding_title">Wi-Fi Client Behavior</b>'
2077 field: "dot11.device/dot11.device.client_map",
2078 id: "client_behavior",
2080 filter: function(opts) {
2082 return (Object.keys(opts['data']['dot11.device']['dot11.device.client_map']).length >= 1);
2089 iterateTitle: function(opts) {
2090 const key = kismet.ObjectByString(opts['data'], opts['basekey'] + 'dot11.client.bssid_key');
2092 return '<a id="dot11_bssid_client_' + key + '" class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Client of ' + kismet.censorMAC(opts['index']) + '</a>';
2095 return '<a class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Client of ' + kismet.censorMAC(opts['index']) + '</a>';
2097 draw: function(opts) {
2098 $('.expander', opts['cell']).simpleexpand();
2103 field: "dot11.client.bssid_key",
2104 title: "Access Point",
2105 draw: function(opts) {
2106 if (opts['key'] === '') {
2107 return "<i>No records for access point</i>";
2109 return '<a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + opts['value'] + '\')">View AP Details</a>';
2114 field: "dot11.client.bssid",
2116 draw: function(opts) {
2117 return kismet.censorMAC(opts['value']);
2121 field: "dot11.client.bssid_key",
2123 draw: function(opts) {
2124 $.get(local_uri_prefix + "devices/by-key/" + opts['value'] +
2125 "/device.json/kismet.device.base.commonname")
2127 opts['container'].html('<i>None</i>');
2129 .done(function(clidata) {
2130 clidata = kismet.sanitizeObject(clidata);
2132 if (clidata === '' || clidata === '""') {
2133 opts['container'].html('<i>None</i>');
2135 opts['container'].html(kismet.censorMAC(clidata));
2141 field: "dot11.client.bssid_key",
2143 draw: function(opts) {
2144 $.get(local_uri_prefix + "devices/by-key/" + opts['value'] +
2145 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.ssid')
2147 opts['container'].html('<i>Unknown</i>');
2149 .done(function(clidata) {
2150 clidata = kismet.sanitizeObject(clidata);
2152 if (clidata === '' || clidata === '""') {
2153 opts['container'].html('<i>Unknown</i>');
2155 opts['container'].html(clidata);
2161 field: "dot11.client.first_time",
2162 title: "First Connected",
2163 draw: kismet_ui.RenderTrimmedTime,
2166 field: "dot11.client.last_time",
2167 title: "Last Connected",
2168 draw: kismet_ui.RenderTrimmedTime,
2171 field: "dot11.client.datasize",
2173 draw: kismet_ui.RenderHumanSize,
2176 field: "dot11.client.datasize_retry",
2177 title: "Retried Data",
2178 draw: kismet_ui.RenderHumanSize,
2181 // Set the field to be the host, and filter on it, but also
2183 field: "dot11.client.dhcp_host",
2186 filterOnEmpty: true,
2187 help: "If a DHCP data packet is seen, the requested hostname and the operating system / vendor of the DHCP client can be extracted.",
2190 field: "dot11.client.dhcp_host",
2191 title: "DHCP Hostname",
2192 empty: "<i>Unknown</i>"
2195 field: "dot11.client.dhcp_vendor",
2196 title: "DHCP Vendor",
2197 empty: "<i>Unknown</i>"
2202 field: "dot11.client.eap_identity",
2203 title: "EAP Identity",
2204 filterOnEmpty: true,
2205 help: "If an EAP handshake (part of joining a WPA-Enterprise protected network) is observed, Kismet may be able to extract the EAP identity of a client; this may represent the users login, or it may be empty or 'anonymouse' when joining a network with a phase-2 authentication, like WPA-PEAP",
2208 field: "dot11.client.cdp_device",
2211 filterOnEmpty: true,
2212 help: "Clients bridged to a wired network may leak CDP (Cisco Discovery Protocol) packets, which can disclose information about the internal wired network.",
2215 field: "dot11.client.cdp_device",
2219 field: "dot11.client.cdp_port",
2221 empty: "<i>Unknown</i>"
2226 field: "dot11.client.ipdata",
2228 filter: function(opts) {
2229 if (kismet.ObjectByString(opts['data'], opts['basekey'] + 'dot11.client.ipdata') == 0)
2232 return (kismet.ObjectByString(opts['data'], opts['basekey'] + 'dot11.client.ipdata/kismet.common.ipdata.address') != 0);
2234 help: "Kismet will attempt to derive the IP ranges in use on a network, either from observed traffic or from DHCP server responses.",
2237 field: "dot11.client.ipdata/kismet.common.ipdata.address",
2238 title: "IP Address",
2241 field: "dot11.client.ipdata/kismet.common.ipdata.netmask",
2243 zero: "<i>Unknown</i>"
2246 field: "dot11.client.ipdata/kismet.common.ipdata.gateway",
2248 zero: "<i>Unknown</i>"
2257 field: "dot11.device/dot11.device.associated_client_map",
2258 id: "client_list_header",
2259 filter: function(opts) {
2261 return (Object.keys(opts['data']['dot11.device']['dot11.device.associated_client_map']).length >= 1);
2266 title: '<b class="k_padding_title">Associated Clients</b>',
2267 help: "An access point typically will have clients associated with it. These client devices can either be wireless devices connected to the access point, or they can be bridged, wired devices on the network the access point is connected to.",
2271 field: "dot11.device/dot11.device.associated_client_map",
2274 filter: function(opts) {
2276 return (Object.keys(opts['data']['dot11.device']['dot11.device.associated_client_map']).length >= 1);
2283 iterateTitle: function(opts) {
2284 return '<a id="associated_client_expander_' + opts['base'] + '" class="expander collapsed" href="#" data-expander-target="#' + opts['containerid'] + '">Client ' + kismet.censorMAC(opts['index']) + '</a>';
2286 draw: function(opts) {
2287 $('.expander', opts['cell']).simpleexpand();
2291 // Dummy field to get us a nested area since we don't have
2292 // a real field in the client list since it's just a key-val
2293 // not a nested object
2297 draw: function(opts) {
2298 return `<div id="associated_client_content_${opts['base']}">`;
2307 finalize: function(data, _target, _options, _storage) {
2308 const apkey = data['kismet.device.base.macaddr'];
2313 Object.values(data['dot11.device']['dot11.device.associated_client_map']).forEach(device => combokeys[device] = 1);
2319 Object.values(data['dot11.device']['dot11.device.client_map']).forEach(device => combokeys[device['dot11.client.bssid_key']] = 1);
2325 devices: Object.keys(combokeys),
2327 'kismet.device.base.macaddr',
2328 'kismet.device.base.key',
2329 'kismet.device.base.type',
2330 'kismet.device.base.commonname',
2331 'kismet.device.base.manuf',
2332 'dot11.device/dot11.device.client_map',
2333 ['dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.ssid', 'dot11.advertisedssid.lastssid'],
2337 const postdata = `json=${encodeURIComponent(JSON.stringify(param))}`;
2339 $.post(`${local_uri_prefix}devices/multikey/as-object/devices.json`, postdata, "json")
2340 .done(function(rdevs) {
2341 const devs = kismet.sanitizeObject(rdevs);
2343 let client_devs = [];
2346 Object.values(data['dot11.device']['dot11.device.client_map']).forEach(device => client_devs.push(device['dot11.client.bssid_key']));
2351 client_devs.forEach(function(v) {
2355 const dev = devs[v];
2357 let lastssid = dev['dot11.advertisedssid.lastssid'];
2359 if (typeof(lastssid) !== 'string')
2360 lastssid = `<i>None</i>`;
2361 else if (lastssid.replace(/\s/g, '').length == 0)
2362 lastssid = `<i>Cloaked / Empty (${lastssid.length} characters)</i>`;
2364 $(`a#dot11_bssid_client_${v}`).html(`Client of ${kismet.censorMAC(dev['kismet.device.base.macaddr'])} (${lastssid})`);
2370 client_devs = Object.values(data['dot11.device']['dot11.device.associated_client_map']);
2375 client_devs.forEach(function(v) {
2379 const dev = devs[v];
2381 $(`#associated_client_expander_${v}`).html(`${kismet.ExtractDeviceName(dev)}`);
2383 $(`#associated_client_content_${v}`).devicedata(dev, {
2387 field: "kismet.device.base.key",
2388 title: "Client Info",
2389 draw: function(opts) {
2390 return '<a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + opts['data']['kismet.device.base.key'] + '\')">View Client Details</a>';
2394 field: "kismet.device.base.commonname",
2396 filterOnEmpty: "true",
2397 empty: "<i>None</i>",
2398 draw: function(opts) {
2399 return kismet.censorMAC(opts['value']);
2403 field: "kismet.device.base.type",
2405 empty: "<i>Unknown</i>"
2409 field: "kismet.device.base.manuf",
2410 title: "Manufacturer",
2411 empty: "<i>Unknown</i>"
2414 field: "dot11.device.client_map[" + apkey + "]/dot11.client.first_time",
2415 title: "First Connected",
2416 draw: kismet_ui.RenderTrimmedTime,
2419 field: "dot11.device.client_map[" + apkey + "]/dot11.client.last_time",
2420 title: "Last Connected",
2421 draw: kismet_ui.RenderTrimmedTime,
2424 field: "dot11.device.client_map[" + apkey + "]/dot11.client.datasize",
2426 draw: kismet_ui.RenderHumanSize,
2429 field: "dot11.device.client_map[" + apkey + "]/dot11.client.datasize_retry",
2430 title: "Retried Data",
2431 draw: kismet_ui.RenderHumanSize,
2441 var ssid_status_element;
2443 var SsidColumns =[];
2445 export const AddSsidColumn = (id, options) => {
2448 sTitle: options.sTitle,
2453 if ('field' in options) {
2454 coldef.field = options.field;
2457 if ('fields' in options) {
2458 coldef.fields = options.fields;
2461 if ('description' in options) {
2462 coldef.description = options.description;
2465 if ('name' in options) {
2466 coldef.name = options.name;
2469 if ('orderable' in options) {
2470 coldef.bSortable = options.orderable;
2473 if ('visible' in options) {
2474 coldef.bVisible = options.visible;
2476 coldef.bVisible = true;
2479 if ('selectable' in options) {
2480 coldef.user_selectable = options.selectable;
2482 coldef.user_selectable = true;
2485 if ('searchable' in options) {
2486 coldef.bSearchable = options.searchable;
2489 if ('width' in options) {
2490 coldef.width = options.width;
2493 if ('sClass' in options) {
2494 coldef.sClass = options.sClass;
2498 if (typeof(coldef.field) === 'string') {
2499 var fs = coldef.field.split('/');
2500 f = fs[fs.length - 1];
2501 } else if (Array.isArray(coldef.field)) {
2502 f = coldef.field[1];
2505 coldef.mData = function(row, type, set) {
2506 return kismet.ObjectByString(row, f);
2509 if ('renderfunc' in options) {
2510 coldef.mRender = options.renderfunc;
2513 if ('drawfunc' in options) {
2514 coldef.kismetdrawfunc = options.drawfunc;
2517 SsidColumns.push(coldef);
2520 export const GetSsidColumns = (showall = false) => {
2521 var ret = new Array();
2523 var order = kismet.getStorage('kismet.ssidtable.columns', []);
2525 if (order.length == 0) {
2526 // sort invisible columns to the end
2527 for (var i in SsidColumns) {
2528 if (!SsidColumns[i].bVisible)
2530 ret.push(SsidColumns[i]);
2533 for (var i in SsidColumns) {
2534 if (SsidColumns[i].bVisible)
2536 ret.push(SsidColumns[i]);
2542 for (var oi in order) {
2548 var sc = SsidColumns.find(function(e, i, a) {
2549 if (e.kismetId === o.id)
2554 if (sc != undefined && sc.user_selectable) {
2560 // Fallback if no columns were selected somehow
2561 if (ret.length == 0) {
2562 // sort invisible columns to the end
2563 for (var i in SsidColumns) {
2564 if (!SsidColumns[i].bVisible)
2566 ret.push(SsidColumns[i]);
2569 for (var i in SsidColumns) {
2570 if (SsidColumns[i].bVisible)
2572 ret.push(SsidColumns[i]);
2579 for (var sci in SsidColumns) {
2580 var sc = SsidColumns[sci];
2582 var rc = ret.find(function(e, i, a) {
2583 if (e.kismetId === sc.kismetId)
2588 if (rc == undefined) {
2589 sc.bVisible = false;
2597 for (var sci in SsidColumns) {
2598 if (!SsidColumns[sci].user_selectable) {
2599 ret.push(SsidColumns[sci]);
2606 export const GetSsidColumnMap = (columns) => {
2609 for (var ci in columns) {
2610 var fields = new Array();
2612 if ('field' in columns[ci])
2613 fields.push(columns[ci]['field']);
2615 if ('fields' in columns[ci])
2616 fields.push.apply(fields, columns[ci]['fields']);
2624 export const GetSsidFields = (selected) => {
2625 var rawret = new Array();
2626 var cols = GetSsidColumns();
2628 for (var i in cols) {
2629 if ('field' in cols[i])
2630 rawret.push(cols[i]['field']);
2632 if ('fields' in cols[i])
2633 rawret.push.apply(rawret, cols[i]['fields']);
2637 var ret = rawret.filter(function(item, pos, self) {
2638 return self.indexOf(item) == pos;
2646 function ScheduleSsidSummary() {
2648 if (kismet_ui.window_visible && ssid_element.is(":visible")) {
2649 var dt = ssid_element.DataTable();
2651 // Save the state. We can't use proper state saving because it seems to break
2652 // the table position
2653 kismet.putStorage('kismet.base.ssidtable.order', JSON.stringify(dt.order()));
2654 kismet.putStorage('kismet.base.ssidtable.search', JSON.stringify(dt.search()));
2656 // Snapshot where we are, because the 'don't reset page' in ajax.reload
2657 // DOES still reset the scroll position
2659 'top': $(dt.settings()[0].nScrollBody).scrollTop(),
2660 'left': $(dt.settings()[0].nScrollBody).scrollLeft()
2662 dt.ajax.reload(function(d) {
2663 // Restore our scroll position
2664 $(dt.settings()[0].nScrollBody).scrollTop( prev_pos.top );
2665 $(dt.settings()[0].nScrollBody).scrollLeft( prev_pos.left );
2673 // Set our timer outside of the datatable callback so that we get called even
2674 // if the ajax load fails
2675 ssidTid = setTimeout(ScheduleSsidSummary, 2000);
2678 function InitializeSsidTable() {
2679 var cols = GetSsidColumns();
2680 var colmap = GetSsidColumnMap(cols);
2681 var fields = GetSsidFields();
2689 if ($.fn.dataTable.isDataTable(ssid_element)) {
2690 ssid_element.DataTable().destroy();
2691 ssid_element.empty();
2695 .on('xhr.dt', function(e, settings, json, xhr) {
2696 json = kismet.sanitizeObject(json);
2699 if (json['recordsFiltered'] != json['recordsTotal'])
2700 ssid_status_element.html(`${json['recordsTotal']} SSIDs (${json['recordsFiltered']} shown after filter)`);
2702 ssid_status_element.html(`${json['recordsTotal']} SSIDs`);
2716 lengthChange: false,
2718 loadingIndicator: true,
2721 url: local_uri_prefix + "phy/phy80211/ssids/views/ssids.json",
2723 json: JSON.stringify(json)
2730 { className: "dt_td", targets: "_all" },
2732 order: [ [ 0, "desc" ] ],
2733 createdRow: function(row, data, index) {
2734 row.id = data['dot11.ssidgroup.hash'];
2736 drawCallback: function(settings) {
2737 var dt = this.api();
2741 }).every(function(rowIdx, tableLoop, rowLoop) {
2742 for (var c in SsidColumns) {
2743 var col = SsidColumns[c];
2745 if (!('kismetdrawfunc') in col)
2749 col.kismetdrawfunc(col, dt, this);
2758 var ssid_dt = ssid_element.DataTable();
2760 // Restore the order
2761 var saved_order = kismet.getStorage('kismet.base.ssidtable.order', "");
2762 if (saved_order !== "")
2763 ssid_dt.order(JSON.parse(saved_order));
2765 // Restore the search
2766 var saved_search = kismet.getStorage('kismet.base.ssidtable.search', "");
2767 if (saved_search !== "")
2768 ssid_dt.search(JSON.parse(saved_search));
2770 // Set an onclick handler to spawn the device details dialog
2771 $('tbody', ssid_element).on('click', 'tr', function () {
2772 SsidDetailWindow(this.id);
2775 $('tbody', ssid_element)
2776 .on( 'mouseenter', 'td', function () {
2777 var ssid_dt = ssid_element.DataTable();
2779 if (typeof(ssid_dt.cell(this).index()) === 'Undefined')
2782 var colIdx = ssid_dt.cell(this).index().column;
2783 var rowIdx = ssid_dt.cell(this).index().row;
2785 // Remove from all cells
2786 $(ssid_dt.cells().nodes()).removeClass('kismet-highlight');
2787 // Highlight the td in this row
2788 $('td', ssid_dt.row(rowIdx).nodes()).addClass('kismet-highlight');
2794 kismet_ui_tabpane.AddTab({
2797 createCallback: function(div) {
2800 class: 'resize_wrapper',
2805 class: 'stripe hover nowrap',
2813 style: 'padding-bottom: 10px;',
2817 ssid_element = $('#ssids', div);
2818 ssid_status_element = $('#ssids_status', div);
2820 InitializeSsidTable();
2821 ScheduleSsidSummary();
2826 AddSsidColumn('col_ssid', {
2828 field: 'dot11.ssidgroup.ssid',
2831 renderfunc: function(d, t, r, m) {
2833 return "<i>Cloaked or Empty SSID</i>";
2834 else if (/^ +$/.test(d))
2835 return "<i>Blank SSID</i>";
2840 AddSsidColumn('col_ssid_len', {
2842 field: 'dot11.ssidgroup.ssid_len',
2843 name: 'SSID Length',
2845 sClass: "dt-body-right",
2848 AddSsidColumn('column_time', {
2849 sTitle: 'Last Seen',
2850 field: 'dot11.ssidgroup.last_time',
2851 description: 'Last-seen time',
2853 renderfunc: function(d, t, r, m) {
2854 return kismet_ui_base.renderLastTime(d, t, r, m);
2861 AddSsidColumn('column_first_time', {
2862 sTitle: 'First Seen',
2863 field: 'dot11.ssidgroup.first_time',
2864 description: 'First-seen time',
2865 renderfunc: function(d, t, r, m) {
2866 return kismet_ui_base.renderLastTime(d, t, r, m);
2873 AddSsidColumn('column_crypt', {
2874 sTitle: 'Encryption',
2875 field: 'dot11.ssidgroup.crypt_set',
2876 description: 'Encryption',
2877 renderfunc: function(d, t, r, m) {
2878 return CryptToHumanReadable(d);
2884 AddSsidColumn('column_probing', {
2885 sTitle: '# Probing',
2886 field: 'dot11.ssidgroup.probing_devices_len',
2887 description: 'Count of probing devices',
2890 sClass: "dt-body-right",
2893 AddSsidColumn('column_responding', {
2894 sTitle: '# Responding',
2895 field: 'dot11.ssidgroup.responding_devices_len',
2896 description: 'Count of responding devices',
2899 sClass: "dt-body-right",
2902 AddSsidColumn('column_advertising', {
2903 sTitle: '# Advertising',
2904 field: 'dot11.ssidgroup.advertising_devices_len',
2905 description: 'Count of advertising devices',
2908 sClass: "dt-body-right",
2911 AddSsidColumn('column_hash', {
2913 field: 'dot11.ssidgroup.hash',
2914 description: 'Hash',
2922 var SsidDetails = new Array();
2924 const AddSsidDetail = function(id, title, pos, options) {
2925 kismet_ui.AddDetail(SsidDetails, id, title, pos, options);
2928 export const SsidDetailWindow = (key) => {
2929 kismet_ui.DetailWindow(key, "SSID Details",
2934 function(panel, options) {
2935 var content = panel.content;
2937 panel.active = true;
2939 window['storage_ssid_' + key] = {};
2940 window['storage_ssid_' + key]['foobar'] = 'bar';
2942 panel.updater = function() {
2943 if (kismet_ui.window_visible) {
2944 $.get(local_uri_prefix + "phy/phy80211/ssids/by-hash/" + key + "/ssid.json")
2945 .done(function(fulldata) {
2946 fulldata = kismet.sanitizeObject(fulldata);
2948 $('.loadoops', panel.content).hide();
2950 panel.headerTitle("SSID: " + fulldata['dot11.ssidgroup.ssid']);
2952 var accordion = $('div#accordion', content);
2954 if (accordion.length == 0) {
2955 accordion = $('<div />', {
2959 content.append(accordion);
2962 var detailslist = SsidDetails;
2964 for (var dii in detailslist) {
2965 var di = detailslist[dii];
2968 if ('filter' in di.options &&
2969 typeof(di.options.filter) === 'function') {
2970 if (di.options.filter(fulldata) == false) {
2975 var vheader = $('h3#header_' + di.id, accordion);
2977 if (vheader.length == 0) {
2978 vheader = $('<h3>', {
2979 id: "header_" + di.id,
2983 accordion.append(vheader);
2986 var vcontent = $('div#' + di.id, accordion);
2988 if (vcontent.length == 0) {
2989 vcontent = $('<div>', {
2992 accordion.append(vcontent);
2995 // Do we have pre-rendered content?
2996 if ('render' in di.options &&
2997 typeof(di.options.render) === 'function') {
2998 vcontent.html(di.options.render(fulldata));
3001 if ('draw' in di.options && typeof(di.options.draw) === 'function') {
3002 di.options.draw(fulldata, vcontent, options, 'storage_ssid_' + key);
3005 if ('finalize' in di.options &&
3006 typeof(di.options.finalize) === 'function') {
3007 di.options.finalize(fulldata, vcontent, options, 'storage_ssid_' + key);
3010 accordion.accordion({ heightStyle: 'fill' });
3012 .fail(function(jqxhr, texterror) {
3013 content.html("<div class=\"loadoops\" style=\"padding: 10px;\"><h1>Oops!</h1><p>An error occurred loading ssid details for key <code>" + key +
3014 "</code>: HTTP code <code>" + jqxhr.status + "</code>, " + texterror + "</div>");
3016 .always(function() {
3017 panel.timerid = setTimeout(function() { panel.updater(); }, 1000);
3020 panel.timerid = setTimeout(function() { panel.updater(); }, 1000);
3028 function(panel, options) {
3029 clearTimeout(panel.timerid);
3030 panel.active = false;
3031 window['storage_ssid_' + key] = {};
3035 /* Custom device details for dot11 data */
3036 AddSsidDetail("ssid", "Wi-Fi (802.11) SSIDs", 0, {
3037 draw: function(data, target, options, storage) {
3038 target.devicedata(data, {
3039 "id": "ssiddetails",
3042 field: 'dot11.ssidgroup.ssid',
3045 draw: function(opts) {
3046 if (typeof(opts['value']) === 'undefined')
3047 return '<i>None</i>';
3048 if (opts['value'].replace(/\s/g, '').length == 0)
3049 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
3051 return `${opts['value']} <i>(${data['dot11.ssidgroup.ssid_len']} characters)</i>`;
3053 help: "SSID advertised or probed by one or more devices",
3056 field: "dot11.ssidgroup.first_time",
3057 title: "First Seen",
3059 draw: kismet_ui.RenderTrimmedTime,
3062 field: "dot11.ssidgroup.last_time",
3065 draw: kismet_ui.RenderTrimmedTime,
3068 field: "dot11.ssidgroup.crypt_set",
3070 title: "Encryption",
3071 draw: function(opts) {
3072 return CryptToHumanReadable(opts['value']);
3074 help: "Encryption at the Wi-Fi layer (open, WEP, and WPA) is defined by the beacon sent by the access point advertising the network. Layer 3 encryption (such as VPNs) is added later and is not advertised as part of the network itself.",
3080 field: "advertising_meta",
3081 id: "advertising header",
3082 filter: function(opts) {
3084 return (Object.keys(opts['data']['dot11.ssidgroup.advertising_devices']).length >= 1);
3089 title: '<b class="k_padding_title">Advertising APs</b>',
3090 help: "Advertising access points have sent a beacon packet with this SSID.",
3094 field: "dot11.ssidgroup.advertising_devices",
3095 id: "advertising_list",
3097 filter: function(opts) {
3099 return (Object.keys(opts['data']['dot11.ssidgroup.advertising_devices']).length >= 1);
3106 iterateTitle: function(opts) {
3107 return '<a id="ssid_expander_advertising_' + opts['base'] + '" class="ssid_expander_advertising expander collapsed" href="#" data-expander-target="#' + opts['containerid'] + '">Access point ' + opts['index'] + '</a>';
3109 draw: function(opts) {
3110 var tb = $('.expander', opts['cell']).simpleexpand();
3114 // Dummy field to get us a nested area since we don't have
3115 // a real field in the client list since it's just a key-val
3116 // not a nested object
3120 draw: function(opts) {
3121 return `<div class="ssid_content_advertising" id="ssid_content_advertising_${opts['base']}">`;
3130 field: "responding_meta",
3131 id: "responding header",
3132 filter: function(opts) {
3134 return (Object.keys(opts['data']['dot11.ssidgroup.responding_devices']).length >= 1);
3139 title: '<b class="k_padding_title">Responding APs</b>',
3140 help: "Responding access points have sent a probe response with this SSID.",
3144 field: "dot11.ssidgroup.responding_devices",
3145 id: "responding_list",
3147 filter: function(opts) {
3149 return (Object.keys(opts['data']['dot11.ssidgroup.responding_devices']).length >= 1);
3156 iterateTitle: function(opts) {
3157 return '<a id="ssid_expander_responding_' + opts['base'] + '" class="ssid_expander_responding expander collapsed" href="#" data-expander-target="#' + opts['containerid'] + '">Access point ' + opts['index'] + '</a>';
3159 draw: function(opts) {
3160 var tb = $('.expander', opts['cell']).simpleexpand();
3164 // Dummy field to get us a nested area since we don't have
3165 // a real field in the client list since it's just a key-val
3166 // not a nested object
3170 draw: function(opts) {
3171 return `<div class="ssid_content_responding" id="ssid_content_responding_${opts['base']}">`;
3180 field: "probing_meta",
3181 id: "probing header",
3182 filter: function(opts) {
3184 return (Object.keys(opts['data']['dot11.ssidgroup.probing_devices']).length >= 1);
3189 title: '<b class="k_padding_title">Probing devices</b>',
3190 help: "Probing devices have sent a probe request or association request with this SSID",
3194 field: "dot11.ssidgroup.probing_devices",
3197 filter: function(opts) {
3199 return (Object.keys(opts['data']['dot11.ssidgroup.probing_devices']).length >= 1);
3206 iterateTitle: function(opts) {
3207 return '<a id="ssid_expander_probing_' + opts['base'] + '" class="ssid_expander_probing expander collapsed" href="#" data-expander-target="#' + opts['containerid'] + '">Wi-Fi Device ' + opts['index'] + '</a>';
3209 draw: function(opts) {
3210 var tb = $('.expander', opts['cell']).simpleexpand();
3214 // Dummy field to get us a nested area since we don't have
3215 // a real field in the client list since it's just a key-val
3216 // not a nested object
3220 draw: function(opts) {
3221 return `<div class="ssid_content_probing" id="ssid_content_probing_${opts['base']}">`;
3231 finalize: function(data, target, options, storage) {
3234 data['dot11.ssidgroup.advertising_devices'].forEach(device => combokeys[device] = 1);
3235 data['dot11.ssidgroup.probing_devices'].forEach(device => combokeys[device] = 1);
3236 data['dot11.ssidgroup.responding_devices'].forEach(device => combokeys[device] = 1);
3239 devices: Object.keys(combokeys),
3241 'kismet.device.base.macaddr',
3242 'kismet.device.base.key',
3243 'kismet.device.base.type',
3244 'kismet.device.base.commonname',
3245 'kismet.device.base.manuf',
3246 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.ssid',
3247 'dot11.device/dot11.device.advertised_ssid_map',
3248 'dot11.device/dot11.device.responded_ssid_map',
3252 var postdata = `json=${encodeURIComponent(JSON.stringify(param))}`;
3254 $.post(`${local_uri_prefix}devices/multikey/as-object/devices.json`, postdata, "json")
3255 .done(function(aggcli) {
3256 aggcli = kismet.sanitizeObject(aggcli);
3258 data['dot11.ssidgroup.advertising_devices'].forEach(function(v) {
3262 var dev = aggcli[v];
3267 dev['dot11.device.advertised_ssid_map'].forEach(function (s) {
3268 if (s['dot11.advertisedssid.ssid_hash'] == data['dot11.ssidgroup.hash']) {
3278 crypttxt = CryptToHumanReadable(devssid['dot11.advertisedssid.crypt_set']);
3283 var titlehtml = `${kismet.ExtractDeviceName(dev)} - ${dev['kismet.device.base.macaddr']}`;
3285 if (crypttxt != null)
3286 titlehtml = `${titlehtml} - ${crypttxt}`;
3288 titlehtml = kismet.censorMAC(titlehtml);
3290 $(`#ssid_expander_advertising_${v}`).html(titlehtml);
3292 $(`#ssid_content_advertising_${v}`).devicedata(dev, {
3293 id: "ssid_adv_data",
3296 field: 'kismet.device.base.key',
3297 title: "Advertising Device",
3298 draw: function(opts) {
3299 return `<a href="#" onclick="kismet_ui.DeviceDetailWindow('${opts['data']['kismet.device.base.key']}')">View Device Details</a>`;
3303 field: "kismet.device.base.macaddr",
3305 draw: function(opts) {
3306 return kismet.censorMAC(`${opts['data']['kismet.device.base.macaddr']} (${opts['data']['kismet.device.base.manuf']})`);
3310 field: 'kismet.device.base.commonname',
3313 empty: "<i>None</i>",
3314 draw: function(opts) {
3315 return kismet.censorMAC(opts['value']);
3319 field: 'kismet.device.base.type',
3322 empty: "<i>Unknown</i>",
3325 field: 'meta_advertised_crypto',
3326 title: "Advertised encryption",
3328 draw: function(opts) {
3330 return CryptToHumanReadable(devssid['dot11.advertisedssid.crypt_set']);
3332 return '<i>Unknown</i>';
3335 help: "The encryption should be the same across all APs advertising the same SSID in the same area, howevever each AP advertises this information independently.",
3338 field: 'meta_advertised_firsttime',
3339 title: "First advertised",
3341 draw: function(opts) {
3343 return kismet_ui.RenderTrimmedTime({value: devssid['dot11.advertisedssid.first_time']});
3345 return '<i>Unknown</i>';
3350 field: 'meta_advertised_lasttime',
3351 title: "Last advertised",
3353 draw: function(opts) {
3355 return kismet_ui.RenderTrimmedTime({value: devssid['dot11.advertisedssid.last_time']});
3357 return '<i>Unknown</i>';
3362 field: 'dot11.advertisedssid.ssid',
3363 title: "Last advertised SSID",
3365 draw: function(opts) {
3366 if (typeof(opts['value']) === 'undefined')
3367 return '<i>None</i>';
3368 if (opts['value'].replace(/\s/g, '').length == 0)
3369 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
3371 return opts['value'];
3379 data['dot11.ssidgroup.responding_devices'].forEach(function(v) {
3383 var dev = aggcli[v];
3388 dev['dot11.device.responded_ssid_map'].forEach(function (s) {
3389 if (s['dot11.advertisedssid.ssid_hash'] == data['dot11.ssidgroup.hash']) {
3399 crypttxt = CryptToHumanReadable(devssid['dot11.advertisedssid.crypt_set']);
3404 var titlehtml = `${dev['kismet.device.base.commonname']} - ${dev['kismet.device.base.macaddr']}`;
3406 if (crypttxt != null)
3407 titlehtml = `${titlehtml} - ${crypttxt}`;
3409 titlehtml = kismet.censorMAC(titlehtml);
3411 $(`#ssid_expander_responding_${v}`).html(titlehtml);
3413 $(`#ssid_content_responding_${v}`).devicedata(dev, {
3414 id: "ssid_adv_data",
3417 field: 'kismet.device.base.key',
3418 title: "Responding Device",
3419 draw: function(opts) {
3420 return `<a href="#" onclick="kismet_ui.DeviceDetailWindow('${opts['data']['kismet.device.base.key']}')">View Device Details</a>`;
3424 field: "kismet.device.base.macaddr",
3426 draw: function(opts) {
3427 return kismet.censorMAC(`${opts['data']['kismet.device.base.macaddr']} (${opts['data']['kismet.device.base.manuf']})`);
3432 field: 'kismet.device.base.commonname',
3434 empty: "<i>None</i>",
3435 draw: function(opts) {
3436 return kismet.censorMAC(opts['value']);
3441 field: 'kismet.device.base.type',
3443 empty: "<i>Unknown</i>",
3446 field: 'meta_advertised_crypto',
3447 title: "Advertised encryption",
3449 draw: function(opts) {
3451 return CryptToHumanReadable(devssid['dot11.advertisedssid.crypt_set']);
3453 return '<i>Unknown</i>';
3456 help: "The encryption should be the same across all APs advertising the same SSID in the same area, howevever each AP advertises this information independently.",
3459 field: 'meta_advertised_firsttime',
3460 title: "First responded",
3462 draw: function(opts) {
3464 return kismet_ui.RenderTrimmedTime({value: devssid['dot11.advertisedssid.first_time']});
3466 return '<i>Unknown</i>';
3471 field: 'meta_advertised_lasttime',
3472 title: "Last responded",
3474 draw: function(opts) {
3476 return kismet_ui.RenderTrimmedTime({value: devssid['dot11.advertisedssid.last_time']});
3478 return '<i>Unknown</i>';
3484 field: 'dot11.advertisedssid.ssid',
3485 title: "Last advertised SSID",
3486 draw: function(opts) {
3487 if (typeof(opts['value']) === 'undefined')
3488 return '<i>None</i>';
3489 if (opts['value'].replace(/\s/g, '').length == 0)
3490 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
3492 return opts['value'];
3500 data['dot11.ssidgroup.probing_devices'].forEach(function(v) {
3504 var dev = aggcli[v];
3506 $(`#ssid_expander_probing_${v}`).html(kismet.censorMAC(`${dev['kismet.device.base.commonname']} - ${dev['kismet.device.base.macaddr']}`));
3508 $(`#ssid_content_probing_${v}`).devicedata(dev, {
3509 id: "ssid_adv_data",
3512 field: 'kismet.device.base.key',
3513 title: "Probing Device",
3514 draw: function(opts) {
3515 return `<a href="#" onclick="kismet_ui.DeviceDetailWindow('${opts['data']['kismet.device.base.key']}')">View Device Details</a>`;
3519 field: "kismet.device.base.macaddr",
3521 draw: function(opts) {
3522 return kismet.censorMAC(`${opts['data']['kismet.device.base.macaddr']} (${opts['data']['kismet.device.base.manuf']})`);
3526 field: 'kismet.device.base.commonname',
3528 empty: "<i>None</i>",
3529 draw: function(opts) {
3530 return kismet.censorMAC(opts['value']);
3534 field: 'kismet.device.base.type',
3536 empty: "<i>Unknown</i>",