Skip to content

Commit

Permalink
app-layer: websockets protocol support
Browse files Browse the repository at this point in the history
Ticket: 2695

Introduces a device EnumStringU8 to ease the use of enumerations
in protocols : logging the string out of the u8,
and for detection, parsing the u8 out of the string
  • Loading branch information
catenacyber committed Dec 14, 2023
1 parent 7d95c4c commit 1549f0b
Show file tree
Hide file tree
Showing 27 changed files with 1,244 additions and 16 deletions.
1 change: 1 addition & 0 deletions doc/userguide/rules/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Suricata Rules
quic-keywords
nfs-keywords
smtp-keywords
websocket-keywords
app-layer
xbits
thresholding
Expand Down
1 change: 1 addition & 0 deletions doc/userguide/rules/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ you can pick from. These are:
* snmp
* tftp
* sip
* websocket

The availability of these protocols depends on whether the protocol
is enabled in the configuration file, suricata.yaml.
Expand Down
50 changes: 50 additions & 0 deletions doc/userguide/rules/websocket-keywords.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
WebSocket Keywords
==================

websocket.payload
-----------------

A sticky buffer on the unmasked payload,
limited by suricata.yaml config value ``websocket.max-payload-size``.

Examples::

websocket.payload; pcre:"/^123[0-9]*/";
websocket.payload content:"swordfish";

``websocket.payload`` is a 'sticky buffer' and can be used as ``fast_pattern``.

websocket.fin
-------------

A boolean to tell if the payload is complete.

Examples::

websocket.fin:true;
websocket.fin:false;

websocket.mask
--------------

Matches on the websocket mask if any.
It uses a 32-bit unsigned integer as value (big-endian).

Examples::

websocket.mask:123456;
websocket.mask:>0;

websocket.opcode
----------------

Matches on the websocket opcode.
It uses a 8-bit unsigned integer as value.
Only 16 values are relevant.
It can also be specified by text from the enumeration

Examples::

websocket.opcode:1;
websocket.opcode:>8;
websocket.opcode:ping;
24 changes: 24 additions & 0 deletions etc/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3823,6 +3823,9 @@
},
"tls": {
"$ref": "#/$defs/stats_applayer_error"
},
"websocket": {
"$ref": "#/$defs/stats_applayer_error"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -3940,6 +3943,9 @@
},
"tls": {
"type": "integer"
},
"websocket": {
"type": "integer"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -4051,6 +4057,9 @@
},
"tls": {
"type": "integer"
},
"websocket": {
"type": "integer"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -5488,6 +5497,21 @@
}
},
"additionalProperties": false
},
"websocket": {
"type": "object",
"properties": {
"fin": {
"type": "boolean"
},
"mask": {
"type": "integer"
},
"opcode": {
"type": "string"
}
},
"additionalProperties": false
}
},
"$defs": {
Expand Down
6 changes: 6 additions & 0 deletions rust/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use proc_macro::TokenStream;

mod applayerevent;
mod applayerframetype;
mod stringenum;

/// The `AppLayerEvent` derive macro generates a `AppLayerEvent` trait
/// implementation for enums that define AppLayerEvents.
Expand Down Expand Up @@ -50,3 +51,8 @@ pub fn derive_app_layer_event(input: TokenStream) -> TokenStream {
pub fn derive_app_layer_frame_type(input: TokenStream) -> TokenStream {
applayerframetype::derive_app_layer_frame_type(input)
}

#[proc_macro_derive(EnumStringU8, attributes(name))]
pub fn derive_enum_string_u8(input: TokenStream) -> TokenStream {
stringenum::derive_enum_string_u8(input)
}
76 changes: 76 additions & 0 deletions rust/derive/src/stringenum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* Copyright (C) 2023 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
* Software Foundation.
*
* 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

extern crate proc_macro;
use super::applayerevent::transform_name;
use proc_macro::TokenStream;
use quote::quote;
use syn::{self, parse_macro_input, DeriveInput};

pub fn derive_enum_string_u8(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = transform_name(&input.ident.to_string());
let mut values = Vec::new();
let mut names = Vec::new();

if let syn::Data::Enum(ref data) = input.data {
for (_, v) in (&data.variants).into_iter().enumerate() {
let fname = transform_name(&v.ident.to_string());
names.push(fname);
if let Some((_, val)) = &v.discriminant {
if let syn::Expr::Lit(l) = val {
if let syn::Lit::Int(li) = &l.lit {
if let Ok(value) = li.base10_parse::<u8>() {
values.push(value);
} else {
panic!("EnumString requires explicit u8");
}
} else {
panic!("EnumString requires explicit literal integer");
}
} else {
panic!("EnumString requires explicit literal");
}
} else {
panic!("EnumString requires explicit values");
}
}
} else {
panic!("EnumString can only be derived for enums");
}

let stringer = syn::Ident::new(&(name.clone() + "_string"), proc_macro2::Span::call_site());
let parser = syn::Ident::new(&(name + "_parse"), proc_macro2::Span::call_site());

let expanded = quote! {
fn #stringer(v: u8) -> Option<&'static str> {
match v {
#( #values => Some(#names) ,)*
_ => None,
}
}

pub(crate) fn #parser(v: &str) -> Option<u8> {
match v {
#( #names => Some(#values) ,)*
_ => None,
}
}
};

proc_macro::TokenStream::from(expanded)
}
1 change: 1 addition & 0 deletions rust/src/applayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ pub unsafe fn AppLayerRegisterParser(parser: *const RustParser, alproto: AppProt

// Defined in app-layer-detect-proto.h
extern {
pub fn AppLayerRegisterExpectationProto(ipproto: u8, alproto: AppProto);
pub fn AppLayerProtoDetectPPRegister(ipproto: u8, portstr: *const c_char, alproto: AppProto,
min_depth: u16, max_depth: u16, dir: u8,
pparser1: ProbeFn, pparser2: ProbeFn);
Expand Down
1 change: 1 addition & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub mod rfb;
pub mod mqtt;
pub mod pgsql;
pub mod telnet;
pub mod websocket;
pub mod applayertemplate;
pub mod rdp;
pub mod x509;
Expand Down
74 changes: 74 additions & 0 deletions rust/src/websocket/detect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* Copyright (C) 2023 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
* Software Foundation.
*
* 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

use super::logger::web_socket_opcode_parse;
use super::websocket::WebSocketTransaction;
use crate::detect::uint::{detect_parse_uint, DetectUintData, DetectUintMode};
use std::ffi::CStr;

#[no_mangle]
pub unsafe extern "C" fn SCWebSocketGetOpcode(tx: &mut WebSocketTransaction) -> u8 {
return tx.pdu.opcode;
}

#[no_mangle]
pub unsafe extern "C" fn SCWebSocketGetFin(tx: &mut WebSocketTransaction) -> bool {
return tx.pdu.fin;
}

#[no_mangle]
pub unsafe extern "C" fn SCWebSocketGetPayload(
tx: &WebSocketTransaction, buffer: *mut *const u8, buffer_len: *mut u32,
) -> bool {
*buffer = tx.pdu.payload.as_ptr();
*buffer_len = tx.pdu.payload.len() as u32;
return true;
}

#[no_mangle]
pub unsafe extern "C" fn SCWebSocketGetMask(
tx: &mut WebSocketTransaction, value: *mut u32,
) -> bool {
if let Some(xorkey) = tx.pdu.mask {
*value = xorkey;
return true;
}
return false;
}

#[no_mangle]
pub unsafe extern "C" fn SCWebSocketParseOpcode(
ustr: *const std::os::raw::c_char,
) -> *mut DetectUintData<u8> {
let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
if let Ok(s) = ft_name.to_str() {
if let Ok((_, ctx)) = detect_parse_uint::<u8>(s) {
let boxed = Box::new(ctx);
return Box::into_raw(boxed) as *mut _;
}
if let Some(arg1) = web_socket_opcode_parse(s) {
let ctx = DetectUintData::<u8> {
arg1,
arg2: 0,
mode: DetectUintMode::DetectUintModeEqual,
};
let boxed = Box::new(ctx);
return Box::into_raw(boxed) as *mut _;
}
}
return std::ptr::null_mut();
}
53 changes: 53 additions & 0 deletions rust/src/websocket/logger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* Copyright (C) 2023 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
* Software Foundation.
*
* 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

use super::websocket::WebSocketTransaction;
use crate::jsonbuilder::{JsonBuilder, JsonError};
use std;
use suricata_derive::EnumStringU8;

#[derive(EnumStringU8)]
pub enum WebSocketOpcode {
Continuation = 0,
Text = 1,
Binary = 2,
Ping = 8,
Pong = 9,
}

fn log_websocket(tx: &WebSocketTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("websocket")?;
js.set_bool("fin", tx.pdu.fin)?;
if let Some(xorkey) = tx.pdu.mask {
js.set_uint("mask", xorkey.into())?;
}
if let Some(val) = web_socket_opcode_string(tx.pdu.opcode) {
js.set_string("opcode", val)?;
} else {
js.set_string("opcode", &format!("unknown-{}", tx.pdu.opcode))?;
}
js.close()?;
Ok(())
}

#[no_mangle]
pub unsafe extern "C" fn rs_websocket_logger_log(
tx: *mut std::os::raw::c_void, js: &mut JsonBuilder,
) -> bool {
let tx = cast_pointer!(tx, WebSocketTransaction);
log_websocket(tx, js).is_ok()
}
23 changes: 23 additions & 0 deletions rust/src/websocket/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* Copyright (C) 2023 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
* Software Foundation.
*
* 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

//! Application layer websocket parser and logger module.

pub mod detect;
pub mod logger;
mod parser;
pub mod websocket;
Loading

0 comments on commit 1549f0b

Please sign in to comment.