Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

detect/sip: add sticky buffers to match headers v4 #11330

Closed
wants to merge 12 commits into from
132 changes: 132 additions & 0 deletions doc/userguide/rules/sip-keywords.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ sip.stat_code Response
sip.stat_msg Response
sip.response_line Response
sip.protocol Both
sip.from Both
sip.to Both
sip.via Both
sip.user_agent Both
sip.content_type Both
sip.content_length Both
============================== ==================

sip.method
Expand Down Expand Up @@ -177,3 +183,129 @@ Example
::

sip.protocol; content:"SIP/2.0"

sip.from
--------

This keyword matches on the From field that can be present in SIP headers.

Syntax
~~~~~~

::

sip.from; content:<from>

Where <from> is the value of the From header.

Example
~~~~~~~

::

sip.from; content:"user"

sip.to
------

This keyword matches on the To field that can be present in SIP headers.

Syntax
~~~~~~

::

sip.to; content:<to>

Where <to> is the value of the To header.

Example
~~~~~~~

::

sip.to; content:"user"

sip.via
--------

This keyword matches on the Via field that can be present in SIP headers.

Syntax
~~~~~~

::

sip.via; content:<via>

Where <via> is the value of the Via header.

Example
~~~~~~~

::

sip.via; content:"SIP/2.0/UDP"

sip.user_agent
--------------

This keyword matches on the User-Agent field that can be present in SIP headers.

Syntax
~~~~~~

::

sip.user_agent; content:<user_agent>

Where <user_agent> is the value of the User-Agent header.

Example
~~~~~~~

::

sip.user_agent; content:"Asterisk"

sip.content_type
----------------

This keyword matches on the Content-Type field that can be present in SIP headers.

Syntax
~~~~~~

::

sip.content_type; content:<content_type>

Where <content_type> is the value of the Content-Type header.

Example
~~~~~~~

::

sip.content_type; content:"application/sdp"

sip.content_length
------------------

This keyword matches on the Content-Length field that can be present in SIP headers.

Syntax
~~~~~~

::

sip.content_length; content:<content_length>

Where <content_length> is the value of the Content-Length header.

Example
~~~~~~~

::

sip.content_length; content:"200"
6 changes: 6 additions & 0 deletions doc/userguide/upgrade.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ Major changes
- It is possible to see an increase of alerts, for the same rule-sets, if you
use many stream/payload rules, due to Suricata triggering TCP stream
reassembly earlier.
- The following sticky buffers for matching SIP headers have been implemented:
- sip.via
- sip.from
- sip.to
- sip.content_type
- sip.content_length

Upgrading 6.0 to 7.0
--------------------
Expand Down
32 changes: 31 additions & 1 deletion rust/src/sip/detect.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2019 Open Information Security Foundation
/* Copyright (C) 2024 Open Information Security Foundation
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: when updating copyright years, we add the current year beside the initial one:

Suggested change
/* Copyright (C) 2024 Open Information Security Foundation
/* Copyright (C) 2019-2024 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
Expand All @@ -19,6 +19,7 @@

use crate::core::Direction;
use crate::sip::sip::SIPTransaction;
use std::ffi::CStr;
use std::ptr;

#[no_mangle]
Expand Down Expand Up @@ -165,3 +166,32 @@ pub unsafe extern "C" fn rs_sip_tx_get_response_line(

return 0;
}

#[no_mangle]
pub unsafe extern "C" fn rs_sip_tx_get_header_value(
tx: &mut SIPTransaction, i: u32, direction: u8, strname: *const std::os::raw::c_char,
buffer: *mut *const u8, buffer_len: *mut u32,
) -> u8 {
let hname: &CStr = CStr::from_ptr(strname);
if let Ok(s) = hname.to_str() {
let headers = match direction.into() {
Direction::ToServer => tx.request.as_ref().map(|r| &r.headers),
Direction::ToClient => tx.response.as_ref().map(|r| &r.headers),
};
if let Some(headers) = headers {
if let Some(header_vals) = headers.get(s) {
if (i as usize) < header_vals.len() {
let value = &header_vals[i as usize];
*buffer = value.as_ptr();
*buffer_len = value.len() as u32;
return 1;
}
}
}
}

*buffer = ptr::null();
*buffer_len = 0;

return 0;
}
40 changes: 30 additions & 10 deletions rust/src/sip/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::sdp::parser::{sdp_parse_message, SdpMessage};
use nom7::bytes::streaming::{tag, take, take_while, take_while1};
use nom7::character::streaming::{char, crlf};
use nom7::character::{is_alphabetic, is_alphanumeric, is_digit, is_space};
use nom7::combinator::{map_res, opt};
use nom7::combinator::{map, map_res, opt};
use nom7::sequence::delimited;
use nom7::{Err, IResult, Needed};
use std;
Expand All @@ -38,7 +38,7 @@ pub struct Request {
pub method: String,
pub path: String,
pub version: String,
pub headers: HashMap<String, String>,
pub headers: HashMap<String, Vec<String>>,

pub request_line_len: u16,
pub headers_len: u16,
Expand All @@ -52,7 +52,7 @@ pub struct Response {
pub version: String,
pub code: String,
pub reason: String,

pub headers: HashMap<String, Vec<String>>,
pub response_line_len: u16,
pub headers_len: u16,
pub body_offset: u16,
Expand Down Expand Up @@ -97,6 +97,22 @@ fn is_header_value(b: u8) -> bool {
is_alphanumeric(b) || is_token_char(b) || b"\"#$&(),/;:<=>?@[]{}()^|~\\\t\n\r ".contains(&b)
}

fn expand_header_name(h: &str) -> &str {
match h {
"i" => "Call-ID",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we detect if some header uses the short or long form ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should we detect that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To get the most expressivity.
I do no say we should do that, I just asked if we can.

I guess this deserves some mention in the docs., like

This keyword matches on the From field that can be present in SIP headers.
It matches both short and regular form, and these forms cannot be distinguished by this keyword.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what you mean. If a header uses the short form, it is 'expanded' ( converted to the long form) to enable matching in both cases with its related sticky buffer.
If this is what you are referring to, it has already been implemented.
If not, I'm unclear on what you mean by 'detecting which form is used by a header.'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant 2 things :

If a header uses the short form, it is 'expanded' ( converted to the long form) to enable matching in both cases with its related sticky buffer.

  1. This should be in the docs

  2. And the doc should also mention that there is only the normalized form, not the raw one (as we can have for http uri for instance where there are 2 keywords)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant 2 things :

If a header uses the short form, it is 'expanded' ( converted to the long form) to enable matching in both cases with its related sticky buffer.

1. This should be in the docs

2. And the doc should also mention that there is only the normalized form, not the raw one (as we can have for http uri for instance where there are 2 keywords)

+1

"m" => "Contact",
"e" => "Content-Encoding",
"l" => "Content-Length",
"c" => "Content-Type",
"f" => "From",
"s" => "Subject",
"k" => "Supported",
"t" => "To",
"v" => "Via",
_ => h,
}
}

pub fn sip_parse_request(oi: &[u8]) -> IResult<&[u8], Request> {
let (i, method) = parse_method(oi)?;
let (i, _) = char(' ')(i)?;
Expand Down Expand Up @@ -135,7 +151,7 @@ pub fn sip_parse_response(oi: &[u8]) -> IResult<&[u8], Response> {
let (i, reason) = parse_reason(i)?;
let (hi, _) = crlf(i)?;
let response_line_len = oi.len() - hi.len();
let (phi, _headers) = parse_headers(hi)?;
let (phi, headers) = parse_headers(hi)?;
let headers_len = hi.len() - phi.len();
let (bi, _) = crlf(phi)?;
let body_offset = oi.len() - bi.len();
Expand All @@ -146,6 +162,7 @@ pub fn sip_parse_response(oi: &[u8]) -> IResult<&[u8], Response> {
version,
code: code.into(),
reason: reason.into(),
headers,

response_line_len: response_line_len as u16,
headers_len: headers_len as u16,
Expand Down Expand Up @@ -199,7 +216,7 @@ fn hcolon(i: &[u8]) -> IResult<&[u8], char> {
}

fn message_header(i: &[u8]) -> IResult<&[u8], Header> {
let (i, n) = header_name(i)?;
let (i, n) = map(header_name, expand_header_name)(i)?;
let (i, _) = hcolon(i)?;
let (i, v) = header_value(i)?;
let (i, _) = crlf(i)?;
Expand All @@ -217,8 +234,8 @@ pub fn sip_take_line(i: &[u8]) -> IResult<&[u8], Option<String>> {
Ok((i, Some(line.into())))
}

pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, String>> {
let mut headers_map: HashMap<String, String> = HashMap::new();
pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, Vec<String>>> {
let mut headers_map: HashMap<String, Vec<String>> = HashMap::new();
loop {
match crlf(input) as IResult<&[u8], _> {
Ok((_, _)) => {
Expand All @@ -229,7 +246,10 @@ pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, String>
Err(Err::Incomplete(e)) => return Err(Err::Incomplete(e)),
};
let (rest, header) = message_header(input)?;
headers_map.insert(header.name, header.value);
headers_map
.entry(header.name)
.or_default()
.push(header.value);
input = rest;
}

Expand Down Expand Up @@ -292,7 +312,7 @@ mod tests {
assert_eq!(req.method, "REGISTER");
assert_eq!(req.path, "sip:sip.cybercity.dk");
assert_eq!(req.version, "SIP/2.0");
assert_eq!(req.headers["Content-Length"], "0");
assert_eq!(req.headers["Content-Length"].first().unwrap(), "0");
}

#[test]
Expand All @@ -308,7 +328,7 @@ mod tests {
assert_eq!(req.method, "REGISTER");
assert_eq!(req.path, "sip:sip.cybercity.dk");
assert_eq!(req.version, "SIP/2.0");
assert_eq!(req.headers["Content-Length"], "4");
assert_eq!(req.headers["Content-Length"].first().unwrap(), "4");
assert_eq!(body, "ABCD".as_bytes());
}

Expand Down
15 changes: 15 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -281,13 +281,21 @@ noinst_HEADERS = \
detect-rpc.h \
detect-sameip.h \
detect-sid.h \
detect-sip-content-length.h \
detect-sip-content-type.h \
detect-sip-headers.h \
detect-sip-headers-stub.h \
detect-sip-from.h \
detect-sip-method.h \
detect-sip-protocol.h \
detect-sip-request-line.h \
detect-sip-response-line.h \
detect-sip-stat-code.h \
detect-sip-stat-msg.h \
detect-sip-to.h \
detect-sip-ua.h \
detect-sip-uri.h \
detect-sip-via.h \
detect-smb-ntlmssp.h \
detect-smb-share.h \
detect-smb-version.h \
Expand Down Expand Up @@ -880,13 +888,20 @@ libsuricata_c_a_SOURCES = \
detect-rpc.c \
detect-sameip.c \
detect-sid.c \
detect-sip-content-length.c \
detect-sip-content-type.c \
detect-sip-headers.c \
detect-sip-from.c \
detect-sip-method.c \
detect-sip-protocol.c \
detect-sip-request-line.c \
detect-sip-response-line.c \
detect-sip-stat-code.c \
detect-sip-stat-msg.c \
detect-sip-to.c \
detect-sip-ua.c \
detect-sip-uri.c \
detect-sip-via.c \
detect-smb-ntlmssp.c \
detect-smb-share.c \
detect-smb-version.c \
Expand Down
2 changes: 2 additions & 0 deletions src/detect-engine-register.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@
#include "detect-sip-stat-msg.h"
#include "detect-sip-request-line.h"
#include "detect-sip-response-line.h"
#include "detect-sip-headers.h"
#include "detect-rfb-secresult.h"
#include "detect-rfb-sectype.h"
#include "detect-rfb-name.h"
Expand Down Expand Up @@ -686,6 +687,7 @@ void SigTableSetup(void)
DetectSipStatMsgRegister();
DetectSipRequestLineRegister();
DetectSipResponseLineRegister();
DetectSipHeadersRegister();
DetectRfbSecresultRegister();
DetectRfbSectypeRegister();
DetectRfbNameRegister();
Expand Down
6 changes: 6 additions & 0 deletions src/detect-engine-register.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ enum DetectKeywordId {
DETECT_AL_SIP_STAT_MSG,
DETECT_AL_SIP_REQUEST_LINE,
DETECT_AL_SIP_RESPONSE_LINE,
DETECT_AL_SIP_HEADER_FROM,
DETECT_AL_SIP_HEADER_TO,
DETECT_AL_SIP_HEADER_VIA,
DETECT_AL_SIP_HEADER_UA,
DETECT_AL_SIP_HEADER_CONTENT_TYPE,
DETECT_AL_SIP_HEADER_CONTENT_LENGTH,
DETECT_AL_RFB_SECRESULT,
DETECT_AL_RFB_SECTYPE,
DETECT_AL_RFB_NAME,
Expand Down
Loading
Loading