mirror of https://github.com/OISF/suricata
parent
78b766048e
commit
44b6aa5e4b
@ -0,0 +1,63 @@
|
||||
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.flags
|
||||
---------------
|
||||
|
||||
Matches on the websocket flags.
|
||||
It uses a 8-bit unsigned integer as value.
|
||||
Only the four upper bits are used.
|
||||
|
||||
The value can also be a list of strings (comma-separated),
|
||||
where each string is the name of a specific bit like `fin` and `comp`,
|
||||
and can be prefixed by `!` for negation.
|
||||
|
||||
websocket.flags uses an :ref:`unsigned 8-bits integer <rules-integer-keywords>`
|
||||
|
||||
Examples::
|
||||
|
||||
websocket.flags:128;
|
||||
websocket.flags:&0x40=0x40;
|
||||
websocket.flags:fin,!comp;
|
||||
|
||||
websocket.mask
|
||||
--------------
|
||||
|
||||
Matches on the websocket mask if any.
|
||||
It uses a 32-bit unsigned integer as value (big-endian).
|
||||
|
||||
websocket.mask uses an :ref:`unsigned 32-bits integer <rules-integer-keywords>`
|
||||
|
||||
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
|
||||
|
||||
websocket.opcode uses an :ref:`unsigned 8-bits integer <rules-integer-keywords>`
|
||||
|
||||
Examples::
|
||||
|
||||
websocket.opcode:1;
|
||||
websocket.opcode:>8;
|
||||
websocket.opcode:ping;
|
@ -0,0 +1,8 @@
|
||||
# WebSocket app-layer event rules.
|
||||
#
|
||||
# These SIDs fall in the 2235000+ range. See:
|
||||
# http://doc.emergingthreats.net/bin/view/Main/SidAllocation and
|
||||
# https://redmine.openinfosecfoundation.org/projects/suricata/wiki/AppLayer
|
||||
|
||||
alert websocket any any -> any any (msg:"SURICATA Websocket skipped end of payload"; app-layer-event:websocket.skip_end_of_payload; classtype:protocol-command-decode; sid:2235000; rev:1;)
|
||||
alert websocket any any -> any any (msg:"SURICATA Websocket reassembly limit reached"; app-layer-event:websocket.reassembly_limit_reached; classtype:protocol-command-decode; sid:2235001; rev:1;)
|
@ -0,0 +1,135 @@
|
||||
/* 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::detect::uint::{
|
||||
detect_parse_uint, detect_parse_uint_enum, DetectUintData, DetectUintMode,
|
||||
};
|
||||
use crate::websocket::parser::WebSocketOpcode;
|
||||
|
||||
use nom7::branch::alt;
|
||||
use nom7::bytes::complete::{is_a, tag};
|
||||
use nom7::combinator::{opt, value};
|
||||
use nom7::multi::many1;
|
||||
use nom7::IResult;
|
||||
|
||||
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 SCWebSocketGetFlags(tx: &mut WebSocketTransaction) -> u8 {
|
||||
return tx.pdu.flags;
|
||||
}
|
||||
|
||||
#[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 Some(ctx) = detect_parse_uint_enum::<u8, WebSocketOpcode>(s) {
|
||||
let boxed = Box::new(ctx);
|
||||
return Box::into_raw(boxed) as *mut _;
|
||||
}
|
||||
}
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
struct WebSocketFlag {
|
||||
neg: bool,
|
||||
value: u8,
|
||||
}
|
||||
|
||||
fn parse_flag_list_item(s: &str) -> IResult<&str, WebSocketFlag> {
|
||||
let (s, _) = opt(is_a(" "))(s)?;
|
||||
let (s, neg) = opt(tag("!"))(s)?;
|
||||
let neg = neg.is_some();
|
||||
let (s, value) = alt((value(0x80, tag("fin")), value(0x40, tag("comp"))))(s)?;
|
||||
let (s, _) = opt(is_a(" ,"))(s)?;
|
||||
Ok((s, WebSocketFlag { neg, value }))
|
||||
}
|
||||
|
||||
fn parse_flag_list(s: &str) -> IResult<&str, Vec<WebSocketFlag>> {
|
||||
return many1(parse_flag_list_item)(s);
|
||||
}
|
||||
|
||||
fn parse_flags(s: &str) -> Option<DetectUintData<u8>> {
|
||||
// try first numerical value
|
||||
if let Ok((_, ctx)) = detect_parse_uint::<u8>(s) {
|
||||
return Some(ctx);
|
||||
}
|
||||
// otherwise, try strings for bitmask
|
||||
if let Ok((_, l)) = parse_flag_list(s) {
|
||||
let mut arg1 = 0;
|
||||
let mut arg2 = 0;
|
||||
for elem in l.iter() {
|
||||
if elem.value & arg1 != 0 {
|
||||
SCLogWarning!("Repeated bitflag for websocket.flags");
|
||||
return None;
|
||||
}
|
||||
arg1 |= elem.value;
|
||||
if !elem.neg {
|
||||
arg2 |= elem.value;
|
||||
}
|
||||
}
|
||||
let ctx = DetectUintData::<u8> {
|
||||
arg1,
|
||||
arg2,
|
||||
mode: DetectUintMode::DetectUintModeBitmask,
|
||||
};
|
||||
return Some(ctx);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn SCWebSocketParseFlags(
|
||||
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 Some(ctx) = parse_flags(s) {
|
||||
let boxed = Box::new(ctx);
|
||||
return Box::into_raw(boxed) as *mut _;
|
||||
}
|
||||
}
|
||||
return std::ptr::null_mut();
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* 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::parser::WebSocketOpcode;
|
||||
use super::websocket::WebSocketTransaction;
|
||||
use crate::detect::EnumString;
|
||||
use crate::jsonbuilder::{JsonBuilder, JsonError};
|
||||
use std;
|
||||
|
||||
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(opcode) = WebSocketOpcode::from_u(tx.pdu.opcode) {
|
||||
js.set_string("opcode", opcode.to_str())?;
|
||||
} 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()
|
||||
}
|
@ -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;
|
@ -0,0 +1,96 @@
|
||||
/* 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 nom7::bytes::streaming::take;
|
||||
use nom7::combinator::cond;
|
||||
use nom7::number::streaming::{be_u16, be_u32, be_u64, be_u8};
|
||||
use nom7::IResult;
|
||||
use suricata_derive::EnumStringU8;
|
||||
|
||||
#[derive(Clone, Debug, Default, EnumStringU8)]
|
||||
#[repr(u8)]
|
||||
pub enum WebSocketOpcode {
|
||||
#[default]
|
||||
Continuation = 0,
|
||||
Text = 1,
|
||||
Binary = 2,
|
||||
Ping = 8,
|
||||
Pong = 9,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct WebSocketPdu {
|
||||
pub flags: u8,
|
||||
pub fin: bool,
|
||||
pub compress: bool,
|
||||
pub opcode: u8,
|
||||
pub mask: Option<u32>,
|
||||
pub payload: Vec<u8>,
|
||||
pub to_skip: u64,
|
||||
}
|
||||
|
||||
// cf rfc6455#section-5.2
|
||||
pub fn parse_message(i: &[u8], max_pl_size: u32) -> IResult<&[u8], WebSocketPdu> {
|
||||
let (i, flags_op) = be_u8(i)?;
|
||||
let fin = (flags_op & 0x80) != 0;
|
||||
let compress = (flags_op & 0x40) != 0;
|
||||
let flags = flags_op & 0xF0;
|
||||
let opcode = flags_op & 0xF;
|
||||
let (i, mask_plen) = be_u8(i)?;
|
||||
let mask_flag = (mask_plen & 0x80) != 0;
|
||||
let (i, payload_len) = match mask_plen & 0x7F {
|
||||
126 => {
|
||||
let (i, val) = be_u16(i)?;
|
||||
Ok((i, val.into()))
|
||||
}
|
||||
127 => be_u64(i),
|
||||
_ => Ok((i, (mask_plen & 0x7F).into())),
|
||||
}?;
|
||||
let (i, xormask) = cond(mask_flag, take(4usize))(i)?;
|
||||
let mask = if mask_flag {
|
||||
let (_, m) = be_u32(xormask.unwrap())?;
|
||||
Some(m)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// we limit payload_len to u32, so as to build on 32-bit system
|
||||
// where we cannot take(usize) with a u64
|
||||
let (to_skip, payload_len) = if payload_len < max_pl_size.into() {
|
||||
(0, payload_len as u32)
|
||||
} else {
|
||||
(payload_len - (max_pl_size as u64), max_pl_size)
|
||||
};
|
||||
let (i, payload_raw) = take(payload_len)(i)?;
|
||||
let mut payload = payload_raw.to_vec();
|
||||
if let Some(xorkey) = xormask {
|
||||
for i in 0..payload.len() {
|
||||
payload[i] ^= xorkey[i % 4];
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
i,
|
||||
WebSocketPdu {
|
||||
flags,
|
||||
fin,
|
||||
compress,
|
||||
opcode,
|
||||
mask,
|
||||
payload,
|
||||
to_skip,
|
||||
},
|
||||
))
|
||||
}
|
@ -0,0 +1,383 @@
|
||||
/* 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::parser;
|
||||
use crate::applayer::{self, *};
|
||||
use crate::conf::conf_get;
|
||||
use crate::core::{AppProto, Direction, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP};
|
||||
use crate::frames::Frame;
|
||||
|
||||
use nom7 as nom;
|
||||
use nom7::Needed;
|
||||
|
||||
use flate2::read::DeflateDecoder;
|
||||
|
||||
use std;
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::CString;
|
||||
use std::io::Read;
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
|
||||
static mut ALPROTO_WEBSOCKET: AppProto = ALPROTO_UNKNOWN;
|
||||
|
||||
static mut WEBSOCKET_MAX_PAYLOAD_SIZE: u32 = 0xFFFF;
|
||||
|
||||
// app-layer-frame-documentation tag start: FrameType enum
|
||||
#[derive(AppLayerFrameType)]
|
||||
pub enum WebSocketFrameType {
|
||||
Header,
|
||||
Pdu,
|
||||
}
|
||||
|
||||
#[derive(AppLayerEvent)]
|
||||
pub enum WebSocketEvent {
|
||||
SkipEndOfPayload,
|
||||
ReassemblyLimitReached,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WebSocketTransaction {
|
||||
tx_id: u64,
|
||||
pub pdu: parser::WebSocketPdu,
|
||||
tx_data: AppLayerTxData,
|
||||
}
|
||||
|
||||
impl WebSocketTransaction {
|
||||
pub fn new(direction: Direction) -> WebSocketTransaction {
|
||||
Self {
|
||||
tx_data: AppLayerTxData::for_direction(direction),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transaction for WebSocketTransaction {
|
||||
fn id(&self) -> u64 {
|
||||
self.tx_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct WebSocketReassemblyBuffer {
|
||||
data: Vec<u8>,
|
||||
compress: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WebSocketState {
|
||||
state_data: AppLayerStateData,
|
||||
tx_id: u64,
|
||||
transactions: VecDeque<WebSocketTransaction>,
|
||||
|
||||
c2s_buf: WebSocketReassemblyBuffer,
|
||||
s2c_buf: WebSocketReassemblyBuffer,
|
||||
|
||||
to_skip_tc: u64,
|
||||
to_skip_ts: u64,
|
||||
}
|
||||
|
||||
impl State<WebSocketTransaction> for WebSocketState {
|
||||
fn get_transaction_count(&self) -> usize {
|
||||
self.transactions.len()
|
||||
}
|
||||
|
||||
fn get_transaction_by_index(&self, index: usize) -> Option<&WebSocketTransaction> {
|
||||
self.transactions.get(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl WebSocketState {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
// Free a transaction by ID.
|
||||
fn free_tx(&mut self, tx_id: u64) {
|
||||
let len = self.transactions.len();
|
||||
let mut found = false;
|
||||
let mut index = 0;
|
||||
for i in 0..len {
|
||||
let tx = &self.transactions[i];
|
||||
if tx.tx_id == tx_id + 1 {
|
||||
found = true;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if found {
|
||||
self.transactions.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tx(&mut self, tx_id: u64) -> Option<&WebSocketTransaction> {
|
||||
self.transactions.iter().find(|tx| tx.tx_id == tx_id + 1)
|
||||
}
|
||||
|
||||
fn new_tx(&mut self, direction: Direction) -> WebSocketTransaction {
|
||||
let mut tx = WebSocketTransaction::new(direction);
|
||||
self.tx_id += 1;
|
||||
tx.tx_id = self.tx_id;
|
||||
return tx;
|
||||
}
|
||||
|
||||
fn parse(
|
||||
&mut self, stream_slice: StreamSlice, direction: Direction, flow: *const Flow,
|
||||
) -> AppLayerResult {
|
||||
let to_skip = if direction == Direction::ToClient {
|
||||
&mut self.to_skip_tc
|
||||
} else {
|
||||
&mut self.to_skip_ts
|
||||
};
|
||||
let input = stream_slice.as_slice();
|
||||
let mut start = input;
|
||||
if *to_skip > 0 {
|
||||
if *to_skip >= input.len() as u64 {
|
||||
*to_skip -= input.len() as u64;
|
||||
return AppLayerResult::ok();
|
||||
} else {
|
||||
start = &input[*to_skip as usize..];
|
||||
*to_skip = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let max_pl_size = unsafe { WEBSOCKET_MAX_PAYLOAD_SIZE };
|
||||
while !start.is_empty() {
|
||||
match parser::parse_message(start, max_pl_size) {
|
||||
Ok((rem, pdu)) => {
|
||||
let _pdu = Frame::new(
|
||||
flow,
|
||||
&stream_slice,
|
||||
start,
|
||||
(start.len() - rem.len() - pdu.payload.len()) as i64,
|
||||
WebSocketFrameType::Header as u8,
|
||||
);
|
||||
let _pdu = Frame::new(
|
||||
flow,
|
||||
&stream_slice,
|
||||
start,
|
||||
(start.len() - rem.len()) as i64,
|
||||
WebSocketFrameType::Pdu as u8,
|
||||
);
|
||||
start = rem;
|
||||
let mut tx = self.new_tx(direction);
|
||||
if pdu.to_skip > 0 {
|
||||
if direction == Direction::ToClient {
|
||||
self.to_skip_tc = pdu.to_skip;
|
||||
} else {
|
||||
self.to_skip_ts = pdu.to_skip;
|
||||
}
|
||||
tx.tx_data.set_event(WebSocketEvent::SkipEndOfPayload as u8);
|
||||
}
|
||||
let buf = if direction == Direction::ToClient {
|
||||
&mut self.s2c_buf
|
||||
} else {
|
||||
&mut self.c2s_buf
|
||||
};
|
||||
if !buf.data.is_empty() || !pdu.fin {
|
||||
if buf.data.is_empty() {
|
||||
buf.compress = pdu.compress;
|
||||
}
|
||||
if buf.data.len() + pdu.payload.len() < max_pl_size as usize {
|
||||
buf.data.extend(&pdu.payload);
|
||||
} else if buf.data.len() < max_pl_size as usize {
|
||||
buf.data
|
||||
.extend(&pdu.payload[..max_pl_size as usize - buf.data.len()]);
|
||||
tx.tx_data
|
||||
.set_event(WebSocketEvent::ReassemblyLimitReached as u8);
|
||||
}
|
||||
}
|
||||
tx.pdu = pdu;
|
||||
if tx.pdu.fin && !buf.data.is_empty() {
|
||||
// the final PDU gets the full reassembled payload
|
||||
std::mem::swap(&mut tx.pdu.payload, &mut buf.data);
|
||||
buf.data.clear();
|
||||
}
|
||||
if buf.compress && tx.pdu.fin {
|
||||
buf.compress = false;
|
||||
// cf RFC 7692 section-7.2.2
|
||||
tx.pdu.payload.extend_from_slice(&[0, 0, 0xFF, 0xFF]);
|
||||
let mut deflater = DeflateDecoder::new(&tx.pdu.payload[..]);
|
||||
let mut v = Vec::new();
|
||||
// do not check result because
|
||||
// deflate with rust backend fails on good input cf https://github.com/rust-lang/flate2-rs/issues/389
|
||||
let _ = deflater.read_to_end(&mut v);
|
||||
if !v.is_empty() {
|
||||
std::mem::swap(&mut tx.pdu.payload, &mut v);
|
||||
}
|
||||
}
|
||||
self.transactions.push_back(tx);
|
||||
}
|
||||
Err(nom::Err::Incomplete(needed)) => {
|
||||
if let Needed::Size(n) = needed {
|
||||
let n = usize::from(n);
|
||||
// Not enough data. just ask for one more byte.
|
||||
let consumed = input.len() - start.len();
|
||||
let needed = start.len() + n;
|
||||
return AppLayerResult::incomplete(consumed as u32, needed as u32);
|
||||
}
|
||||
return AppLayerResult::err();
|
||||
}
|
||||
Err(_) => {
|
||||
return AppLayerResult::err();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Input was fully consumed.
|
||||
return AppLayerResult::ok();
|
||||
}
|
||||
}
|
||||
|
||||
// C exports.
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rs_websocket_probing_parser(
|
||||
_flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8,
|
||||
) -> AppProto {
|
||||
if !input.is_null() {
|
||||
let slice = build_slice!(input, input_len as usize);
|
||||
if !slice.is_empty() {
|
||||
// just check reserved bits are zeroed, except RSV1
|
||||
// as RSV1 is used for compression cf RFC 7692
|
||||
if slice[0] & 0x30 == 0 {
|
||||
return ALPROTO_WEBSOCKET;
|
||||
}
|
||||
return ALPROTO_FAILED;
|
||||
}
|
||||
}
|
||||
return ALPROTO_UNKNOWN;
|
||||
}
|
||||
|
||||
extern "C" fn rs_websocket_state_new(
|
||||
_orig_state: *mut c_void, _orig_proto: AppProto,
|
||||
) -> *mut c_void {
|
||||
let state = WebSocketState::new();
|
||||
let boxed = Box::new(state);
|
||||
return Box::into_raw(boxed) as *mut c_void;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn rs_websocket_state_free(state: *mut c_void) {
|
||||
std::mem::drop(Box::from_raw(state as *mut WebSocketState));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn rs_websocket_state_tx_free(state: *mut c_void, tx_id: u64) {
|
||||
let state = cast_pointer!(state, WebSocketState);
|
||||
state.free_tx(tx_id);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn rs_websocket_parse_request(
|
||||
flow: *const Flow, state: *mut c_void, _pstate: *mut c_void, stream_slice: StreamSlice,
|
||||
_data: *const c_void,
|
||||
) -> AppLayerResult {
|
||||
let state = cast_pointer!(state, WebSocketState);
|
||||
state.parse(stream_slice, Direction::ToServer, flow)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn rs_websocket_parse_response(
|
||||
flow: *const Flow, state: *mut c_void, _pstate: *mut c_void, stream_slice: StreamSlice,
|
||||
_data: *const c_void,
|
||||
) -> AppLayerResult {
|
||||
let state = cast_pointer!(state, WebSocketState);
|
||||
state.parse(stream_slice, Direction::ToClient, flow)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn rs_websocket_state_get_tx(state: *mut c_void, tx_id: u64) -> *mut c_void {
|
||||
let state = cast_pointer!(state, WebSocketState);
|
||||
match state.get_tx(tx_id) {
|
||||
Some(tx) => {
|
||||
return tx as *const _ as *mut _;
|
||||
}
|
||||
None => {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn rs_websocket_state_get_tx_count(state: *mut c_void) -> u64 {
|
||||
let state = cast_pointer!(state, WebSocketState);
|
||||
return state.tx_id;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn rs_websocket_tx_get_alstate_progress(
|
||||
_tx: *mut c_void, _direction: u8,
|
||||
) -> c_int {
|
||||
return 1;
|
||||
}
|
||||
|
||||
export_tx_data_get!(rs_websocket_get_tx_data, WebSocketTransaction);
|
||||
export_state_data_get!(rs_websocket_get_state_data, WebSocketState);
|
||||
|
||||
// Parser name as a C style string.
|
||||
const PARSER_NAME: &[u8] = b"websocket\0";
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rs_websocket_register_parser() {
|
||||
let parser = RustParser {
|
||||
name: PARSER_NAME.as_ptr() as *const c_char,
|
||||
default_port: std::ptr::null(),
|
||||
ipproto: IPPROTO_TCP,
|
||||
probe_ts: Some(rs_websocket_probing_parser),
|
||||
probe_tc: Some(rs_websocket_probing_parser),
|
||||
min_depth: 0,
|
||||
max_depth: 16,
|
||||
state_new: rs_websocket_state_new,
|
||||
state_free: rs_websocket_state_free,
|
||||
tx_free: rs_websocket_state_tx_free,
|
||||
parse_ts: rs_websocket_parse_request,
|
||||
parse_tc: rs_websocket_parse_response,
|
||||
get_tx_count: rs_websocket_state_get_tx_count,
|
||||
get_tx: rs_websocket_state_get_tx,
|
||||
tx_comp_st_ts: 1,
|
||||
tx_comp_st_tc: 1,
|
||||
tx_get_progress: rs_websocket_tx_get_alstate_progress,
|
||||
get_eventinfo: Some(WebSocketEvent::get_event_info),
|
||||
get_eventinfo_byid: Some(WebSocketEvent::get_event_info_by_id),
|
||||
localstorage_new: None,
|
||||
localstorage_free: None,
|
||||
get_tx_files: None,
|
||||
get_tx_iterator: Some(
|
||||
applayer::state_get_tx_iterator::<WebSocketState, WebSocketTransaction>,
|
||||
),
|
||||
get_tx_data: rs_websocket_get_tx_data,
|
||||
get_state_data: rs_websocket_get_state_data,
|
||||
apply_tx_config: None,
|
||||
flags: 0, // do not accept gaps as there is no good way to resync
|
||||
truncate: None,
|
||||
get_frame_id_by_name: Some(WebSocketFrameType::ffi_id_from_name),
|
||||
get_frame_name_by_id: Some(WebSocketFrameType::ffi_name_from_id),
|
||||
};
|
||||
|
||||
let ip_proto_str = CString::new("tcp").unwrap();
|
||||
|
||||
if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
|
||||
let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
|
||||
ALPROTO_WEBSOCKET = alproto;
|
||||
if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
|
||||
let _ = AppLayerRegisterParser(&parser, alproto);
|
||||
}
|
||||
SCLogDebug!("Rust websocket parser registered.");
|
||||
if let Some(val) = conf_get("app-layer.protocols.websocket.max-payload-size") {
|
||||
if let Ok(v) = val.parse::<u32>() {
|
||||
WEBSOCKET_MAX_PAYLOAD_SIZE = v;
|
||||
} else {
|
||||
SCLogError!("Invalid value for websocket.max-payload-size");
|
||||
}
|
||||
}
|
||||
AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_WEBSOCKET);
|
||||
} else {
|
||||
SCLogDebug!("Protocol detector and parser disabled for WEBSOCKET.");
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* \author Philippe Antoine
|
||||
*/
|
||||
|
||||
#include "suricata-common.h"
|
||||
#include "detect.h"
|
||||
#include "detect-parse.h"
|
||||
#include "detect-engine.h"
|
||||
#include "detect-engine-content-inspection.h"
|
||||
#include "detect-engine-uint.h"
|
||||
#include "detect-engine-prefilter.h"
|
||||
#include "detect-websocket.h"
|
||||
|
||||
#include "rust.h"
|
||||
|
||||
static int websocket_tx_id = 0;
|
||||
static int websocket_payload_id = 0;
|
||||
|
||||
/**
|
||||
* \internal
|
||||
* \brief this function will free memory associated with DetectWebSocketOpcodeData
|
||||
*
|
||||
* \param de pointer to DetectWebSocketOpcodeData
|
||||
*/
|
||||
static void DetectWebSocketOpcodeFree(DetectEngineCtx *de_ctx, void *de_ptr)
|
||||
{
|
||||
rs_detect_u8_free(de_ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* \internal
|
||||
* \brief Function to match opcode of a websocket tx
|
||||
*
|
||||
* \param det_ctx Pointer to the pattern matcher thread.
|
||||
* \param f Pointer to the current flow.
|
||||
* \param flags Flags.
|
||||
* \param state App layer state.
|
||||
* \param txv Pointer to the transaction.
|
||||
* \param s Pointer to the Signature.
|
||||
* \param ctx Pointer to the sigmatch that we will cast into DetectWebSocketOpcodeData.
|
||||
*
|
||||
* \retval 0 no match.
|
||||
* \retval 1 match.
|
||||
*/
|
||||
static int DetectWebSocketOpcodeMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags,
|
||||
void *state, void *txv, const Signature *s, const SigMatchCtx *ctx)
|
||||
{
|
||||
const DetectU8Data *de = (const DetectU8Data *)ctx;
|
||||
uint8_t opc = SCWebSocketGetOpcode(txv);
|
||||
return DetectU8Match(opc, de);
|
||||
}
|
||||
|
||||
/**
|
||||
* \internal
|
||||
* \brief this function is used to add the parsed sigmatch into the current signature
|
||||
*
|
||||
* \param de_ctx pointer to the Detection Engine Context
|
||||
* \param s pointer to the Current Signature
|
||||
* \param rawstr pointer to the user provided options
|
||||
*
|
||||
* \retval 0 on Success
|
||||
* \retval -1 on Failure
|
||||
*/
|
||||
static int DetectWebSocketOpcodeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
|
||||
{
|
||||
if (DetectSignatureSetAppProto(s, ALPROTO_WEBSOCKET) < 0)
|
||||
return -1;
|
||||
|
||||
DetectU8Data *de = SCWebSocketParseOpcode(rawstr);
|
||||
if (de == NULL)
|
||||
return -1;
|
||||
|
||||
if (SigMatchAppendSMToList(
|
||||
de_ctx, s, DETECT_WEBSOCKET_OPCODE, (SigMatchCtx *)de, websocket_tx_id) == NULL) {
|
||||
DetectWebSocketOpcodeFree(de_ctx, de);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \internal
|
||||
* \brief this function will free memory associated with DetectWebSocketMaskData
|
||||
*
|
||||
* \param de pointer to DetectWebSocketMaskData
|
||||
*/
|
||||
static void DetectWebSocketMaskFree(DetectEngineCtx *de_ctx, void *de_ptr)
|
||||
{
|
||||
rs_detect_u32_free(de_ptr);
|
||||
}
|
||||
|
||||
static int DetectWebSocketMaskMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags,
|
||||
void *state, void *txv, const Signature *s, const SigMatchCtx *ctx)
|
||||
{
|
||||
uint32_t val;
|
||||
const DetectU32Data *du32 = (const DetectU32Data *)ctx;
|
||||
if (SCWebSocketGetMask(txv, &val)) {
|
||||
return DetectU32Match(val, du32);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DetectWebSocketMaskSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
|
||||
{
|
||||
if (DetectSignatureSetAppProto(s, ALPROTO_WEBSOCKET) < 0)
|
||||
return -1;
|
||||
|
||||
DetectU32Data *du32 = DetectU32Parse(rawstr);
|
||||
if (du32 == NULL)
|
||||
return -1;
|
||||
|
||||
if (SigMatchAppendSMToList(
|
||||
de_ctx, s, DETECT_WEBSOCKET_MASK, (SigMatchCtx *)du32, websocket_tx_id) == NULL) {
|
||||
DetectWebSocketMaskFree(de_ctx, du32);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void DetectWebSocketFlagsFree(DetectEngineCtx *de_ctx, void *de_ptr)
|
||||
{
|
||||
rs_detect_u8_free(de_ptr);
|
||||
}
|
||||
|
||||
static int DetectWebSocketFlagsMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags,
|
||||
void *state, void *txv, const Signature *s, const SigMatchCtx *ctx)
|
||||
{
|
||||
const DetectU8Data *de = (const DetectU8Data *)ctx;
|
||||
uint8_t val = SCWebSocketGetFlags(txv);
|
||||
return DetectU8Match(val, de);
|
||||
}
|
||||
|
||||
static int DetectWebSocketFlagsSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
|
||||
{
|
||||
if (DetectSignatureSetAppProto(s, ALPROTO_WEBSOCKET) < 0)
|
||||
return -1;
|
||||
|
||||
DetectU8Data *de = SCWebSocketParseFlags(rawstr);
|
||||
if (de == NULL)
|
||||
return -1;
|
||||
|
||||
if (SigMatchAppendSMToList(
|
||||
de_ctx, s, DETECT_WEBSOCKET_FLAGS, (SigMatchCtx *)de, websocket_tx_id) == NULL) {
|
||||
DetectWebSocketOpcodeFree(de_ctx, de);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DetectWebSocketPayloadSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr)
|
||||
{
|
||||
if (DetectBufferSetActiveList(de_ctx, s, websocket_payload_id) < 0)
|
||||
return -1;
|
||||
|
||||
if (DetectSignatureSetAppProto(s, ALPROTO_WEBSOCKET) != 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx,
|
||||
const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv,
|
||||
const int list_id)
|
||||
{
|
||||
InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
|
||||
if (buffer->inspect == NULL) {
|
||||
const uint8_t *b = NULL;
|
||||
uint32_t b_len = 0;
|
||||
|
||||
if (!SCWebSocketGetPayload(txv, &b, &b_len))
|
||||
return NULL;
|
||||
if (b == NULL || b_len == 0)
|
||||
return NULL;
|
||||
|
||||
InspectionBufferSetup(det_ctx, list_id, buffer, b, b_len);
|
||||
InspectionBufferApplyTransforms(buffer, transforms);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Registration function for websocket.opcode: keyword
|
||||
*/
|
||||
void DetectWebsocketRegister(void)
|
||||
{
|
||||
sigmatch_table[DETECT_WEBSOCKET_OPCODE].name = "websocket.opcode";
|
||||
sigmatch_table[DETECT_WEBSOCKET_OPCODE].desc = "match WebSocket opcode";
|
||||
sigmatch_table[DETECT_WEBSOCKET_OPCODE].url = "/rules/websocket-keywords.html#websocket-opcode";
|
||||
sigmatch_table[DETECT_WEBSOCKET_OPCODE].AppLayerTxMatch = DetectWebSocketOpcodeMatch;
|
||||
sigmatch_table[DETECT_WEBSOCKET_OPCODE].Setup = DetectWebSocketOpcodeSetup;
|
||||
sigmatch_table[DETECT_WEBSOCKET_OPCODE].Free = DetectWebSocketOpcodeFree;
|
||||
|
||||
DetectAppLayerInspectEngineRegister("websocket.tx", ALPROTO_WEBSOCKET, SIG_FLAG_TOSERVER, 1,
|
||||
DetectEngineInspectGenericList, NULL);
|
||||
DetectAppLayerInspectEngineRegister("websocket.tx", ALPROTO_WEBSOCKET, SIG_FLAG_TOCLIENT, 1,
|
||||
DetectEngineInspectGenericList, NULL);
|
||||
|
||||
websocket_tx_id = DetectBufferTypeGetByName("websocket.tx");
|
||||
|
||||
sigmatch_table[DETECT_WEBSOCKET_MASK].name = "websocket.mask";
|
||||
sigmatch_table[DETECT_WEBSOCKET_MASK].desc = "match WebSocket mask";
|
||||
sigmatch_table[DETECT_WEBSOCKET_MASK].url = "/rules/websocket-keywords.html#websocket-mask";
|
||||
sigmatch_table[DETECT_WEBSOCKET_MASK].AppLayerTxMatch = DetectWebSocketMaskMatch;
|
||||
sigmatch_table[DETECT_WEBSOCKET_MASK].Setup = DetectWebSocketMaskSetup;
|
||||
sigmatch_table[DETECT_WEBSOCKET_MASK].Free = DetectWebSocketMaskFree;
|
||||
|
||||
sigmatch_table[DETECT_WEBSOCKET_FLAGS].name = "websocket.flags";
|
||||
sigmatch_table[DETECT_WEBSOCKET_FLAGS].desc = "match WebSocket flags";
|
||||
sigmatch_table[DETECT_WEBSOCKET_FLAGS].url = "/rules/websocket-keywords.html#websocket-flags";
|
||||
sigmatch_table[DETECT_WEBSOCKET_FLAGS].AppLayerTxMatch = DetectWebSocketFlagsMatch;
|
||||
sigmatch_table[DETECT_WEBSOCKET_FLAGS].Setup = DetectWebSocketFlagsSetup;
|
||||
sigmatch_table[DETECT_WEBSOCKET_FLAGS].Free = DetectWebSocketFlagsFree;
|
||||
|
||||
sigmatch_table[DETECT_WEBSOCKET_PAYLOAD].name = "websocket.payload";
|
||||
sigmatch_table[DETECT_WEBSOCKET_PAYLOAD].desc = "match WebSocket payload";
|
||||
sigmatch_table[DETECT_WEBSOCKET_PAYLOAD].url =
|
||||
"/rules/websocket-keywords.html#websocket-payload";
|
||||
sigmatch_table[DETECT_WEBSOCKET_PAYLOAD].Setup = DetectWebSocketPayloadSetup;
|
||||
sigmatch_table[DETECT_WEBSOCKET_PAYLOAD].flags |= SIGMATCH_NOOPT;
|
||||
DetectAppLayerInspectEngineRegister("websocket.payload", ALPROTO_WEBSOCKET, SIG_FLAG_TOSERVER,
|
||||
0, DetectEngineInspectBufferGeneric, GetData);
|
||||
DetectAppLayerInspectEngineRegister("websocket.payload", ALPROTO_WEBSOCKET, SIG_FLAG_TOCLIENT,
|
||||
0, DetectEngineInspectBufferGeneric, GetData);
|
||||
DetectAppLayerMpmRegister("websocket.payload", SIG_FLAG_TOSERVER, 2,
|
||||
PrefilterGenericMpmRegister, GetData, ALPROTO_WEBSOCKET, 1);
|
||||
DetectAppLayerMpmRegister("websocket.payload", SIG_FLAG_TOCLIENT, 2,
|
||||
PrefilterGenericMpmRegister, GetData, ALPROTO_WEBSOCKET, 1);
|
||||
websocket_payload_id = DetectBufferTypeGetByName("websocket.payload");
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* \author Philippe Antoine
|
||||
*/
|
||||
|
||||
#ifndef __DETECT_WEBSOCKET_H__
|
||||
#define __DETECT_WEBSOCKET_H__
|
||||
|
||||
void DetectWebsocketRegister(void);
|
||||
|
||||
#endif /* __DETECT_WEBSOCKET_H__ */
|
Loading…
Reference in New Issue