Skip to content

Commit

Permalink
show deauth frame counts #406
Browse files Browse the repository at this point in the history
  • Loading branch information
lennartkoopmann committed May 8, 2021
1 parent 26ef3d6 commit 04d6b01
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 13 deletions.
6 changes: 4 additions & 2 deletions src/main/java/horse/wtf/nzyme/database/Database.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import horse.wtf.nzyme.bandits.database.BanditMapper;
import horse.wtf.nzyme.bandits.database.ContactMapper;
import horse.wtf.nzyme.configuration.leader.LeaderConfiguration;
import horse.wtf.nzyme.dot11.deauth.db.DeauthenticationMonitorRecordingMapper;
import horse.wtf.nzyme.dot11.networks.beaconrate.BeaconRateMapper;
import horse.wtf.nzyme.dot11.networks.sentry.db.SentrySSIDMapper;
import horse.wtf.nzyme.dot11.networks.signalstrength.SignalIndexHistogramHistoryDBEntryMapper;
Expand Down Expand Up @@ -32,7 +33,7 @@ public class Database {
.toFormatter().withZoneUTC();

public static final DateTimeFormatter DATE_TIME_FORMATTER_WITH_ZONE = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm:ss.SSSZ")
.appendPattern("yyyy-MM-dd HH:mm:ssZ")
.toFormatter();

public static final DateTimeFormatter BUCKET_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
Expand All @@ -58,7 +59,8 @@ public void initializeAndMigrate() throws LiquibaseException {
.registerRowMapper(new BanditMapper())
.registerRowMapper(new BanditIdentifierMapper())
.registerRowMapper(new ContactMapper())
.registerRowMapper(new SentrySSIDMapper());
.registerRowMapper(new SentrySSIDMapper())
.registerRowMapper(new DeauthenticationMonitorRecordingMapper());

// Run migrations against underlying JDBC connection.
JdbcConnection connection = new JdbcConnection(jdbi.open().getConnection());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private void sync() {
nzyme.getDatabase().useHandle(handle -> handle.createUpdate(
"INSERT INTO deauth_monitor(total_frame_count, created_at) VALUES(:frame_count, :created_at)")
.bind("frame_count", count)
.bind("created_at", DateTime.now())
.bind("created_at", DateTime.now().withMillisOfSecond(0))
.execute()
);
} catch(Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* This file is part of nzyme.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/

package horse.wtf.nzyme.dot11.deauth.db;

import com.google.auto.value.AutoValue;
import org.joda.time.DateTime;

@AutoValue
public abstract class DeauthenticationMonitorRecording {

public abstract Long totalFrameCount();
public abstract DateTime createdAt();

public static DeauthenticationMonitorRecording create(Long totalFrameCount, DateTime createdAt) {
return builder()
.totalFrameCount(totalFrameCount)
.createdAt(createdAt)
.build();
}

public static Builder builder() {
return new AutoValue_DeauthenticationMonitorRecording.Builder();
}

@AutoValue.Builder
public abstract static class Builder {
public abstract Builder totalFrameCount(Long totalFrameCount);

public abstract Builder createdAt(DateTime createdAt);

public abstract DeauthenticationMonitorRecording build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* This file is part of nzyme.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/

package horse.wtf.nzyme.dot11.deauth.db;

import horse.wtf.nzyme.database.Database;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.statement.StatementContext;
import org.joda.time.DateTime;

import java.sql.ResultSet;
import java.sql.SQLException;

public class DeauthenticationMonitorRecordingMapper implements RowMapper<DeauthenticationMonitorRecording> {

@Override
public DeauthenticationMonitorRecording map(ResultSet rs, StatementContext ctx) throws SQLException {
return DeauthenticationMonitorRecording.create(
rs.getLong("total_frame_count"),
DateTime.parse(rs.getString("created_at"), Database.DATE_TIME_FORMATTER_WITH_ZONE)
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ private void syncDatabase() {
nzyme.getDatabase().useHandle(handle -> handle.createUpdate(
"INSERT INTO sentry_ssids(ssid, first_seen, last_seen) VALUES(:ssid, :first_seen, :last_seen)")
.bind("ssid", entry.ssid())
.bind("first_seen", entry.firstSeen())
.bind("last_seen", entry.lastSeen())
.bind("first_seen", entry.firstSeen().withMillisOfSecond(0))
.bind("last_seen", entry.lastSeen().withMillisOfSecond(0))
.execute()
);
} else {
nzyme.getDatabase().useHandle(handle -> handle.createUpdate(
"UPDATE sentry_ssids SET last_seen = :last_seen WHERE ssid = :ssid")
.bind("last_seen", entry.lastSeen())
.bind("last_seen", entry.lastSeen().withMillisOfSecond(0))
.bind("ssid", entry.ssid())
.execute()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import horse.wtf.nzyme.NzymeLeader;
import horse.wtf.nzyme.alerts.Alert;
import horse.wtf.nzyme.bandits.Contact;
import horse.wtf.nzyme.dot11.deauth.db.DeauthenticationMonitorRecording;
import horse.wtf.nzyme.dot11.probes.Dot11Probe;
import horse.wtf.nzyme.measurements.Measurement;
import horse.wtf.nzyme.measurements.MeasurementType;
Expand Down Expand Up @@ -55,6 +56,7 @@ public class DashboardResource {
private static final Logger LOG = LogManager.getLogger(DashboardResource.class);

private static final String MEASUREMENTS_QUERY = "SELECT * FROM measurements WHERE measurement_type = ? AND created_at > (current_timestamp at time zone 'UTC' - interval '1 day') ORDER BY created_at ASC;";
private static final String DEAUTH_QUERY = "SELECT * FROM deauth_monitor WHERE created_at > (current_timestamp - interval '1 day') ORDER BY created_at ASC;";

@Inject
private NzymeLeader nzyme;
Expand Down Expand Up @@ -84,6 +86,12 @@ public Response dashboard() {
.list()
));

Map<String, Long> deauthHistogram = buildDeauthHistogram(nzyme.getDatabase().withHandle(handle ->
handle.createQuery(DEAUTH_QUERY)
.mapTo(DeauthenticationMonitorRecording.class)
.list()
));

List<ContactResponse> contacts = Lists.newArrayList();

for (Contact contact : nzyme.getContactManager().findContacts(5, 0).values()) {
Expand Down Expand Up @@ -126,6 +134,7 @@ public Response dashboard() {
activeContacts,
systemHealthStatus,
frameThroughputHistogram,
deauthHistogram,
AlertsListResponse.create(alerts.size(), alerts),
contacts,
ProbesListResponse.create(probes.size(), probes)
Expand All @@ -134,12 +143,7 @@ public Response dashboard() {
}

private Map<String, Long> buildMeasurementHistogram(List<Measurement> measurements) {
Map<String, Long> clientCountHistogram = new TreeMap<>();

// Always have an x-axis for full 24 hours to avoid weird diagonal connections.
for (int i = 1; i < 24*60; i++) {
clientCountHistogram.put(DateTime.now(DateTimeZone.UTC).minusMinutes(i).withSecondOfMinute(0).withMillisOfSecond(0).toString(), 0L);
}
Map<String, Long> clientCountHistogram = buildEmptyDailyHistogram();

if (measurements != null && !measurements.isEmpty()) {
for (Measurement measurement : measurements) {
Expand All @@ -150,4 +154,27 @@ private Map<String, Long> buildMeasurementHistogram(List<Measurement> measuremen
return clientCountHistogram;
}

private Map<String, Long> buildDeauthHistogram(List<DeauthenticationMonitorRecording> recordings) {
Map<String, Long> deauthHistogram = buildEmptyDailyHistogram();

if (recordings != null && !recordings.isEmpty()) {
for (DeauthenticationMonitorRecording recording : recordings) {
deauthHistogram.put(recording.createdAt().withZone(DateTimeZone.UTC).withSecondOfMinute(0).withMillisOfSecond(0).toString(), recording.totalFrameCount());
}
}

return deauthHistogram;
}

private Map<String, Long> buildEmptyDailyHistogram() {
Map<String, Long> histo = new TreeMap<>();

// Always have an x-axis for full 24 hours to avoid weird diagonal connections.
for (int i = 1; i < 24*60; i++) {
histo.put(DateTime.now(DateTimeZone.UTC).minusMinutes(i).withSecondOfMinute(0).withMillisOfSecond(0).toString(), 0L);
}

return histo;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public abstract class DashboardResponse {
@JsonProperty("frame_throughput_histogram")
public abstract Map<String, Long> frameThroughputHistogram();

@JsonProperty("deauth_frame_histogram")
public abstract Map<String, Long> deauthFrameHistogram();

@JsonProperty("alerts")
public abstract AlertsListResponse alerts();

Expand All @@ -51,12 +54,13 @@ public abstract class DashboardResponse {
@JsonProperty("probes")
public abstract ProbesListResponse probes();

public static DashboardResponse create(long activeAlerts, long activeContacts, SystemStatus.HEALTH systemHealthStatus, Map<String, Long> frameThroughputHistogram, AlertsListResponse alerts, List<ContactResponse> contacts, ProbesListResponse probes) {
public static DashboardResponse create(long activeAlerts, long activeContacts, SystemStatus.HEALTH systemHealthStatus, Map<String, Long> frameThroughputHistogram, Map<String, Long> deauthFrameHistogram, AlertsListResponse alerts, List<ContactResponse> contacts, ProbesListResponse probes) {
return builder()
.activeAlerts(activeAlerts)
.activeContacts(activeContacts)
.systemHealthStatus(systemHealthStatus)
.frameThroughputHistogram(frameThroughputHistogram)
.deauthFrameHistogram(deauthFrameHistogram)
.alerts(alerts)
.contacts(contacts)
.probes(probes)
Expand All @@ -77,6 +81,8 @@ public abstract static class Builder {

public abstract Builder frameThroughputHistogram(Map<String, Long> frameThroughputHistogram);

public abstract Builder deauthFrameHistogram(Map<String, Long> deauthFrameHistogram);

public abstract Builder alerts(AlertsListResponse alerts);

public abstract Builder contacts(List<ContactResponse> contacts);
Expand Down
101 changes: 101 additions & 0 deletions web-interface/src/components/charts/SimpleBarChart.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';

import Plot from 'react-plotly.js';

class SimpleBarChart extends React.Component {

constructor(props) {
super(props);

this.state = {
data: props.data
};
}

componentWillReceiveProps(nextProps) {
this.setState({ data: nextProps.data });
}

render() {
let x = [];
let y = [];

const data = this.state.data;

let finalData = this.props.finalData;
if (!finalData) {
Object.keys(data).forEach(function (key) {
x.push(new Date(key));
y.push(data[key]);
});

finalData = [
{
x: x,
y: y,
type: "bar",
line: {width: 1, shape: "linear", color: "#2983fe"},
}
];
}

let marginLeft = this.props.customMarginLeft ? this.props.customMarginLeft : 25;
let marginRight = this.props.customMarginRight ? this.props.customMarginRight : 0;
let marginTop = this.props.customMarginTop ? this.props.customMarginTop : 25;
let marginBottom = this.props.customMarginBottom ? this.props.customMarginBottom : 50;

return (
<Plot
style={{ width: '100%', height: '100%' }}
data={finalData}
layout={{
height: this.props.height,
width: this.props.width,
font: {
family: "'Inconsolata', monospace",
size: 10,
color: this.props.textColor ? this.props.textColor : "#ffffff"
},
margin: { l: marginLeft, r: marginRight, b: marginBottom, t: marginTop, pad: 0 },
title: { text: this.props.title },
paper_bgcolor: this.props.backgroundColor ? this.props.backgroundColor : "#0c0d16",
plot_bgcolor: this.props.backgroundColor ? this.props.backgroundColor : "#0c0d16",
showlegend: false,
dragmode: false,
clickmode: "none",
hovermode: this.props.disableHover ? false : "x",
hoverlabel: {
font: { size: 11 },
namelength: -1
},
barmode: "stack",
boxgap: 0,
xaxis: {
fixedrange: true,
title: this.props.xaxistitle,
zerolinecolor: "#1f2134",
linecolor: "#11121f",
gridcolor: "#1f2134"
},
yaxis: {
fixedrange: true,
title: this.props.yaxistitle,
zerolinecolor: "#1f2134",
linecolor: "#11121f",
gridcolor: "#1f2134"
},
annotations: this.props.annotations ? this.props.annotations : [],
shapes: this.props.shapes
}}
config={{
displayModeBar: false,
autosize: true,
responsive: true
}}
/>
)
}

}

export default SimpleBarChart;
8 changes: 8 additions & 0 deletions web-interface/src/components/overview/Overview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import AlertsTable from "../alerts/AlertsTable";
import ContactsTable from "../bandits/ContactsTable";
import ProbesTable from "../system/ProbesTable";
import DashboardService from "../../services/DashboardService";
import DeauthFramesWidget from "./widgets/DeauthFramesWidget";

class Overview extends React.Component {

Expand Down Expand Up @@ -64,6 +65,13 @@ class Overview extends React.Component {
</div>
</div>

<div className="row mt-md-3">
<div className="col-md-12">
<h4>802.11 Deauthentication Frames</h4>
<DeauthFramesWidget deauthFrameHistogram={this.state.dashboard.deauth_frame_histogram} />
</div>
</div>

<div className="row mt-md-3">
<div className="col-md-12">
<h4>Alerts (last 5)</h4>
Expand Down
Loading

0 comments on commit 04d6b01

Please sign in to comment.