evebox 0.13.0

A web based Suricata event manager
Documentation
// Copyright (C) 2020 Jason Ish
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

use bytes::{BufMut, BytesMut};
use std::net::IpAddr;

use crate::eve::eve::EveJson;
use crate::packet;

const MAGIC: u32 = 0xa1b2_c3d4;
const VERSION_MAJOR: u16 = 2;
const VERSION_MINOR: u16 = 4;

const FILE_HEADER_LEN: usize = 30;
const PACKET_HEADER_LEN: usize = 4;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("unsupported: {0}")]
    Unsupported(String),
    #[error("missing field: {0}")]
    MissingField(String),
    #[error("invalid protocol: {0}")]
    InvalidProto(String),
    #[error("failed to decode payload: {0}")]
    PayloadDecodeError(base64::DecodeError),
    #[error("bad address: {0}")]
    BadAddr(std::net::AddrParseError),
    #[error("mismatched ip address versions")]
    MissMatchedIpAddrVersions,
    #[error("invalid source port")]
    InvalidSourcePort,
    #[error("invalid destination port")]
    InvalidDestinationPort,
    #[error("IPv6 not supported")]
    Ipv6NotSupported,
    #[error("protocol not supported: {0}")]
    ProtocolNotSupported(String),
}

#[repr(C)]
pub enum LinkType {
    Null = 0,
    Ethernet = 1,
    Raw = 101,
}

impl LinkType {
    pub fn from(val: u8) -> Option<LinkType> {
        match val {
            0 => Some(Self::Null),
            1 => Some(Self::Ethernet),
            101 => Some(Self::Raw),
            _ => None,
        }
    }
}

pub fn packet_from_payload(event: &EveJson) -> Result<Vec<u8>, Error> {
    let payload = if let Some(payload) = &event["payload"].as_str() {
        base64::decode(payload).map_err(Error::PayloadDecodeError)?
    } else {
        return Err(Error::MissingField("payload".to_string()));
    };
    let proto = if let Some(proto) = &event["proto"].as_str() {
        packet::Protocol::from_name(proto)
            .ok_or_else(|| Error::InvalidProto((*proto).to_string()))?
    } else {
        return Err(Error::MissingField("proto".to_string()));
    };
    let src_ip = if let Some(src_ip) = &event["src_ip"].as_str() {
        src_ip.parse::<IpAddr>().map_err(Error::BadAddr)?
    } else {
        return Err(Error::MissingField("src_ip".to_string()));
    };
    let dest_ip = if let Some(dest_ip) = &event["dest_ip"].as_str() {
        dest_ip.parse::<IpAddr>().map_err(Error::BadAddr)?
    } else {
        return Err(Error::MissingField("dest_ip".to_string()));
    };

    match proto {
        packet::Protocol::Tcp => {
            let src_port = &event["src_port"]
                .as_u64()
                .ok_or(Error::InvalidDestinationPort)?;
            let dest_port = &event["dest_port"]
                .as_u64()
                .ok_or(Error::InvalidSourcePort)?;
            match (src_ip, dest_ip) {
                (IpAddr::V4(src), IpAddr::V4(dst)) => {
                    let tcp = packet::TcpBuilder::new(*src_port as u16, *dest_port as u16)
                        .payload(payload)
                        .build();
                    let packet = packet::Ip4Builder::new()
                        .source(src)
                        .destination(dst)
                        .protocol(proto)
                        .payload(tcp)
                        .build();
                    return Ok(packet);
                }
                (IpAddr::V6(_src), IpAddr::V6(_dst)) => {
                    return Err(Error::Ipv6NotSupported);
                }
                _ => {
                    return Err(Error::MissMatchedIpAddrVersions);
                }
            }
        }
        packet::Protocol::Udp => {
            let src_port = &event["src_port"]
                .as_u64()
                .ok_or(Error::InvalidDestinationPort)?;
            let dest_port = &event["dest_port"]
                .as_u64()
                .ok_or(Error::InvalidSourcePort)?;
            match (src_ip, dest_ip) {
                (IpAddr::V4(src), IpAddr::V4(dst)) => {
                    let udp = packet::UdpBuilder::new(*src_port as u16, *dest_port as u16)
                        .payload(payload)
                        .build();
                    let packet = packet::Ip4Builder::new()
                        .source(src)
                        .destination(dst)
                        .protocol(proto)
                        .payload(udp)
                        .build();
                    return Ok(packet);
                }
                (IpAddr::V6(_src), IpAddr::V6(_dst)) => {
                    return Err(Error::Ipv6NotSupported);
                }
                _ => {
                    return Err(Error::MissMatchedIpAddrVersions);
                }
            }
        }
        _ => {
            return Err(Error::ProtocolNotSupported(format!("{:?}", proto)));
        }
    }
}

pub fn create(linktype: u32, ts: chrono::DateTime<chrono::Utc>, packet: &[u8]) -> Vec<u8> {
    let mut buf = BytesMut::with_capacity(FILE_HEADER_LEN + PACKET_HEADER_LEN + packet.len());

    // Write out the file header.
    buf.put_u32_le(MAGIC);
    buf.put_u16_le(VERSION_MAJOR);
    buf.put_u16_le(VERSION_MINOR);
    buf.put_u32_le(0); // This zone (GMT to local correction)
    buf.put_u32_le(0); // Accuracy of timestamps (sigfigs)
    buf.put_u32_le(0); // Snap length
    buf.put_u32_le(linktype); // Data link type

    // The record header.
    buf.put_u32_le(ts.timestamp() as u32);
    buf.put_u32_le(ts.timestamp_subsec_micros());
    buf.put_u32_le(packet.len() as u32);
    buf.put_u32_le(packet.len() as u32);
    buf.put_slice(&packet);

    buf.to_vec()
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::eve::Eve;

    #[test]
    fn test_pcap_file() {
        let linktype = LinkType::Ethernet;
        let eve_timestamp = "2020-05-01T08:50:23.297919-0600";
        let packet_base64 =
            "oDafTEwo2MuK7aFGCABFAAAobXBAAEAGMBcKEAELzE/F3qMmAbtL2EhIK8jWtFAQAenVIwAAAAAAAAAA";
        let ts = crate::eve::parse_eve_timestamp(eve_timestamp).unwrap();
        let packet = base64::decode(packet_base64).unwrap();
        let _pcap_buffer = super::create(linktype as u32, ts, &packet);
    }

    #[test]
    fn test_packet_from_payload() {
        let event: EveJson = serde_json::from_str(TEST_EVE_RECORD).unwrap();
        let packet = super::packet_from_payload(&event).unwrap();
        let ts = event.timestamp().unwrap();
        let _pcap_buffer = super::create(LinkType::Raw as u32, ts, &packet);
    }

    const TEST_EVE_RECORD: &str = r#"
{
    "@timestamp": "2020-05-01T13:13:37.621Z",
    "alert": {
      "action": "allowed",
      "category": "A Network Trojan was detected",
      "gid": 1,
      "metadata": {
        "created_at": [
          "2015_10_06"
        ],
        "updated_at": [
          "2015_10_06"
        ]
      },
      "rev": 1,
      "severity": 1,
      "signature": "ET MALWARE ELF/muBoT IRC Activity 4",
      "signature_id": 2021915
    },
    "community_id": "1:Fc54mFg4nYz5CcocWFqQcWc38po=",
    "dest_ip": "10.16.1.10",
    "dest_port": 801,
    "evebox": {
      "filename": "/var/log/suricata/eve.json"
    },
    "event_type": "alert",
    "flow": {
      "bytes_toclient": 44205860244,
      "bytes_toserver": 56980285098,
      "pkts_toclient": 45765558,
      "pkts_toserver": 45613601,
      "start": "2020-04-30T22:59:51.502309-0600"
    },
    "flow_id": 2195577295579685,
    "host": "server1.unx.ca",
    "in_iface": "eno1",
    "metadata": {
      "flowbits": [
        "ET.HB.Response.CI",
        "ET.HB.Response.SI"
      ]
    },
    "packet": "bDvlJzW6ABEyF0nwCABFAAXcU/VAAEAGyvkKEAEEChABCggBAyG2gRJvgUzqtYAQB9OhogAAAQEICgDk/MxsMrfsfGdvbGFudHVzLmNvbSI7IGRpc3RhbmNlOjE7IHdpdGhpbjoxMzsgcmVmZXJlbmNlOnVybCxzc2xibC5hYnVzZS5jaDsgY2xhc3N0eXBlOnRyb2phbi1hY3Rpdml0eTsgc2lkOjIwMjE5MTE7IHJldjoyOyBtZXRhZGF0YTphdHRhY2tfdGFyZ2V0IENsaWVudF9FbmRwb2ludCwgZGVwbG95bWVudCBQZXJpbWV0ZXIsIHRhZyBTU0xfTWFsaWNpb3VzX0NlcnQsIHNpZ25hdHVyZV9zZXZlcml0eSBNYWpvciwgY3JlYXRlZF9hdCAyMDE1XzEwXzA2LCB1cGRhdGVkX2F0IDIwMTZfMDdfMDE7KQphbGVydCB0Y3AgYW55IGFueSAtPiBhbnkgYW55IChtc2c6IkVUIE1BTFdBUkUgRUxGL211Qm9UIElSQyBBY3Rpdml0eSAxIjsgZmxvdzplc3RhYmxpc2hlZCxmcm9tX3NlcnZlcjsgY29udGVudDoiTk9USUNFIjsgY29udGVudDoifDNhfG11Qm9UfDIwfFByaXZ8MjB8VmVyc2lvbiI7IGZhc3RfcGF0dGVybjsgZGlzdGFuY2U6MDsgcmVmZXJlbmNlOnVybCxwYXN0ZWJpbi5jb20vRUgxU0g5YUw7IGNsYXNzdHlwZTp0cm9qYW4tYWN0aXZpdHk7IHNpZDoyMDIxOTEyOyByZXY6MTsgbWV0YWRhdGE6Y3JlYXRlZF9hdCAyMDE1XzEwXzA2LCB1cGRhdGVkX2F0IDIwMTVfMTBfMDY7KQphbGVydCB0Y3AgYW55IGFueSAtPiBhbnkgYW55IChtc2c6IkVUIE1BTFdBUkUgRUxGL211Qm9UIElSQyBBY3Rpdml0eSAyIjsgZmxvdzplc3RhYmxpc2hlZCxmcm9tX3NlcnZlcjsgY29udGVudDoiTk9USUNFIjsgY29udGVudDoifDNhfG11Qm9UfDIwfHNheXN8MjB8IjsgZmFzdF9wYXR0ZXJuOyBkaXN0YW5jZTowOyByZWZlcmVuY2U6dXJsLHBhc3RlYmluLmNvbS9FSDFTSDlhTDsgY2xhc3N0eXBlOnRyb2phbi1hY3Rpdml0eTsgc2lkOjIwMjE5MTM7IHJldjoxOyBtZXRhZGF0YTpjcmVhdGVkX2F0IDIwMTVfMTBfMDYsIHVwZGF0ZWRfYXQgMjAxNV8xMF8wNjspCmFsZXJ0IHRjcCBhbnkgYW55IC0+IGFueSBhbnkgKG1zZzoiRVQgTUFMV0FSRSBFTEYvbXVCb1QgSVJDIEFjdGl2aXR5IDMiOyBmbG93OmVzdGFibGlzaGVkLGZyb21fc2VydmVyOyBjb250ZW50OiJOT1RJQ0UiOyBjb250ZW50OiJ8M2F8W0FwYWNoZSAvIFBIUCA1LngiOyBmYXN0X3BhdHRlcm47IGRpc3RhbmNlOjA7IHJlZmVyZW5jZTp1cmwscGFzdGViaW4uY29tL0VIMVNIOWFMOyBjbGFzc3R5cGU6dHJvamFuLWFjdGl2aXR5OyBzaWQ6MjAyMTkxNDsgcmV2OjE7IG1ldGFkYXRhOmNyZWF0ZWRfYXQgMjAxNV8xMF8wNiwgdXBkYXRlZF9hdCAyMDE1XzEwXzA2OykKYWxlcnQgdGNwIGFueSBhbnkgLT4gYW55IGFueSAobXNnOiJFVCBNQUxXQVJFIEVMRi9tdUJvVCBJUkMgQWN0aXZpdHkgNCI7IGZsb3c6ZXN0YWJsaXNoZWQsZnJvbV9zZXJ2ZXI7IGNvbnRlbnQ6Ik5PVElDRSI7IGNvbnRlbnQ6IkZMT09EIDx0YXJnZXQ+IDxwb3J0PiA8c2Vjcz4iOyBmYXN0X3BhdHRlcm47IGRpc3RhbmNlOjA7IHJlZmVyZW5jZTp1cmwscGFzdGU=",
    "packet_info": {
      "linktype": 1
    },
    "payload": "fGdvbGFudHVzLmNvbSI7IGRpc3RhbmNlOjE7IHdpdGhpbjoxMzsgcmVmZXJlbmNlOnVybCxzc2xibC5hYnVzZS5jaDsgY2xhc3N0eXBlOnRyb2phbi1hY3Rpdml0eTsgc2lkOjIwMjE5MTE7IHJldjoyOyBtZXRhZGF0YTphdHRhY2tfdGFyZ2V0IENsaWVudF9FbmRwb2ludCwgZGVwbG95bWVudCBQZXJpbWV0ZXIsIHRhZyBTU0xfTWFsaWNpb3VzX0NlcnQsIHNpZ25hdHVyZV9zZXZlcml0eSBNYWpvciwgY3JlYXRlZF9hdCAyMDE1XzEwXzA2LCB1cGRhdGVkX2F0IDIwMTZfMDdfMDE7KQphbGVydCB0Y3AgYW55IGFueSAtPiBhbnkgYW55IChtc2c6IkVUIE1BTFdBUkUgRUxGL211Qm9UIElSQyBBY3Rpdml0eSAxIjsgZmxvdzplc3RhYmxpc2hlZCxmcm9tX3NlcnZlcjsgY29udGVudDoiTk9USUNFIjsgY29udGVudDoifDNhfG11Qm9UfDIwfFByaXZ8MjB8VmVyc2lvbiI7IGZhc3RfcGF0dGVybjsgZGlzdGFuY2U6MDsgcmVmZXJlbmNlOnVybCxwYXN0ZWJpbi5jb20vRUgxU0g5YUw7IGNsYXNzdHlwZTp0cm9qYW4tYWN0aXZpdHk7IHNpZDoyMDIxOTEyOyByZXY6MTsgbWV0YWRhdGE6Y3JlYXRlZF9hdCAyMDE1XzEwXzA2LCB1cGRhdGVkX2F0IDIwMTVfMTBfMDY7KQphbGVydCB0Y3AgYW55IGFueSAtPiBhbnkgYW55IChtc2c6IkVUIE1BTFdBUkUgRUxGL211Qm9UIElSQyBBY3Rpdml0eSAyIjsgZmxvdzplc3RhYmxpc2hlZCxmcm9tX3NlcnZlcjsgY29udGVudDoiTk9USUNFIjsgY29udGVudDoifDNhfG11Qm9UfDIwfHNheXN8MjB8IjsgZmFzdF9wYXR0ZXJuOyBkaXN0YW5jZTowOyByZWZlcmVuY2U6dXJsLHBhc3RlYmluLmNvbS9FSDFTSDlhTDsgY2xhc3N0eXBlOnRyb2phbi1hY3Rpdml0eTsgc2lkOjIwMjE5MTM7IHJldjoxOyBtZXRhZGF0YTpjcmVhdGVkX2F0IDIwMTVfMTBfMDYsIHVwZGF0ZWRfYXQgMjAxNV8xMF8wNjspCmFsZXJ0IHRjcCBhbnkgYW55IC0+IGFueSBhbnkgKG1zZzoiRVQgTUFMV0FSRSBFTEYvbXVCb1QgSVJDIEFjdGl2aXR5IDMiOyBmbG93OmVzdGFibGlzaGVkLGZyb21fc2VydmVyOyBjb250ZW50OiJOT1RJQ0UiOyBjb250ZW50OiJ8M2F8W0FwYWNoZSAvIFBIUCA1LngiOyBmYXN0X3BhdHRlcm47IGRpc3RhbmNlOjA7IHJlZmVyZW5jZTp1cmwscGFzdGViaW4uY29tL0VIMVNIOWFMOyBjbGFzc3R5cGU6dHJvamFuLWFjdGl2aXR5OyBzaWQ6MjAyMTkxNDsgcmV2OjE7IG1ldGFkYXRhOmNyZWF0ZWRfYXQgMjAxNV8xMF8wNiwgdXBkYXRlZF9hdCAyMDE1XzEwXzA2OykKYWxlcnQgdGNwIGFueSBhbnkgLT4gYW55IGFueSAobXNnOiJFVCBNQUxXQVJFIEVMRi9tdUJvVCBJUkMgQWN0aXZpdHkgNCI7IGZsb3c6ZXN0YWJsaXNoZWQsZnJvbV9zZXJ2ZXI7IGNvbnRlbnQ6Ik5PVElDRSI7IGNvbnRlbnQ6IkZMT09EIDx0YXJnZXQ+IDxwb3J0PiA8c2Vjcz4iOyBmYXN0X3BhdHRlcm47IGRpc3RhbmNlOjA7IHJlZmVyZW5jZTp1cmwscGFzdGU=",
    "payload_printable": "|golantus.com\"; distance:1; within:13; reference:url,sslbl.abuse.ch; classtype:trojan-activity; sid:2021911; rev:2; metadata:attack_target Client_Endpoint, deployment Perimeter, tag SSL_Malicious_Cert, signature_severity Major, created_at 2015_10_06, updated_at 2016_07_01;)\nalert tcp any any -> any any (msg:\"ET MALWARE ELF/muBoT IRC Activity 1\"; flow:established,from_server; content:\"NOTICE\"; content:\"|3a|muBoT|20|Priv|20|Version\"; fast_pattern; distance:0; reference:url,pastebin.com/EH1SH9aL; classtype:trojan-activity; sid:2021912; rev:1; metadata:created_at 2015_10_06, updated_at 2015_10_06;)\nalert tcp any any -> any any (msg:\"ET MALWARE ELF/muBoT IRC Activity 2\"; flow:established,from_server; content:\"NOTICE\"; content:\"|3a|muBoT|20|says|20|\"; fast_pattern; distance:0; reference:url,pastebin.com/EH1SH9aL; classtype:trojan-activity; sid:2021913; rev:1; metadata:created_at 2015_10_06, updated_at 2015_10_06;)\nalert tcp any any -> any any (msg:\"ET MALWARE ELF/muBoT IRC Activity 3\"; flow:established,from_server; content:\"NOTICE\"; content:\"|3a|[Apache / PHP 5.x\"; fast_pattern; distance:0; reference:url,pastebin.com/EH1SH9aL; classtype:trojan-activity; sid:2021914; rev:1; metadata:created_at 2015_10_06, updated_at 2015_10_06;)\nalert tcp any any -> any any (msg:\"ET MALWARE ELF/muBoT IRC Activity 4\"; flow:established,from_server; content:\"NOTICE\"; content:\"FLOOD <target> <port> <secs>\"; fast_pattern; distance:0; reference:url,paste",
    "proto": "TCP",
    "src_ip": "10.16.1.4",
    "src_port": 2049,
    "stream": 0,
    "tags": [
      "evebox.elastic-import"
    ],
    "timestamp": "2020-05-01T07:13:37.621315-0600"
}
"#;
}