/* Copyright (C) 2013-2021 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 Victor Julien * * Logs frames in JSON format. * */ #include "suricata-common.h" #include "detect.h" #include "flow.h" #include "conf.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-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-frames.h" #include "app-layer-dnp3.h" #include "app-layer-htp.h" #include "app-layer-htp-xff.h" #include "app-layer-ftp.h" #include "util-classification-config.h" #include "stream-tcp.h" #include "output.h" #include "output-json.h" #include "output-json-frame.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" #define MODULE_NAME "JsonFrameLog" #define JSON_STREAM_BUFFER_SIZE 4096 typedef struct FrameJsonOutputCtx_ { LogFileCtx *file_ctx; uint16_t flags; uint32_t payload_buffer_size; OutputJsonCtx *eve_ctx; } FrameJsonOutputCtx; typedef struct JsonFrameLogThread_ { MemBuffer *payload_buffer; FrameJsonOutputCtx *json_output_ctx; OutputJsonThreadCtx *ctx; } JsonFrameLogThread; #if 0 // TODO see if this is useful in some way static inline bool NeedsAsHex(uint8_t c) { if (!isprint(c)) return true; switch (c) { case '/': case ';': case ':': case '\\': case ' ': case '|': case '"': case '`': case '\'': return true; } return false; } static void PayloadAsHex(const uint8_t *data, uint32_t data_len, char *str, size_t str_len) { bool hex = false; for (uint32_t i = 0; i < data_len; i++) { if (NeedsAsHex(data[i])) { char hex_str[4]; snprintf(hex_str, sizeof(hex_str), "%s%02X", !hex ? "|" : " ", data[i]); strlcat(str, hex_str, str_len); hex = true; } else { char p_str[3]; snprintf(p_str, sizeof(p_str), "%s%c", hex ? "|" : "", data[i]); strlcat(str, p_str, str_len); hex = false; } } if (hex) { strlcat(str, "|", str_len); } } #endif static void FrameAddPayloadTCP(JsonBuilder *js, const TcpStream *stream, const Frame *frame) { uint32_t sb_data_len = 0; const uint8_t *data = NULL; uint64_t data_offset = 0; // TODO consider ACK'd if (frame->offset < STREAM_BASE_OFFSET(stream)) { if (StreamingBufferGetData(&stream->sb, &data, &sb_data_len, &data_offset) == 0) { SCLogDebug("NO DATA1"); return; } } else { data_offset = (uint64_t)frame->offset; SCLogDebug("data_offset %" PRIu64, data_offset); if (StreamingBufferGetDataAtOffset( &stream->sb, &data, &sb_data_len, (uint64_t)data_offset) == 0) { SCLogDebug("NO DATA1"); return; } } if (data == NULL || sb_data_len == 0) { SCLogDebug("NO DATA2"); return; } if (frame->len >= 0) { sb_data_len = MIN(frame->len, (int32_t)sb_data_len); } SCLogDebug("frame data_offset %" PRIu64 ", data_len %u frame len %" PRIi64, data_offset, sb_data_len, frame->len); bool complete = false; if (frame->len > 0) { const uint64_t frame_re = frame->offset + (uint64_t)frame->len; const uint64_t data_re = data_offset + sb_data_len; complete = frame_re <= data_re; } jb_set_bool(js, "complete", complete); uint32_t data_len = MIN(sb_data_len, 256); jb_set_base64(js, "payload", data, data_len); uint8_t printable_buf[data_len + 1]; uint32_t o = 0; PrintStringsToBuffer(printable_buf, &o, data_len + 1, data, data_len); printable_buf[data_len] = '\0'; jb_set_string(js, "payload_printable", (char *)printable_buf); #if 0 char pretty_buf[data_len * 4 + 1]; pretty_buf[0] = '\0'; PayloadAsHex(data, data_len, pretty_buf, data_len * 4 + 1); jb_set_string(js, "payload_hex", pretty_buf); #endif } static void FrameAddPayloadUDP(JsonBuilder *js, const Packet *p, const Frame *frame) { DEBUG_VALIDATE_BUG_ON(frame->offset >= p->payload_len); if (frame->offset >= p->payload_len) return; uint32_t frame_len; if (frame->len == -1) { frame_len = p->payload_len - frame->offset; } else { frame_len = (uint32_t)frame->len; } if (frame->offset + frame_len > p->payload_len) { frame_len = p->payload_len - frame->offset; JB_SET_FALSE(js, "complete"); } else { JB_SET_TRUE(js, "complete"); } const uint8_t *data = p->payload + frame->offset; const uint32_t data_len = frame_len; const uint32_t log_data_len = MIN(data_len, 256); jb_set_base64(js, "payload", data, log_data_len); uint8_t printable_buf[log_data_len + 1]; uint32_t o = 0; PrintStringsToBuffer(printable_buf, &o, log_data_len + 1, data, log_data_len); printable_buf[log_data_len] = '\0'; jb_set_string(js, "payload_printable", (char *)printable_buf); #if 0 char pretty_buf[data_len * 4 + 1]; pretty_buf[0] = '\0'; PayloadAsHex(data, data_len, pretty_buf, data_len * 4 + 1); jb_set_string(js, "payload_hex", pretty_buf); #endif } // TODO separate between stream_offset and frame_offset /** \brief log a single frame * \note ipproto argument is passed to assist static code analyzers */ void FrameJsonLogOneFrame(const uint8_t ipproto, const Frame *frame, const Flow *f, const TcpStream *stream, const Packet *p, JsonBuilder *jb) { DEBUG_VALIDATE_BUG_ON(ipproto != p->proto); DEBUG_VALIDATE_BUG_ON(ipproto != f->proto); jb_open_object(jb, "frame"); if (frame->type == FRAME_STREAM_TYPE) { jb_set_string(jb, "type", "stream"); } else { jb_set_string(jb, "type", AppLayerParserGetFrameNameById(ipproto, f->alproto, frame->type)); } jb_set_uint(jb, "id", frame->id); jb_set_string(jb, "direction", PKT_IS_TOSERVER(p) ? "toserver" : "toclient"); if (ipproto == IPPROTO_TCP) { DEBUG_VALIDATE_BUG_ON(stream == NULL); jb_set_uint(jb, "stream_offset", frame->offset); if (frame->len < 0) { uint64_t usable = StreamTcpGetUsable(stream, true); uint64_t len = usable - frame->offset; jb_set_uint(jb, "length", len); } else { jb_set_uint(jb, "length", frame->len); } FrameAddPayloadTCP(jb, stream, frame); } else { jb_set_uint(jb, "length", frame->len); FrameAddPayloadUDP(jb, p, frame); } if (frame->flags & FRAME_FLAG_TX_ID_SET) { jb_set_uint(jb, "tx_id", frame->tx_id); } jb_close(jb); } static int FrameJsonUdp( JsonFrameLogThread *aft, const Packet *p, Flow *f, FramesContainer *frames_container) { FrameJsonOutputCtx *json_output_ctx = aft->json_output_ctx; Frames *frames; if (PKT_IS_TOSERVER(p)) { frames = &frames_container->toserver; } else { frames = &frames_container->toclient; } for (uint32_t idx = 0; idx < frames->cnt; idx++) { Frame *frame = FrameGetByIndex(frames, idx); if (frame == NULL || frame->flags & FRAME_FLAG_LOGGED) continue; /* First initialize the address info (5-tuple). */ JsonAddrInfo addr = json_addr_info_zero; JsonAddrInfoInit(p, LOG_DIR_PACKET, &addr); JsonBuilder *jb = CreateEveHeader(p, LOG_DIR_PACKET, "frame", &addr, json_output_ctx->eve_ctx); if (unlikely(jb == NULL)) return TM_ECODE_OK; jb_set_string(jb, "app_proto", AppProtoToString(f->alproto)); FrameJsonLogOneFrame(IPPROTO_UDP, frame, p->flow, NULL, p, jb); OutputJsonBuilderBuffer(jb, aft->ctx); jb_free(jb); frame->flags |= FRAME_FLAG_LOGGED; } return TM_ECODE_OK; } static int FrameJson(ThreadVars *tv, JsonFrameLogThread *aft, const Packet *p) { FrameJsonOutputCtx *json_output_ctx = aft->json_output_ctx; BUG_ON(p->flow == NULL); FramesContainer *frames_container = AppLayerFramesGetContainer(p->flow); if (frames_container == NULL) return TM_ECODE_OK; if (p->proto == IPPROTO_UDP) { return FrameJsonUdp(aft, p, p->flow, frames_container); } BUG_ON(p->proto != IPPROTO_TCP); BUG_ON(p->flow->protoctx == NULL); /* TODO can we set these EOF flags once per packet? We have them in detect, tx, file, filedata, * etc */ const bool last_pseudo = (p->flowflags & FLOW_PKT_LAST_PSEUDO) != 0; Frames *frames; TcpSession *ssn = p->flow->protoctx; bool eof = (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED); TcpStream *stream; if (PKT_IS_TOSERVER(p)) { stream = &ssn->client; frames = &frames_container->toserver; SCLogDebug("TOSERVER base %" PRIu64 ", app %" PRIu64, STREAM_BASE_OFFSET(stream), STREAM_APP_PROGRESS(stream)); eof |= AppLayerParserStateIssetFlag(p->flow->alparser, APP_LAYER_PARSER_EOF_TS) != 0; } else { stream = &ssn->server; frames = &frames_container->toclient; eof |= AppLayerParserStateIssetFlag(p->flow->alparser, APP_LAYER_PARSER_EOF_TC) != 0; } eof |= last_pseudo; SCLogDebug("eof %s", eof ? "true" : "false"); for (uint32_t idx = 0; idx < frames->cnt; idx++) { Frame *frame = FrameGetByIndex(frames, idx); if (frame != NULL) { if (frame->flags & FRAME_FLAG_LOGGED) continue; int64_t abs_offset = (int64_t)frame->offset + (int64_t)STREAM_BASE_OFFSET(stream); int64_t win = STREAM_APP_PROGRESS(stream) - abs_offset; if (!eof && win < frame->len && win < 2500) { SCLogDebug("frame id %" PRIi64 " len %" PRIi64 ", win %" PRIi64 ", skipping logging", frame->id, frame->len, win); continue; } /* First initialize the address info (5-tuple). */ JsonAddrInfo addr = json_addr_info_zero; JsonAddrInfoInit(p, LOG_DIR_PACKET, &addr); JsonBuilder *jb = CreateEveHeader(p, LOG_DIR_PACKET, "frame", &addr, json_output_ctx->eve_ctx); if (unlikely(jb == NULL)) return TM_ECODE_OK; jb_set_string(jb, "app_proto", AppProtoToString(p->flow->alproto)); FrameJsonLogOneFrame(IPPROTO_TCP, frame, p->flow, stream, p, jb); OutputJsonBuilderBuffer(jb, aft->ctx); jb_free(jb); frame->flags |= FRAME_FLAG_LOGGED; } else if (frame != NULL) { SCLogDebug("frame %p id %" PRIi64, frame, frame->id); } } return TM_ECODE_OK; } static int JsonFrameLogger(ThreadVars *tv, void *thread_data, const Packet *p) { JsonFrameLogThread *aft = thread_data; return FrameJson(tv, aft, p); } static int JsonFrameLogCondition(ThreadVars *tv, void *thread_data, const Packet *p) { if (p->flow == NULL || p->flow->alproto == ALPROTO_UNKNOWN) return FALSE; if ((p->proto == IPPROTO_TCP || p->proto == IPPROTO_UDP) && p->flow->alparser != NULL) { FramesContainer *frames_container = AppLayerFramesGetContainer(p->flow); if (frames_container == NULL) return FALSE; Frames *frames; if (PKT_IS_TOSERVER(p)) { frames = &frames_container->toserver; } else { frames = &frames_container->toclient; } return (frames->cnt != 0); } return FALSE; } static TmEcode JsonFrameLogThreadInit(ThreadVars *t, const void *initdata, void **data) { JsonFrameLogThread *aft = SCCalloc(1, sizeof(JsonFrameLogThread)); if (unlikely(aft == NULL)) return TM_ECODE_FAILED; if (initdata == NULL) { SCLogDebug("Error getting context for EveLogFrame. \"initdata\" argument NULL"); goto error_exit; } /** Use the Output Context (file pointer and mutex) */ FrameJsonOutputCtx *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 JsonFrameLogThreadDeinit(ThreadVars *t, void *data) { JsonFrameLogThread *aft = (JsonFrameLogThread *)data; if (aft == NULL) { return TM_ECODE_OK; } MemBufferFree(aft->payload_buffer); FreeEveThreadCtx(aft->ctx); /* clear memory */ memset(aft, 0, sizeof(JsonFrameLogThread)); SCFree(aft); return TM_ECODE_OK; } static void JsonFrameLogDeInitCtxSub(OutputCtx *output_ctx) { SCLogDebug("cleaning up sub output_ctx %p", output_ctx); FrameJsonOutputCtx *json_output_ctx = (FrameJsonOutputCtx *)output_ctx->data; if (json_output_ctx != NULL) { SCFree(json_output_ctx); } SCFree(output_ctx); } /** * \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 JsonFrameLogInitCtxSub(ConfNode *conf, OutputCtx *parent_ctx) { OutputInitResult result = { NULL, false }; OutputJsonCtx *ajt = parent_ctx->data; FrameJsonOutputCtx *json_output_ctx = NULL; OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); if (unlikely(output_ctx == NULL)) return result; json_output_ctx = SCMalloc(sizeof(FrameJsonOutputCtx)); if (unlikely(json_output_ctx == NULL)) { goto error; } memset(json_output_ctx, 0, sizeof(FrameJsonOutputCtx)); json_output_ctx->file_ctx = ajt->file_ctx; json_output_ctx->eve_ctx = ajt; output_ctx->data = json_output_ctx; output_ctx->DeInit = JsonFrameLogDeInitCtxSub; FrameConfigEnableAll(); 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 JsonFrameLogRegister(void) { OutputRegisterPacketSubModule(LOGGER_JSON_FRAME, "eve-log", MODULE_NAME, "eve-log.frame", JsonFrameLogInitCtxSub, JsonFrameLogger, JsonFrameLogCondition, JsonFrameLogThreadInit, JsonFrameLogThreadDeinit, NULL); }