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

dns: parse and log soa data - v5 #5331

Closed
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
41 changes: 31 additions & 10 deletions doc/userguide/output/eve/eve-json-format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -401,11 +401,30 @@ Outline of fields seen in the different kinds of DNS events:
* "rcode": (ex: NOERROR)
* "rrname": Resource Record Name (ex: a domain name)
* "rrtype": Resource Record Type (ex: A, AAAA, NS, PTR)
* "rdata": Resource Data (ex. IP that domain name resolves to)
* "rdata": Resource Data (ex: IP that domain name resolves to)
* "ttl": Time-To-Live for this resource record

More complex DNS record types may log additional fields for resource data:

One can also control which RR types are logged explicitly from additional custom field enabled in the suricata.yaml file. If custom field is not specified, all RR types are logged. More than 50 values can be specified with the custom field and can be used as following:
* "soa": Section containing fields for the SOA (start of authority) record type

* "mname": Primary name server for this zone
* "rname": Authority's mailbox
* "serial": Serial version number
* "refresh": Refresh interval (seconds)
* "retry": Retry interval (seconds)
* "expire": Upper time limit until zone is no longer authoritative (seconds)
* "minimum": Minimum ttl for records in this zone (seconds)

* "sshfp": section containing fields for the SSHFP (ssh fingerprint) record type

* "fingerprint": Hex format of the fingerprint (ex: ``12:34:56:78:9a:bc:de:...``)
* "algo": Algorithm number (ex: 1 for RSA, 2 for DSS)
* "type": Fingerprint type (ex: 1 for SHA-1)

One can control which RR types are logged by using the "types" field in the
suricata.yaml file. If this field is not specified, all RR types are logged.
More than 50 values can be specified with this field as shown below:


::
Expand All @@ -423,14 +442,16 @@ One can also control which RR types are logged explicitly from additional custom
types:
- alert
- dns:
# control logging of queries and answers
# default yes, no to disable
query: yes # enable logging of DNS queries
answer: yes # enable logging of DNS answers
# control which RR types are logged
# all enabled if custom not specified
#custom: [a, aaaa, cname, mx, ns, ptr, txt]
custom: [a, ns, md, mf, cname, soa, mb, mg, mr, null,
# Control logging of requests and responses:
# - requests: enable logging of DNS queries
# - responses: enable logging of DNS answers
# By default both requests and responses are logged.
requests: yes
responses: yes
# DNS record types to log, based on the query type.
# Default: all.
#types: [a, aaaa, cname, mx, ns, ptr, txt]
types: [a, ns, md, mf, cname, soa, mb, mg, mr, null,
wks, ptr, hinfo, minfo, mx, txt, rp, afsdb, x25, isdn,
rt, nsap, nsapptr, sig, key, px, gpos, aaaa, loc, nxt,
srv, atma, naptr, kx, cert, a6, dname, opt, apl, ds,
Expand Down
49 changes: 48 additions & 1 deletion rust/src/dns/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,60 @@ pub struct DNSQueryEntry {
pub rrclass: u16,
}

#[derive(Debug,PartialEq)]
pub struct DNSRDataSOA {
/// Primary name server for this zone
pub mname: Vec<u8>,
/// Authority's mailbox
pub rname: Vec<u8>,
/// Serial version number
pub serial: u32,
/// Refresh interval (seconds)
pub refresh: u32,
/// Retry interval (seconds)
pub retry: u32,
/// Upper time limit until zone is no longer authoritative (seconds)
pub expire: u32,
/// Minimum ttl for records in this zone (seconds)
pub minimum: u32,
}

#[derive(Debug,PartialEq)]
pub struct DNSRDataSSHFP {
/// Algorithm number
pub algo: u8,
/// Fingerprint type
pub fp_type: u8,
/// Fingerprint
pub fingerprint: Vec<u8>,
}

/// Represents RData of various formats
#[derive(Debug,PartialEq)]
pub enum DNSRData {
// RData is an address
A(Vec<u8>),
AAAA(Vec<u8>),
// RData is a domain name
CNAME(Vec<u8>),
PTR(Vec<u8>),
MX(Vec<u8>),
// RData is text
TXT(Vec<u8>),
// RData has several fields
SOA(DNSRDataSOA),
SSHFP(DNSRDataSSHFP),
// RData for remaining types is sometimes ignored
Unknown(Vec<u8>),
}

#[derive(Debug,PartialEq)]
pub struct DNSAnswerEntry {
pub name: Vec<u8>,
pub rrtype: u16,
pub rrclass: u16,
pub ttl: u32,
pub data: Vec<u8>,
pub data: DNSRData,
}

#[derive(Debug)]
Expand Down
128 changes: 75 additions & 53 deletions rust/src/dns/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,25 +395,38 @@ pub fn dns_print_addr(addr: &Vec<u8>) -> std::string::String {
}
}

/// Log the SSHPF in an DNSAnswerEntry.
fn dns_log_sshfp(answer: &DNSAnswerEntry) -> Result<Option<JsonBuilder>, JsonError>
{
// Need at least 3 bytes - TODO: log something if we don't?
if answer.data.len() < 3 {
return Ok(None)
}
/// Log SOA section fields.
fn dns_log_soa(soa: &DNSRDataSOA) -> Result<JsonBuilder, JsonError> {
let mut js = JsonBuilder::new_object();

js.set_string_from_bytes("mname", &soa.mname)?;
js.set_string_from_bytes("rname", &soa.rname)?;
js.set_uint("serial", soa.serial as u64)?;
js.set_uint("refresh", soa.refresh as u64)?;
js.set_uint("retry", soa.retry as u64)?;
js.set_uint("expire", soa.expire as u64)?;
js.set_uint("minimum", soa.minimum as u64)?;

js.close()?;
return Ok(js);
}

let mut sshfp = JsonBuilder::new_object();
/// Log SSHFP section fields.
fn dns_log_sshfp(sshfp: &DNSRDataSSHFP) -> Result<JsonBuilder, JsonError>
{
let mut js = JsonBuilder::new_object();

let mut hex = Vec::new();
for byte in &answer.data[2..] {
for byte in &sshfp.fingerprint {
hex.push(format!("{:02x}", byte));
}
sshfp.set_string("fingerprint", &hex.join(":"))?;
sshfp.set_uint("algo", answer.data[0] as u64)?;
sshfp.set_uint("type", answer.data[1] as u64)?;

return Ok(Some(sshfp));
js.set_string("fingerprint", &hex.join(":"))?;
js.set_uint("algo", sshfp.algo as u64)?;
js.set_uint("type", sshfp.fp_type as u64)?;

js.close()?;
return Ok(js);
}

fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, JsonError>
Expand All @@ -424,21 +437,22 @@ fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, Js
jsa.set_string("rrtype", &dns_rrtype_string(answer.rrtype))?;
jsa.set_uint("ttl", answer.ttl as u64)?;

match answer.rrtype {
DNS_RECORD_TYPE_A | DNS_RECORD_TYPE_AAAA => {
jsa.set_string("rdata", &dns_print_addr(&answer.data))?;
match &answer.data {
DNSRData::A(addr) | DNSRData::AAAA(addr) => {
jsa.set_string("rdata", &dns_print_addr(&addr))?;
}
DNSRData::CNAME(bytes) |
DNSRData::MX(bytes) |
DNSRData::TXT(bytes) |
DNSRData::PTR(bytes) => {
jsa.set_string_from_bytes("rdata", &bytes)?;
}
DNSRData::SOA(soa) => {
jsa.set_object("soa", &dns_log_soa(&soa)?)?;
}
DNSRData::SSHFP(sshfp) => {
jsa.set_object("sshfp", &dns_log_sshfp(&sshfp)?)?;
}
DNS_RECORD_TYPE_CNAME |
DNS_RECORD_TYPE_MX |
DNS_RECORD_TYPE_TXT |
DNS_RECORD_TYPE_PTR => {
jsa.set_string_from_bytes("rdata", &answer.data)?;
},
DNS_RECORD_TYPE_SSHFP => {
if let Some(sshfp) = dns_log_sshfp(answer)? {
jsa.set_object("sshfp", &sshfp)?;
}
},
_ => {}
}

Expand Down Expand Up @@ -488,37 +502,44 @@ fn dns_log_json_answer(js: &mut JsonBuilder, response: &DNSResponse, flags: u64)

if flags & LOG_FORMAT_GROUPED != 0 {
let type_string = dns_rrtype_string(answer.rrtype);
match answer.rrtype {
DNS_RECORD_TYPE_A | DNS_RECORD_TYPE_AAAA => {
match &answer.data {
DNSRData::A(addr) | DNSRData::AAAA(addr) => {
if !answer_types.contains_key(&type_string) {
answer_types.insert(type_string.to_string(),
JsonBuilder::new_array());
}
if let Some(a) = answer_types.get_mut(&type_string) {
a.append_string(&dns_print_addr(&answer.data))?;
a.append_string(&dns_print_addr(&addr))?;
}
}
DNS_RECORD_TYPE_CNAME |
DNS_RECORD_TYPE_MX |
DNS_RECORD_TYPE_TXT |
DNS_RECORD_TYPE_PTR => {
DNSRData::CNAME(bytes) |
DNSRData::MX(bytes) |
DNSRData::TXT(bytes) |
DNSRData::PTR(bytes) => {
if !answer_types.contains_key(&type_string) {
answer_types.insert(type_string.to_string(),
JsonBuilder::new_array());
}
if let Some(a) = answer_types.get_mut(&type_string) {
a.append_string_from_bytes(&bytes)?;
}
},
DNSRData::SOA(soa) => {
if !answer_types.contains_key(&type_string) {
answer_types.insert(type_string.to_string(),
JsonBuilder::new_array());
}
if let Some(a) = answer_types.get_mut(&type_string) {
a.append_string_from_bytes(&answer.data)?;
a.append_object(&dns_log_soa(&soa)?)?;
}
},
DNS_RECORD_TYPE_SSHFP => {
DNSRData::SSHFP(sshfp) => {
if !answer_types.contains_key(&type_string) {
answer_types.insert(type_string.to_string(),
JsonBuilder::new_array());
}
if let Some(a) = answer_types.get_mut(&type_string) {
if let Some(sshfp) = dns_log_sshfp(&answer)? {
a.append_object(&sshfp)?;
}
a.append_object(&dns_log_sshfp(&sshfp)?)?;
}
},
_ => {}
Expand Down Expand Up @@ -659,21 +680,22 @@ fn dns_log_json_answer_v1(header: &DNSHeader, answer: &DNSAnswerEntry)
js.set_string("rrtype", &dns_rrtype_string(answer.rrtype))?;
js.set_uint("ttl", answer.ttl as u64)?;

match answer.rrtype {
DNS_RECORD_TYPE_A | DNS_RECORD_TYPE_AAAA => {
js.set_string("rdata", &dns_print_addr(&answer.data))?;
match &answer.data {
DNSRData::A(addr) | DNSRData::AAAA(addr) => {
js.set_string("rdata", &dns_print_addr(&addr))?;
}
DNSRData::CNAME(bytes) |
DNSRData::MX(bytes) |
DNSRData::TXT(bytes) |
DNSRData::PTR(bytes) => {
js.set_string_from_bytes("rdata", &bytes)?;
}
DNSRData::SOA(soa) => {
js.set_object("soa", &dns_log_soa(&soa)?)?;
}
DNSRData::SSHFP(sshfp) => {
js.set_object("sshfp", &dns_log_sshfp(&sshfp)?)?;
}
DNS_RECORD_TYPE_CNAME |
DNS_RECORD_TYPE_MX |
DNS_RECORD_TYPE_TXT |
DNS_RECORD_TYPE_PTR => {
js.set_string_from_bytes("rdata", &answer.data)?;
},
DNS_RECORD_TYPE_SSHFP => {
if let Some(sshfp) = dns_log_sshfp(&answer)? {
js.set_object("sshfp", &sshfp)?;
}
},
_ => {}
}

Expand Down
39 changes: 30 additions & 9 deletions rust/src/dns/lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,38 @@ pub extern "C" fn rs_dns_lua_get_answer_table(clua: &mut CLuaState,
lua.pushstring(&String::from_utf8_lossy(&answer.name));
lua.settable(-3);

if answer.data.len() > 0 {
lua.pushstring("addr");
match answer.rrtype {
DNS_RECORD_TYPE_A | DNS_RECORD_TYPE_AAAA => {
lua.pushstring(&dns_print_addr(&answer.data));
// All rdata types are pushed to "addr" for backwards compatibility
match answer.data {
DNSRData::A(ref bytes) | DNSRData::AAAA(ref bytes) => {
if bytes.len() > 0 {
lua.pushstring("addr");
lua.pushstring(&dns_print_addr(&bytes));
lua.settable(-3);
}
_ => {
lua.pushstring(&String::from_utf8_lossy(&answer.data));
},
DNSRData::CNAME(ref bytes) |
DNSRData::MX(ref bytes) |
DNSRData::TXT(ref bytes) |
DNSRData::PTR(ref bytes) |
DNSRData::Unknown(ref bytes) => {
if bytes.len() > 0 {
lua.pushstring("addr");
lua.pushstring(&String::from_utf8_lossy(&bytes));
lua.settable(-3);
}
}
lua.settable(-3);
},
DNSRData::SOA(ref soa) => {
if soa.mname.len() > 0 {
lua.pushstring("addr");
lua.pushstring(&String::from_utf8_lossy(&soa.mname));
lua.settable(-3);
}
},
DNSRData::SSHFP(ref sshfp) => {
lua.pushstring("addr");
lua.pushstring(&String::from_utf8_lossy(&sshfp.fingerprint));
lua.settable(-3);
},
}
lua.settable(-3);
}
Expand Down
Loading