#include "ips_comp.h" #include #include "debug.h" #include "new_table_entry.h" #include "ips_entry.h" #include "ips_configuration.h" #include "ips_signatures.h" #include "ips_metric.h" #include "generic_rulebase/parameters_config.h" #include "config.h" #include "virtual_modifiers.h" #include "helper.h" #include "ips_common_types.h" #include "nginx_attachment_common.h" using namespace std; USE_DEBUG_FLAG(D_IPS); static const Buffer header_sep(": ", 2, Buffer::MemoryType::STATIC); static const Buffer line_sep("\r\n", 2, Buffer::MemoryType::STATIC); static const Buffer space(" ", 1, Buffer::MemoryType::STATIC); static const Buffer x_forworded_for_key("x-forworded-for", 15, Buffer::MemoryType::STATIC); static const Buffer log_sep(", ", 2, Buffer::MemoryType::STATIC); static const Buffer empty_buffer("", 0, Buffer::MemoryType::STATIC); static const string cookie("cookie"); static const string oauth("_oauth2_proxy"); static const string jsessionid("jsessionid"); static const string xff("x-forwarded-for"); static const string header("header"); static const string source_ip("source ip"); class IPSComp::Impl : public Singleton::Provide::SelfInterface, public Listener, public Listener, public Listener, public Listener, public Listener, public Listener, public Listener, public Listener, public Listener { static constexpr auto DROP = ngx_http_cp_verdict_e::TRAFFIC_VERDICT_DROP; static constexpr auto ACCEPT = ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT; static constexpr auto INSPECT = ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INSPECT; class SigsFirstTierAgg { public: const shared_ptr & getHook(const set &new_pat) { auto old_size = pats.size(); pats.insert(new_pat.begin(), new_pat.end()); if (pats.size() != old_size) { if (!hook->prepare(pats).ok()) { reportConfigurationError("failed to compile first tier"); } } return hook; } private: set pats; shared_ptr hook = make_shared(); }; public: void preload() { function cb = [&](){ clearAggCache(); }; registerConfigPrepareCb(cb); registerConfigLoadCb(cb); registerConfigAbortCb(cb); } void init() { ips_metric.init( "IPS Stats", ReportIS::AudienceTeam::AGENT_CORE, ReportIS::IssuingEngine::AGENT_CORE, std::chrono::minutes(10), true, ReportIS::Audience::SECURITY ); ips_metric.registerListener(); registerListener(); table = Singleton::Consume::by(); env = Singleton::Consume::by(); } void fini() { unregisterListener(); } void upon(const NewTableEntry &) override { if (isSignatureListsEmpty()) return; auto table = Singleton::Consume::by(); table->createState(); table->getState().uponEnteringContext(); } string getListenerName() const override { return "ips application"; } EventVerdict respond(const NewHttpTransactionEvent &event) override { if (isSignatureListsEmpty()) return ACCEPT; table->createState(); auto &ips_state = table->getState(); ips_state.uponEnteringContext(); auto leave_context = make_scope_exit([&ips_state] () { ips_state.uponLeavingContext(); }); Buffer method(event.getHttpMethod()); ips_state.addPendingContext("HTTP_METHOD", method); Buffer uri(event.getURI()); ips_state.addPendingContext("HTTP_COMPLETE_URL_ENCODED", uri); auto decoder = makeVirtualContainer>(event.getURI()); vector decoded_url(decoder.begin(), decoder.end()); auto start = find(decoded_url.begin(), decoded_url.end(), static_cast('?')); if (start != decoded_url.end()) { vector query(start + 1, decoded_url.end()); ips_state.addPendingContext("HTTP_QUERY_DECODED", Buffer(move(query))); } vector path(decoded_url.begin(), start); ips_state.addPendingContext("HTTP_PATH_DECODED", Buffer(move(path))); ips_state.addPendingContext("HTTP_COMPLETE_URL_DECODED", Buffer(move(decoded_url))); Buffer protocol(event.getHttpProtocol()); ips_state.addPendingContext("HTTP_PROTOCOL", protocol); auto full_line = method + space + uri + space + protocol + line_sep; ips_state.addPendingContext("HTTP_RAW", full_line); return INSPECT; } static string getHeaderContextName(const Buffer &name) { string name_str = name; transform(name_str.begin(), name_str.end(), name_str.begin(), ::toupper); return "HTTP_REQUEST_HEADER_" + name_str; } EventVerdict respond(const HttpRequestHeaderEvent &event) override { if (!table->hasState()) return ACCEPT; auto &ips_state = table->getState(); ips_state.uponEnteringContext(); auto leave_context = make_scope_exit([&ips_state] () { ips_state.uponLeavingContext(); }); auto header_value = event.getKey() + header_sep + event.getValue(); ips_state.addPendingContext("HTTP_REQUEST_ONE_HEADER", header_value); auto full_header = header_value + line_sep; ips_state.addPendingContext("HTTP_REQUEST_HEADER", full_header); ips_state.addPendingContext(getHeaderContextName(event.getKey()), event.getValue()); ips_state.addPendingContext("HTTP_RAW", full_header); auto max_size = getConfigurationWithDefault(1536, "IPS", "Max Field Size"); // Add request header for log auto maybe_req_headers_for_log = ips_state.getTransactionData(IPSCommonTypes::requests_header_for_log); if (!maybe_req_headers_for_log.ok()) { ips_state.setTransactionData(IPSCommonTypes::requests_header_for_log, header_value); } else if ((maybe_req_headers_for_log.unpack()).size() + log_sep.size() + header_value.size() < max_size) { Buffer request_headers_for_log = maybe_req_headers_for_log.unpack() + log_sep; request_headers_for_log += header_value; ips_state.setTransactionData(IPSCommonTypes::requests_header_for_log, request_headers_for_log); } addRequestHdr(event.getKey(), event.getValue()); if (event.isLastHeader()) { for (auto &context : ips_state.getPendingContexts()) { if (isDropContext(context.first, context.second)) setDrop(ips_state); } ips_state.clearPendingContexts(); if (isDrop(ips_state)) return DROP; } return INSPECT; } EventVerdict respond(const HttpRequestBodyEvent &event) override { if (!table->hasState()) return ACCEPT; auto &ips_state = table->getState(); ips_state.uponEnteringContext(); auto leave_context = make_scope_exit([&ips_state] () { ips_state.uponLeavingContext(); }); if (isDropContext("HTTP_REQUEST_BODY", event.getData())) setDrop(ips_state); if (!ips_state.isFlagSet("HttpRequestData")) { ips_state.setFlag("HttpRequestData"); auto data = ips_state.getBuffer("HTTP_METHOD") + space + ips_state.getBuffer("HTTP_COMPLETE_URL_DECODED") + space + ips_state.getBuffer("HTTP_PROTOCOL") + line_sep + ips_state.getBuffer("HTTP_REQUEST_HEADER") + line_sep + event.getData(); if (isDropContext("HTTP_REQUEST_DATA", data)) setDrop(ips_state); } if (isDropContext("HTTP_RAW", event.getData())) setDrop(ips_state); return INSPECT; } EventVerdict respond(const EndRequestEvent &) override { if (!table->hasState()) return ACCEPT; auto &ips_state = table->getState(); ips_state.uponEnteringContext(); auto leave_context = make_scope_exit([&ips_state] () { ips_state.uponLeavingContext(); }); if (!ips_state.isFlagSet("HttpRequestData")) { ips_state.setFlag("HttpRequestData"); auto data = ips_state.getBuffer("HTTP_METHOD") + space + ips_state.getBuffer("HTTP_COMPLETE_URL_DECODED") + space + ips_state.getBuffer("HTTP_PROTOCOL") + line_sep + ips_state.getBuffer("HTTP_REQUEST_HEADER") + line_sep; if (isDropContext("HTTP_REQUEST_DATA", data)) return DROP; } if (isDrop(ips_state)) return DROP; if (isContextActive("HTTP_RESPONSE_HEADER")) return INSPECT; if (isContextActive("HTTP_RESPONSE_BODY")) return INSPECT; return ACCEPT; } EventVerdict respond(const ResponseCodeEvent &event) override { if (!table->hasState()) return ACCEPT; auto &ips_state = table->getState(); ips_state.uponEnteringContext(); auto leave_context = make_scope_exit([&ips_state] () { ips_state.uponLeavingContext(); }); Buffer buf(reinterpret_cast(&event), sizeof(event), Buffer::MemoryType::VOLATILE); if (isDropContext("HTTP_RESPONSE_CODE", buf)) return DROP; return INSPECT; } EventVerdict respond(const HttpResponseHeaderEvent &event) override { if (!table->hasState()) return ACCEPT; auto &ips_state = table->getState(); ips_state.uponEnteringContext(); auto leave_context = make_scope_exit([&ips_state] () { ips_state.uponLeavingContext(); }); if (isDropContext("HTTP_RESPONSE_HEADER", event.getValue())) return DROP; return INSPECT; } EventVerdict respond(const HttpResponseBodyEvent &event) override { if (!table->hasState()) return ACCEPT; auto &ips_state = table->getState(); ips_state.uponEnteringContext(); auto leave_context = make_scope_exit([&ips_state] () { ips_state.uponLeavingContext(); }); if (isDropContext("HTTP_RESPONSE_BODY", event.getData())) return DROP; return event.isLastChunk() ? ACCEPT : INSPECT; } EventVerdict respond (const EndTransactionEvent &) override { return ACCEPT; } private: static void setDrop(IPSEntry &state) { state.setDrop(); } static bool isDrop(const IPSEntry &state) { return state.isDrop(); } bool isDropContext(const string &name, const Buffer &buf) { auto responeses = ParsedContext(buf, name, 0).query(); for (auto &reponse : responeses) { if (reponse == ParsedContextReply::DROP) return true; } return false; } static bool isContextActive(const string &context) { return !getConfigurationWithDefault(IPSSignatures(), "IPS", "IpsProtections").isEmpty(context) || !getConfigurationWithDefault(SnortSignatures(), "IPSSnortSigs", "SnortProtections").isEmpty(context); } static bool isSignatureListsEmpty() { return getConfigurationWithDefault(IPSSignatures(), "IPS", "IpsProtections").isEmpty() && getConfigurationWithDefault(SnortSignatures(), "IPSSnortSigs", "SnortProtections").isEmpty(); } void addRequestHdr(const Buffer &name, const Buffer &value) { auto &ips_state = table->getState(); ips_state.setTransactionData(name, value); } shared_ptr getHook(const string &context_name, const set &patterns) override { return tier_aggs[context_name].getHook(patterns); } void clearAggCache() { tier_aggs.clear(); } I_Table *table = nullptr; I_Environment *env = nullptr; IPSSignatureSubTypes::IPSMetric ips_metric; map tier_aggs; }; IPSComp::IPSComp() : Component("IPSComp"), pimpl(make_unique()) {} IPSComp::~IPSComp() {} void IPSComp::preload() { registerExpectedResource("IPS", "protections"); registerExpectedResource("IPS", "VersionId"); registerExpectedResource("IPSSnortSigs", "protections"); registerExpectedConfiguration("IPS", "IpsConfigurations"); registerExpectedConfiguration("IPS", "Max Field Size"); registerExpectedConfiguration("IPS", "IpsProtections"); registerExpectedConfiguration("IPSSnortSigs", "SnortProtections"); registerExpectedConfigFile("ips", Config::ConfigFileType::Policy); registerExpectedConfigFile("ips", Config::ConfigFileType::Data); registerExpectedConfigFile("snort", Config::ConfigFileType::Policy); ParameterException::preload(); pimpl->preload(); } void IPSComp::init() { pimpl->init(); } void IPSComp::fini() { pimpl->fini(); }