From ba5e8502647f6c5c5708f72498895037ccaa24ba Mon Sep 17 00:00:00 2001 From: Giuseppe Longo Date: Sat, 7 Mar 2026 11:04:15 +0100 Subject: [PATCH] sip: make pattern matching more robust SIP and SSDP share method names like NOTIFY and SUBSCRIBE, causing SSDP traffic to be misidentified as SIP. Add a probing parser callback that checks for "SIP/" in the payload before accepting a pattern match. Example of a misidentified flow before the fix: {"timestamp":"2014-02-27T19:44:43.164211+0100","flow_id":986757542077835,"event_type":"flow","src_ip":"192.168.1.1","src_port":9489,"dest_ip":"239.255.255.250 ","dest_port":1900,"ip_v":4,"proto":"UDP","app_proto":"sip","flow":{"..."}} After the fix: {"timestamp":"2014-02-27T19:44:43.164211+0100","flow_id":986757542077835,"event_type":"flow","src_ip":"192.168.1.1","src_port":9489,"dest_ip":"239.255.255.250 ","dest_port":1900,"ip_v":4,"proto":"UDP","app_proto":"failed","flow":{"..."}} Ticket #8355 --- rust/src/sip/parser.rs | 32 ++++++++++++++++++++-- rust/src/sip/sip.rs | 60 +++++++++++++++++++++++++++++++----------- 2 files changed, 74 insertions(+), 18 deletions(-) diff --git a/rust/src/sip/parser.rs b/rust/src/sip/parser.rs index d6ed09f882..ae7a0b72c6 100644 --- a/rust/src/sip/parser.rs +++ b/rust/src/sip/parser.rs @@ -1,4 +1,4 @@ -/* Copyright (C) 2019-2022 Open Information Security Foundation +/* Copyright (C) 2019-2026 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -18,9 +18,10 @@ // written by Giuseppe Longo use crate::sdp::parser::{sdp_parse_message, SdpMessage}; -use nom8::bytes::streaming::{tag, take, take_while, take_while1}; +use nom8::bytes::streaming::{tag, take, take_until, take_while, take_while1}; use nom8::character::streaming::{char, crlf}; use nom8::combinator::{map, map_res, opt}; +use nom8::error::{Error, ErrorKind}; use nom8::sequence::delimited; use nom8::{AsChar, Err, IResult, Needed, Parser}; use std; @@ -112,6 +113,21 @@ fn expand_header_name(h: &str) -> &str { } } +pub fn sip_probe_protocol(input: &[u8]) -> IResult<&[u8], ()> { + let len = std::cmp::min(input.len(), 65536); + let i = &input[..len]; + + if tag::<_, _, Error<&[u8]>>("SIP/").parse(i).is_ok() { + return Ok((input, ())); + } + + if take_until::<_, _, Error<&[u8]>>("SIP/").parse(i).is_ok() { + Ok((input, ())) + } else { + Err(Err::Error(Error::new(i, ErrorKind::Tag))) + } +} + pub fn parse_request(oi: &[u8]) -> IResult<&[u8], Request> { let (i, method) = parse_method(oi)?; let (i, _) = char(' ').parse(i)?; @@ -364,6 +380,18 @@ mod tests { assert_eq!(result, "SIP/2.0"); } + #[test] + fn test_probe_sip_request() { + let buf = b"REGISTER sip:sip.example.com SIP/2.0\r\n"; + assert!(sip_probe_protocol(buf).is_ok()); + } + + #[test] + fn test_probe_sip_response() { + let buf = b"SIP/2.0 200 OK\r\n"; + assert!(sip_probe_protocol(buf).is_ok()); + } + #[test] fn test_header_multi_value() { let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\ diff --git a/rust/src/sip/sip.rs b/rust/src/sip/sip.rs index bcec343d0f..eccdca90ae 100755 --- a/rust/src/sip/sip.rs +++ b/rust/src/sip/sip.rs @@ -1,4 +1,4 @@ -/* Copyright (C) 2019-2022 Open Information Security Foundation +/* Copyright (C) 2019-2026 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -20,7 +20,8 @@ use crate::applayer::{self, *}; use crate::core; use crate::core::{ - sc_app_layer_parser_trigger_raw_stream_inspection, ALPROTO_UNKNOWN, IPPROTO_TCP, IPPROTO_UDP, + sc_app_layer_parser_trigger_raw_stream_inspection, ALPROTO_FAILED, ALPROTO_UNKNOWN, + IPPROTO_TCP, IPPROTO_UDP, }; use crate::direction::Direction; use crate::flow::Flow; @@ -34,6 +35,7 @@ use suricata_sys::sys::{ AppLayerParserState, AppProto, SCAppLayerParserConfParserEnabled, SCAppLayerParserRegisterLogger, SCAppLayerParserStateIssetFlag, SCAppLayerProtoDetectConfProtoDetectionEnabled, SCAppLayerProtoDetectPMRegisterPatternCS, + SCAppLayerProtoDetectPMRegisterPatternCSwPP, }; // app-layer-frame-documentation tag start: FrameType enum @@ -492,21 +494,41 @@ unsafe extern "C" fn sip_parse_response_tcp( state.parse_response_tcp(flow, stream_slice) } +unsafe extern "C" fn sip_probing_parser( + _f: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + if input.is_null() || input_len == 0 { + return ALPROTO_UNKNOWN; + } + let buf = std::slice::from_raw_parts(input, input_len as usize); + match sip_probe_protocol(buf) { + Ok(_) => ALPROTO_SIP, + Err(Err::Incomplete(_)) => ALPROTO_UNKNOWN, + Err(_) => ALPROTO_FAILED, + } +} + fn register_pattern_probe(proto: u8) -> i8 { + let methods_with_probe: Vec<&str> = vec![ + "ACK\0", + "INFO\0", + "NOTIFY\0", + "SUBSCRIBE\0", + "OPTIONS\0", + "UPDATE\0", + ]; + let methods: Vec<&str> = vec![ "REGISTER\0", "INVITE\0", - "ACK\0", "BYE\0", "CANCEL\0", "REFER\0", "PRACK\0", - "SUBSCRIBE\0", - "NOTIFY\0", "PUBLISH\0", "MESSAGE\0", - "INFO\0", ]; + let mut r = 0; unsafe { for method in methods { @@ -520,6 +542,22 @@ fn register_pattern_probe(proto: u8) -> i8 { Direction::ToServer as u8, ); } + + for method in methods_with_probe { + let depth = (method.len() - 1) as u16; + r |= SCAppLayerProtoDetectPMRegisterPatternCSwPP( + proto, + ALPROTO_SIP, + method.as_ptr() as *const std::os::raw::c_char, + depth, + 0, + Direction::ToServer as u8, + Some(sip_probing_parser), + 0, + 0, + ); + } + r |= SCAppLayerProtoDetectPMRegisterPatternCS( proto, ALPROTO_SIP, @@ -528,16 +566,6 @@ fn register_pattern_probe(proto: u8) -> i8 { 0, Direction::ToClient as u8, ); - if proto == core::IPPROTO_UDP { - r |= SCAppLayerProtoDetectPMRegisterPatternCS( - proto, - ALPROTO_SIP, - "UPDATE\0".as_ptr() as *const std::os::raw::c_char, - "UPDATE".len() as u16, - 0, - Direction::ToServer as u8, - ); - } } if r == 0 {