diff options
Diffstat (limited to '')
-rw-r--r-- | src/output-json-alert.c | 1187 |
1 files changed, 1187 insertions, 0 deletions
diff --git a/src/output-json-alert.c b/src/output-json-alert.c new file mode 100644 index 0000000..a7df106 --- /dev/null +++ b/src/output-json-alert.c @@ -0,0 +1,1187 @@ +/* Copyright (C) 2013-2023 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Tom DeCanio <td@npulsetech.com> + * + * Logs alerts in JSON format. + * + */ + +#include "suricata-common.h" +#include "packet.h" +#include "detect.h" +#include "flow.h" +#include "conf.h" + +#include "stream.h" +#include "threads.h" +#include "tm-threads.h" +#include "threadvars.h" +#include "util-debug.h" + +#include "util-logopenfile.h" +#include "util-misc.h" +#include "util-time.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" + +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-reference.h" +#include "detect-metadata.h" +#include "app-layer-parser.h" +#include "app-layer-dnp3.h" +#include "app-layer-htp.h" +#include "app-layer-htp-xff.h" +#include "app-layer-ftp.h" +#include "app-layer-frames.h" +#include "util-classification-config.h" +#include "util-syslog.h" +#include "log-pcap.h" + +#include "output.h" +#include "output-json.h" +#include "output-json-alert.h" +#include "output-json-dnp3.h" +#include "output-json-dns.h" +#include "output-json-http.h" +#include "output-json-tls.h" +#include "output-json-ssh.h" +#include "rust.h" +#include "output-json-smtp.h" +#include "output-json-email-common.h" +#include "output-json-nfs.h" +#include "output-json-smb.h" +#include "output-json-flow.h" +#include "output-json-sip.h" +#include "output-json-rfb.h" +#include "output-json-mqtt.h" +#include "output-json-ike.h" +#include "output-json-modbus.h" +#include "output-json-frame.h" +#include "output-json-quic.h" + +#include "util-byte.h" +#include "util-privs.h" +#include "util-print.h" +#include "util-proto-name.h" +#include "util-optimize.h" +#include "util-buffer.h" +#include "util-validate.h" + +#include "action-globals.h" + +#define MODULE_NAME "JsonAlertLog" + +#define LOG_JSON_PAYLOAD BIT_U16(0) +#define LOG_JSON_PACKET BIT_U16(1) +#define LOG_JSON_PAYLOAD_BASE64 BIT_U16(2) +#define LOG_JSON_TAGGED_PACKETS BIT_U16(3) +#define LOG_JSON_APP_LAYER BIT_U16(4) +#define LOG_JSON_FLOW BIT_U16(5) +#define LOG_JSON_HTTP_BODY BIT_U16(6) +#define LOG_JSON_HTTP_BODY_BASE64 BIT_U16(7) +#define LOG_JSON_RULE_METADATA BIT_U16(8) +#define LOG_JSON_RULE BIT_U16(9) +#define LOG_JSON_VERDICT BIT_U16(10) + +#define METADATA_DEFAULTS ( LOG_JSON_FLOW | \ + LOG_JSON_APP_LAYER | \ + LOG_JSON_RULE_METADATA) + +#define JSON_BODY_LOGGING (LOG_JSON_HTTP_BODY | LOG_JSON_HTTP_BODY_BASE64) + +#define JSON_STREAM_BUFFER_SIZE 4096 + +typedef struct AlertJsonOutputCtx_ { + LogFileCtx* file_ctx; + uint16_t flags; + uint32_t payload_buffer_size; + HttpXFFCfg *xff_cfg; + HttpXFFCfg *parent_xff_cfg; + OutputJsonCtx *eve_ctx; +} AlertJsonOutputCtx; + +typedef struct JsonAlertLogThread_ { + MemBuffer *payload_buffer; + AlertJsonOutputCtx* json_output_ctx; + OutputJsonThreadCtx *ctx; +} JsonAlertLogThread; + +/* Callback function to pack payload contents from a stream into a buffer + * so we can report them in JSON output. */ +static int AlertJsonDumpStreamSegmentCallback( + const Packet *p, TcpSegment *seg, void *data, const uint8_t *buf, uint32_t buflen) +{ + MemBuffer *payload = (MemBuffer *)data; + MemBufferWriteRaw(payload, buf, buflen); + + return 1; +} + +static void AlertJsonTls(const Flow *f, JsonBuilder *js) +{ + SSLState *ssl_state = (SSLState *)FlowGetAppState(f); + if (ssl_state) { + jb_open_object(js, "tls"); + + JsonTlsLogJSONExtended(js, ssl_state); + + jb_close(js); + } + + return; +} + +static void AlertJsonSsh(const Flow *f, JsonBuilder *js) +{ + void *ssh_state = FlowGetAppState(f); + if (ssh_state) { + JsonBuilderMark mark = { 0, 0, 0 }; + void *tx_ptr = rs_ssh_state_get_tx(ssh_state, 0); + jb_get_mark(js, &mark); + jb_open_object(js, "ssh"); + if (rs_ssh_log_json(tx_ptr, js)) { + jb_close(js); + } else { + jb_restore_mark(js, &mark); + } + } + + return; +} + +static void AlertJsonHttp2(const Flow *f, const uint64_t tx_id, JsonBuilder *js) +{ + void *h2_state = FlowGetAppState(f); + if (h2_state) { + void *tx_ptr = rs_http2_state_get_tx(h2_state, tx_id); + if (tx_ptr) { + JsonBuilderMark mark = { 0, 0, 0 }; + jb_get_mark(js, &mark); + jb_open_object(js, "http"); + if (rs_http2_log_json(tx_ptr, js)) { + jb_close(js); + } else { + jb_restore_mark(js, &mark); + } + } + } + + return; +} + +static void AlertJsonDnp3(const Flow *f, const uint64_t tx_id, JsonBuilder *js) +{ + DNP3State *dnp3_state = (DNP3State *)FlowGetAppState(f); + if (dnp3_state) { + DNP3Transaction *tx = AppLayerParserGetTx(IPPROTO_TCP, ALPROTO_DNP3, + dnp3_state, tx_id); + if (tx) { + JsonBuilderMark mark = { 0, 0, 0 }; + jb_get_mark(js, &mark); + bool logged = false; + jb_open_object(js, "dnp3"); + if (tx->is_request && tx->done) { + jb_open_object(js, "request"); + JsonDNP3LogRequest(js, tx); + jb_close(js); + logged = true; + } + if (!tx->is_request && tx->done) { + jb_open_object(js, "response"); + JsonDNP3LogResponse(js, tx); + jb_close(js); + logged = true; + } + if (logged) { + /* Close dnp3 object. */ + jb_close(js); + } else { + jb_restore_mark(js, &mark); + } + } + } +} + +static void AlertJsonDns(const Flow *f, const uint64_t tx_id, JsonBuilder *js) +{ + void *dns_state = (void *)FlowGetAppState(f); + if (dns_state) { + void *txptr = AppLayerParserGetTx(f->proto, ALPROTO_DNS, + dns_state, tx_id); + if (txptr) { + jb_open_object(js, "dns"); + JsonBuilder *qjs = JsonDNSLogQuery(txptr); + if (qjs != NULL) { + jb_set_object(js, "query", qjs); + jb_free(qjs); + } + JsonBuilder *ajs = JsonDNSLogAnswer(txptr); + if (ajs != NULL) { + jb_set_object(js, "answer", ajs); + jb_free(ajs); + } + jb_close(js); + } + } + return; +} + +static void AlertJsonSNMP(const Flow *f, const uint64_t tx_id, JsonBuilder *js) +{ + void *snmp_state = (void *)FlowGetAppState(f); + if (snmp_state != NULL) { + void *tx = AppLayerParserGetTx(f->proto, ALPROTO_SNMP, snmp_state, + tx_id); + if (tx != NULL) { + jb_open_object(js, "snmp"); + rs_snmp_log_json_response(js, tx); + jb_close(js); + } + } +} + +static void AlertJsonRDP(const Flow *f, const uint64_t tx_id, JsonBuilder *js) +{ + void *rdp_state = (void *)FlowGetAppState(f); + if (rdp_state != NULL) { + void *tx = AppLayerParserGetTx(f->proto, ALPROTO_RDP, rdp_state, + tx_id); + if (tx != NULL) { + JsonBuilderMark mark = { 0, 0, 0 }; + jb_get_mark(js, &mark); + if (!rs_rdp_to_json(tx, js)) { + jb_restore_mark(js, &mark); + } + } + } +} + +static void AlertJsonBitTorrentDHT(const Flow *f, const uint64_t tx_id, JsonBuilder *js) +{ + void *bittorrent_dht_state = (void *)FlowGetAppState(f); + if (bittorrent_dht_state != NULL) { + void *tx = + AppLayerParserGetTx(f->proto, ALPROTO_BITTORRENT_DHT, bittorrent_dht_state, tx_id); + if (tx != NULL) { + JsonBuilderMark mark = { 0, 0, 0 }; + jb_get_mark(js, &mark); + jb_open_object(js, "bittorrent_dht"); + if (rs_bittorrent_dht_logger_log(tx, js)) { + jb_close(js); + } else { + jb_restore_mark(js, &mark); + } + } + } +} + +static void AlertJsonSourceTarget(const Packet *p, const PacketAlert *pa, + JsonBuilder *js, JsonAddrInfo *addr) +{ + jb_open_object(js, "source"); + if (pa->s->flags & SIG_FLAG_DEST_IS_TARGET) { + jb_set_string(js, "ip", addr->src_ip); + switch (p->proto) { + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + break; + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_SCTP: + jb_set_uint(js, "port", addr->sp); + break; + } + } else if (pa->s->flags & SIG_FLAG_SRC_IS_TARGET) { + jb_set_string(js, "ip", addr->dst_ip); + switch (p->proto) { + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + break; + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_SCTP: + jb_set_uint(js, "port", addr->dp); + break; + } + } + jb_close(js); + + jb_open_object(js, "target"); + if (pa->s->flags & SIG_FLAG_DEST_IS_TARGET) { + jb_set_string(js, "ip", addr->dst_ip); + switch (p->proto) { + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + break; + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_SCTP: + jb_set_uint(js, "port", addr->dp); + break; + } + } else if (pa->s->flags & SIG_FLAG_SRC_IS_TARGET) { + jb_set_string(js, "ip", addr->src_ip); + switch (p->proto) { + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + break; + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_SCTP: + jb_set_uint(js, "port", addr->sp); + break; + } + } + jb_close(js); +} + +static void AlertJsonMetadata(AlertJsonOutputCtx *json_output_ctx, + const PacketAlert *pa, JsonBuilder *js) +{ + if (pa->s->metadata && pa->s->metadata->json_str) { + jb_set_formatted(js, pa->s->metadata->json_str); + } +} + +void AlertJsonHeader(void *ctx, const Packet *p, const PacketAlert *pa, JsonBuilder *js, + uint16_t flags, JsonAddrInfo *addr, char *xff_buffer) +{ + AlertJsonOutputCtx *json_output_ctx = (AlertJsonOutputCtx *)ctx; + const char *action = "allowed"; + /* use packet action if rate_filter modified the action */ + if (unlikely(pa->flags & PACKET_ALERT_RATE_FILTER_MODIFIED)) { + if (PacketCheckAction(p, ACTION_DROP_REJECT)) { + action = "blocked"; + } + } else { + if (pa->action & ACTION_REJECT_ANY) { + action = "blocked"; + } else if ((pa->action & ACTION_DROP) && EngineModeIsIPS()) { + action = "blocked"; + } + } + + /* Add tx_id to root element for correlation with other events. */ + /* json_object_del(js, "tx_id"); */ + if (pa->flags & PACKET_ALERT_FLAG_TX) { + jb_set_uint(js, "tx_id", pa->tx_id); + } + + jb_open_object(js, "alert"); + + jb_set_string(js, "action", action); + jb_set_uint(js, "gid", pa->s->gid); + jb_set_uint(js, "signature_id", pa->s->id); + jb_set_uint(js, "rev", pa->s->rev); + /* TODO: JsonBuilder should handle unprintable characters like + * SCJsonString. */ + jb_set_string(js, "signature", pa->s->msg ? pa->s->msg: ""); + jb_set_string(js, "category", pa->s->class_msg ? pa->s->class_msg: ""); + jb_set_uint(js, "severity", pa->s->prio); + + if (p->tenant_id > 0) { + jb_set_uint(js, "tenant_id", p->tenant_id); + } + + if (addr && pa->s->flags & SIG_FLAG_HAS_TARGET) { + AlertJsonSourceTarget(p, pa, js, addr); + } + + if ((json_output_ctx != NULL) && (flags & LOG_JSON_RULE_METADATA)) { + AlertJsonMetadata(json_output_ctx, pa, js); + } + + if (flags & LOG_JSON_RULE) { + jb_set_string(js, "rule", pa->s->sig_str); + } + if (xff_buffer && xff_buffer[0]) { + jb_set_string(js, "xff", xff_buffer); + } + + jb_close(js); +} + +static void AlertJsonTunnel(const Packet *p, JsonBuilder *js) +{ + if (p->root == NULL) { + return; + } + + jb_open_object(js, "tunnel"); + + enum PktSrcEnum pkt_src; + uint64_t pcap_cnt; + JsonAddrInfo addr = json_addr_info_zero; + JsonAddrInfoInit(p->root, 0, &addr); + pcap_cnt = p->root->pcap_cnt; + pkt_src = p->root->pkt_src; + + jb_set_string(js, "src_ip", addr.src_ip); + jb_set_uint(js, "src_port", addr.sp); + jb_set_string(js, "dest_ip", addr.dst_ip); + jb_set_uint(js, "dest_port", addr.dp); + jb_set_string(js, "proto", addr.proto); + + jb_set_uint(js, "depth", p->recursion_level); + if (pcap_cnt != 0) { + jb_set_uint(js, "pcap_cnt", pcap_cnt); + } + jb_set_string(js, "pkt_src", PktSrcToString(pkt_src)); + jb_close(js); +} + +static void AlertAddPayload(AlertJsonOutputCtx *json_output_ctx, JsonBuilder *js, const Packet *p) +{ + if (json_output_ctx->flags & LOG_JSON_PAYLOAD_BASE64) { + jb_set_base64(js, "payload", p->payload, p->payload_len); + } + + if (json_output_ctx->flags & LOG_JSON_PAYLOAD) { + uint8_t printable_buf[p->payload_len + 1]; + uint32_t offset = 0; + PrintStringsToBuffer(printable_buf, &offset, + p->payload_len + 1, + p->payload, p->payload_len); + printable_buf[p->payload_len] = '\0'; + jb_set_string(js, "payload_printable", (char *)printable_buf); + } +} + +static void AlertAddAppLayer(const Packet *p, JsonBuilder *jb, + const uint64_t tx_id, const uint16_t option_flags) +{ + const AppProto proto = FlowGetAppProtocol(p->flow); + JsonBuilderMark mark = { 0, 0, 0 }; + switch (proto) { + case ALPROTO_HTTP1: + // TODO: Could result in an empty http object being logged. + jb_open_object(jb, "http"); + if (EveHttpAddMetadata(p->flow, tx_id, jb)) { + if (option_flags & LOG_JSON_HTTP_BODY) { + EveHttpLogJSONBodyPrintable(jb, p->flow, tx_id); + } + if (option_flags & LOG_JSON_HTTP_BODY_BASE64) { + EveHttpLogJSONBodyBase64(jb, p->flow, tx_id); + } + } + jb_close(jb); + break; + case ALPROTO_TLS: + AlertJsonTls(p->flow, jb); + break; + case ALPROTO_SSH: + AlertJsonSsh(p->flow, jb); + break; + case ALPROTO_SMTP: + jb_get_mark(jb, &mark); + jb_open_object(jb, "smtp"); + if (EveSMTPAddMetadata(p->flow, tx_id, jb)) { + jb_close(jb); + } else { + jb_restore_mark(jb, &mark); + } + jb_get_mark(jb, &mark); + jb_open_object(jb, "email"); + if (EveEmailAddMetadata(p->flow, tx_id, jb)) { + jb_close(jb); + } else { + jb_restore_mark(jb, &mark); + } + break; + case ALPROTO_NFS: + /* rpc */ + jb_get_mark(jb, &mark); + jb_open_object(jb, "rpc"); + if (EveNFSAddMetadataRPC(p->flow, tx_id, jb)) { + jb_close(jb); + } else { + jb_restore_mark(jb, &mark); + } + /* nfs */ + jb_get_mark(jb, &mark); + jb_open_object(jb, "nfs"); + if (EveNFSAddMetadata(p->flow, tx_id, jb)) { + jb_close(jb); + } else { + jb_restore_mark(jb, &mark); + } + break; + case ALPROTO_SMB: + jb_get_mark(jb, &mark); + jb_open_object(jb, "smb"); + if (EveSMBAddMetadata(p->flow, tx_id, jb)) { + jb_close(jb); + } else { + jb_restore_mark(jb, &mark); + } + break; + case ALPROTO_SIP: + JsonSIPAddMetadata(jb, p->flow, tx_id); + break; + case ALPROTO_RFB: + jb_get_mark(jb, &mark); + if (!JsonRFBAddMetadata(p->flow, tx_id, jb)) { + jb_restore_mark(jb, &mark); + } + break; + case ALPROTO_FTPDATA: + jb_get_mark(jb, &mark); + jb_open_object(jb, "ftp_data"); + EveFTPDataAddMetadata(p->flow, jb); + jb_close(jb); + break; + case ALPROTO_DNP3: + AlertJsonDnp3(p->flow, tx_id, jb); + break; + case ALPROTO_HTTP2: + AlertJsonHttp2(p->flow, tx_id, jb); + break; + case ALPROTO_DNS: + AlertJsonDns(p->flow, tx_id, jb); + break; + case ALPROTO_IKE: + jb_get_mark(jb, &mark); + if (!EveIKEAddMetadata(p->flow, tx_id, jb)) { + jb_restore_mark(jb, &mark); + } + break; + case ALPROTO_MQTT: + jb_get_mark(jb, &mark); + if (!JsonMQTTAddMetadata(p->flow, tx_id, jb)) { + jb_restore_mark(jb, &mark); + } + break; + case ALPROTO_QUIC: + jb_get_mark(jb, &mark); + if (!JsonQuicAddMetadata(p->flow, tx_id, jb)) { + jb_restore_mark(jb, &mark); + } + break; + case ALPROTO_SNMP: + AlertJsonSNMP(p->flow, tx_id, jb); + break; + case ALPROTO_RDP: + AlertJsonRDP(p->flow, tx_id, jb); + break; + case ALPROTO_MODBUS: + jb_get_mark(jb, &mark); + if (!JsonModbusAddMetadata(p->flow, tx_id, jb)) { + jb_restore_mark(jb, &mark); + } + break; + case ALPROTO_BITTORRENT_DHT: + AlertJsonBitTorrentDHT(p->flow, tx_id, jb); + break; + default: + break; + } +} + +static void AlertAddFiles(const Packet *p, JsonBuilder *jb, const uint64_t tx_id) +{ + const uint8_t direction = + (p->flowflags & FLOW_PKT_TOSERVER) ? STREAM_TOSERVER : STREAM_TOCLIENT; + FileContainer *ffc = NULL; + if (p->flow->alstate != NULL) { + void *tx = AppLayerParserGetTx(p->flow->proto, p->flow->alproto, p->flow->alstate, tx_id); + if (tx) { + AppLayerGetFileState files = + AppLayerParserGetTxFiles(p->flow, p->flow->alstate, tx, direction); + ffc = files.fc; + } + } + if (ffc != NULL) { + File *file = ffc->head; + bool isopen = false; + while (file) { + if (!isopen) { + isopen = true; + jb_open_array(jb, "files"); + } + jb_start_object(jb); + EveFileInfo(jb, file, tx_id, file->flags); + jb_close(jb); + file = file->next; + } + if (isopen) { + jb_close(jb); + } + } +} + +static void AlertAddFrame(const Packet *p, JsonBuilder *jb, const int64_t frame_id) +{ + if (p->flow == NULL || (p->proto == IPPROTO_TCP && p->flow->protoctx == NULL)) + return; + + FramesContainer *frames_container = AppLayerFramesGetContainer(p->flow); + if (frames_container == NULL) + return; + + Frames *frames = NULL; + TcpStream *stream = NULL; + if (p->proto == IPPROTO_TCP) { + TcpSession *ssn = p->flow->protoctx; + if (PKT_IS_TOSERVER(p)) { + stream = &ssn->client; + frames = &frames_container->toserver; + } else { + stream = &ssn->server; + frames = &frames_container->toclient; + } + Frame *frame = FrameGetById(frames, frame_id); + if (frame != NULL) { + FrameJsonLogOneFrame(IPPROTO_TCP, frame, p->flow, stream, p, jb); + } + } else if (p->proto == IPPROTO_UDP) { + if (PKT_IS_TOSERVER(p)) { + frames = &frames_container->toserver; + } else { + frames = &frames_container->toclient; + } + Frame *frame = FrameGetById(frames, frame_id); + if (frame != NULL) { + FrameJsonLogOneFrame(IPPROTO_UDP, frame, p->flow, NULL, p, jb); + } + } +} + +/** + * \brief Build verdict object + * + * \param p Pointer to Packet current being logged + * + */ +void EveAddVerdict(JsonBuilder *jb, const Packet *p) +{ + jb_open_object(jb, "verdict"); + + /* add verdict info */ + if (PacketCheckAction(p, ACTION_REJECT_ANY)) { + // check rule to define type of reject packet sent + if (EngineModeIsIPS()) { + JB_SET_STRING(jb, "action", "drop"); + } else { + JB_SET_STRING(jb, "action", "alert"); + } + if (PacketCheckAction(p, ACTION_REJECT)) { + JB_SET_STRING(jb, "reject-target", "to_client"); + } else if (PacketCheckAction(p, ACTION_REJECT_DST)) { + JB_SET_STRING(jb, "reject-target", "to_server"); + } else if (PacketCheckAction(p, ACTION_REJECT_BOTH)) { + JB_SET_STRING(jb, "reject-target", "both"); + } + jb_open_array(jb, "reject"); + switch (p->proto) { + case IPPROTO_UDP: + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + jb_append_string(jb, "icmp-prohib"); + break; + case IPPROTO_TCP: + jb_append_string(jb, "tcp-reset"); + break; + } + jb_close(jb); + + } else if (PacketCheckAction(p, ACTION_DROP) && EngineModeIsIPS()) { + JB_SET_STRING(jb, "action", "drop"); + } else if (p->alerts.alerts[p->alerts.cnt].action & ACTION_PASS) { + JB_SET_STRING(jb, "action", "pass"); + } else { + // TODO make sure we don't have a situation where this wouldn't work + JB_SET_STRING(jb, "action", "alert"); + } + + /* Close verdict */ + jb_close(jb); +} + +static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p) +{ + MemBuffer *payload = aft->payload_buffer; + AlertJsonOutputCtx *json_output_ctx = aft->json_output_ctx; + + if (p->alerts.cnt == 0 && !(p->flags & PKT_HAS_TAG)) + return TM_ECODE_OK; + + for (int i = 0; i < p->alerts.cnt; i++) { + const PacketAlert *pa = &p->alerts.alerts[i]; + if (unlikely(pa->s == NULL)) { + continue; + } + + /* First initialize the address info (5-tuple). */ + JsonAddrInfo addr = json_addr_info_zero; + JsonAddrInfoInit(p, LOG_DIR_PACKET, &addr); + + /* Check for XFF, overwriting address info if needed. */ + HttpXFFCfg *xff_cfg = json_output_ctx->xff_cfg != NULL ? json_output_ctx->xff_cfg + : json_output_ctx->parent_xff_cfg; + int have_xff_ip = 0; + char xff_buffer[XFF_MAXLEN]; + xff_buffer[0] = 0; + if ((xff_cfg != NULL) && !(xff_cfg->flags & XFF_DISABLED) && p->flow != NULL) { + if (FlowGetAppProtocol(p->flow) == ALPROTO_HTTP1) { + if (pa->flags & PACKET_ALERT_FLAG_TX) { + have_xff_ip = HttpXFFGetIPFromTx(p->flow, pa->tx_id, xff_cfg, + xff_buffer, XFF_MAXLEN); + } else { + have_xff_ip = HttpXFFGetIP(p->flow, xff_cfg, xff_buffer, + XFF_MAXLEN); + } + } + + if (have_xff_ip && xff_cfg->flags & XFF_OVERWRITE) { + if (p->flowflags & FLOW_PKT_TOCLIENT) { + strlcpy(addr.dst_ip, xff_buffer, JSON_ADDR_LEN); + } else { + strlcpy(addr.src_ip, xff_buffer, JSON_ADDR_LEN); + } + /* Clear have_xff_ip so the xff field does not get + * logged below. */ + have_xff_ip = false; + } + if (have_xff_ip && !(xff_cfg->flags & XFF_EXTRADATA)) { + // reset xff_buffer so as not to log it + xff_buffer[0] = 0; + } + } + + JsonBuilder *jb = + CreateEveHeader(p, LOG_DIR_PACKET, "alert", &addr, json_output_ctx->eve_ctx); + if (unlikely(jb == NULL)) + return TM_ECODE_OK; + + + /* alert */ + AlertJsonHeader(json_output_ctx, p, pa, jb, json_output_ctx->flags, &addr, xff_buffer); + + if (IS_TUNNEL_PKT(p)) { + AlertJsonTunnel(p, jb); + } + + if (p->flow != NULL) { + if (json_output_ctx->flags & LOG_JSON_APP_LAYER) { + AlertAddAppLayer(p, jb, pa->tx_id, json_output_ctx->flags); + } + /* including fileinfo data is configured by the metadata setting */ + if (json_output_ctx->flags & LOG_JSON_RULE_METADATA) { + AlertAddFiles(p, jb, pa->tx_id); + } + + EveAddAppProto(p->flow, jb); + + if (p->flowflags & FLOW_PKT_TOSERVER) { + jb_set_string(jb, "direction", "to_server"); + } else { + jb_set_string(jb, "direction", "to_client"); + } + + if (json_output_ctx->flags & LOG_JSON_FLOW) { + jb_open_object(jb, "flow"); + EveAddFlow(p->flow, jb); + if (p->flowflags & FLOW_PKT_TOCLIENT) { + jb_set_string(jb, "src_ip", addr.dst_ip); + jb_set_string(jb, "dest_ip", addr.src_ip); + if (addr.sp > 0) { + jb_set_uint(jb, "src_port", addr.dp); + jb_set_uint(jb, "dest_port", addr.sp); + } + } else { + jb_set_string(jb, "src_ip", addr.src_ip); + jb_set_string(jb, "dest_ip", addr.dst_ip); + if (addr.sp > 0) { + jb_set_uint(jb, "src_port", addr.sp); + jb_set_uint(jb, "dest_port", addr.dp); + } + } + jb_close(jb); + } + } + + /* payload */ + if (json_output_ctx->flags & (LOG_JSON_PAYLOAD | LOG_JSON_PAYLOAD_BASE64)) { + int stream = (p->proto == IPPROTO_TCP) ? + (pa->flags & (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_STREAM_MATCH) ? + 1 : 0) : 0; + + /* Is this a stream? If so, pack part of it into the payload field */ + if (stream) { + uint8_t flag; + + MemBufferReset(payload); + + if (p->flowflags & FLOW_PKT_TOSERVER) { + flag = STREAM_DUMP_TOCLIENT; + } else { + flag = STREAM_DUMP_TOSERVER; + } + + StreamSegmentForEach((const Packet *)p, flag, + AlertJsonDumpStreamSegmentCallback, + (void *)payload); + if (payload->offset) { + if (json_output_ctx->flags & LOG_JSON_PAYLOAD_BASE64) { + jb_set_base64(jb, "payload", payload->buffer, payload->offset); + } + + if (json_output_ctx->flags & LOG_JSON_PAYLOAD) { + uint8_t printable_buf[payload->offset + 1]; + uint32_t offset = 0; + PrintStringsToBuffer(printable_buf, &offset, + sizeof(printable_buf), + payload->buffer, payload->offset); + jb_set_string(jb, "payload_printable", (char *)printable_buf); + } + } else if (p->payload_len) { + /* Fallback on packet payload */ + AlertAddPayload(json_output_ctx, jb, p); + } + } else { + /* This is a single packet and not a stream */ + AlertAddPayload(json_output_ctx, jb, p); + } + + jb_set_uint(jb, "stream", stream); + } + + if (pa->flags & PACKET_ALERT_FLAG_FRAME) { + AlertAddFrame(p, jb, pa->frame_id); + } + + /* base64-encoded full packet */ + if (json_output_ctx->flags & LOG_JSON_PACKET) { + EvePacket(p, jb, 0); + } + + char *pcap_filename = PcapLogGetFilename(); + if (pcap_filename != NULL) { + jb_set_string(jb, "capture_file", pcap_filename); + } + + if (json_output_ctx->flags & LOG_JSON_VERDICT) { + EveAddVerdict(jb, p); + } + + OutputJsonBuilderBuffer(jb, aft->ctx); + jb_free(jb); + } + + if ((p->flags & PKT_HAS_TAG) && (json_output_ctx->flags & + LOG_JSON_TAGGED_PACKETS)) { + JsonBuilder *packetjs = + CreateEveHeader(p, LOG_DIR_PACKET, "packet", NULL, json_output_ctx->eve_ctx); + if (unlikely(packetjs != NULL)) { + EvePacket(p, packetjs, 0); + OutputJsonBuilderBuffer(packetjs, aft->ctx); + jb_free(packetjs); + } + } + + return TM_ECODE_OK; +} + +static int AlertJsonDecoderEvent(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p) +{ + AlertJsonOutputCtx *json_output_ctx = aft->json_output_ctx; + char timebuf[64]; + + if (p->alerts.cnt == 0) + return TM_ECODE_OK; + + CreateIsoTimeString(p->ts, timebuf, sizeof(timebuf)); + + for (int i = 0; i < p->alerts.cnt; i++) { + const PacketAlert *pa = &p->alerts.alerts[i]; + if (unlikely(pa->s == NULL)) { + continue; + } + + JsonBuilder *jb = jb_new_object(); + if (unlikely(jb == NULL)) { + return TM_ECODE_OK; + } + + /* just the timestamp, no tuple */ + jb_set_string(jb, "timestamp", timebuf); + + AlertJsonHeader(json_output_ctx, p, pa, jb, json_output_ctx->flags, NULL, NULL); + + OutputJsonBuilderBuffer(jb, aft->ctx); + jb_free(jb); + } + + return TM_ECODE_OK; +} + +static int JsonAlertLogger(ThreadVars *tv, void *thread_data, const Packet *p) +{ + JsonAlertLogThread *aft = thread_data; + + if (PKT_IS_IPV4(p) || PKT_IS_IPV6(p)) { + return AlertJson(tv, aft, p); + } else if (p->alerts.cnt > 0) { + return AlertJsonDecoderEvent(tv, aft, p); + } + return 0; +} + +static int JsonAlertLogCondition(ThreadVars *tv, void *thread_data, const Packet *p) +{ + if (p->alerts.cnt || (p->flags & PKT_HAS_TAG)) { + return TRUE; + } + return FALSE; +} + +static TmEcode JsonAlertLogThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + JsonAlertLogThread *aft = SCCalloc(1, sizeof(JsonAlertLogThread)); + if (unlikely(aft == NULL)) + return TM_ECODE_FAILED; + + if (initdata == NULL) + { + SCLogDebug("Error getting context for EveLogAlert. \"initdata\" argument NULL"); + goto error_exit; + } + + /** Use the Output Context (file pointer and mutex) */ + AlertJsonOutputCtx *json_output_ctx = ((OutputCtx *)initdata)->data; + + aft->payload_buffer = MemBufferCreateNew(json_output_ctx->payload_buffer_size); + if (aft->payload_buffer == NULL) { + goto error_exit; + } + aft->ctx = CreateEveThreadCtx(t, json_output_ctx->eve_ctx); + if (!aft->ctx) { + goto error_exit; + } + + aft->json_output_ctx = json_output_ctx; + + *data = (void *)aft; + return TM_ECODE_OK; + +error_exit: + if (aft->payload_buffer != NULL) { + MemBufferFree(aft->payload_buffer); + } + SCFree(aft); + return TM_ECODE_FAILED; +} + +static TmEcode JsonAlertLogThreadDeinit(ThreadVars *t, void *data) +{ + JsonAlertLogThread *aft = (JsonAlertLogThread *)data; + if (aft == NULL) { + return TM_ECODE_OK; + } + + MemBufferFree(aft->payload_buffer); + FreeEveThreadCtx(aft->ctx); + + /* clear memory */ + memset(aft, 0, sizeof(JsonAlertLogThread)); + + SCFree(aft); + return TM_ECODE_OK; +} + +static void JsonAlertLogDeInitCtxSub(OutputCtx *output_ctx) +{ + SCLogDebug("cleaning up sub output_ctx %p", output_ctx); + + AlertJsonOutputCtx *json_output_ctx = (AlertJsonOutputCtx *) output_ctx->data; + + if (json_output_ctx != NULL) { + HttpXFFCfg *xff_cfg = json_output_ctx->xff_cfg; + if (xff_cfg != NULL) { + SCFree(xff_cfg); + } + SCFree(json_output_ctx); + } + SCFree(output_ctx); +} + +static void SetFlag(const ConfNode *conf, const char *name, uint16_t flag, uint16_t *out_flags) +{ + DEBUG_VALIDATE_BUG_ON(conf == NULL); + const char *setting = ConfNodeLookupChildValue(conf, name); + if (setting != NULL) { + if (ConfValIsTrue(setting)) { + *out_flags |= flag; + } else { + *out_flags &= ~flag; + } + } +} + +#define DEFAULT_LOG_FILENAME "alert.json" + +static void JsonAlertLogSetupMetadata(AlertJsonOutputCtx *json_output_ctx, + ConfNode *conf) +{ + static bool warn_no_meta = false; + uint32_t payload_buffer_size = JSON_STREAM_BUFFER_SIZE; + uint16_t flags = METADATA_DEFAULTS; + + if (conf != NULL) { + /* Check for metadata to enable/disable. */ + ConfNode *metadata = ConfNodeLookupChild(conf, "metadata"); + if (metadata != NULL) { + if (metadata->val != NULL && ConfValIsFalse(metadata->val)) { + flags &= ~METADATA_DEFAULTS; + } else if (ConfNodeHasChildren(metadata)) { + ConfNode *rule_metadata = ConfNodeLookupChild(metadata, "rule"); + if (rule_metadata) { + SetFlag(rule_metadata, "raw", LOG_JSON_RULE, &flags); + SetFlag(rule_metadata, "metadata", LOG_JSON_RULE_METADATA, + &flags); + } + SetFlag(metadata, "flow", LOG_JSON_FLOW, &flags); + SetFlag(metadata, "app-layer", LOG_JSON_APP_LAYER, &flags); + } + } + + /* Non-metadata toggles. */ + SetFlag(conf, "payload", LOG_JSON_PAYLOAD_BASE64, &flags); + SetFlag(conf, "packet", LOG_JSON_PACKET, &flags); + SetFlag(conf, "tagged-packets", LOG_JSON_TAGGED_PACKETS, &flags); + SetFlag(conf, "payload-printable", LOG_JSON_PAYLOAD, &flags); + SetFlag(conf, "http-body-printable", LOG_JSON_HTTP_BODY, &flags); + SetFlag(conf, "http-body", LOG_JSON_HTTP_BODY_BASE64, &flags); + SetFlag(conf, "verdict", LOG_JSON_VERDICT, &flags); + + /* Check for obsolete flags and warn that they have no effect. */ + static const char *deprecated_flags[] = { "http", "tls", "ssh", "smtp", "dnp3", "app-layer", + "flow", NULL }; + for (int i = 0; deprecated_flags[i] != NULL; i++) { + if (ConfNodeLookupChildValue(conf, deprecated_flags[i]) != NULL) { + SCLogWarning("Found deprecated eve-log.alert flag \"%s\", this flag has no effect", + deprecated_flags[i]); + } + } + + const char *payload_buffer_value = ConfNodeLookupChildValue(conf, "payload-buffer-size"); + + if (payload_buffer_value != NULL) { + uint32_t value; + if (ParseSizeStringU32(payload_buffer_value, &value) < 0) { + SCLogError("Error parsing " + "payload-buffer-size - %s. Killing engine", + payload_buffer_value); + exit(EXIT_FAILURE); + } else { + payload_buffer_size = value; + } + } + + if (!warn_no_meta && flags & JSON_BODY_LOGGING) { + if (((flags & LOG_JSON_APP_LAYER) == 0)) { + SCLogWarning("HTTP body logging has been configured, however, " + "metadata logging has not been enabled. HTTP body logging will be " + "disabled."); + flags &= ~JSON_BODY_LOGGING; + warn_no_meta = true; + } + } + + json_output_ctx->payload_buffer_size = payload_buffer_size; + } + + if (flags & LOG_JSON_RULE_METADATA) { + DetectEngineSetParseMetadata(); + } + + json_output_ctx->flags |= flags; +} + +static HttpXFFCfg *JsonAlertLogGetXffCfg(ConfNode *conf) +{ + HttpXFFCfg *xff_cfg = NULL; + if (conf != NULL && ConfNodeLookupChild(conf, "xff") != NULL) { + xff_cfg = SCCalloc(1, sizeof(HttpXFFCfg)); + if (likely(xff_cfg != NULL)) { + HttpXFFGetCfg(conf, xff_cfg); + } + } + return xff_cfg; +} + +/** + * \brief Create a new LogFileCtx for "fast" output style. + * \param conf The configuration node for this output. + * \return A LogFileCtx pointer on success, NULL on failure. + */ +static OutputInitResult JsonAlertLogInitCtxSub(ConfNode *conf, OutputCtx *parent_ctx) +{ + OutputInitResult result = { NULL, false }; + OutputJsonCtx *ajt = parent_ctx->data; + AlertJsonOutputCtx *json_output_ctx = NULL; + + OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); + if (unlikely(output_ctx == NULL)) + return result; + + json_output_ctx = SCMalloc(sizeof(AlertJsonOutputCtx)); + if (unlikely(json_output_ctx == NULL)) { + goto error; + } + memset(json_output_ctx, 0, sizeof(AlertJsonOutputCtx)); + + json_output_ctx->file_ctx = ajt->file_ctx; + json_output_ctx->eve_ctx = ajt; + + JsonAlertLogSetupMetadata(json_output_ctx, conf); + json_output_ctx->xff_cfg = JsonAlertLogGetXffCfg(conf); + if (json_output_ctx->xff_cfg == NULL) { + json_output_ctx->parent_xff_cfg = ajt->xff_cfg; + } + + output_ctx->data = json_output_ctx; + output_ctx->DeInit = JsonAlertLogDeInitCtxSub; + + result.ctx = output_ctx; + result.ok = true; + return result; + +error: + if (json_output_ctx != NULL) { + SCFree(json_output_ctx); + } + if (output_ctx != NULL) { + SCFree(output_ctx); + } + + return result; +} + +void JsonAlertLogRegister (void) +{ + OutputRegisterPacketSubModule(LOGGER_JSON_ALERT, "eve-log", MODULE_NAME, + "eve-log.alert", JsonAlertLogInitCtxSub, JsonAlertLogger, + JsonAlertLogCondition, JsonAlertLogThreadInit, JsonAlertLogThreadDeinit, + NULL); +} |