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

Ssh frames 5734 v3 #11415

Closed
wants to merge 3 commits into from
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
9 changes: 9 additions & 0 deletions doc/userguide/rules/ssh-keywords.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ Suricata has several rule keywords to match on different elements of SSH
connections.


Frames
------

The SSH parser supports the following frames:

* ssh.record_hdr
* ssh.record_data
* ssh.record_pdu

ssh.proto
---------
Match on the version of the SSH protocol used. ``ssh.proto`` is a sticky buffer,
Expand Down
94 changes: 75 additions & 19 deletions rust/src/ssh/ssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::core::*;
use nom7::Err;
use std::ffi::CString;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::frames::Frame;

static mut ALPROTO_SSH: AppProto = ALPROTO_UNKNOWN;
static HASSH_ENABLED: AtomicBool = AtomicBool::new(false);
Expand All @@ -29,6 +30,14 @@ fn hassh_is_enabled() -> bool {
HASSH_ENABLED.load(Ordering::Relaxed)
}

// app-layer-frame-documentation tag start: FrameType enum
#[derive(AppLayerFrameType)]
pub enum SshFrameType {
RecordHdr,
Copy link
Member

Choose a reason for hiding this comment

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

Q: No other frames seem to have this naming scheme with Record prefix. Why did you choose it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because SSH has different stages : banner, records, and encrypted

So, the frames are about records only

Copy link
Member

Choose a reason for hiding this comment

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

oh. thank you!

RecordData,
RecordPdu,
}

#[derive(AppLayerEvent)]
pub enum SSHEvent {
InvalidBanner,
Expand Down Expand Up @@ -109,6 +118,7 @@ impl SSHState {

fn parse_record(
&mut self, mut input: &[u8], resp: bool, pstate: *mut std::os::raw::c_void,
flow: *const Flow, stream_slice: &StreamSlice,
) -> AppLayerResult {
let (hdr, ohdr) = if !resp {
(&mut self.transaction.cli_hdr, &self.transaction.srv_hdr)
Expand Down Expand Up @@ -149,6 +159,30 @@ impl SSHState {
while !input.is_empty() {
match parser::ssh_parse_record(input) {
Ok((rem, head)) => {
let _pdu = Frame::new(
flow,
stream_slice,
input,
SSH_RECORD_HEADER_LEN as i64,
SshFrameType::RecordHdr as u8,
Some(0),
);
let _pdu = Frame::new(
flow,
stream_slice,
&input[SSH_RECORD_HEADER_LEN..],
(head.pkt_len - 2) as i64,
SshFrameType::RecordData as u8,
Some(0),
);
let _pdu = Frame::new(
flow,
stream_slice,
input,
(head.pkt_len + 4) as i64,
SshFrameType::RecordPdu as u8,
Some(0),
);
SCLogDebug!("SSH valid record {}", head);
match head.msg_code {
parser::MessageCode::Kexinit if hassh_is_enabled() => {
Expand Down Expand Up @@ -180,6 +214,30 @@ impl SSHState {
Err(Err::Incomplete(_)) => {
match parser::ssh_parse_record_header(input) {
Ok((rem, head)) => {
let _pdu = Frame::new(
flow,
stream_slice,
input,
SSH_RECORD_HEADER_LEN as i64,
SshFrameType::RecordHdr as u8,
Some(0),
);
let _pdu = Frame::new(
flow,
stream_slice,
&input[SSH_RECORD_HEADER_LEN..],
(head.pkt_len - 2) as i64,
SshFrameType::RecordData as u8,
Some(0),
);
let _pdu = Frame::new(
flow,
stream_slice,
input,
(head.pkt_len + 4) as i64,
SshFrameType::RecordPdu as u8,
Some(0),
);
SCLogDebug!("SSH valid record header {}", head);
let remlen = rem.len() as u32;
hdr.record_left = head.pkt_len - 2 - remlen;
Expand Down Expand Up @@ -210,15 +268,12 @@ impl SSHState {
}
Err(Err::Incomplete(_)) => {
//we may have consumed data from previous records
if input.len() < SSH_RECORD_HEADER_LEN {
//do not trust nom incomplete value
return AppLayerResult::incomplete(
(il - input.len()) as u32,
SSH_RECORD_HEADER_LEN as u32,
);
} else {
panic!("SSH invalid length record header");
}
debug_validate_bug_on!(input.len() >= SSH_RECORD_HEADER_LEN);
//do not trust nom incomplete value
return AppLayerResult::incomplete(
(il - input.len()) as u32,
SSH_RECORD_HEADER_LEN as u32,
);
}
Err(_e) => {
SCLogDebug!("SSH invalid record header {}", _e);
Expand All @@ -239,6 +294,7 @@ impl SSHState {

fn parse_banner(
&mut self, input: &[u8], resp: bool, pstate: *mut std::os::raw::c_void,
flow: *const Flow, stream_slice: &StreamSlice,
) -> AppLayerResult {
let hdr = if !resp {
&mut self.transaction.cli_hdr
Expand All @@ -248,7 +304,7 @@ impl SSHState {
if hdr.flags == SSHConnectionState::SshStateBannerWaitEol {
match parser::ssh_parse_line(input) {
Ok((rem, _)) => {
let mut r = self.parse_record(rem, resp, pstate);
let mut r = self.parse_record(rem, resp, pstate, flow, stream_slice);
if r.is_incomplete() {
//adds bytes consumed by banner to incomplete result
r.consumed += (input.len() - rem.len()) as u32;
Expand Down Expand Up @@ -288,7 +344,7 @@ impl SSHState {
);
self.set_event(SSHEvent::LongBanner);
}
let mut r = self.parse_record(rem, resp, pstate);
let mut r = self.parse_record(rem, resp, pstate, flow, stream_slice);
if r.is_incomplete() {
//adds bytes consumed by banner to incomplete result
r.consumed += (input.len() - rem.len()) as u32;
Expand Down Expand Up @@ -352,33 +408,33 @@ pub extern "C" fn rs_ssh_state_tx_free(_state: *mut std::os::raw::c_void, _tx_id

#[no_mangle]
pub unsafe extern "C" fn rs_ssh_parse_request(
_flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
stream_slice: StreamSlice,
_data: *const std::os::raw::c_void
) -> AppLayerResult {
let state = &mut cast_pointer!(state, SSHState);
let buf = stream_slice.as_slice();
let hdr = &mut state.transaction.cli_hdr;
if hdr.flags < SSHConnectionState::SshStateBannerDone {
return state.parse_banner(buf, false, pstate);
return state.parse_banner(buf, false, pstate, flow, &stream_slice);
} else {
return state.parse_record(buf, false, pstate);
return state.parse_record(buf, false, pstate, flow, &stream_slice);
}
}

#[no_mangle]
pub unsafe extern "C" fn rs_ssh_parse_response(
_flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
stream_slice: StreamSlice,
_data: *const std::os::raw::c_void
) -> AppLayerResult {
let state = &mut cast_pointer!(state, SSHState);
let buf = stream_slice.as_slice();
let hdr = &mut state.transaction.srv_hdr;
if hdr.flags < SSHConnectionState::SshStateBannerDone {
return state.parse_banner(buf, true, pstate);
return state.parse_banner(buf, true, pstate, flow, &stream_slice);
} else {
return state.parse_record(buf, true, pstate);
return state.parse_record(buf, true, pstate, flow, &stream_slice);
}
}

Expand Down Expand Up @@ -465,8 +521,8 @@ pub unsafe extern "C" fn rs_ssh_register_parser() {
apply_tx_config: None,
flags: 0,
truncate: None,
get_frame_id_by_name: None,
get_frame_name_by_id: None,
get_frame_id_by_name: Some(SshFrameType::ffi_id_from_name),
get_frame_name_by_id: Some(SshFrameType::ffi_name_from_id),
};

let ip_proto_str = CString::new("tcp").unwrap();
Expand Down
8 changes: 7 additions & 1 deletion src/detect.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,13 @@ static void DetectRun(ThreadVars *th_v,
goto end;
}
const TcpSession *ssn = p->flow->protoctx;
if (ssn && (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) == 0) {
bool setting_nopayload = p->flow->alparser &&
AppLayerParserStateIssetFlag(
p->flow->alparser, APP_LAYER_PARSER_NO_INSPECTION) &&
!(p->flags & PKT_NOPAYLOAD_INSPECTION);
// we may be right after disabling app-layer (ssh)
if (ssn &&
((ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) == 0 || setting_nopayload)) {
// PACKET_PROFILING_DETECT_START(p, PROF_DETECT_TX);
DetectRunFrames(th_v, de_ctx, det_ctx, p, pflow, &scratch);
// PACKET_PROFILING_DETECT_END(p, PROF_DETECT_TX);
Expand Down
Loading