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 (var 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 < 2484)
230 return (in_freq - 2407) / 5;
231 else if (in_freq >= 4910 && in_freq <= 4980)
232 return (in_freq - 4000) / 5;
233 else if (in_freq <= 45000)
234 return (in_freq - 5000) / 5;
235 else if (in_freq >= 58320 && in_freq <= 64800)
236 return (in_freq - 56160) / 2160;
238 return kismet.HumanReadableFrequency(in_freq);
241 /* Highlight WPA handshakes */
242 kismet_ui.AddDeviceRowHighlight({
243 name: "WPA Handshake",
244 description: "Network contains a complete WPA handshake",
246 defaultcolor: "#F00",
249 'dot11.device/dot11.device.wpa_handshake_list'
251 selector: function(data) {
253 for (const dev in data['dot11.device']['dot11.device.wpa_handshake_list']) {
256 for (const p of data['dot11.device']['dot11.device.wpa_handshake_list'][dev]) {
257 pmask = pmask | (1 << p['dot11.eapol.message_num']);
259 if ((pmask & 0x06) == 0x06 || (pmask & 0x0C) == 0x0C)
272 /* Highlight WPA RSN PMKID */
273 kismet_ui.AddDeviceRowHighlight({
275 description: "Network contains a RSN PMKID packet",
277 defaultcolor: "#F55",
280 'dot11.device/dot11.device.pmkid_packet'
282 selector: function(data) {
284 return 'dot11.device.pmkid_packet' in data && data['dot11.device.pmkid_packet'] != 0;
291 kismet_ui.AddDeviceRowHighlight({
292 name: "Wi-Fi Device",
293 description: "Highlight all Wi-Fi devices",
295 defaultcolor: "#99ff99",
296 defaultenable: false,
298 'kismet.device.base.phyname',
300 selector: function(data) {
301 return ('kismet.device.base.phyname' in data && data['kismet.device.base.phyname'] === 'IEEE802.11');
305 kismet_ui.AddDeviceColumn('wifi_clients', {
307 field: 'dot11.device/dot11.device.num_associated_clients',
308 description: 'Related Wi-Fi devices (associated and bridged)',
310 sClass: "dt-body-right",
313 kismet_ui.AddDeviceColumn('wifi_last_bssid', {
315 field: 'dot11.device/dot11.device.last_bssid',
316 description: 'Last associated BSSID',
320 renderfunc: function(d, t, r, m) {
328 return kismet.censorMAC(d);
332 kismet_ui.AddDeviceColumn('wifi_bss_uptime', {
334 field: 'dot11.device/dot11.device.bss_timestamp',
335 description: 'Estimated device uptime (from BSS timestamp)',
339 visible: false, // Off by default
340 renderfunc: function(d, t, r, m) {
341 return kismet_ui_base.renderUsecTime(d, t, r, m);
345 // Hidden column to fetch qbss state
346 kismet_ui.AddDeviceColumn('column_qbss_hidden', {
347 sTitle: 'qbss_available',
348 field: 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.dot11e_qbss',
349 name: 'qbss_available',
356 kismet_ui.AddDeviceColumn('wifi_qbss_usage', {
357 sTitle: 'QBSS Chan Usage',
358 // field: 'dot11.device/dot11.device.bss_timestamp',
359 field: 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.dot11e_channel_utilization_perc',
360 description: '802.11e QBSS channel utilization',
366 renderfunc: function(d, t, r, m) {
369 if (r['dot11.advertisedssid.dot11e_qbss'] == 1) {
373 perc = Number.parseFloat(d).toPrecision(4) + "%";
376 return '<div class="percentage-border"><span class="percentage-text">' + perc + '</span><div class="percentage-fill" style="width:' + d + '%"></div></div>';
380 kismet_ui.AddDeviceColumn('wifi_qbss_clients', {
382 field: 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.dot11e_qbss_stations',
383 description: '802.11e QBSS user count',
386 sClass: "dt-body-right",
387 renderfunc: function(d, t, r, m) {
388 if (r['dot11.advertisedssid.dot11e_qbss'] == 1) {
396 /* Custom device details for dot11 data */
397 kismet_ui.AddDeviceDetail("dot11", "Wi-Fi (802.11)", 0, {
398 filter: function(data) {
400 return (data['kismet.device.base.phyname'] === "IEEE802.11");
405 draw: function(data, target, options, storage) {
406 target.devicedata(data, {
407 "id": "dot11DeviceData",
410 field: 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.ssid',
411 title: "Last Beaconed SSID (AP)",
413 draw: function(opts) {
414 if (typeof(opts['value']) === 'undefined')
415 return '<i>None</i>';
416 if (opts['value'].replace(/\s/g, '').length == 0)
417 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
418 return opts['value'];
420 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",
423 field: "dot11.device/dot11.device.last_probed_ssid_record/dot11.probedssid.ssid",
425 title: "Last Probed SSID (Client)",
426 empty: "<i>None</i>",
427 help: "If present, the last SSID (network name) probed for by a device as a client looking for a network.",
428 draw: function(opts) {
429 if (typeof(opts['value']) === 'undefined')
430 return '<i>None</i>';
431 if (opts['value'].replace(/\s/g, '').length == 0)
432 return '<i>Empty (' + opts['value'].length + ' spaces)</i>'
433 return opts['value'];
437 field: "dot11.device/dot11.device.last_bssid",
440 filter: function(opts) {
442 return opts['value'].replace(/0:/g, '').length != 0;
447 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.",
448 draw: function(opts) {
449 var mac = kismet.censorMAC(opts['value']);
454 $('<span>').html(mac)
458 'class': 'copyuri pseudolink fa fa-copy',
459 'style': 'padding-left: 5px;',
460 'data-clipboard-text': `${mac}`,
469 field: "dot11.device/dot11.device.bss_timestamp",
472 draw: function(opts) {
473 if (opts['value'] == 0)
476 var data_sec = opts['value'] / 1000000;
478 var days = Math.floor(data_sec / 86400);
479 var hours = Math.floor((data_sec / 3600) % 24);
480 var minutes = Math.floor((data_sec / 60) % 60);
481 var seconds = Math.floor(data_sec % 60);
486 ret = ret + days + "d ";
487 if (hours > 0 || days > 0)
488 ret = ret + hours + "h ";
489 if (minutes > 0 || hours > 0 || days > 0)
490 ret = ret + minutes + "m ";
491 ret = ret + seconds + "s";
495 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.",
499 field: "dot11_fingerprint_group",
501 groupTitle: "Fingerprints",
502 id: "dot11_fingerprint_group",
505 field: "dot11.device/dot11.device.beacon_fingerprint",
508 empty: "<i>None</i>",
509 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.",
515 field: "dot11_packet_group",
517 groupTitle: "Packets",
518 id: "dot11_packet_group",
522 field: "graph_field_dot11",
525 render: function(opts) {
530 style: 'width: 50%; height: 200px; padding-bottom: 5px; float: left;',
532 .append('<div><b><center>Overall Packets</center></b></div>')
541 style: 'width: 50%; height: 200px; padding-bottom: 5px; float: right;',
543 .append('<div><b><center>Data Packets</center></b></div>')
553 draw: function(opts) {
555 var overalllegend = ['Management', 'Data'];
557 opts['data']['kismet.device.base.packets.llc'],
558 opts['data']['kismet.device.base.packets.data'],
561 'rgba(46, 99, 162, 1)',
562 'rgba(96, 149, 212, 1)',
563 'rgba(136, 189, 252, 1)',
567 labels: overalllegend,
571 backgroundColor: colors,
577 if ('dot11overalldonut' in window[storage]) {
578 window[storage].dot11overalldonut.data.datasets[0].data = overalldata;
579 window[storage].dot11overalldonut.update();
581 window[storage].dot11overalldonut =
582 new Chart($('#overalldonut', opts['container']), {
587 maintainAspectRatio: false,
602 window[storage].dot11overalldonut.render();
605 var datalegend = ['Data', 'Retry', 'Frag'];
607 opts['data']['kismet.device.base.packets.data'],
608 opts['data']['dot11.device']['dot11.device.num_retries'],
609 opts['data']['dot11.device']['dot11.device.num_fragments'],
612 var databarChartData = {
617 backgroundColor: colors,
623 if ('dot11datadonut' in window[storage]) {
624 window[storage].dot11datadonut.data.datasets[0].data = datadata;
625 window[storage].dot11datadonut.update();
627 window[storage].dot11datadonut =
628 new Chart($('#datadonut', opts['container']), {
630 data: databarChartData,
633 maintainAspectRatio: false,
648 window[storage].dot11datadonut.render();
654 field: "kismet.device.base.packets.total",
656 title: "Total Packets",
657 help: "Total packet count seen of all packet types",
660 field: "kismet.device.base.packets.llc",
662 title: "LLC/Management",
663 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.",
666 field: "kismet.device.base.packets.data",
668 title: "Data Packets",
669 help: "Wi-Fi data packets encode the actual data being sent by the device.",
672 field: "kismet.device.base.packets.error",
674 title: "Error/Invalid Packets",
675 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.",
678 field: "dot11.device/dot11.device.num_fragments",
680 title: "Fragmented Packets",
681 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.",
684 field: "dot11.device/dot11.device.num_retries",
686 title: "Retried Packets",
687 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.",
690 field: "dot11.device/dot11.device.datasize",
692 title: "Data (size)",
693 draw: kismet_ui.RenderHumanSize,
694 help: "The amount of data transmitted by this device",
697 field: "dot11.device/dot11.device.datasize_retry",
699 title: "Retried Data",
700 draw: kismet_ui.RenderHumanSize,
701 help: "The amount of data re-transmitted by this device, due to lost packets and automatic retry.",
707 field: "dot11_extras",
709 help: "Some devices advertise additional information about capabilities via additional tag fields when joining a network.",
710 filter: function(opts) {
712 if (opts['data']['dot11.device']['dot11.device.min_tx_power'] != 0)
715 if (opts['data']['dot11.device']['dot11.device.max_tx_power'] != 0)
718 if (opts['data']['dot11.device']['dot11.device.supported_channels'].length > 0)
727 groupTitle: "Additional Capabilities",
730 field: "dot11.device/dot11.device.min_tx_power",
732 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.",
736 field: "dot11.device/dot11.device.max_tx_power",
738 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.",
742 field: "dot11.device/dot11.device.supported_channels",
743 title: "Supported Channels",
744 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.",
745 filter: function(opts) {
747 return (opts['data']['dot11.device']['dot11.device.supported_channels'].length);
752 draw: function(opts) {
754 return opts['data']['dot11.device']['dot11.device.supported_channels'].join(',');
764 field: "dot11.device/dot11.device.wpa_handshake_list",
765 id: "wpa_handshake_title",
766 filter: function(opts) {
768 return (Object.keys(opts['data']['dot11.device']['dot11.device.wpa_handshake_list']).length);
773 groupTitle: "WPA Handshakes",
776 field: "dot11.device/dot11.device.wpa_handshake_list",
778 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.",
779 filter: function(opts) {
781 return (Object.keys(opts['data']['dot11.device']['dot11.device.wpa_handshake_list']).length);
793 draw: function(opts) {
794 return kismet.censorMAC(opts['index']);
801 draw: function(opts) {
803 for (const p of kismet.ObjectByString(opts['data'], opts['basekey'])) {
804 hs = hs | (1 << p['dot11.eapol.message_num']);
809 for (const p of [1, 2, 3, 4]) {
819 field: "wpa_handshake_download",
820 id: "handshake_download",
821 title: "Handshake PCAP",
822 draw: function(opts) {
824 for (const p of kismet.ObjectByString(opts['data'], opts['basekey'])) {
825 hs = hs | (1 << p['dot11.eapol.message_num']);
828 // We need packets 1&2 or 2&3 to be able to crack the handshake
831 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>';
834 var key = opts['data']['kismet.device.base.key'];
835 var url = `<a href="phy/phy80211/by-key/${key}/device/${opts['index']}/pcap/handshake.pcap">` +
836 '<i class="fa fa-download"></i> Download Pcap File</a>' +
845 field: "dot11.device/dot11.device.pmkid_packet",
847 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.",
850 groupTitle: "WPA RSN PMKID",
854 field: "pmkid_download",
855 id: "pmkid_download",
856 title: "WPA PMKID PCAP",
857 draw: function(opts) {
858 var key = opts['data']['kismet.device.base.key'];
859 var url = '<a href="phy/phy80211/by-key/' + key + '/pcap/handshake-pmkid.pcap">' +
860 '<i class="fa fa-download"></i> Download Pcap File</a>';
869 field: "dot11.device/dot11.device.probed_ssid_map",
870 id: "probed_ssid_header",
871 filter: function(opts) {
873 return (Object.keys(opts['data']['dot11.device']['dot11.device.probed_ssid_map']).length >= 1);
878 title: '<b class="k_padding_title">Probed SSIDs</b>',
879 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.",
883 field: "dot11.device/dot11.device.probed_ssid_map",
886 filter: function(opts) {
888 return (Object.keys(opts['data']['dot11.device']['dot11.device.probed_ssid_map']).length >= 1);
896 iterateTitle: function(opts) {
897 var lastprobe = opts['value'][opts['index']];
898 var lastpssid = lastprobe['dot11.probedssid.ssid'];
899 var key = "probessid" + opts['index'];
901 if (lastpssid === '')
902 lastpssid = "<i>Broadcast</i>";
904 if (lastpssid.replace(/\s/g, '').length == 0)
905 lastpssid = '<i>Empty (' + lastpssid.length + ' spaces)</i>'
907 return '<a id="' + key + '" class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Probed SSID ' + lastpssid + '</a>';
910 draw: function(opts) {
911 var tb = $('.expander', opts['cell']).simpleexpand();
916 field: "dot11.probedssid.ssid",
917 title: "Probed SSID",
918 empty: "<i>Broadcast</i>",
919 draw: function(opts) {
920 if (typeof(opts['value']) === 'undefined')
921 return '<i>None</i>';
922 if (opts['value'].replace(/\s/g, '').length == 0)
923 return 'Empty (' + opts['value'].length + ' spaces)'
924 return opts['value'];
928 field: "dot11.probedssid.wpa_mfp_required",
930 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.",
931 draw: function(opts) {
933 return "Required (802.11w)";
935 if (opts['base']['dot11.probedssid.wpa_mfp_supported'])
936 return "Supported (802.11w)";
938 return "Unavailable";
942 field: "dot11.probedssid.first_time",
945 draw: kismet_ui.RenderTrimmedTime,
948 field: "dot11.probedssid.last_time",
951 draw: kismet_ui.RenderTrimmedTime,
955 // Location is its own group
956 groupTitle: "Avg. Location",
957 id: "avg.dot11.probedssid.location",
960 groupField: "dot11.probedssid.location",
964 // Don't show location if we don't know it
965 filter: function(opts) {
966 return (kismet.ObjectByString(opts['base'], "dot11.probedssid.location/kismet.common.location.avg_loc/kismet.common.location.fix") >= 2);
971 field: "kismet.common.location.avg_loc/kismet.common.location.geopoint",
974 draw: function(opts) {
976 if (opts['value'][1] == 0 || opts['value'][0] == 0)
977 return "<i>Unknown</i>";
979 return kismet.censorLocation(opts['value'][1]) + ", " + kismet.censorLocation(opts['value'][0]);
981 return "<i>Unknown</i>";
986 field: "kismet.common.location.avg_loc/kismet.common.location.alt",
989 filter: function(opts) {
990 return (kismet.ObjectByString(opts['base'], "kismet.common.location.avg_loc/kismet.common.location.fix") >= 3);
992 draw: function(opts) {
994 return kismet_ui.renderHeightDistance(opts['value']);
996 return "<i>Unknown</i>";
1004 field: "dot11.probedssid.dot11r_mobility",
1005 title: "802.11r Mobility",
1007 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.",
1008 draw: function(opts) { return "Enabled"; }
1011 field: "dot11.probedssid.dot11r_mobility_domain_id",
1012 title: "Mobility Domain",
1014 help: "The 802.11r standard allows for fast roaming between access points on the same network."
1017 field: "dot11.probedssid.wps_manuf",
1018 title: "WPS Manufacturer",
1019 filterOnEmpty: true,
1020 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."
1023 field: "dot11.probedssid.wps_device_name",
1024 title: "WPS Device",
1025 filterOnEmpty: true,
1026 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.",
1029 field: "dot11.probedssid.wps_model_name",
1031 filterOnEmpty: true,
1032 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.",
1035 field: "dot11.probedssid.wps_model_number",
1036 title: "WPS Model #",
1037 filterOnEmpty: true,
1038 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.",
1041 field: "dot11.probedssid.wps_serial_number",
1042 title: "WPS Serial #",
1043 filterOnEmpty: true,
1044 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.",
1052 field: "dot11.device/dot11.device.advertised_ssid_map",
1053 id: "advertised_ssid_header",
1054 filter: function(opts) {
1056 return (Object.keys(opts['data']['dot11.device']['dot11.device.advertised_ssid_map']).length >= 1);
1061 title: '<b class="k_padding_title">Advertised SSIDs</b>',
1062 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.",
1066 field: "dot11.device/dot11.device.advertised_ssid_map",
1067 id: "advertised_ssid",
1069 filter: function(opts) {
1071 return (Object.keys(opts['data']['dot11.device']['dot11.device.advertised_ssid_map']).length >= 1);
1078 iterateTitle: function(opts) {
1079 var lastssid = opts['value'][opts['index']]['dot11.advertisedssid.ssid'];
1080 var lastowessid = opts['value'][opts['index']]['dot11.advertisedssid.owe_ssid'];
1082 if (lastssid === '') {
1083 if ('dot11.advertisedssid.owe_ssid' in opts['value'][opts['index']] && lastowessid !== '') {
1084 return "SSID: " + lastowessid + " <i>(OWE)</i>";
1087 return "SSID: <i>Unknown</i>";
1090 return "SSID: " + lastssid;
1094 field: "dot11.advertisedssid.ssid",
1096 draw: function(opts) {
1097 if (opts['value'].replace(/\s/g, '').length == 0) {
1098 if ('dot11.advertisedssid.owe_ssid' in opts['base']) {
1099 return "<i>SSID advertised as OWE</i>";
1101 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
1105 return opts['value'];
1107 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.",
1110 field: "dot11.advertisedssid.owe_ssid",
1113 filterOnEmpty: true,
1114 help: "Opportunistic Wireless Encryption (OWE) advertises the original SSID on an alternate BSSID.",
1117 field: "dot11.advertisedssid.owe_bssid",
1120 filterOnEmpty: true,
1121 help: "Opportunistic Wireless Encryption (OWE) advertises the original SSID with a reference to the linked BSSID.",
1122 draw: function(opts) {
1123 $.get(local_uri_prefix + "devices/by-mac/" + opts['value'] + "/devices.json")
1125 opts['container'].html(opts['value']);
1127 .done(function(clidata) {
1128 clidata = kismet.sanitizeObject(clidata);
1130 for (var cl of clidata) {
1131 if (cl['kismet.device.base.phyname'] === 'IEEE802.11') {
1132 opts['container'].html(opts['value'] + ' <a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + cl['kismet.device.base.key'] + '\')">View AP Details</a>');
1137 opts['container'].html(opts['value']);
1142 field: "dot11.advertisedssid.dot11s.meshid",
1145 filterOnEmpty: true,
1146 help: "802.11s mesh id, present only in meshing networks.",
1149 field: "dot11.advertisedssid.crypt_set",
1151 title: "Encryption",
1152 draw: function(opts) {
1153 return CryptToHumanReadable(opts['value']);
1155 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.",
1158 field: "dot11.advertisedssid.wpa_mfp_required",
1161 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.",
1162 draw: function(opts) {
1164 return "Required (802.11w)";
1166 if (opts['base']['dot11.advertisedssid.wpa_mfp_supported'])
1167 return "Supported (802.11w)";
1169 if (opts['base']['dot11.advertisedssid.cisco_client_mfp'])
1170 return "Supported (Cisco)";
1172 return "Unavailable";
1176 field: "dot11.advertisedssid.channel",
1179 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.",
1182 field: "dot11.advertisedssid.ht_mode",
1185 help: "802.11n and 802.11AC networks operate on expanded channels; HT40, HT80 HT160, or HT80+80 (found only on 802.11ac wave2 gear).",
1189 field: "dot11.advertisedssid.ht_center_1",
1192 help: "802.11AC networks operate on expanded channels. This is the frequency of the center of the expanded channel.",
1194 draw: function(opts) {
1195 return opts['value'] + " (Channel " + (opts['value'] - 5000) / 5 + ")";
1199 field: "dot11.advertisedssid.ht_center_2",
1202 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.",
1204 draw: function(opts) {
1205 return opts['value'] + " (Channel " + (opts['value'] - 5000) / 5 + ")";
1209 field: "dot11.advertisedssid.beacon_info",
1211 title: "Beacon Info",
1212 filterOnEmpty: true,
1213 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.",
1216 field: "dot11.advertisedssid.dot11s.gateway",
1218 title: "Mesh Gateway",
1219 help: "An 802.11s mesh device in gateway mode bridges Wi-Fi traffic to another network, such as a wired Ethernet connection.",
1220 filterOnEmpty: true,
1221 draw: function(opts) {
1222 if (opts['value'] == 1)
1224 return "Disabled (not a gateway)";
1228 field: "dot11.advertisedssid.dot11s.forwarding",
1230 title: "Mesh Forwarding",
1231 help: "An 802.11s mesh device may forward packets to another mesh device for delivery.",
1232 filterOnEmpty: true,
1233 draw: function(opts) {
1234 if (opts['value'] == 1)
1235 return "Forwarding";
1240 field: "dot11.advertisedssid.dot11s.num_peerings",
1242 title: "Mesh Peers",
1243 help: "Number of mesh peers for this device in an 802.11s network.",
1244 filterOnEmpty: true,
1247 field: "dot11.advertisedssid.dot11e_qbss_stations",
1249 title: "Connected Stations",
1250 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.",
1251 filter: function(opts) {
1253 return (opts['base']['dot11.advertisedssid.dot11e_qbss'] == 1);
1260 field: "dot11.advertisedssid.dot11e_channel_utilization_perc",
1262 title: "Channel Utilization",
1263 help: "Access points which provide 802.11e / QBSS calculate the estimated channel saturation as part of the channel quality of service.",
1264 draw: function(opts) {
1267 if (opts['value'] == 0) {
1270 perc = Number.parseFloat(opts['value']).toPrecision(4) + "%";
1273 return '<div class="percentage-border"><span class="percentage-text">' + perc + '</span><div class="percentage-fill" style="width:' + opts['value'] + '%"></div></div>';
1275 filter: function(opts) {
1277 return (opts['base']['dot11.advertisedssid.dot11e_qbss'] == 1);
1284 field: "dot11.advertisedssid.ccx_txpower",
1286 title: "Cisco CCX TxPower",
1288 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.",
1289 draw: function(opts) {
1290 return opts['value'] + "dBm";
1294 field: "dot11.advertisedssid.dot11r_mobility",
1296 title: "802.11r Mobility",
1298 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.",
1299 draw: function(opts) { return "Enabled"; }
1302 field: "dot11.advertisedssid.dot11r_mobility_domain_id",
1304 title: "Mobility Domain",
1306 help: "The 802.11r standard allows for fast roaming between access points on the same network."
1309 field: "dot11.advertisedssid.first_time",
1311 title: "First Seen",
1312 draw: kismet_ui.RenderTrimmedTime,
1315 field: "dot11.advertisedssid.last_time",
1318 draw: kismet_ui.RenderTrimmedTime,
1322 // Location is its own group
1323 groupTitle: "Avg. Location",
1324 id: "avg.dot11.advertisedssid.location",
1327 groupField: "dot11.advertisedssid.location",
1331 // Don't show location if we don't know it
1332 filter: function(opts) {
1333 return (kismet.ObjectByString(opts['base'], "dot11.advertisedssid.location/kismet.common.location.avg_loc/kismet.common.location.fix") >= 2);
1338 field: "kismet.common.location.avg_loc/kismet.common.location.geopoint",
1341 draw: function(opts) {
1343 if (opts['value'][1] == 0 || opts['value'][0] == 0)
1344 return "<i>Unknown</i>";
1346 return kismet.censorLocation(opts['value'][1]) + ", " + kismet.censorLocation(opts['value'][0]);
1348 return "<i>Unknown</i>";
1353 field: "kismet.common.location.avg_loc/kismet.common.location.alt",
1356 filter: function(opts) {
1357 return (kismet.ObjectByString(opts['base'], "kismet.common.location.avg_loc/kismet.common.location.fix") >= 3);
1359 draw: function(opts) {
1361 return kismet_ui.renderHeightDistance(opts['value']);
1363 return "<i>Unknown</i>";
1371 field: "dot11.advertisedssid.beaconrate",
1373 title: "Beacon Rate",
1374 draw: function(opts) {
1375 return opts['value'] + '/sec';
1377 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.",
1380 field: "dot11.advertisedssid.maxrate",
1383 draw: function(opts) {
1384 return opts['value'] + ' MBit/s';
1386 help: "The maximum basic transmission rate supported by this access point",
1389 field: "dot11.advertisedssid.dot11d_country",
1391 title: "802.11d Country",
1392 filterOnEmpty: true,
1393 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.",
1396 field: "dot11.advertisedssid.wps_manuf",
1398 title: "WPS Manufacturer",
1399 filterOnEmpty: true,
1400 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."
1403 field: "dot11.advertisedssid.wps_device_name",
1405 title: "WPS Device",
1406 filterOnEmpty: true,
1407 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.",
1410 field: "dot11.advertisedssid.wps_model_name",
1413 filterOnEmpty: true,
1414 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.",
1417 field: "dot11.advertisedssid.wps_model_number",
1419 title: "WPS Model #",
1420 filterOnEmpty: true,
1421 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.",
1424 field: "dot11.advertisedssid.wps_serial_number",
1426 title: "WPS Serial #",
1427 filterOnEmpty: true,
1428 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.",
1432 field: "dot11.advertisedssid.ie_tag_content",
1434 filterOnEmpty: true,
1435 id: "dot11_ssid_ietags",
1436 title: '<b class="k_padding_title">IE tags</b>',
1437 help: "IE tags in beacons define the network and the access point attributes; because dot11_keep_ietags is true, Kismet tracks these here.",
1441 field: "dot11.advertisedssid.ie_tag_content",
1443 id: "advertised_ietags",
1444 filterOnEmpty: true,
1447 render: function(opts) {
1448 return '<table id="tagdump" border="0" />';
1451 draw: function(opts) {
1452 $('table#tagdump', opts['container']).empty();
1453 for (var ie in opts['value']) {
1454 var tag = opts['value'][ie];
1458 class: 'alternating'
1467 .html("<b>" + tag['dot11.ietag.number'] + "</b>")
1476 if (tag['dot11.ietag.oui'] != 0) {
1477 var oui = ("000000" + tag['dot11.ietag.oui'].toString(16)).substr(-6).replace(/(..)/g, '$1:').slice(0, -1);
1479 if (tag['dot11.ietag.oui_manuf'].length != 0)
1480 oui = oui + " (" + tag['dot11.ietag.oui_manuf'] + ")";
1482 $('#tagno', pretty_tag).append(
1488 if (tag['dot11.ietag.subtag'] >= 0) {
1489 $('#tagno', pretty_tag).append(
1491 .text("Subtag " + tag['dot11.ietag.subtag'])
1495 var hexdumps = pretty_hexdump(hexstr_to_bytes(tag['dot11.ietag.data']));
1497 for (var i in hexdumps) {
1498 $('#hexdump', pretty_tag).append(
1507 $('table#tagdump', opts['container']).append(pretty_tag);
1518 field: "dot11.device/dot11.device.responded_ssid_map",
1519 id: "responded_ssid_header",
1520 filter: function(opts) {
1522 return (Object.keys(opts['data']['dot11.device']['dot11.device.responded_ssid_map']).length >= 1);
1527 title: '<b class="k_padding_title">Responded SSIDs</b>',
1528 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.",
1532 field: "dot11.device/dot11.device.responded_ssid_map",
1533 id: "responded_ssid",
1535 filter: function(opts) {
1537 return (Object.keys(opts['data']['dot11.device']['dot11.device.responded_ssid_map']).length >= 1);
1544 iterateTitle: function(opts) {
1545 var lastssid = opts['value'][opts['index']]['dot11.advertisedssid.ssid'];
1546 var lastowessid = opts['value'][opts['index']]['dot11.advertisedssid.owe_ssid'];
1548 if (lastssid === '') {
1549 if ('dot11.advertisedssid.owe_ssid' in opts['value'][opts['index']] && lastowessid !== '') {
1550 return "SSID: " + lastowessid + " <i>(OWE)</i>";
1553 return "SSID: <i>Unknown</i>";
1556 return "SSID: " + lastssid;
1560 field: "dot11.advertisedssid.ssid",
1562 draw: function(opts) {
1563 if (opts['value'].replace(/\s/g, '').length == 0) {
1564 if ('dot11.advertisedssid.owe_ssid' in opts['base']) {
1565 return "<i>SSID advertised as OWE</i>";
1567 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
1571 return opts['value'];
1573 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.",
1576 field: "dot11.advertisedssid.owe_ssid",
1579 filterOnEmpty: true,
1580 help: "Opportunistic Wireless Encryption (OWE) advertises the original SSID on an alternate BSSID.",
1583 field: "dot11.advertisedssid.owe_bssid",
1586 filterOnEmpty: true,
1587 help: "Opportunistic Wireless Encryption (OWE) advertises the original SSID with a reference to the linked BSSID.",
1588 draw: function(opts) {
1589 $.get(local_uri_prefix + "devices/by-mac/" + opts['value'] + "/devices.json")
1591 opts['container'].html(opts['value']);
1593 .done(function(clidata) {
1594 clidata = kismet.sanitizeObject(clidata);
1596 for (var cl of clidata) {
1597 if (cl['kismet.device.base.phyname'] === 'IEEE802.11') {
1598 opts['container'].html(opts['value'] + ' <a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + cl['kismet.device.base.key'] + '\')">View AP Details</a>');
1603 opts['container'].html(opts['value']);
1608 field: "dot11.advertisedssid.crypt_set",
1610 title: "Encryption",
1611 draw: function(opts) {
1612 return CryptToHumanReadable(opts['value']);
1614 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.",
1617 field: "dot11.advertisedssid.wpa_mfp_required",
1620 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.",
1621 draw: function(opts) {
1623 return "Required (802.11w)";
1625 if (opts['base']['dot11.advertisedssid.wpa_mfp_supported'])
1626 return "Supported (802.11w)";
1628 if (opts['base']['dot11.advertisedssid.cisco_client_mfp'])
1629 return "Supported (Cisco)";
1631 return "Unavailable";
1635 field: "dot11.advertisedssid.ht_mode",
1638 help: "802.11n and 802.11AC networks operate on expanded channels; HT40, HT80 HT160, or HT80+80 (found only on 802.11ac wave2 gear).",
1642 field: "dot11.advertisedssid.ht_center_1",
1645 help: "802.11AC networks operate on expanded channels. This is the frequency of the center of the expanded channel.",
1647 draw: function(opts) {
1648 return opts['value'] + " (Channel " + (opts['value'] - 5000) / 5 + ")";
1652 field: "dot11.advertisedssid.ht_center_2",
1655 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.",
1657 draw: function(opts) {
1658 return opts['value'] + " (Channel " + (opts['value'] - 5000) / 5 + ")";
1662 field: "dot11.advertisedssid.ccx_txpower",
1664 title: "Cisco CCX TxPower",
1666 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.",
1667 draw: function(opts) {
1668 return opts['value'] + "dBm";
1672 field: "dot11.advertisedssid.dot11r_mobility",
1674 title: "802.11r Mobility",
1676 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.",
1677 draw: function(opts) { return "Enabled"; }
1680 field: "dot11.advertisedssid.dot11r_mobility_domain_id",
1682 title: "Mobility Domain",
1684 help: "The 802.11r standard allows for fast roaming between access points on the same network."
1687 field: "dot11.advertisedssid.first_time",
1689 title: "First Seen",
1690 draw: kismet_ui.RenderTrimmedTime,
1693 field: "dot11.advertisedssid.last_time",
1696 draw: kismet_ui.RenderTrimmedTime,
1700 // Location is its own group
1701 groupTitle: "Avg. Location",
1702 id: "avg.dot11.advertisedssid.location",
1705 groupField: "dot11.advertisedssid.location",
1709 // Don't show location if we don't know it
1710 filter: function(opts) {
1711 return (kismet.ObjectByString(opts['base'], "dot11.advertisedssid.location/kismet.common.location.avg_loc/kismet.common.location.fix") >= 2);
1716 field: "kismet.common.location.avg_loc/kismet.common.location.geopoint",
1719 draw: function(opts) {
1721 if (opts['value'][1] == 0 || opts['value'][0] == 0)
1722 return "<i>Unknown</i>";
1724 return kismet.censorLocation(opts['value'][1]) + ", " + kismet.censorLocation(opts['value'][0]);
1726 return "<i>Unknown</i>";
1731 field: "kismet.common.location.avg_loc/kismet.common.location.alt",
1734 filter: function(opts) {
1735 return (kismet.ObjectByString(opts['base'], "kismet.common.location.avg_loc/kismet.common.location.fix") >= 3);
1737 draw: function(opts) {
1739 return kismet_ui.renderHeightDistance(opts['value']);
1741 return "<i>Unknown</i>";
1750 field: "dot11.advertisedssid.maxrate",
1753 draw: function(opts) {
1754 return opts['value'] + ' MBit/s';
1756 help: "The maximum basic transmission rate supported by this access point",
1759 field: "dot11.advertisedssid.dot11d_country",
1761 title: "802.11d Country",
1762 filterOnEmpty: true,
1763 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.",
1766 field: "dot11.advertisedssid.wps_manuf",
1768 title: "WPS Manufacturer",
1769 filterOnEmpty: true,
1770 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."
1773 field: "dot11.advertisedssid.wps_device_name",
1775 title: "WPS Device",
1776 filterOnEmpty: true,
1777 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.",
1780 field: "dot11.advertisedssid.wps_model_name",
1783 filterOnEmpty: true,
1784 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.",
1787 field: "dot11.advertisedssid.wps_model_number",
1789 title: "WPS Model #",
1790 filterOnEmpty: true,
1791 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.",
1794 field: "dot11.advertisedssid.wps_serial_number",
1796 title: "WPS Serial #",
1797 filterOnEmpty: true,
1798 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.",
1802 field: "dot11.advertisedssid.ie_tag_content",
1804 filterOnEmpty: true,
1805 id: "dot11_ssid_ietags",
1806 title: '<b class="k_padding_title">IE tags</b>',
1807 help: "IE tags in beacons define the network and the access point attributes; because dot11_keep_ietags is true, Kismet tracks these here.",
1811 field: "dot11.advertisedssid.ie_tag_content",
1813 id: "advertised_ietags",
1814 filterOnEmpty: true,
1817 render: function(opts) {
1818 return '<table id="tagdump" border="0" />';
1821 draw: function(opts) {
1822 $('table#tagdump', opts['container']).empty();
1823 for (var ie in opts['value']) {
1824 var tag = opts['value'][ie];
1828 class: 'alternating'
1837 .html("<b>" + tag['dot11.ietag.number'] + "</b>")
1846 if (tag['dot11.ietag.oui'] != 0) {
1847 var oui = ("000000" + tag['dot11.ietag.oui'].toString(16)).substr(-6).replace(/(..)/g, '$1:').slice(0, -1);
1849 if (tag['dot11.ietag.oui_manuf'].length != 0)
1850 oui = oui + " (" + tag['dot11.ietag.oui_manuf'] + ")";
1852 $('#tagno', pretty_tag).append(
1858 if (tag['dot11.ietag.subtag'] >= 0) {
1859 $('#tagno', pretty_tag).append(
1861 .text("Subtag " + tag['dot11.ietag.subtag'])
1865 var hexdumps = pretty_hexdump(hexstr_to_bytes(tag['dot11.ietag.data']));
1867 for (var i in hexdumps) {
1868 $('#hexdump', pretty_tag).append(
1877 $('table#tagdump', opts['container']).append(pretty_tag);
1887 field: "dot11_bssts_similar",
1888 id: "bssts_similar_header",
1889 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.",
1890 filter: function(opts) {
1892 return (Object.keys(opts['data']['kismet.device.base.related_devices']['dot11_bssts_similar']).length >= 1);
1897 title: '<b class="k_padding_title">Shared Hardware (Uptime)</b>'
1901 field: "kismet.device.base.related_devices/dot11_bssts_similar",
1902 id: "bssts_similar",
1904 filter: function(opts) {
1906 return (Object.keys(opts['data']['kismet.device.base.related_devices']['dot11_bssts_similar']).length >= 1);
1913 iterateTitle: function(opts) {
1914 var key = kismet.ObjectByString(opts['data'], opts['basekey']);
1916 return '<a id="' + key + '" class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Shared with ' + opts['data'] + '</a>';
1919 return '<a class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Shared with ' + opts['data'] + '</a>';
1921 draw: function(opts) {
1922 var tb = $('.expander', opts['cell']).simpleexpand();
1924 var key = kismet.ObjectByString(opts['data'], opts['basekey']);
1925 var alink = $('a#' + key, opts['cell']);
1926 $.get(local_uri_prefix + "devices/by-key/" + key + "/device.json")
1927 .done(function(data) {
1928 data = kismet.sanitizeObject(data);
1931 var ssid = data['dot11.device']['dot11.device.last_beaconed_ssid_record']['dot11.advertisedssid.ssid'];
1932 var mac = kismet.censorMAC(data['kismet.device.base.macaddr']);
1937 if (ssid == "" || typeof(data) === 'undefined')
1938 ssid = "<i>n/a</i>";
1940 alink.html("Related to " + mac + " (" + ssid + ")");
1946 field: "dot11.client.bssid_key",
1947 title: "Access Point",
1948 draw: function(opts) {
1949 if (opts['key'] === '') {
1950 return "<i>No records for access point</i>";
1952 return '<a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + opts['base'] + '\')">View AP Details</a>';
1960 field: "dot11_wps_uuid_identical",
1961 id: "wps_uuid_identical_header",
1962 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.",
1963 filter: function(opts) {
1965 return (Object.keys(opts['data']['kismet.device.base.related_devices']['dot11_uuid_e']).length >= 1);
1970 title: '<b class="k_padding_title">Shared Hardware (WPS UUID)</b>'
1974 field: "kismet.device.base.related_devices/dot11_uuid_e",
1975 id: "wps_uuid_identical",
1977 filter: function(opts) {
1979 return (Object.keys(opts['data']['kismet.device.base.related_devices']['dot11_uuid_e']).length >= 1);
1986 iterateTitle: function(opts) {
1987 var key = kismet.ObjectByString(opts['data'], opts['basekey']);
1989 return '<a id="' + key + '" class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Same WPS UUID as ' + opts['data'] + '</a>';
1992 return '<a class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Same WPS UUID as ' + opts['data'] + '</a>';
1994 draw: function(opts) {
1995 var tb = $('.expander', opts['cell']).simpleexpand();
1997 var key = kismet.ObjectByString(opts['data'], opts['basekey']);
1998 var alink = $('a#' + key, opts['cell']);
1999 $.get(local_uri_prefix + "devices/by-key/" + key + "/device.json")
2000 .done(function(data) {
2001 data = kismet.sanitizeObject(data);
2003 var mac = "<i>unknown</i>";
2006 mac = kismet.censorMAC(data['kismet.device.base.macaddr']);
2011 alink.html("Related to " + mac);
2017 field: "dot11.client.bssid_key",
2018 title: "Access Point",
2019 draw: function(opts) {
2020 if (opts['key'] === '') {
2021 return "<i>No records for access point</i>";
2023 return '<a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + opts['base'] + '\')">View AP Details</a>';
2031 field: "dot11.device/dot11.device.client_map",
2032 id: "client_behavior_header",
2033 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).",
2034 filter: function(opts) {
2036 return (Object.keys(opts['data']['dot11.device']['dot11.device.client_map']).length >= 1);
2041 title: '<b class="k_padding_title">Wi-Fi Client Behavior</b>'
2045 field: "dot11.device/dot11.device.client_map",
2046 id: "client_behavior",
2048 filter: function(opts) {
2050 return (Object.keys(opts['data']['dot11.device']['dot11.device.client_map']).length >= 1);
2057 iterateTitle: function(opts) {
2058 var key = kismet.ObjectByString(opts['data'], opts['basekey'] + 'dot11.client.bssid_key');
2060 return '<a id="dot11_bssid_client_' + key + '" class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Client of ' + kismet.censorMAC(opts['index']) + '</a>';
2063 return '<a class="expander collapsed" data-expander-target="#' + opts['containerid'] + '" href="#">Client of ' + kismet.censorMAC(opts['index']) + '</a>';
2065 draw: function(opts) {
2066 var tb = $('.expander', opts['cell']).simpleexpand();
2071 field: "dot11.client.bssid_key",
2072 title: "Access Point",
2073 draw: function(opts) {
2074 if (opts['key'] === '') {
2075 return "<i>No records for access point</i>";
2077 return '<a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + opts['value'] + '\')">View AP Details</a>';
2082 field: "dot11.client.bssid",
2084 draw: function(opts) {
2085 return kismet.censorMAC(opts['value']);
2089 field: "dot11.client.bssid_key",
2091 draw: function(opts) {
2092 $.get(local_uri_prefix + "devices/by-key/" + opts['value'] +
2093 "/device.json/kismet.device.base.commonname")
2095 opts['container'].html('<i>None</i>');
2097 .done(function(clidata) {
2098 clidata = kismet.sanitizeObject(clidata);
2100 if (clidata === '' || clidata === '""') {
2101 opts['container'].html('<i>None</i>');
2103 opts['container'].html(kismet.censorMAC(clidata));
2109 field: "dot11.client.bssid_key",
2111 draw: function(opts) {
2112 $.get(local_uri_prefix + "devices/by-key/" + opts['value'] +
2113 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.ssid')
2115 opts['container'].html('<i>Unknown</i>');
2117 .done(function(clidata) {
2118 clidata = kismet.sanitizeObject(clidata);
2120 if (clidata === '' || clidata === '""') {
2121 opts['container'].html('<i>Unknown</i>');
2123 opts['container'].html(clidata);
2129 field: "dot11.client.first_time",
2130 title: "First Connected",
2131 draw: kismet_ui.RenderTrimmedTime,
2134 field: "dot11.client.last_time",
2135 title: "Last Connected",
2136 draw: kismet_ui.RenderTrimmedTime,
2139 field: "dot11.client.datasize",
2141 draw: kismet_ui.RenderHumanSize,
2144 field: "dot11.client.datasize_retry",
2145 title: "Retried Data",
2146 draw: kismet_ui.RenderHumanSize,
2149 // Set the field to be the host, and filter on it, but also
2151 field: "dot11.client.dhcp_host",
2154 filterOnEmpty: true,
2155 help: "If a DHCP data packet is seen, the requested hostname and the operating system / vendor of the DHCP client can be extracted.",
2158 field: "dot11.client.dhcp_host",
2159 title: "DHCP Hostname",
2160 empty: "<i>Unknown</i>"
2163 field: "dot11.client.dhcp_vendor",
2164 title: "DHCP Vendor",
2165 empty: "<i>Unknown</i>"
2170 field: "dot11.client.eap_identity",
2171 title: "EAP Identity",
2172 filterOnEmpty: true,
2173 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",
2176 field: "dot11.client.cdp_device",
2179 filterOnEmpty: true,
2180 help: "Clients bridged to a wired network may leak CDP (Cisco Discovery Protocol) packets, which can disclose information about the internal wired network.",
2183 field: "dot11.client.cdp_device",
2187 field: "dot11.client.cdp_port",
2189 empty: "<i>Unknown</i>"
2194 field: "dot11.client.ipdata",
2196 filter: function(opts) {
2197 if (kismet.ObjectByString(opts['data'], opts['basekey'] + 'dot11.client.ipdata') == 0)
2200 return (kismet.ObjectByString(opts['data'], opts['basekey'] + 'dot11.client.ipdata/kismet.common.ipdata.address') != 0);
2202 help: "Kismet will attempt to derive the IP ranges in use on a network, either from observed traffic or from DHCP server responses.",
2205 field: "dot11.client.ipdata/kismet.common.ipdata.address",
2206 title: "IP Address",
2209 field: "dot11.client.ipdata/kismet.common.ipdata.netmask",
2211 zero: "<i>Unknown</i>"
2214 field: "dot11.client.ipdata/kismet.common.ipdata.gateway",
2216 zero: "<i>Unknown</i>"
2225 field: "dot11.device/dot11.device.associated_client_map",
2226 id: "client_list_header",
2227 filter: function(opts) {
2229 return (Object.keys(opts['data']['dot11.device']['dot11.device.associated_client_map']).length >= 1);
2234 title: '<b class="k_padding_title">Associated Clients</b>',
2235 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.",
2239 field: "dot11.device/dot11.device.associated_client_map",
2242 filter: function(opts) {
2244 return (Object.keys(opts['data']['dot11.device']['dot11.device.associated_client_map']).length >= 1);
2251 iterateTitle: function(opts) {
2252 return '<a id="associated_client_expander_' + opts['base'] + '" class="expander collapsed" href="#" data-expander-target="#' + opts['containerid'] + '">Client ' + kismet.censorMAC(opts['index']) + '</a>';
2254 draw: function(opts) {
2255 var tb = $('.expander', opts['cell']).simpleexpand();
2259 // Dummy field to get us a nested area since we don't have
2260 // a real field in the client list since it's just a key-val
2261 // not a nested object
2265 draw: function(opts) {
2266 return `<div id="associated_client_content_${opts['base']}">`;
2275 finalize: function(data, target, options, storage) {
2276 var apkey = data['kismet.device.base.macaddr'];
2281 Object.values(data['dot11.device']['dot11.device.associated_client_map']).forEach(device => combokeys[device] = 1);
2287 Object.values(data['dot11.device']['dot11.device.client_map']).forEach(device => combokeys[device['dot11.client.bssid_key']] = 1);
2293 devices: Object.keys(combokeys),
2295 'kismet.device.base.macaddr',
2296 'kismet.device.base.key',
2297 'kismet.device.base.type',
2298 'kismet.device.base.commonname',
2299 'kismet.device.base.manuf',
2300 'dot11.device/dot11.device.client_map',
2301 ['dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.ssid', 'dot11.advertisedssid.lastssid'],
2305 var postdata = `json=${encodeURIComponent(JSON.stringify(param))}`;
2307 $.post(`${local_uri_prefix}devices/multikey/as-object/devices.json`, postdata, "json")
2308 .done(function(devs) {
2309 var devs = kismet.sanitizeObject(devs);
2311 var client_devs = [];
2314 Object.values(data['dot11.device']['dot11.device.client_map']).forEach(device => client_devs.push(device['dot11.client.bssid_key']));
2319 client_devs.forEach(function(v) {
2325 var lastssid = dev['dot11.advertisedssid.lastssid'];
2327 if (typeof(lastssid) !== 'string')
2328 lastssid = `<i>None</i>`;
2329 else if (lastssid.replace(/\s/g, '').length == 0)
2330 lastssid = `<i>Cloaked / Empty (${lastssid.length} characters)</i>`;
2332 $(`a#dot11_bssid_client_${v}`).html(`Client of ${kismet.censorMAC(dev['kismet.device.base.macaddr'])} (${lastssid})`);
2338 client_devs = Object.values(data['dot11.device']['dot11.device.associated_client_map']);
2343 client_devs.forEach(function(v) {
2349 $(`#associated_client_expander_${v}`).html(`${kismet.censorMAC(dev['kismet.device.base.commonname'])}`);
2351 $(`#associated_client_content_${v}`).devicedata(dev, {
2355 field: "kismet.device.base.key",
2356 title: "Client Info",
2357 draw: function(opts) {
2358 return '<a href="#" onclick="kismet_ui.DeviceDetailWindow(\'' + opts['data']['kismet.device.base.key'] + '\')">View Client Details</a>';
2362 field: "kismet.device.base.commonname",
2364 filterOnEmpty: "true",
2365 empty: "<i>None</i>",
2366 draw: function(opts) {
2367 return kismet.censorMAC(opts['value']);
2371 field: "kismet.device.base.type",
2373 empty: "<i>Unknown</i>"
2377 field: "kismet.device.base.manuf",
2378 title: "Manufacturer",
2379 empty: "<i>Unknown</i>"
2382 field: "dot11.device.client_map[" + apkey + "]/dot11.client.first_time",
2383 title: "First Connected",
2384 draw: kismet_ui.RenderTrimmedTime,
2387 field: "dot11.device.client_map[" + apkey + "]/dot11.client.last_time",
2388 title: "Last Connected",
2389 draw: kismet_ui.RenderTrimmedTime,
2392 field: "dot11.device.client_map[" + apkey + "]/dot11.client.datasize",
2394 draw: kismet_ui.RenderHumanSize,
2397 field: "dot11.device.client_map[" + apkey + "]/dot11.client.datasize_retry",
2398 title: "Retried Data",
2399 draw: kismet_ui.RenderHumanSize,
2409 var ssid_status_element;
2411 var SsidColumns = new Array();
2413 export const AddSsidColumn = (id, options) => {
2416 sTitle: options.sTitle,
2421 if ('field' in options) {
2422 coldef.field = options.field;
2425 if ('fields' in options) {
2426 coldef.fields = options.fields;
2429 if ('description' in options) {
2430 coldef.description = options.description;
2433 if ('name' in options) {
2434 coldef.name = options.name;
2437 if ('orderable' in options) {
2438 coldef.bSortable = options.orderable;
2441 if ('visible' in options) {
2442 coldef.bVisible = options.visible;
2444 coldef.bVisible = true;
2447 if ('selectable' in options) {
2448 coldef.user_selectable = options.selectable;
2450 coldef.user_selectable = true;
2453 if ('searchable' in options) {
2454 coldef.bSearchable = options.searchable;
2457 if ('width' in options) {
2458 coldef.width = options.width;
2461 if ('sClass' in options) {
2462 coldef.sClass = options.sClass;
2466 if (typeof(coldef.field) === 'string') {
2467 var fs = coldef.field.split('/');
2468 f = fs[fs.length - 1];
2469 } else if (Array.isArray(coldef.field)) {
2470 f = coldef.field[1];
2473 coldef.mData = function(row, type, set) {
2474 return kismet.ObjectByString(row, f);
2477 if ('renderfunc' in options) {
2478 coldef.mRender = options.renderfunc;
2481 if ('drawfunc' in options) {
2482 coldef.kismetdrawfunc = options.drawfunc;
2485 SsidColumns.push(coldef);
2488 export const GetSsidColumns = (showall = false) => {
2489 var ret = new Array();
2491 var order = kismet.getStorage('kismet.ssidtable.columns', []);
2493 if (order.length == 0) {
2494 // sort invisible columns to the end
2495 for (var i in SsidColumns) {
2496 if (!SsidColumns[i].bVisible)
2498 ret.push(SsidColumns[i]);
2501 for (var i in SsidColumns) {
2502 if (SsidColumns[i].bVisible)
2504 ret.push(SsidColumns[i]);
2510 for (var oi in order) {
2516 var sc = SsidColumns.find(function(e, i, a) {
2517 if (e.kismetId === o.id)
2522 if (sc != undefined && sc.user_selectable) {
2528 // Fallback if no columns were selected somehow
2529 if (ret.length == 0) {
2530 // sort invisible columns to the end
2531 for (var i in SsidColumns) {
2532 if (!SsidColumns[i].bVisible)
2534 ret.push(SsidColumns[i]);
2537 for (var i in SsidColumns) {
2538 if (SsidColumns[i].bVisible)
2540 ret.push(SsidColumns[i]);
2547 for (var sci in SsidColumns) {
2548 var sc = SsidColumns[sci];
2550 var rc = ret.find(function(e, i, a) {
2551 if (e.kismetId === sc.kismetId)
2556 if (rc == undefined) {
2557 sc.bVisible = false;
2565 for (var sci in SsidColumns) {
2566 if (!SsidColumns[sci].user_selectable) {
2567 ret.push(SsidColumns[sci]);
2574 export const GetSsidColumnMap = (columns) => {
2577 for (var ci in columns) {
2578 var fields = new Array();
2580 if ('field' in columns[ci])
2581 fields.push(columns[ci]['field']);
2583 if ('fields' in columns[ci])
2584 fields.push.apply(fields, columns[ci]['fields']);
2592 export const GetSsidFields = (selected) => {
2593 var rawret = new Array();
2594 var cols = GetSsidColumns();
2596 for (var i in cols) {
2597 if ('field' in cols[i])
2598 rawret.push(cols[i]['field']);
2600 if ('fields' in cols[i])
2601 rawret.push.apply(rawret, cols[i]['fields']);
2605 var ret = rawret.filter(function(item, pos, self) {
2606 return self.indexOf(item) == pos;
2614 function ScheduleSsidSummary() {
2616 if (kismet_ui.window_visible && ssid_element.is(":visible")) {
2617 var dt = ssid_element.DataTable();
2619 // Save the state. We can't use proper state saving because it seems to break
2620 // the table position
2621 kismet.putStorage('kismet.base.ssidtable.order', JSON.stringify(dt.order()));
2622 kismet.putStorage('kismet.base.ssidtable.search', JSON.stringify(dt.search()));
2624 // Snapshot where we are, because the 'don't reset page' in ajax.reload
2625 // DOES still reset the scroll position
2627 'top': $(dt.settings()[0].nScrollBody).scrollTop(),
2628 'left': $(dt.settings()[0].nScrollBody).scrollLeft()
2630 dt.ajax.reload(function(d) {
2631 // Restore our scroll position
2632 $(dt.settings()[0].nScrollBody).scrollTop( prev_pos.top );
2633 $(dt.settings()[0].nScrollBody).scrollLeft( prev_pos.left );
2641 // Set our timer outside of the datatable callback so that we get called even
2642 // if the ajax load fails
2643 ssidTid = setTimeout(ScheduleSsidSummary, 2000);
2646 function InitializeSsidTable() {
2647 var cols = GetSsidColumns();
2648 var colmap = GetSsidColumnMap(cols);
2649 var fields = GetSsidFields();
2657 if ($.fn.dataTable.isDataTable(ssid_element)) {
2658 ssid_element.DataTable().destroy();
2659 ssid_element.empty();
2663 .on('xhr.dt', function(e, settings, json, xhr) {
2664 json = kismet.sanitizeObject(json);
2667 if (json['recordsFiltered'] != json['recordsTotal'])
2668 ssid_status_element.html(`${json['recordsTotal']} SSIDs (${json['recordsFiltered']} shown after filter)`);
2670 ssid_status_element.html(`${json['recordsTotal']} SSIDs`);
2684 lengthChange: false,
2686 loadingIndicator: true,
2689 url: local_uri_prefix + "phy/phy80211/ssids/views/ssids.json",
2691 json: JSON.stringify(json)
2698 { className: "dt_td", targets: "_all" },
2700 order: [ [ 0, "desc" ] ],
2701 createdRow: function(row, data, index) {
2702 row.id = data['dot11.ssidgroup.hash'];
2704 drawCallback: function(settings) {
2705 var dt = this.api();
2709 }).every(function(rowIdx, tableLoop, rowLoop) {
2710 for (var c in SsidColumns) {
2711 var col = SsidColumns[c];
2713 if (!('kismetdrawfunc') in col)
2717 col.kismetdrawfunc(col, dt, this);
2726 var ssid_dt = ssid_element.DataTable();
2728 // Restore the order
2729 var saved_order = kismet.getStorage('kismet.base.ssidtable.order', "");
2730 if (saved_order !== "")
2731 ssid_dt.order(JSON.parse(saved_order));
2733 // Restore the search
2734 var saved_search = kismet.getStorage('kismet.base.ssidtable.search', "");
2735 if (saved_search !== "")
2736 ssid_dt.search(JSON.parse(saved_search));
2738 // Set an onclick handler to spawn the device details dialog
2739 $('tbody', ssid_element).on('click', 'tr', function () {
2740 SsidDetailWindow(this.id);
2743 $('tbody', ssid_element)
2744 .on( 'mouseenter', 'td', function () {
2745 var ssid_dt = ssid_element.DataTable();
2747 if (typeof(ssid_dt.cell(this).index()) === 'Undefined')
2750 var colIdx = ssid_dt.cell(this).index().column;
2751 var rowIdx = ssid_dt.cell(this).index().row;
2753 // Remove from all cells
2754 $(ssid_dt.cells().nodes()).removeClass('kismet-highlight');
2755 // Highlight the td in this row
2756 $('td', ssid_dt.row(rowIdx).nodes()).addClass('kismet-highlight');
2762 kismet_ui_tabpane.AddTab({
2765 createCallback: function(div) {
2768 class: 'resize_wrapper',
2773 class: 'stripe hover nowrap',
2781 style: 'padding-bottom: 10px;',
2785 ssid_element = $('#ssids', div);
2786 ssid_status_element = $('#ssids_status', div);
2788 InitializeSsidTable();
2789 ScheduleSsidSummary();
2794 AddSsidColumn('col_ssid', {
2796 field: 'dot11.ssidgroup.ssid',
2799 renderfunc: function(d, t, r, m) {
2801 return "<i>Cloaked or Empty SSID</i>";
2802 else if (/^ +$/.test(d))
2803 return "<i>Blank SSID</i>";
2808 AddSsidColumn('col_ssid_len', {
2810 field: 'dot11.ssidgroup.ssid_len',
2811 name: 'SSID Length',
2813 sClass: "dt-body-right",
2816 AddSsidColumn('column_time', {
2817 sTitle: 'Last Seen',
2818 field: 'dot11.ssidgroup.last_time',
2819 description: 'Last-seen time',
2821 renderfunc: function(d, t, r, m) {
2822 return kismet_ui_base.renderLastTime(d, t, r, m);
2829 AddSsidColumn('column_first_time', {
2830 sTitle: 'First Seen',
2831 field: 'dot11.ssidgroup.first_time',
2832 description: 'First-seen time',
2833 renderfunc: function(d, t, r, m) {
2834 return kismet_ui_base.renderLastTime(d, t, r, m);
2841 AddSsidColumn('column_crypt', {
2842 sTitle: 'Encryption',
2843 field: 'dot11.ssidgroup.crypt_set',
2844 description: 'Encryption',
2845 renderfunc: function(d, t, r, m) {
2846 return CryptToHumanReadable(d);
2852 AddSsidColumn('column_probing', {
2853 sTitle: '# Probing',
2854 field: 'dot11.ssidgroup.probing_devices_len',
2855 description: 'Count of probing devices',
2858 sClass: "dt-body-right",
2861 AddSsidColumn('column_responding', {
2862 sTitle: '# Responding',
2863 field: 'dot11.ssidgroup.responding_devices_len',
2864 description: 'Count of responding devices',
2867 sClass: "dt-body-right",
2870 AddSsidColumn('column_advertising', {
2871 sTitle: '# Advertising',
2872 field: 'dot11.ssidgroup.advertising_devices_len',
2873 description: 'Count of advertising devices',
2876 sClass: "dt-body-right",
2879 AddSsidColumn('column_hash', {
2881 field: 'dot11.ssidgroup.hash',
2882 description: 'Hash',
2890 var SsidDetails = new Array();
2892 const AddSsidDetail = function(id, title, pos, options) {
2893 kismet_ui.AddDetail(SsidDetails, id, title, pos, options);
2896 export const SsidDetailWindow = (key) => {
2897 kismet_ui.DetailWindow(key, "SSID Details",
2902 function(panel, options) {
2903 var content = panel.content;
2905 panel.active = true;
2907 window['storage_ssid_' + key] = {};
2908 window['storage_ssid_' + key]['foobar'] = 'bar';
2910 panel.updater = function() {
2911 if (kismet_ui.window_visible) {
2912 $.get(local_uri_prefix + "phy/phy80211/ssids/by-hash/" + key + "/ssid.json")
2913 .done(function(fulldata) {
2914 fulldata = kismet.sanitizeObject(fulldata);
2916 $('.loadoops', panel.content).hide();
2918 panel.headerTitle("SSID: " + fulldata['dot11.ssidgroup.ssid']);
2920 var accordion = $('div#accordion', content);
2922 if (accordion.length == 0) {
2923 accordion = $('<div />', {
2927 content.append(accordion);
2930 var detailslist = SsidDetails;
2932 for (var dii in detailslist) {
2933 var di = detailslist[dii];
2936 if ('filter' in di.options &&
2937 typeof(di.options.filter) === 'function') {
2938 if (di.options.filter(fulldata) == false) {
2943 var vheader = $('h3#header_' + di.id, accordion);
2945 if (vheader.length == 0) {
2946 vheader = $('<h3>', {
2947 id: "header_" + di.id,
2951 accordion.append(vheader);
2954 var vcontent = $('div#' + di.id, accordion);
2956 if (vcontent.length == 0) {
2957 vcontent = $('<div>', {
2960 accordion.append(vcontent);
2963 // Do we have pre-rendered content?
2964 if ('render' in di.options &&
2965 typeof(di.options.render) === 'function') {
2966 vcontent.html(di.options.render(fulldata));
2969 if ('draw' in di.options && typeof(di.options.draw) === 'function') {
2970 di.options.draw(fulldata, vcontent, options, 'storage_ssid_' + key);
2973 if ('finalize' in di.options &&
2974 typeof(di.options.finalize) === 'function') {
2975 di.options.finalize(fulldata, vcontent, options, 'storage_ssid_' + key);
2978 accordion.accordion({ heightStyle: 'fill' });
2980 .fail(function(jqxhr, texterror) {
2981 content.html("<div class=\"loadoops\" style=\"padding: 10px;\"><h1>Oops!</h1><p>An error occurred loading ssid details for key <code>" + key +
2982 "</code>: HTTP code <code>" + jqxhr.status + "</code>, " + texterror + "</div>");
2984 .always(function() {
2985 panel.timerid = setTimeout(function() { panel.updater(); }, 1000);
2988 panel.timerid = setTimeout(function() { panel.updater(); }, 1000);
2996 function(panel, options) {
2997 clearTimeout(panel.timerid);
2998 panel.active = false;
2999 window['storage_ssid_' + key] = {};
3003 /* Custom device details for dot11 data */
3004 AddSsidDetail("ssid", "Wi-Fi (802.11) SSIDs", 0, {
3005 draw: function(data, target, options, storage) {
3006 target.devicedata(data, {
3007 "id": "ssiddetails",
3010 field: 'dot11.ssidgroup.ssid',
3013 draw: function(opts) {
3014 if (typeof(opts['value']) === 'undefined')
3015 return '<i>None</i>';
3016 if (opts['value'].replace(/\s/g, '').length == 0)
3017 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
3019 return `${opts['value']} <i>(${data['dot11.ssidgroup.ssid_len']} characters)</i>`;
3021 help: "SSID advertised or probed by one or more devices",
3024 field: "dot11.ssidgroup.first_time",
3025 title: "First Seen",
3027 draw: kismet_ui.RenderTrimmedTime,
3030 field: "dot11.ssidgroup.last_time",
3033 draw: kismet_ui.RenderTrimmedTime,
3036 field: "dot11.ssidgroup.crypt_set",
3038 title: "Encryption",
3039 draw: function(opts) {
3040 return CryptToHumanReadable(opts['value']);
3042 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.",
3048 field: "advertising_meta",
3049 id: "advertising header",
3050 filter: function(opts) {
3052 return (Object.keys(opts['data']['dot11.ssidgroup.advertising_devices']).length >= 1);
3057 title: '<b class="k_padding_title">Advertising APs</b>',
3058 help: "Advertising access points have sent a beacon packet with this SSID.",
3062 field: "dot11.ssidgroup.advertising_devices",
3063 id: "advertising_list",
3065 filter: function(opts) {
3067 return (Object.keys(opts['data']['dot11.ssidgroup.advertising_devices']).length >= 1);
3074 iterateTitle: function(opts) {
3075 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>';
3077 draw: function(opts) {
3078 var tb = $('.expander', opts['cell']).simpleexpand();
3082 // Dummy field to get us a nested area since we don't have
3083 // a real field in the client list since it's just a key-val
3084 // not a nested object
3088 draw: function(opts) {
3089 return `<div class="ssid_content_advertising" id="ssid_content_advertising_${opts['base']}">`;
3098 field: "responding_meta",
3099 id: "responding header",
3100 filter: function(opts) {
3102 return (Object.keys(opts['data']['dot11.ssidgroup.responding_devices']).length >= 1);
3107 title: '<b class="k_padding_title">Responding APs</b>',
3108 help: "Responding access points have sent a probe response with this SSID.",
3112 field: "dot11.ssidgroup.responding_devices",
3113 id: "responding_list",
3115 filter: function(opts) {
3117 return (Object.keys(opts['data']['dot11.ssidgroup.responding_devices']).length >= 1);
3124 iterateTitle: function(opts) {
3125 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>';
3127 draw: function(opts) {
3128 var tb = $('.expander', opts['cell']).simpleexpand();
3132 // Dummy field to get us a nested area since we don't have
3133 // a real field in the client list since it's just a key-val
3134 // not a nested object
3138 draw: function(opts) {
3139 return `<div class="ssid_content_responding" id="ssid_content_responding_${opts['base']}">`;
3148 field: "probing_meta",
3149 id: "probing header",
3150 filter: function(opts) {
3152 return (Object.keys(opts['data']['dot11.ssidgroup.probing_devices']).length >= 1);
3157 title: '<b class="k_padding_title">Probing devices</b>',
3158 help: "Probing devices have sent a probe request or association request with this SSID",
3162 field: "dot11.ssidgroup.probing_devices",
3165 filter: function(opts) {
3167 return (Object.keys(opts['data']['dot11.ssidgroup.probing_devices']).length >= 1);
3174 iterateTitle: function(opts) {
3175 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>';
3177 draw: function(opts) {
3178 var tb = $('.expander', opts['cell']).simpleexpand();
3182 // Dummy field to get us a nested area since we don't have
3183 // a real field in the client list since it's just a key-val
3184 // not a nested object
3188 draw: function(opts) {
3189 return `<div class="ssid_content_probing" id="ssid_content_probing_${opts['base']}">`;
3199 finalize: function(data, target, options, storage) {
3202 data['dot11.ssidgroup.advertising_devices'].forEach(device => combokeys[device] = 1);
3203 data['dot11.ssidgroup.probing_devices'].forEach(device => combokeys[device] = 1);
3204 data['dot11.ssidgroup.responding_devices'].forEach(device => combokeys[device] = 1);
3207 devices: Object.keys(combokeys),
3209 'kismet.device.base.macaddr',
3210 'kismet.device.base.key',
3211 'kismet.device.base.type',
3212 'kismet.device.base.commonname',
3213 'kismet.device.base.manuf',
3214 'dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.ssid',
3215 'dot11.device/dot11.device.advertised_ssid_map',
3216 'dot11.device/dot11.device.responded_ssid_map',
3220 var postdata = `json=${encodeURIComponent(JSON.stringify(param))}`;
3222 $.post(`${local_uri_prefix}devices/multikey/as-object/devices.json`, postdata, "json")
3223 .done(function(aggcli) {
3224 aggcli = kismet.sanitizeObject(aggcli);
3226 data['dot11.ssidgroup.advertising_devices'].forEach(function(v) {
3230 var dev = aggcli[v];
3235 dev['dot11.device.advertised_ssid_map'].forEach(function (s) {
3236 if (s['dot11.advertisedssid.ssid_hash'] == data['dot11.ssidgroup.hash']) {
3246 crypttxt = CryptToHumanReadable(devssid['dot11.advertisedssid.crypt_set']);
3251 var titlehtml = `${dev['kismet.device.base.commonname']} - ${dev['kismet.device.base.macaddr']}`;
3253 if (crypttxt != null)
3254 titlehtml = `${titlehtml} - ${crypttxt}`;
3256 titlehtml = kismet.censorMAC(titlehtml);
3258 $(`#ssid_expander_advertising_${v}`).html(titlehtml);
3260 $(`#ssid_content_advertising_${v}`).devicedata(dev, {
3261 id: "ssid_adv_data",
3264 field: 'kismet.device.base.key',
3265 title: "Advertising Device",
3266 draw: function(opts) {
3267 return `<a href="#" onclick="kismet_ui.DeviceDetailWindow('${opts['data']['kismet.device.base.key']}')">View Device Details</a>`;
3271 field: "kismet.device.base.macaddr",
3273 draw: function(opts) {
3274 return kismet.censorMAC(`${opts['data']['kismet.device.base.macaddr']} (${opts['data']['kismet.device.base.manuf']})`);
3278 field: 'kismet.device.base.commonname',
3281 empty: "<i>None</i>",
3282 draw: function(opts) {
3283 return kismet.censorMAC(opts['value']);
3287 field: 'kismet.device.base.type',
3290 empty: "<i>Unknown</i>",
3293 field: 'meta_advertised_crypto',
3294 title: "Advertised encryption",
3296 draw: function(opts) {
3298 return CryptToHumanReadable(devssid['dot11.advertisedssid.crypt_set']);
3300 return '<i>Unknown</i>';
3303 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.",
3306 field: 'meta_advertised_firsttime',
3307 title: "First advertised",
3309 draw: function(opts) {
3311 return kismet_ui.RenderTrimmedTime({value: devssid['dot11.advertisedssid.first_time']});
3313 return '<i>Unknown</i>';
3318 field: 'meta_advertised_lasttime',
3319 title: "Last advertised",
3321 draw: function(opts) {
3323 return kismet_ui.RenderTrimmedTime({value: devssid['dot11.advertisedssid.last_time']});
3325 return '<i>Unknown</i>';
3330 field: 'dot11.advertisedssid.ssid',
3331 title: "Last advertised SSID",
3333 draw: function(opts) {
3334 if (typeof(opts['value']) === 'undefined')
3335 return '<i>None</i>';
3336 if (opts['value'].replace(/\s/g, '').length == 0)
3337 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
3339 return opts['value'];
3347 data['dot11.ssidgroup.responding_devices'].forEach(function(v) {
3351 var dev = aggcli[v];
3356 dev['dot11.device.responded_ssid_map'].forEach(function (s) {
3357 if (s['dot11.advertisedssid.ssid_hash'] == data['dot11.ssidgroup.hash']) {
3367 crypttxt = CryptToHumanReadable(devssid['dot11.advertisedssid.crypt_set']);
3372 var titlehtml = `${dev['kismet.device.base.commonname']} - ${dev['kismet.device.base.macaddr']}`;
3374 if (crypttxt != null)
3375 titlehtml = `${titlehtml} - ${crypttxt}`;
3377 titlehtml = kismet.censorMAC(titlehtml);
3379 $(`#ssid_expander_responding_${v}`).html(titlehtml);
3381 $(`#ssid_content_responding_${v}`).devicedata(dev, {
3382 id: "ssid_adv_data",
3385 field: 'kismet.device.base.key',
3386 title: "Responding Device",
3387 draw: function(opts) {
3388 return `<a href="#" onclick="kismet_ui.DeviceDetailWindow('${opts['data']['kismet.device.base.key']}')">View Device Details</a>`;
3392 field: "kismet.device.base.macaddr",
3394 draw: function(opts) {
3395 return kismet.censorMAC(`${opts['data']['kismet.device.base.macaddr']} (${opts['data']['kismet.device.base.manuf']})`);
3400 field: 'kismet.device.base.commonname',
3402 empty: "<i>None</i>",
3403 draw: function(opts) {
3404 return kismet.censorMAC(opts['value']);
3409 field: 'kismet.device.base.type',
3411 empty: "<i>Unknown</i>",
3414 field: 'meta_advertised_crypto',
3415 title: "Advertised encryption",
3417 draw: function(opts) {
3419 return CryptToHumanReadable(devssid['dot11.advertisedssid.crypt_set']);
3421 return '<i>Unknown</i>';
3424 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.",
3427 field: 'meta_advertised_firsttime',
3428 title: "First responded",
3430 draw: function(opts) {
3432 return kismet_ui.RenderTrimmedTime({value: devssid['dot11.advertisedssid.first_time']});
3434 return '<i>Unknown</i>';
3439 field: 'meta_advertised_lasttime',
3440 title: "Last responded",
3442 draw: function(opts) {
3444 return kismet_ui.RenderTrimmedTime({value: devssid['dot11.advertisedssid.last_time']});
3446 return '<i>Unknown</i>';
3452 field: 'dot11.advertisedssid.ssid',
3453 title: "Last advertised SSID",
3454 draw: function(opts) {
3455 if (typeof(opts['value']) === 'undefined')
3456 return '<i>None</i>';
3457 if (opts['value'].replace(/\s/g, '').length == 0)
3458 return '<i>Cloaked / Empty (' + opts['value'].length + ' spaces)</i>';
3460 return opts['value'];
3468 data['dot11.ssidgroup.probing_devices'].forEach(function(v) {
3472 var dev = aggcli[v];
3474 $(`#ssid_expander_probing_${v}`).html(kismet.censorMAC(`${dev['kismet.device.base.commonname']} - ${dev['kismet.device.base.macaddr']}`));
3476 $(`#ssid_content_probing_${v}`).devicedata(dev, {
3477 id: "ssid_adv_data",
3480 field: 'kismet.device.base.key',
3481 title: "Probing Device",
3482 draw: function(opts) {
3483 return `<a href="#" onclick="kismet_ui.DeviceDetailWindow('${opts['data']['kismet.device.base.key']}')">View Device Details</a>`;
3487 field: "kismet.device.base.macaddr",
3489 draw: function(opts) {
3490 return kismet.censorMAC(`${opts['data']['kismet.device.base.macaddr']} (${opts['data']['kismet.device.base.manuf']})`);
3494 field: 'kismet.device.base.commonname',
3496 empty: "<i>None</i>",
3497 draw: function(opts) {
3498 return kismet.censorMAC(opts['value']);
3502 field: 'kismet.device.base.type',
3504 empty: "<i>Unknown</i>",