Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -3830,6 +3830,9 @@
},
"tls": {
"$ref": "#/$defs/stats_applayer_error"
},
"websocket": {
"$ref": "#/$defs/stats_applayer_error"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -3947,6 +3950,9 @@
},
"tls": {
"type": "integer"
},
"websocket": {
"type": "integer"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -4058,6 +4064,9 @@
},
"tls": {
"type": "integer"
},
"websocket": {
"type": "integer"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -5495,6 +5504,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/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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it be more idiomatic if the derive was done as a From or FromStr implementation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So rather ToString and FromStr ?
You are my reference for what is idiomatic in rust :-p

Is there a performance cost to do type conversion from integer to enum ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh, I misread this.. So it essentially goes from u8 -> WebSocketOpCode -> &str? My first feeling is the derive macro does seem like a little overkill since WebSocketOpcode doesn't seem to be used at all in the code, other than behind the derive macro? But that aside..

I think the derived code should implement a trait, or an impl block rather than a make function. Normally you might add a to_str() method...

impl WebSocketOpCode {
    fn to_str(&self) -> 'static &str {
        ...
    }

    // Or as a direct replacement.  Call like WebSocketOpcode::to_str()
    fn to_str() -> Option<&'static str> {
        
    }
}

Of course this assumes that you have a constructed WebSocketOpcode already, which it probably makes sense that tx.pdu.opcode might be an instance of, which could be done with an implementation of

impl From<u8> for WebSocketOpcode

I guess it feels odd that this enum exists only to generate some bare functions, but is never actually used?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So it essentially goes from u8 -> WebSocketOpCode -> &str?

Nope, not using WebSocketOpCode itself

the derive macro does seem like a little overkill

I want to code only once the match between integer value and string...
How do I achieve that without overkill ?
And without risking a typo if I code 2 functions (one stringer and one from str)

Of course this assumes that you have a constructed WebSocketOpcode already

I do not

which it probably makes sense that tx.pdu.opcode might be an instance of

It does not seem to fit for me asWebSocketOpcode enum has a limited number of values, and I still want opcode 3 that is unknown to be parsed...
Can/should I improve my enum ? Like adding a case Unknown(u8) ?

I guess it feels odd that this enum exists only to generate some bare functions, but is never actually used?

I agree.

Sum up :

  • Can/should I improve my enum ? Like adding a case Unknown(u8) ?
  • Is there a batter way than derive macro ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can/should I improve my enum ? Like adding a case Unknown(u8) ?

This makes sense.

Is there a batter way than derive macro ?

Implement the methods on the enum, like from_str and to_str?

This looks interesting as well: https://github.com/Peternator7/strum

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is no standard to_str right ?

Should I use https://doc.rust-lang.org/std/string/trait.ToString.html ? That is to_string(&self) -> String so that allocates even if it is a static string... I think not

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is no standard to_str right ?

Should I use https://doc.rust-lang.org/std/string/trait.ToString.html ? That is to_string(&self) -> String so that allocates even if it is a static string... I think not

No there isn't, but that doesn't mean you can implement it/derive it directly on the WebSocketOpCode.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Strum does not seem to work for displaying the value of a case Unknown(u8)

cf code in https://docs.rs/strum_macros/0.25.3/strum_macros/derive.Display.html where Color::Blue(10) does only display blue without the 10

Ah, I didn't look at it that closely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why did you not use strum for AppLayerEvent derive ?
Looks quite similar

Yeah, they do. derive(AppLayerEvent) was added to implement the AppLayerEvent trait we use in some generic functions and has some additional Suri only methods like get_event_info.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You can check next version of the PR ;-)

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