Logging as is done in TLS.
Detection using the generic generic ja3.string keyword
Ticket: #5143
}
}
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_tx_get_ja3(
+ tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ if let Some(ja3) = &tx.ja3 {
+ *buffer = ja3.as_ptr();
+ *buffer_len = ja3.len() as u32;
+ 1
+ } else {
+ *buffer = ptr::null();
+ *buffer_len = 0;
+ 0
+ }
+}
+
#[no_mangle]
pub unsafe extern "C" fn rs_quic_tx_get_version(
tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32,
// We remap the Vec<TlsExtension> from tls_parser::parse_tls_extensions because of
// the lifetime of TlsExtension due to references to the slice used for parsing
pub extv: Vec<QuicTlsExtension>,
+ pub ja3: String,
}
#[derive(Debug, PartialEq)]
pub values: Vec<Vec<u8>>,
}
+fn quic_tls_ja3_client_extends(ja3: &mut String, exts: Vec<TlsExtension>) {
+ ja3.push_str(",");
+ let mut dash = false;
+ for e in &exts {
+ match e {
+ TlsExtension::EllipticCurves(x) => {
+ for ec in x {
+ if dash {
+ ja3.push_str("-");
+ } else {
+ dash = true;
+ }
+ ja3.push_str(&ec.0.to_string());
+ }
+ }
+ _ => {}
+ }
+ }
+ ja3.push_str(",");
+ dash = false;
+ for e in &exts {
+ match e {
+ TlsExtension::EcPointFormats(x) => {
+ for ec in *x {
+ if dash {
+ ja3.push_str("-");
+ } else {
+ dash = true;
+ }
+ ja3.push_str(&ec.to_string());
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
// get interesting stuff out of parsed tls extensions
-fn quic_get_tls_extensions(input: Option<&[u8]>) -> Vec<QuicTlsExtension> {
+fn quic_get_tls_extensions(
+ input: Option<&[u8]>, ja3: &mut String, client: bool,
+) -> Vec<QuicTlsExtension> {
let mut extv = Vec::new();
if let Some(extr) = input {
if let Ok((_, exts)) = parse_tls_extensions(extr) {
+ let mut dash = false;
for e in &exts {
let etype = TlsExtensionType::from(e);
+ if dash {
+ ja3.push_str("-");
+ } else {
+ dash = true;
+ }
+ ja3.push_str(&u16::from(etype).to_string());
let mut values = Vec::new();
match e {
TlsExtension::SNI(x) => {
}
extv.push(QuicTlsExtension { etype, values })
}
+ if client {
+ quic_tls_ja3_client_extends(ja3, exts);
+ }
}
}
return extv;
if let Handshake(hs) = msg {
match hs {
ClientHello(ch) => {
+ let mut ja3 = String::with_capacity(256);
+ ja3.push_str(&u16::from(ch.version).to_string());
+ ja3.push_str(",");
+ let mut dash = false;
+ for c in &ch.ciphers {
+ if dash {
+ ja3.push_str("-");
+ } else {
+ dash = true;
+ }
+ ja3.push_str(&u16::from(*c).to_string());
+ }
+ ja3.push_str(",");
let ciphers = ch.ciphers;
- let extv = quic_get_tls_extensions(ch.ext);
- return Ok((rest, Frame::Crypto(Crypto { ciphers, extv })));
+ let extv = quic_get_tls_extensions(ch.ext, &mut ja3, true);
+ return Ok((rest, Frame::Crypto(Crypto { ciphers, extv, ja3 })));
}
ServerHello(sh) => {
+ let mut ja3 = String::with_capacity(256);
+ ja3.push_str(&u16::from(sh.version).to_string());
+ ja3.push_str(",");
+ ja3.push_str(&u16::from(sh.cipher).to_string());
+ ja3.push_str(",");
let ciphers = vec![sh.cipher];
- let extv = quic_get_tls_extensions(sh.ext);
- return Ok((rest, Frame::Crypto(Crypto { ciphers, extv })));
+ let extv = quic_get_tls_extensions(sh.ext, &mut ja3, false);
+ return Ok((rest, Frame::Crypto(Crypto { ciphers, extv, ja3 })));
}
_ => {}
}
use super::parser::QuicType;
use super::quic::QuicTransaction;
use crate::jsonbuilder::{JsonBuilder, JsonError};
+use digest::Digest;
+use digest::Update;
+use md5::Md5;
fn quic_tls_extension_name(e: u16) -> Option<String> {
match e {
js.close()?;
}
+ if let Some(ja3) = &tx.ja3 {
+ if tx.client {
+ js.open_object("ja3")?;
+ } else {
+ js.open_object("ja3s")?;
+ }
+ let hash = format!("{:x}", Md5::new().chain(&ja3).finalize());
+ js.set_string("hash", &hash)?;
+ js.set_string("string", ja3)?;
+ js.close()?;
+ }
if tx.extv.len() > 0 {
js.open_array("extensions")?;
for e in &tx.extv {
pub sni: Option<Vec<u8>>,
pub ua: Option<Vec<u8>>,
pub extv: Vec<QuicTlsExtension>,
+ pub ja3: Option<String>,
+ pub client: bool,
tx_data: AppLayerTxData,
}
impl QuicTransaction {
fn new(
header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>,
- extv: Vec<QuicTlsExtension>,
+ extv: Vec<QuicTlsExtension>, ja3: Option<String>, client: bool,
) -> Self {
let cyu = Cyu::generate(&header, &data.frames);
QuicTransaction {
sni,
ua,
extv,
+ ja3,
+ client,
tx_data: AppLayerTxData::new(),
}
}
fn new_tx(
&mut self, header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>,
- extb: Vec<QuicTlsExtension>,
+ extb: Vec<QuicTlsExtension>, ja3: Option<String>, client: bool,
) {
- let mut tx = QuicTransaction::new(header, data, sni, ua, extb);
+ let mut tx = QuicTransaction::new(header, data, sni, ua, extb, ja3, client);
self.max_tx_id += 1;
tx.tx_id = self.max_tx_id;
self.transactions.push(tx);
fn handle_frames(&mut self, data: QuicData, header: QuicHeader, to_server: bool) {
let mut sni: Option<Vec<u8>> = None;
let mut ua: Option<Vec<u8>> = None;
+ let mut ja3: Option<String> = None;
let mut extv: Vec<QuicTlsExtension> = Vec::new();
for frame in &data.frames {
match frame {
}
}
Frame::Crypto(c) => {
+ ja3 = Some(c.ja3.clone());
for e in &c.extv {
if e.etype == TlsExtensionType::ServerName && e.values.len() > 0 {
sni = Some(e.values[0].to_vec());
_ => {}
}
}
- self.new_tx(header, data, sni, ua, extv);
+ self.new_tx(header, data, sni, ua, extv, ja3, to_server);
}
fn parse(&mut self, input: &[u8], to_server: bool) -> bool {
None,
None,
Vec::new(),
+ None,
+ to_server,
);
continue;
}
void *txv, const int list_id);
static int g_tls_ja3_str_buffer_id = 0;
+static InspectionBuffer *GetJa3Data(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) {
+ uint32_t b_len = 0;
+ const uint8_t *b = NULL;
+
+ if (rs_quic_tx_get_ja3(txv, &b, &b_len) != 1)
+ 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 keyword: ja3.string
*/
DetectAppLayerMpmRegister2("ja3.string", SIG_FLAG_TOSERVER, 2,
PrefilterGenericMpmRegister, GetData, ALPROTO_TLS, 0);
+ DetectAppLayerMpmRegister2("ja3.string", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister,
+ GetJa3Data, ALPROTO_QUIC, 1);
+
+ DetectAppLayerInspectEngineRegister2("ja3.string", ALPROTO_QUIC, SIG_FLAG_TOSERVER, 1,
+ DetectEngineInspectBufferGeneric, GetJa3Data);
+
DetectBufferTypeSetDescriptionByName("ja3.string", "TLS JA3 string");
g_tls_ja3_str_buffer_id = DetectBufferTypeGetByName("ja3.string");
if (DetectBufferSetActiveList(s, g_tls_ja3_str_buffer_id) < 0)
return -1;
- if (DetectSignatureSetAppProto(s, ALPROTO_TLS) < 0)
+ if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_TLS && s->alproto != ALPROTO_QUIC) {
+ SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting protocols.");
return -1;
+ }
/* try to enable JA3 */
SSLEnableJA3();
void *txv, const int list_id);
static int g_tls_ja3s_str_buffer_id = 0;
+static InspectionBuffer *GetJa3Data(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) {
+ uint32_t b_len = 0;
+ const uint8_t *b = NULL;
+
+ if (rs_quic_tx_get_ja3(txv, &b, &b_len) != 1)
+ 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 keyword: ja3s.string
*/
DetectAppLayerMpmRegister2("ja3s.string", SIG_FLAG_TOCLIENT, 2,
PrefilterGenericMpmRegister, GetData, ALPROTO_TLS, 0);
+ DetectAppLayerMpmRegister2("ja3s.string", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister,
+ GetJa3Data, ALPROTO_QUIC, 1);
+
+ DetectAppLayerInspectEngineRegister2("ja3s.string", ALPROTO_QUIC, SIG_FLAG_TOCLIENT, 1,
+ DetectEngineInspectBufferGeneric, GetJa3Data);
+
DetectBufferTypeSetDescriptionByName("ja3s.string", "TLS JA3S string");
g_tls_ja3s_str_buffer_id = DetectBufferTypeGetByName("ja3s.string");
if (DetectBufferSetActiveList(s, g_tls_ja3s_str_buffer_id) < 0)
return -1;
- if (DetectSignatureSetAppProto(s, ALPROTO_TLS) < 0)
+ if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_TLS && s->alproto != ALPROTO_QUIC) {
+ SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting protocols.");
return -1;
+ }
/* try to enable JA3 */
SSLEnableJA3();