Skip to content

Instantly share code, notes, and snippets.

@grigorescu
Created September 14, 2017 18:35
Show Gist options
  • Save grigorescu/f4275949ffbe2ad540500ced52cf777f to your computer and use it in GitHub Desktop.
Save grigorescu/f4275949ffbe2ad540500ced52cf777f to your computer and use it in GitHub Desktop.
##! A detection script for UDP DoS Flows
@load base/protocols/conn
module UDPDoS;
export {
redef enum Notice::Type += {
Detected,
};
## Number of bytes transferred before marking a connection as bulk
const size_threshold = 1024*1024 &redef; #1 megabytes
## Time during which we check whether a connection's size exceeds the
## :bro:see:`UDPDoS::size_threshold`.
const max_time = 2 min &redef;
## Raised when a Bulk data channel is detected.
##
## c: The connection pertaining to the Bulk data channel.
global connection_detected: event(c: connection, from: string);
## The initial criteria used to determine whether to start polling
## the connection for the :bro:see:`Bulk::size_threshold` to have
## been exceeded.
## c: The connection which may possibly be a Bulk data channel.
##
## Returns: true if the connection should be further polled for an
## exceeded :bro:see:`Bulk::size_threshold`, else false.
const dos_initial_criteria: function(c: connection): bool &redef;
const AFS_ports: set[port] = {4711/udp, 7000/udp, 7001/udp, 7003/udp};
const dos_ports: set[port] = {19/udp, 53/udp, 69/udp, 111/udp, 123/udp, 161/udp, 520/udp, 1434/udp, 1900/udp} &redef;
const run_on_all_ports = F &redef;
}
function is_bad(c: connection): bool
{
local relevant_size = Site::is_local_addr(c$id$orig_h) ? c$orig$num_bytes_ip : c$resp$num_bytes_ip;
local other_size = Site::is_local_addr(c$id$orig_h) ? c$resp$num_bytes_ip : c$orig$num_bytes_ip;
return (relevant_size > size_threshold && other_size > 100);
}
event ConnThreshold::bytes_threshold_crossed(c: connection, threshold: count, is_orig: bool)
{
if ( dos_initial_criteria(c) && is_bad(c)) {
event UDPDoS::connection_detected(c, "threshold");
}
}
function dos_initial_criteria(c: connection): bool
{
local id = c$id;
# If we aren't running on all ports, the port must be in dos_ports
if (run_on_all_ports == F && !(c$id$orig_p in dos_ports || c$id$resp_p in dos_ports)) {
return F;
}
# We only care about UDP
if (get_port_transport_proto(id$resp_p) != udp) {
return F;
}
local local_orig = Site::is_local_addr(id$orig_h);
local local_resp = Site::is_local_addr(id$resp_h);
# We want inside->out or outside->in
if (local_orig == local_resp) {
return F;
}
# But we don't care about RFC1918 address space
# Or neighbor nets
if (local_orig && (Site::is_private_addr(id$resp_h) || Site::is_neighbor_addr(id$resp_h))) {
return F;
}
if (local_resp && (Site::is_private_addr(id$orig_h) || Site::is_neighbor_addr(id$orig_h))) {
return F;
}
# https://en.wikipedia.org/wiki/QUIC
if (local_orig && id$resp_p == 443/udp) {
return F;
}
if (id$orig_p in AFS_ports && id$resp_p in AFS_ports) {
return F;
}
return T;
}
event udp_dos_timeout(c: connection)
{
if (!c?$duration) {
ConnThreshold::delete_bytes_threshold(c, size_threshold, F);
}
}
event new_connection(c: connection) &priority=-3
{
if ( dos_initial_criteria(c) ) {
ConnThreshold::set_bytes_threshold(c, size_threshold, F);
schedule max_time { udp_dos_timeout(c) };
}
}
event connection_state_remove(c: connection)
{
if ( dos_initial_criteria(c) && is_bad(c)) {
event UDPDoS::connection_detected(c, "remove");
}
}
event connection_detected(c: connection, from: string) {
local bytes = c$orig$num_bytes_ip + c$resp$num_bytes_ip;
local msg = fmt("UDP DoS between %s:%s and %s:%s - orig_bytes=%d resp_bytes=%d from=%s",
c$id$orig_h, c$id$orig_p, c$id$resp_h, c$id$resp_p, c$orig$num_bytes_ip, c$resp$num_bytes_ip, from);
local ident = cat(c$id$orig_h, "-", c$id$resp_h);
NOTICE([$note=Detected,
$msg=msg,
$conn=c,
$identifier=ident,
$sub=cat(bytes)
]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment