diff --git a/doc/userguide/output/eve/eve-json-format.rst b/doc/userguide/output/eve/eve-json-format.rst index c5569c852f5b..b1f887313c07 100644 --- a/doc/userguide/output/eve/eve-json-format.rst +++ b/doc/userguide/output/eve/eve-json-format.rst @@ -3086,6 +3086,12 @@ The default DHCP logging level only logs enough information to map a MAC address to an IP address. Enable extended mode to log all DHCP message types in full detail. +When a DHCP message carries the Option Overload entry (option 52, +RFC 2132), the BOOTP ``sname`` and ``file`` header fields are used as +extra option storage. Suricata parses any options found in those +continuation areas alongside the standard options block, so values +carried in either area show up in the same EVE fields below. + Fields ~~~~~~ diff --git a/rust/src/dhcp/dhcp.rs b/rust/src/dhcp/dhcp.rs index 731e78b9a816..18cfc2da537a 100644 --- a/rust/src/dhcp/dhcp.rs +++ b/rust/src/dhcp/dhcp.rs @@ -42,6 +42,7 @@ pub const DHCP_OPT_DNS_SERVER: u8 = 6; pub const DHCP_OPT_HOSTNAME: u8 = 12; pub const DHCP_OPT_REQUESTED_IP: u8 = 50; pub const DHCP_OPT_ADDRESS_TIME: u8 = 51; +pub const DHCP_OPT_OVERLOAD: u8 = 52; pub const DHCP_OPT_TYPE: u8 = 53; //unused pub const DHCP_OPT_SERVER_ID: u8 = 54; pub const DHCP_OPT_PARAMETER_LIST: u8 = 55; diff --git a/rust/src/dhcp/parser.rs b/rust/src/dhcp/parser.rs index a33b6400dd32..60ab7559b863 100644 --- a/rust/src/dhcp/parser.rs +++ b/rust/src/dhcp/parser.rs @@ -195,6 +195,36 @@ pub fn parse_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { } } +fn find_overload_value(options: &[DHCPOption]) -> u8 { + for opt in options { + if opt.code == DHCP_OPT_OVERLOAD { + if let DHCPOptionWrapper::Generic(ref g) = opt.option { + if !g.data.is_empty() { + return g.data[0]; + } + } + } + } + 0 +} + +fn parse_overloaded_field(field: &[u8], options: &mut Vec) { + let mut next = field; + while !next.is_empty() { + match parse_option(next) { + Ok((rem, option)) => { + let done = option.code == DHCP_OPT_END; + options.push(option); + next = rem; + if done { + break; + } + } + Err(_) => break, + } + } +} + pub fn parse_dhcp(input: &[u8]) -> IResult<&[u8], DHCPMessage> { match parse_header(input) { Ok((rem, header)) => { @@ -218,6 +248,14 @@ pub fn parse_dhcp(input: &[u8]) -> IResult<&[u8], DHCPMessage> { } } } + let overload = find_overload_value(&options); + if overload & 0x01 != 0 { + parse_overloaded_field(&header.bootfilename, &mut options); + } + if overload & 0x02 != 0 { + parse_overloaded_field(&header.servername, &mut options); + } + let message = DHCPMessage { header, options, @@ -274,6 +312,144 @@ mod tests { assert_eq!(message.options[4].code, DHCP_OPT_END); } + #[test] + fn test_parse_sname_overload() { + let mut buf: Vec = Vec::new(); + buf.extend_from_slice(&[BOOTP_REPLY, 1, 6, 0]); + buf.extend_from_slice(&[0x24, 0x59, 0x3b, 0x3e, 0, 0, 0, 0]); + buf.extend_from_slice(&[0; 16]); + buf.extend_from_slice(&[0; 16]); + let mut sname = vec![ + 0x06, 0x04, 10, 100, 0, 2, 0x03, 0x04, 10, 100, 0, 2, 0x0f, 0x09, b'e', b'v', b'i', + b'l', b'.', b'c', b'o', b'r', b'p', 0xff, + ]; + sname.resize(64, 0); + buf.extend_from_slice(&sname); + buf.extend_from_slice(&[0; 128]); + buf.extend_from_slice(&[0x63, 0x82, 0x53, 0x63]); + buf.extend_from_slice(&[0x35, 0x01, DHCP_TYPE_ACK, 0x34, 0x01, 0x02, 0xff]); + + let (_rem, message) = parse_dhcp(&buf).unwrap(); + assert!(!message.malformed_options); + assert!(!message.truncated_options); + + let codes: Vec = message.options.iter().map(|o| o.code).collect(); + assert!(codes.contains(&DHCP_OPT_OVERLOAD)); + assert!(codes.contains(&DHCP_OPT_DNS_SERVER)); + assert!(codes.contains(&DHCP_OPT_ROUTERS)); + assert!(codes.contains(&15)); + + let dns = message + .options + .iter() + .find(|o| o.code == DHCP_OPT_DNS_SERVER) + .expect("DNS option missing"); + if let DHCPOptionWrapper::Generic(ref g) = dns.option { + assert_eq!(g.data, vec![10, 100, 0, 2]); + } else { + panic!("DNS option had wrong wrapper variant"); + } + } + + #[test] + fn test_parse_no_overload_leaves_sname_alone() { + let mut buf: Vec = Vec::new(); + buf.extend_from_slice(&[BOOTP_REPLY, 1, 6, 0]); + buf.extend_from_slice(&[0; 8]); + buf.extend_from_slice(&[0; 16]); + buf.extend_from_slice(&[0; 16]); + let mut sname = vec![0x06, 0x04, 10, 100, 0, 2, 0xff]; + sname.resize(64, 0); + buf.extend_from_slice(&sname); + buf.extend_from_slice(&[0; 128]); + buf.extend_from_slice(&[0x63, 0x82, 0x53, 0x63]); + buf.extend_from_slice(&[0x35, 0x01, DHCP_TYPE_ACK, 0xff]); + + let (_rem, message) = parse_dhcp(&buf).unwrap(); + let codes: Vec = message.options.iter().map(|o| o.code).collect(); + assert!(!codes.contains(&DHCP_OPT_DNS_SERVER)); + } + + #[test] + fn test_parse_file_overload() { + let mut buf: Vec = Vec::new(); + buf.extend_from_slice(&[BOOTP_REPLY, 1, 6, 0]); + buf.extend_from_slice(&[0; 8]); + buf.extend_from_slice(&[0; 16]); + buf.extend_from_slice(&[0; 16]); + let mut sname = b"tftp.example.com\0".to_vec(); + sname.resize(64, 0); + buf.extend_from_slice(&sname); + let mut file = vec![12, 0x05, b'h', b'o', b's', b't', b'1', 0xff]; + file.resize(128, 0); + buf.extend_from_slice(&file); + buf.extend_from_slice(&[0x63, 0x82, 0x53, 0x63]); + buf.extend_from_slice(&[0x35, 0x01, DHCP_TYPE_ACK, 0x34, 0x01, 0x01, 0xff]); + + let (_rem, message) = parse_dhcp(&buf).unwrap(); + let codes: Vec = message.options.iter().map(|o| o.code).collect(); + assert!(codes.contains(&DHCP_OPT_HOSTNAME)); + assert!(!codes.contains(&DHCP_OPT_DNS_SERVER)); + assert!(!codes.contains(&DHCP_OPT_ROUTERS)); + } + + #[test] + fn test_parse_overload_both() { + let mut buf: Vec = Vec::new(); + buf.extend_from_slice(&[BOOTP_REPLY, 1, 6, 0]); + buf.extend_from_slice(&[0; 8]); + buf.extend_from_slice(&[0; 16]); + buf.extend_from_slice(&[0; 16]); + let mut sname = vec![0x06, 0x04, 9, 9, 9, 9, 0xff]; + sname.resize(64, 0); + buf.extend_from_slice(&sname); + let mut file = vec![0x03, 0x04, 8, 8, 8, 8, 0xff]; + file.resize(128, 0); + buf.extend_from_slice(&file); + buf.extend_from_slice(&[0x63, 0x82, 0x53, 0x63]); + buf.extend_from_slice(&[0x35, 0x01, DHCP_TYPE_ACK, 0x34, 0x01, 0x03, 0xff]); + + let (_rem, message) = parse_dhcp(&buf).unwrap(); + let dns = message + .options + .iter() + .find(|o| o.code == DHCP_OPT_DNS_SERVER) + .unwrap(); + let router = message + .options + .iter() + .find(|o| o.code == DHCP_OPT_ROUTERS) + .unwrap(); + if let DHCPOptionWrapper::Generic(ref g) = dns.option { + assert_eq!(g.data, vec![9, 9, 9, 9]); + } else { + panic!() + } + if let DHCPOptionWrapper::Generic(ref g) = router.option { + assert_eq!(g.data, vec![8, 8, 8, 8]); + } else { + panic!() + } + } + + #[test] + fn test_parse_overload_garbage_field_is_safe() { + let mut buf: Vec = Vec::new(); + buf.extend_from_slice(&[BOOTP_REPLY, 1, 6, 0]); + buf.extend_from_slice(&[0; 8]); + buf.extend_from_slice(&[0; 16]); + buf.extend_from_slice(&[0; 16]); + let mut sname = vec![0x06, 0xfe, 0xaa, 0xbb]; + sname.resize(64, 0); + buf.extend_from_slice(&sname); + buf.extend_from_slice(&[0; 128]); + buf.extend_from_slice(&[0x63, 0x82, 0x53, 0x63]); + buf.extend_from_slice(&[0x35, 0x01, DHCP_TYPE_ACK, 0x34, 0x01, 0x02, 0xff]); + + let result = parse_dhcp(&buf); + assert!(result.is_ok()); + } + #[test] fn test_parse_client_id_too_short() { // Length field of 0.