js_norm_module.h
js_normalizer.cc
js_normalizer.h
+ js_pdf_norm.cc
+ js_pdf_norm.h
js_tokenizer.h
pdf_tokenizer.h
)
len = jsn_ctx->script_size();
data = jsn_ctx->get_script();
+
+ if (data and len)
+ trace_logf(1, js_trace, TRACE_DUMP, packet,
+ "js_data[%u]: %.*s\n", (unsigned)len, (int)len, (const char*)data);
+}
+
+void JSNorm::flush_data(const void*& data, size_t& len)
+{
+ len = jsn_ctx->script_size();
+ data = jsn_ctx->take_script();
+}
+
+void JSNorm::flush_data()
+{
+ delete[] jsn_ctx->take_script();
+}
+
+void JSNorm::get_data(const void*& data, size_t& len)
+{
+ len = jsn_ctx->script_size();
+ data = jsn_ctx->get_script();
}
bool JSNorm::pre_proc()
namespace snort
{
-class JSNorm
+class SO_PUBLIC JSNorm
{
public:
JSNorm(JSNormConfig*, bool ext_script_type = false);
{ ++pdu_cnt; }
void normalize(const void*, size_t, const void*&, size_t&);
+ void get_data(const void*&, size_t&);
+ void flush_data(const void*&, size_t&);
+ void flush_data();
protected:
virtual bool pre_proc();
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2022-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// js_pdf_norm.cc author Cisco
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "js_pdf_norm.h"
+
+#include "trace/trace_api.h"
+
+#include "js_norm_module.h"
+
+using namespace jsn;
+using namespace snort;
+
+bool PDFJSNorm::pre_proc()
+{
+ if (src_ptr >= src_end)
+ return false;
+
+ const Packet* packet = DetectionEngine::get_current_packet();
+
+ if (!ext_script_type)
+ {
+ trace_logf(1, js_trace, TRACE_PROC, packet,
+ "PDF starts\n");
+ ext_script_type = true;
+ }
+ else
+ {
+ trace_logf(2, js_trace, TRACE_PROC, packet,
+ "PDF continues\n");
+ }
+
+ buf_pdf_in.pubsetbuf(nullptr, 0)
+ ->pubsetbuf(const_cast<char*>((const char*)src_ptr), src_end - src_ptr);
+ pdf_out.clear();
+ delete[] buf_pdf_out.take_data();
+
+ auto r = extractor.process();
+
+ if (r != PDFTokenizer::PDFRet::EOS)
+ {
+ trace_logf(2, js_trace, TRACE_PROC, DetectionEngine::get_current_packet(),
+ "pdf processing failed: %d\n", (int)r);
+ return false;
+ }
+
+ src_ptr = (const uint8_t*)buf_pdf_out.data();
+ src_end = src_ptr + buf_pdf_out.data_len();
+
+ // script object not found
+ if (!src_ptr)
+ return false;
+
+ return true;
+}
+
+bool PDFJSNorm::post_proc(int ret)
+{
+ src_ptr = src_end; // one time per PDU, even if JS Normalizer has not finished
+
+ return JSNorm::post_proc(ret);
+}
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2022-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// js_pdf_norm.h author Cisco
+
+#ifndef JS_PDF_NORM_H
+#define JS_PDF_NORM_H
+
+#include <FlexLexer.h>
+#include <cstring>
+
+#include "js_norm/js_norm.h"
+#include "js_norm/pdf_tokenizer.h"
+#include "utils/streambuf.h"
+
+namespace snort
+{
+
+class SO_PUBLIC PDFJSNorm : public JSNorm
+{
+public:
+ static bool is_pdf(const void* data, size_t len)
+ {
+ constexpr char magic[] = "%PDF-1.";
+ constexpr int magic_len = sizeof(magic) - 1;
+ return magic_len < len and !strncmp((const char*)data, magic, magic_len);
+ }
+
+ PDFJSNorm(JSNormConfig* cfg) :
+ JSNorm(cfg), pdf_in(&buf_pdf_in), pdf_out(&buf_pdf_out), extractor(pdf_in, pdf_out)
+ { }
+
+protected:
+ bool pre_proc() override;
+ bool post_proc(int) override;
+
+private:
+ snort::istreambuf_glue buf_pdf_in;
+ snort::ostreambuf_infl buf_pdf_out;
+ std::istream pdf_in;
+ std::ostream pdf_out;
+ jsn::PDFTokenizer extractor;
+};
+
+}
+
+#endif
class JSTestConfig;
#endif // CATCH_TEST_BUILD || BENCHMARK_TEST
-class JSTokenizer : public yyFlexLexer
+class SO_PUBLIC JSTokenizer : public yyFlexLexer
{
private:
enum JSToken
#include <stack>
#include <vector>
+#include "main/snort_types.h"
+
#define PDFTOKENIZER_NAME_MAX_SIZE 16
namespace jsn
{
-class PDFTokenizer : public yyFlexLexer
+class SO_PUBLIC PDFTokenizer : public yyFlexLexer
{
public:
enum PDFRet
}
}
-void HttpJSNorm::flush_data(const void*& data, size_t& len)
-{
- len = jsn_ctx->script_size();
- data = jsn_ctx->take_script();
-}
-
bool HttpInlineJSNorm::pre_proc()
{
assert(mpse_otag);
if (src_ptr >= src_end)
return false;
- const Packet* packet = DetectionEngine::get_current_packet();
-
if (!ext_script_type)
- {
HttpModule::increment_peg_counts(PEG_JS_PDF);
- trace_logf(1, js_trace, TRACE_PROC, packet,
- "PDF starts\n");
- ext_script_type = true;
- }
- else
- {
- trace_logf(2, js_trace, TRACE_PROC, packet,
- "PDF continues\n");
- }
- // an input stream should not write to its buffer
- buf_pdf_in.pubsetbuf(nullptr, 0)
- ->pubsetbuf(const_cast<char*>((const char*)src_ptr), src_end - src_ptr);
-
- pdf_out.clear();
- delete[] buf_pdf_out.take_data();
-
- auto r = extractor.process();
-
- if (r != PDFTokenizer::PDFRet::EOS)
- {
- trace_logf(2, js_trace, TRACE_PROC, DetectionEngine::get_current_packet(),
- "pdf processing failed: %d\n", (int)r);
- return false;
- }
-
- src_ptr = (const uint8_t*)buf_pdf_out.data();
- src_end = src_ptr + buf_pdf_out.data_len();
-
- // script object not found
- if (!src_ptr)
- return false;
-
- return true;
+ return PDFJSNorm::pre_proc();
}
bool HttpPDFJSNorm::post_proc(int ret)
{
- src_ptr = src_end; // one time per PDU, even if JS Normalizer has not finished
-
script_continue = ret == (int)jsn::JSTokenizer::SCRIPT_CONTINUE;
- return JSNorm::post_proc(ret);
+ return PDFJSNorm::post_proc(ret);
}
#define HTTP_JS_NORM_H
#include <cstring>
-#include <FlexLexer.h>
#include "js_norm/js_norm.h"
-#include "js_norm/pdf_tokenizer.h"
+#include "js_norm/js_pdf_norm.h"
#include "search_engines/search_tool.h"
-#include "utils/streambuf.h"
#include "http_field.h"
#include "http_flow_data.h"
void js_normalize(const Field& input, Field& output, const HttpParaList*, HttpInfractions*, HttpEventGen*);
-class HttpJSNorm : public snort::JSNorm
+class HttpJSNorm
{
public:
- HttpJSNorm(JSNormConfig* jsn_config) : snort::JSNorm(jsn_config) {}
+ virtual ~HttpJSNorm() {}
- void flush_data(const void*&, size_t&);
+ virtual snort::JSNorm& ctx() = 0;
void link(const void* page, HttpEventGen* http_events_, HttpInfractions* infs)
{ page_start = (const uint8_t*)page; http_events = http_events_; infractions = infs; }
bool script_continue = false;
};
-class HttpInlineJSNorm : public HttpJSNorm
+class HttpInlineJSNorm : public snort::JSNorm, public HttpJSNorm
{
public:
HttpInlineJSNorm(JSNormConfig* jsn_config, uint64_t tid, snort::SearchTool* mpse_open_tag,
snort::SearchTool* mpse_tag_attr) :
- HttpJSNorm(jsn_config), mpse_otag(mpse_open_tag), mpse_attr(mpse_tag_attr), output_size(0), ext_ref_type(false)
+ JSNorm(jsn_config), mpse_otag(mpse_open_tag), mpse_attr(mpse_tag_attr), output_size(0), ext_ref_type(false)
{ trans_num = tid; }
+ snort::JSNorm& ctx() override
+ { return *this; }
+
protected:
bool pre_proc() override;
bool post_proc(int) override;
bool ext_ref_type;
};
-class HttpExternalJSNorm : public HttpJSNorm
+class HttpExternalJSNorm : public snort::JSNorm, public HttpJSNorm
{
public:
- HttpExternalJSNorm(JSNormConfig* jsn_config, uint64_t tid) : HttpJSNorm(jsn_config)
+ HttpExternalJSNorm(JSNormConfig* jsn_config, uint64_t tid) : JSNorm(jsn_config)
{ trans_num = tid; }
+ snort::JSNorm& ctx() override
+ { return *this; }
+
protected:
bool pre_proc() override;
bool post_proc(int) override;
};
-class HttpPDFJSNorm : public HttpJSNorm
+class HttpPDFJSNorm : public snort::PDFJSNorm, public HttpJSNorm
{
public:
- static bool is_pdf(const void* data, size_t len)
- {
- constexpr char magic[] = "%PDF-1.";
- constexpr int magic_len = sizeof(magic) - 1;
- return magic_len < len and !strncmp((const char*)data, magic, magic_len);
- }
-
HttpPDFJSNorm(JSNormConfig* jsn_config, uint64_t tid) :
- HttpJSNorm(jsn_config), pdf_in(&buf_pdf_in), pdf_out(&buf_pdf_out), extractor(pdf_in, pdf_out)
+ PDFJSNorm(jsn_config)
{ trans_num = tid; }
+ snort::JSNorm& ctx() override
+ { return *this; }
+
protected:
bool pre_proc() override;
bool post_proc(int) override;
-
-private:
- snort::istreambuf_glue buf_pdf_in;
- snort::ostreambuf_infl buf_pdf_out;
- std::istream pdf_in;
- std::ostream pdf_out;
- jsn::PDFTokenizer extractor;
};
#endif
do_file_decompression(decoded_body, decompressed_file_body);
if (decompressed_file_body.length() > 0 and session_data->js_ctx[source_id])
- session_data->js_ctx[source_id]->tick();
+ session_data->js_ctx[source_id]->ctx().tick();
uint32_t& partial_detect_length = session_data->partial_detect_length[source_id];
uint8_t*& partial_detect_buffer = session_data->partial_detect_buffer[source_id];
return norm_js_data;
}
- if (decompressed_file_body.length() <= 0)
+ int src_len = decompressed_file_body.length();
+
+ if (src_len <= 0)
{
norm_js_data.set(STAT_NO_SOURCE);
return norm_js_data;
return norm_js_data;
}
+ const void* src = decompressed_file_body.start();
const void* dst = nullptr;
size_t dst_len = HttpCommon::STAT_NOT_PRESENT;
- auto back = !session_data->partial_flush[source_id];
+ bool back = !session_data->partial_flush[source_id];
- jsn->link(decompressed_file_body.start(), session_data->events[source_id], infractions);
- jsn->normalize(decompressed_file_body.start(), decompressed_file_body.length(), dst, dst_len);
+ jsn->link(src, session_data->events[source_id], infractions);
+ jsn->ctx().normalize(src, src_len, dst, dst_len);
debug_logf(4, js_trace, TRACE_PROC, DetectionEngine::get_current_packet(),
"input data was %s\n", back ? "last one in PDU" : "a part of PDU");
else
{
if (back)
- jsn->flush_data(dst, dst_len);
-
- trace_logf(1, js_trace, TRACE_DUMP, DetectionEngine::get_current_packet(),
- "js_data[%u]: %.*s\n", (unsigned)dst_len, (int)dst_len, (const char*)dst);
-
+ jsn->ctx().flush_data(dst, dst_len);
norm_js_data.set(dst_len, (const uint8_t*)dst, back);
}
{ CountType::SUM, "total_bytes", "total HTTP data bytes inspected" },
{ CountType::SUM, "js_inline_scripts", "total number of inline JavaScripts processed" },
{ CountType::SUM, "js_external_scripts", "total number of external JavaScripts processed" },
- { CountType::SUM, "js_pdf_scripts", "total number of PDF JavaScripts processed" },
+ { CountType::SUM, "js_pdf_scripts", "total number of PDF files processed" },
{ CountType::SUM, "skip_mime_attach", "total number of HTTP requests with too many MIME attachments to inspect" },
{ CountType::END, nullptr, nullptr }
};
the file API. The file API extracts and decodes the attachments. file_data
is then set to the start of these extracted/decoded attachments. This
inspector also identifies and whitelists the IMAPS traffic.
+
+IMAP inspector uses PDFJSNorm class to extract and normalize JavaScript
+in PDF files. The normalized JavaScript is then available in the js_data buffer.
+The js_data buffer follows the JIT approach, thus, to perform the normalization,
+the rule with the js_data IPS option must be present as well as the js_norm module
+is configured.
#include "imap.h"
#include "detection/detection_engine.h"
+#include "js_norm/js_pdf_norm.h"
#include "log/messages.h"
#include "profiler/profiler.h"
#include "protocols/packet.h"
{ CountType::SUM, "uu_decoded_bytes", "total uu decoded bytes" },
{ CountType::SUM, "non_encoded_attachments", "total non-encoded attachments extracted" },
{ CountType::SUM, "non_encoded_bytes", "total non-encoded extracted bytes" },
+ { CountType::SUM, "js_pdf_scripts", "total number of PDF files processed" },
{ CountType::END, nullptr, nullptr }
};
ImapFlowData::~ImapFlowData()
{
- if(session.mime_ssn)
- delete(session.mime_ssn);
+ delete session.mime_ssn;
+ delete session.jsn;
assert(imapstats.concurrent_sessions > 0);
imapstats.concurrent_sessions--;
}
unsigned ImapFlowData::inspector_id = 0;
+
static IMAPData* get_session_data(Flow* flow)
{
ImapFlowData* fd = (ImapFlowData*)flow->get_flow_data(ImapFlowData::inspector_id);
return fd ? &fd->session : nullptr;
}
+static inline PDFJSNorm* acquire_js_ctx(IMAPData& imap_ssn, const void* data, size_t len)
+{
+ if (imap_ssn.jsn)
+ return imap_ssn.jsn;
+
+ JSNormConfig* cfg = get_inspection_policy()->jsn_config;
+ if (cfg and PDFJSNorm::is_pdf(data, len))
+ {
+ imap_ssn.jsn = new PDFJSNorm(cfg);
+ ++imapstats.js_pdf_scripts;
+ }
+
+ return imap_ssn.jsn;
+}
+
static IMAPData* SetNewIMAPData(IMAP_PROTO_CONF* config, Packet* p)
{
IMAPData* imap_ssn;
imap_ssn->state = STATE_COMMAND;
imap_ssn->state_flags = 0;
imap_ssn->body_read = imap_ssn->body_len = 0;
+
+ delete imap_ssn->jsn;
+ imap_ssn->jsn = nullptr;
}
static void IMAP_GetEOL(const uint8_t* ptr, const uint8_t* end,
{
if (imap_ssn->state == STATE_DATA)
{
- if ( imap_ssn->body_len > imap_ssn->body_read)
+ if (imap_ssn->body_len > imap_ssn->body_read)
{
int len = imap_ssn->body_len - imap_ssn->body_read;
- if ( (end - ptr) < len )
+ if ((end - ptr) < len)
{
data_end = end;
len = data_end - ptr;
data_end = ptr + len;
FilePosition position = get_file_position(p);
-
int data_len = end - ptr;
- ptr = imap_ssn->mime_ssn->process_mime_data(p, ptr, data_len, false,
- position);
- if ( ptr < data_end)
+
+ if (isFileStart(position))
+ {
+ delete imap_ssn->jsn;
+ imap_ssn->jsn = nullptr;
+ }
+
+ ptr = imap_ssn->mime_ssn->process_mime_data(p, ptr, data_len, false, position);
+ if (imap_ssn->jsn)
+ imap_ssn->jsn->tick();
+ if (ptr < data_end)
len = len - (data_end - ptr);
imap_ssn->body_read += len;
if (imap_ssn->state == STATE_DATA)
{
body_start = (const uint8_t*)memchr((const char*)ptr, '{', (eol - ptr));
- if ( body_start == nullptr )
+ if (body_start == nullptr)
{
imap_ssn->state = STATE_UNKNOWN;
}
else
{
- if ( (body_start + 1) < eol )
+ if ((body_start + 1) < eol)
{
uint32_t len =
(uint32_t)SnortStrtoul((const char*)(body_start + 1), &eptr, 10);
{
imap_ssn->session_flags &= ~IMAP_FLAG_CHECK_SSL;
}
- if ( (*ptr != '*') && (*ptr !='+') && (*ptr != '\r') && (*ptr != '\n') )
+ if ((*ptr != '*') && (*ptr != '+') && (*ptr != '\r') && (*ptr != '\n'))
{
DetectionEngine::queue_event(GID_IMAP, IMAP_UNKNOWN_RESP);
}
int pkt_dir = IMAP_Setup(p, imap_ssn);
+ if (imap_ssn->jsn)
+ imap_ssn->jsn->flush_data();
+
if (pkt_dir == IMAP_PKT_FROM_CLIENT)
{
/* This packet should be a tls client hello */
bool Imap::get_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& b)
{
- switch (ibt)
- {
- case InspectionBuffer::IBT_VBA:
- {
- IMAPData* imap_ssn = get_session_data(p->flow);
+ IMAPData* imap_ssn = get_session_data(p->flow);
+ assert(imap_ssn);
- if (!imap_ssn)
- return false;
+ const void* dst = nullptr;
+ size_t dst_len = 0;
- const BufferData& vba_buf = imap_ssn->mime_ssn->get_vba_inspect_buf();
+ switch (ibt)
+ {
+ case InspectionBuffer::IBT_VBA:
+ {
+ const BufferData& vba_buf = imap_ssn->mime_ssn->get_vba_inspect_buf();
+ dst = vba_buf.data_ptr();
+ dst_len = vba_buf.length();
+ break;
+ }
- if (vba_buf.data_ptr() && vba_buf.length())
- {
- b.data = vba_buf.data_ptr();
- b.len = vba_buf.length();
- return true;
- }
- else
- return false;
+ case InspectionBuffer::IBT_JS_DATA:
+ {
+ auto& dp = DetectionEngine::get_file_data(p->context);
+ auto jsn = acquire_js_ctx(*imap_ssn, dp.data, dp.len);
+ if (jsn)
+ {
+ jsn->get_data(dst, dst_len);
+ if (dst and dst_len)
+ break;
+ jsn->normalize(dp.data, dp.len, dst, dst_len);
}
+ break;
+ }
- default:
- break;
+ default:
+ return false;
}
- return false;
+ b.data = (const uint8_t*)dst;
+ b.len = dst_len;
+
+ return dst && dst_len;
}
+
bool Imap::get_fp_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& b)
{
// Fast pattern buffers only supplied at specific times
{
"file_data",
"vba_data",
+ "js_data",
nullptr
};
#else
const BaseApi* sin_imap = &imap_api.base;
#endif
-
bool is_end_of_data(snort::Flow* ssn) override;
};
+namespace snort
+{
+class PDFJSNorm;
+}
+
struct IMAPData
{
int state;
uint32_t body_len;
uint32_t body_read;
ImapMime* mime_ssn;
+ snort::PDFJSNorm* jsn;
};
class ImapFlowData : public snort::FlowData
PegCount ssl_search_abandoned;
PegCount ssl_srch_abandoned_early;
snort::MimeStats mime_stats;
+ PegCount js_pdf_scripts;
};
extern const PegInfo imap_peg_names[];
file API. The file API extracts and decodes the attachments. file_data is
then set to the start of these extracted/decoded attachments. This
inspector also identifies and whitelists the POPS traffic.
+
+POP inspector uses PDFJSNorm class to extract and normalize JavaScript
+in PDF files. The normalized JavaScript is then available in the js_data buffer.
+The js_data buffer follows the JIT approach, thus, to perform the normalization,
+the rule with the js_data IPS option must be present as well as the js_norm module
+is configured.
#include "pop.h"
#include "detection/detection_engine.h"
+#include "js_norm/js_pdf_norm.h"
#include "log/messages.h"
#include "profiler/profiler.h"
#include "protocols/packet.h"
{ CountType::SUM, "uu_decoded_bytes", "total uu decoded bytes" },
{ CountType::SUM, "non_encoded_attachments", "total non-encoded attachments extracted" },
{ CountType::SUM, "non_encoded_bytes", "total non-encoded extracted bytes" },
+ { CountType::SUM, "js_pdf_scripts", "total number of PDF files processed" },
{ CountType::END, nullptr, nullptr }
};
PopFlowData::~PopFlowData()
{
- if (session.mime_ssn)
- delete(session.mime_ssn);
+ delete session.mime_ssn;
+ delete session.jsn;
assert(popstats.concurrent_sessions > 0);
popstats.concurrent_sessions--;
}
unsigned PopFlowData::inspector_id = 0;
+
static POPData* get_session_data(Flow* flow)
{
PopFlowData* fd = (PopFlowData*)flow->get_flow_data(PopFlowData::inspector_id);
return fd ? &fd->session : nullptr;
}
+static inline PDFJSNorm* acquire_js_ctx(POPData& pop_ssn, const void* data, size_t len)
+{
+ if (pop_ssn.jsn)
+ return pop_ssn.jsn;
+
+ JSNormConfig* cfg = get_inspection_policy()->jsn_config;
+ if (cfg and PDFJSNorm::is_pdf(data, len))
+ {
+ pop_ssn.jsn = new PDFJSNorm(cfg);
+ ++popstats.js_pdf_scripts;
+ }
+
+ return pop_ssn.jsn;
+}
+
static POPData* SetNewPOPData(POP_PROTO_CONF* config, Packet* p)
{
POPData* pop_ssn;
pop_ssn->state = STATE_COMMAND;
pop_ssn->prev_response = 0;
pop_ssn->state_flags = 0;
+
+ delete pop_ssn->jsn;
+ pop_ssn->jsn = nullptr;
}
static void POP_GetEOL(const uint8_t* ptr, const uint8_t* end,
//ptr = POP_HandleData(p, ptr, end);
FilePosition position = get_file_position(p);
int len = end - ptr;
+
+ if (isFileStart(position))
+ {
+ delete pop_ssn->jsn;
+ pop_ssn->jsn = nullptr;
+ }
+
ptr = pop_ssn->mime_ssn->process_mime_data(p, ptr, len, false, position);
+ if (pop_ssn->jsn)
+ pop_ssn->jsn->tick();
continue;
}
POP_GetEOL(ptr, end, &eol, &eolm);
popstats.total_bytes += p->dsize;
int pkt_dir = POP_Setup(p, pop_ssn);
+ if (pop_ssn->jsn)
+ pop_ssn->jsn->flush_data();
+
if (pkt_dir == POP_PKT_FROM_CLIENT)
{
/* This packet should be a tls client hello */
bool Pop::get_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& b)
{
- // Fast pattern buffers only supplied at specific times
- switch (ibt)
- {
- case InspectionBuffer::IBT_VBA:
- {
- POPData* pop_ssn = get_session_data(p->flow);
+ POPData* pop_ssn = get_session_data(p->flow);
+ assert(pop_ssn);
- if (!pop_ssn)
- return false;
+ const void* dst = nullptr;
+ size_t dst_len = 0;
- const BufferData& vba_buf = pop_ssn->mime_ssn->get_vba_inspect_buf();
+ switch (ibt)
+ {
+ case InspectionBuffer::IBT_VBA:
+ {
+ const BufferData& vba_buf = pop_ssn->mime_ssn->get_vba_inspect_buf();
+ dst = vba_buf.data_ptr();
+ dst_len = vba_buf.length();
+ break;
+ }
- if (vba_buf.data_ptr() && vba_buf.length())
- {
- b.data = vba_buf.data_ptr();
- b.len = vba_buf.length();
- return true;
- }
- else
- return false;
+ case InspectionBuffer::IBT_JS_DATA:
+ {
+ auto& dp = DetectionEngine::get_file_data(p->context);
+ auto jsn = acquire_js_ctx(*pop_ssn, dp.data, dp.len);
+ if (jsn)
+ {
+ jsn->get_data(dst, dst_len);
+ if (dst and dst_len)
+ break;
+ jsn->normalize(dp.data, dp.len, dst, dst_len);
}
+ break;
+ }
- default:
- break;
+ default:
+ return false;
}
- return false;
+
+ b.data = (const uint8_t*)dst;
+ b.len = dst_len;
+
+ return dst && dst_len;
}
bool Pop::get_fp_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& b)
{
"file_data",
"vba_data",
+ "js_data",
nullptr
};
#else
const BaseApi* sin_pop = &pop_api.base;
#endif
-
bool is_end_of_data(snort::Flow* ssn) override;
};
+namespace snort
+{
+class PDFJSNorm;
+}
+
struct POPData
{
int state;
int state_flags;
int session_flags;
PopMime* mime_ssn;
+ snort::PDFJSNorm* jsn;
};
class PopFlowData : public snort::FlowData
PegCount ssl_search_abandoned;
PegCount ssl_srch_abandoned_early;
snort::MimeStats mime_stats;
+ PegCount js_pdf_scripts;
};
extern const PegInfo pop_peg_names[];
SMTP inspector logs the filename, email addresses, attachment names when
configured. The SMTP commands are also normalized based on the config.
+
+SMTP inspector uses PDFJSNorm class to extract and normalize JavaScript
+in PDF files. The normalized JavaScript is then available in the js_data buffer.
+The js_data buffer follows the JIT approach, thus, to perform the normalization,
+the rule with the js_data IPS option must be present as well as the js_norm module
+is configured.
#include "detection/detection_engine.h"
#include "detection/detection_util.h"
+#include "js_norm/js_pdf_norm.h"
#include "log/messages.h"
#include "log/unified2.h"
#include "profiler/profiler.h"
{ CountType::SUM, "uu_decoded_bytes", "total uu decoded bytes" },
{ CountType::SUM, "non_encoded_attachments", "total non-encoded attachments extracted" },
{ CountType::SUM, "non_encoded_bytes", "total non-encoded extracted bytes" },
+ { CountType::SUM, "js_pdf_scripts", "total number of PDF files processed" },
{ CountType::END, nullptr, nullptr }
};
SmtpFlowData::~SmtpFlowData()
{
- if ( session.mime_ssn )
- delete session.mime_ssn;
-
- if ( session.auth_name )
- snort_free(session.auth_name);
+ delete session.mime_ssn;
+ delete session.jsn;
+ snort_free(session.auth_name);
assert(smtpstats.concurrent_sessions > 0);
smtpstats.concurrent_sessions--;
}
unsigned SmtpFlowData::inspector_id = 0;
+
static SMTPData* get_session_data(Flow* flow)
{
SmtpFlowData* fd = (SmtpFlowData*)flow->get_flow_data(SmtpFlowData::inspector_id);
return fd ? &fd->session : nullptr;
}
+static inline PDFJSNorm* acquire_js_ctx(SMTPData& smtp_ssn, const void* data, size_t len)
+{
+ if (smtp_ssn.jsn)
+ return smtp_ssn.jsn;
+
+ JSNormConfig* cfg = get_inspection_policy()->jsn_config;
+ if (cfg and PDFJSNorm::is_pdf(data, len))
+ {
+ smtp_ssn.jsn = new PDFJSNorm(cfg);
+ ++smtpstats.js_pdf_scripts;
+ }
+
+ return smtp_ssn.jsn;
+}
+
static SMTPData* SetNewSMTPData(SmtpProtoConf* config, Packet* p)
{
SMTPData* smtp_ssn;
SMTPData* smtp_ssn = get_session_data(ssn);
smtp_ssn->state = STATE_COMMAND;
smtp_ssn->state_flags = (smtp_ssn->state_flags & SMTP_FLAG_ABANDON_EVT) ? SMTP_FLAG_ABANDON_EVT : 0;
+
+ delete smtp_ssn->jsn;
+ smtp_ssn->jsn = nullptr;
}
static inline int InspectPacket(Packet* p)
smtp_ssn->state = STATE_TLS_CLIENT_PEND;
smtp_ssn->client_requested_starttls = true;
}
-
+
break;
case CMD_X_LINK2STATE:
const uint8_t* end = p->data + p->dsize;
if (smtp_ssn->state == STATE_CONNECT)
- {
smtp_ssn->state = STATE_COMMAND;
- }
while ((ptr != nullptr) && (ptr < end))
{
case STATE_DATA:
case STATE_BDATA:
position = get_file_position(p);
+ if (isFileStart(position))
+ {
+ delete smtp_ssn->jsn;
+ smtp_ssn->jsn = nullptr;
+ }
ptr = smtp_ssn->mime_ssn->process_mime_data(p, ptr, len, true, position);
//ptr = SMTP_HandleData(p, ptr, end, &(smtp_ssn->mime_ssn));
+ if (smtp_ssn->jsn)
+ smtp_ssn->jsn->tick();
break;
case STATE_XEXCH50:
if (smtp_normalizing)
else
{
smtp_ssn->server_accepted_starttls = true;
-
+
OpportunisticTlsEvent event(p, p->flow->service);
DataBus::publish(OPPORTUNISTIC_TLS_EVENT, event, p->flow);
++smtpstats.starttls;
/* reset normalization stuff */
smtp_normalizing = false;
smtpstats.total_bytes += p->dsize;
+ if (smtp_ssn->jsn)
+ smtp_ssn->jsn->flush_data();
if (pkt_dir == SMTP_PKT_FROM_SERVER)
{
smtp_ssn->state = STATE_TLS_SERVER_PEND;
}
}
-
+
if(smtp_ssn->state == STATE_TLS_CLIENT_PEND)
smtp_ssn->state = STATE_COMMAND;
bool Smtp::get_fp_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& b)
{
- if ( ibt != InspectionBuffer::IBT_VBA )
- return false;
-
SMTPData* smtp_ssn = get_session_data(p->flow);
+ assert(smtp_ssn);
- if (!smtp_ssn)
- return false;
+ const void* dst = nullptr;
+ size_t dst_len = 0;
- const BufferData& vba_buf = smtp_ssn->mime_ssn->get_vba_inspect_buf();
+ switch (ibt)
+ {
+ case InspectionBuffer::IBT_VBA:
+ {
+ const BufferData& vba_buf = smtp_ssn->mime_ssn->get_vba_inspect_buf();
+ dst = vba_buf.data_ptr();
+ dst_len = vba_buf.length();
+ break;
+ }
- if ( vba_buf.data_ptr() && vba_buf.length() )
+ case InspectionBuffer::IBT_JS_DATA:
{
- b.data = vba_buf.data_ptr();
- b.len = vba_buf.length();
- return true;
+ auto& dp = DetectionEngine::get_file_data(p->context);
+ auto jsn = acquire_js_ctx(*smtp_ssn, dp.data, dp.len);
+ if (jsn)
+ {
+ jsn->get_data(dst, dst_len);
+ if (dst and dst_len)
+ break;
+ jsn->normalize(dp.data, dp.len, dst, dst_len);
+ }
+ break;
}
- return false;
+
+ default:
+ return false;
+ }
+
+ b.data = (const uint8_t*)dst;
+ b.len = dst_len;
+
+ return dst && dst_len;
}
//-------------------------------------------------------------------------
{
"file_data",
"vba_data",
+ "js_data",
nullptr
};
delete p.context;
}
#endif
-
bool is_end_of_data(snort::Flow* ssn) override;
};
+namespace snort
+{
+class PDFJSNorm;
+}
+
struct SMTPData
{
//Initialize structure with default values
session_flags{0},
dat_chunk{0},
mime_ssn{nullptr},
+ jsn{nullptr},
auth_name{nullptr},
client_requested_starttls{false},
pipelined_command_counter{0},
int session_flags;
uint32_t dat_chunk;
SmtpMime* mime_ssn;
+ snort::PDFJSNorm* jsn;
SMTPAuthName* auth_name;
bool client_requested_starttls;
size_t pipelined_command_counter;
PegCount ssl_search_abandoned;
PegCount ssl_search_abandoned_too_soon;
snort::MimeStats mime_stats;
+ PegCount js_pdf_scripts;
};
extern const PegInfo smtp_peg_names[];
* * Process data boundary and flush each file based on boundary*/
static inline bool process_data(SmtpPafData* pfdata, uint8_t data)
{
- if (flush_based_length(pfdata)|| check_data_end(&(pfdata->data_end_state), data))
+ if (flush_based_length(pfdata) || check_data_end(&(pfdata->data_end_state), data))
{
/*Clean up states*/
pfdata->smtp_state = SMTP_PAF_CMD_STATE;
#include <utility>
#include <vector>
+#include "main/snort_types.h"
+
namespace snort
{
// an input stream over set of buffers,
// the buffer doesn't take ownership over the memory,
// no intermediate buffering between chunks
-class istreambuf_glue : public std::streambuf
+class SO_PUBLIC istreambuf_glue : public std::streambuf
{
public:
istreambuf_glue();
};
// an output stream over extensible array
-class ostreambuf_infl : public std::streambuf
+class SO_PUBLIC ostreambuf_infl : public std::streambuf
{
public:
static constexpr size_t size_limit = 1 << 20;