rust/ssh: convert parser to nom7 functions

pull/6581/head
Pierre Chifflier 4 years ago committed by Victor Julien
parent 8a584c211e
commit 74be8b94ec

@ -15,10 +15,15 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
use nom::combinator::rest;
use nom::number::streaming::{be_u32, be_u8};
use md5::Md5;
use digest::Digest; use digest::Digest;
use md5::Md5;
use nom7::branch::alt;
use nom7::bytes::streaming::{is_not, tag, take};
use nom7::character::streaming::char;
use nom7::combinator::{complete, rest, verify};
use nom7::multi::length_data;
use nom7::number::streaming::{be_u32, be_u8};
use nom7::IResult;
use std::fmt; use std::fmt;
#[derive(PartialEq, Eq, Copy, Clone, Debug)] #[derive(PartialEq, Eq, Copy, Clone, Debug)]
@ -84,16 +89,14 @@ pub struct SshBanner<'a> {
// Could be simplified adding dummy \n at the end // Could be simplified adding dummy \n at the end
// or use nom5 nom::bytes::complete::is_not // or use nom5 nom::bytes::complete::is_not
named!(pub ssh_parse_banner<SshBanner>, pub fn ssh_parse_banner(i: &[u8]) -> IResult<&[u8], SshBanner> {
do_parse!( let (i, _) = tag("SSH-")(i)?;
tag!("SSH-") >> let (i, protover) = is_not("-")(i)?;
protover: is_not!("-") >> let (i, _) = char('-')(i)?;
char!('-') >> let (i, swver) = alt((complete(is_not(" \r\n")), rest))(i)?;
swver: alt!( complete!( is_not!(" \r\n") ) | rest ) >> //remaining after space is comments
//remaining after space is comments Ok((i, SshBanner { protover, swver }))
(SshBanner{protover, swver}) }
)
);
#[derive(PartialEq)] #[derive(PartialEq)]
pub struct SshRecordHeader { pub struct SshRecordHeader {
@ -112,33 +115,39 @@ impl fmt::Display for SshRecordHeader {
} }
} }
named!(pub ssh_parse_record_header<SshRecordHeader>, pub fn ssh_parse_record_header(i: &[u8]) -> IResult<&[u8], SshRecordHeader> {
do_parse!( let (i, pkt_len) = verify(be_u32, |&val| val > 1)(i)?;
pkt_len: verify!(be_u32, |&val| val > 1) >> let (i, padding_len) = be_u8(i)?;
padding_len: be_u8 >> let (i, msg_code) = be_u8(i)?;
msg_code: be_u8 >> Ok((
(SshRecordHeader{pkt_len: pkt_len, i,
padding_len: padding_len, SshRecordHeader {
msg_code: MessageCode::from_u8(msg_code)}) pkt_len,
) padding_len,
); msg_code: MessageCode::from_u8(msg_code),
},
))
}
//test for evasion against pkt_len=0or1... //test for evasion against pkt_len=0or1...
named!(pub ssh_parse_record<SshRecordHeader>, pub fn ssh_parse_record(i: &[u8]) -> IResult<&[u8], SshRecordHeader> {
do_parse!( let (i, pkt_len) = verify(be_u32, |&val| val > 1)(i)?;
pkt_len: verify!(be_u32, |&val| val > 1) >> let (i, padding_len) = be_u8(i)?;
padding_len: be_u8 >> let (i, msg_code) = be_u8(i)?;
msg_code: be_u8 >> let (i, _) = take((pkt_len - 2) as usize)(i)?;
take!((pkt_len-2) as usize) >> Ok((
(SshRecordHeader{pkt_len: pkt_len, i,
padding_len: padding_len, SshRecordHeader {
msg_code: MessageCode::from_u8(msg_code)}) pkt_len,
) padding_len,
); msg_code: MessageCode::from_u8(msg_code),
},
))
}
#[derive(Debug,PartialEq)] #[derive(Debug, PartialEq)]
pub struct SshPacketKeyExchange<'a> { pub struct SshPacketKeyExchange<'a> {
pub cookie: &'a[u8], pub cookie: &'a [u8],
pub kex_algs: &'a [u8], pub kex_algs: &'a [u8],
pub server_host_key_algs: &'a [u8], pub server_host_key_algs: &'a [u8],
pub encr_algs_client_to_server: &'a [u8], pub encr_algs_client_to_server: &'a [u8],
@ -156,67 +165,84 @@ pub struct SshPacketKeyExchange<'a> {
const SSH_HASSH_STRING_DELIMITER_SLICE: [u8; 1] = [b';']; const SSH_HASSH_STRING_DELIMITER_SLICE: [u8; 1] = [b';'];
impl<'a> SshPacketKeyExchange<'a> { impl<'a> SshPacketKeyExchange<'a> {
pub fn generate_hassh(&self, hassh_string: &mut Vec<u8>, hassh: &mut Vec<u8>, to_server: &bool) { pub fn generate_hassh(
let slices = if *to_server { &self, hassh_string: &mut Vec<u8>, hassh: &mut Vec<u8>, to_server: &bool,
[self.kex_algs, &SSH_HASSH_STRING_DELIMITER_SLICE, ) {
self.encr_algs_server_to_client, &SSH_HASSH_STRING_DELIMITER_SLICE, let slices = if *to_server {
self.mac_algs_server_to_client, &SSH_HASSH_STRING_DELIMITER_SLICE, [
self.comp_algs_server_to_client]} self.kex_algs,
else { &SSH_HASSH_STRING_DELIMITER_SLICE,
[self.kex_algs, &SSH_HASSH_STRING_DELIMITER_SLICE, self.encr_algs_server_to_client,
self.encr_algs_client_to_server, &SSH_HASSH_STRING_DELIMITER_SLICE, &SSH_HASSH_STRING_DELIMITER_SLICE,
self.mac_algs_client_to_server, &SSH_HASSH_STRING_DELIMITER_SLICE, self.mac_algs_server_to_client,
self.comp_algs_client_to_server] &SSH_HASSH_STRING_DELIMITER_SLICE,
self.comp_algs_server_to_client,
]
} else {
[
self.kex_algs,
&SSH_HASSH_STRING_DELIMITER_SLICE,
self.encr_algs_client_to_server,
&SSH_HASSH_STRING_DELIMITER_SLICE,
self.mac_algs_client_to_server,
&SSH_HASSH_STRING_DELIMITER_SLICE,
self.comp_algs_client_to_server,
]
}; };
// reserving memory // reserving memory
hassh_string.reserve_exact(slices.iter().fold(0, |acc, x| acc + x.len())); hassh_string.reserve_exact(slices.iter().fold(0, |acc, x| acc + x.len()));
// copying slices to hassh string // copying slices to hassh string
slices.iter().for_each(|&x| hassh_string.extend_from_slice(x)); slices
.iter()
.for_each(|&x| hassh_string.extend_from_slice(x));
hassh.extend(format!("{:x}", Md5::new().chain(&hassh_string).finalize()).as_bytes()); hassh.extend(format!("{:x}", Md5::new().chain(&hassh_string).finalize()).as_bytes());
} }
} }
named!(parse_string<&[u8]>, do_parse!( #[inline]
len: be_u32 >> fn parse_string(i: &[u8]) -> IResult<&[u8], &[u8]> {
string: take!(len) >> length_data(be_u32)(i)
( string ) }
));
named!(pub ssh_parse_key_exchange<SshPacketKeyExchange>, do_parse!( pub fn ssh_parse_key_exchange(i: &[u8]) -> IResult<&[u8], SshPacketKeyExchange> {
cookie: take!(16) >> let (i, cookie) = take(16_usize)(i)?;
kex_algs: parse_string >> let (i, kex_algs) = parse_string(i)?;
server_host_key_algs: parse_string >> let (i, server_host_key_algs) = parse_string(i)?;
encr_algs_client_to_server: parse_string >> let (i, encr_algs_client_to_server) = parse_string(i)?;
encr_algs_server_to_client: parse_string >> let (i, encr_algs_server_to_client) = parse_string(i)?;
mac_algs_client_to_server: parse_string >> let (i, mac_algs_client_to_server) = parse_string(i)?;
mac_algs_server_to_client: parse_string >> let (i, mac_algs_server_to_client) = parse_string(i)?;
comp_algs_client_to_server: parse_string >> let (i, comp_algs_client_to_server) = parse_string(i)?;
comp_algs_server_to_client: parse_string >> let (i, comp_algs_server_to_client) = parse_string(i)?;
langs_client_to_server: parse_string >> let (i, langs_client_to_server) = parse_string(i)?;
langs_server_to_client: parse_string >> let (i, langs_server_to_client) = parse_string(i)?;
first_kex_packet_follows: be_u8 >> let (i, first_kex_packet_follows) = be_u8(i)?;
reserved: be_u32 >> let (i, reserved) = be_u32(i)?;
( SshPacketKeyExchange { Ok((
cookie: cookie, i,
kex_algs: kex_algs, SshPacketKeyExchange {
server_host_key_algs: server_host_key_algs, cookie,
encr_algs_client_to_server: encr_algs_client_to_server, kex_algs,
encr_algs_server_to_client: encr_algs_server_to_client, server_host_key_algs,
mac_algs_client_to_server: mac_algs_client_to_server, encr_algs_client_to_server,
mac_algs_server_to_client: mac_algs_server_to_client, encr_algs_server_to_client,
comp_algs_client_to_server: comp_algs_client_to_server, mac_algs_client_to_server,
comp_algs_server_to_client: comp_algs_server_to_client, mac_algs_server_to_client,
langs_client_to_server: langs_client_to_server, comp_algs_client_to_server,
langs_server_to_client: langs_server_to_client, comp_algs_server_to_client,
first_kex_packet_follows: first_kex_packet_follows, langs_client_to_server,
reserved: reserved, langs_server_to_client,
} ) first_kex_packet_follows,
)); reserved,
},
))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use nom7::{Err, Needed};
/// Simple test of some valid data. /// Simple test of some valid data.
#[test] #[test]
@ -695,7 +721,7 @@ mod tests {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
if let Err(e) = ssh_parse_key_exchange(&server_key_exchange) { if let Err(e) = ssh_parse_key_exchange(&server_key_exchange) {
assert_eq!(e, nom::Err::Incomplete(nom::Needed::Size(16755))); assert_eq!(e, Err::Incomplete(Needed::new(15964)));
} }
else { else {
panic!("ssh_parse_key_exchange() parsed malicious key_exchange"); panic!("ssh_parse_key_exchange() parsed malicious key_exchange");

@ -19,6 +19,7 @@ use super::parser;
use crate::applayer::*; use crate::applayer::*;
use crate::core::STREAM_TOSERVER; use crate::core::STREAM_TOSERVER;
use crate::core::{self, AppProto, Flow, ALPROTO_UNKNOWN, IPPROTO_TCP}; use crate::core::{self, AppProto, Flow, ALPROTO_UNKNOWN, IPPROTO_TCP};
use nom7::Err;
use std::ffi::CString; use std::ffi::CString;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@ -200,7 +201,7 @@ impl SSHState {
input = rem; input = rem;
//header and complete data (not returned) //header and complete data (not returned)
} }
Err(nom::Err::Incomplete(_)) => { Err(Err::Incomplete(_)) => {
match parser::ssh_parse_record_header(input) { match parser::ssh_parse_record_header(input) {
Ok((rem, head)) => { Ok((rem, head)) => {
SCLogDebug!("SSH valid record header {}", head); SCLogDebug!("SSH valid record header {}", head);
@ -231,7 +232,7 @@ impl SSHState {
} }
return AppLayerResult::ok(); return AppLayerResult::ok();
} }
Err(nom::Err::Incomplete(_)) => { Err(Err::Incomplete(_)) => {
//we may have consumed data from previous records //we may have consumed data from previous records
if input.len() < SSH_RECORD_HEADER_LEN { if input.len() < SSH_RECORD_HEADER_LEN {
//do not trust nom incomplete value //do not trust nom incomplete value

Loading…
Cancel
Save