mirror of
https://github.com/openappsec/openappsec.git
synced 2025-09-29 11:16:30 +03:00
My 11th 2023 update
This commit is contained in:
17
components/security_apps/ips/CMakeLists.txt
Normal file
17
components/security_apps/ips/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
include_directories(include)
|
||||
|
||||
add_library(ips
|
||||
ips_comp.cc
|
||||
ips_entry.cc
|
||||
ips_signatures.cc
|
||||
simple_protection.cc
|
||||
compound_protection.cc
|
||||
ips_configuration.cc
|
||||
helper_open_source.cc
|
||||
ips_basic_policy.cc
|
||||
snort_basic_policy.cc
|
||||
ips_metric.cc
|
||||
ips_common_types.cc
|
||||
)
|
||||
|
||||
add_subdirectory(ips_ut)
|
216
components/security_apps/ips/compound_protection.cc
Normal file
216
components/security_apps/ips/compound_protection.cc
Normal file
@@ -0,0 +1,216 @@
|
||||
#include "compound_protection.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "rule_detection.h"
|
||||
#include "ips_entry.h"
|
||||
#include "ips_comp.h"
|
||||
#include "debug.h"
|
||||
|
||||
using namespace std;
|
||||
using MatchType = IPSSignatureSubTypes::BaseSignature::MatchType;
|
||||
|
||||
USE_DEBUG_FLAG(D_IPS);
|
||||
|
||||
CompoundProtection::Impl::Impl(const string &_sig_name, SignaturesVector &&sig_vec, Operation oper)
|
||||
:
|
||||
sig_name(_sig_name),
|
||||
sub_signatures(move(sig_vec)),
|
||||
operation(oper),
|
||||
table(Singleton::Consume<I_Table>::by<IPSComp>())
|
||||
{
|
||||
for (const auto &sig : sub_signatures) {
|
||||
const auto &sub_sig_context = sig->getContext();
|
||||
for (auto &new_context : sub_sig_context) {
|
||||
bool is_new_context = true;
|
||||
for (auto &old_context : contexts) {
|
||||
if (new_context == old_context) {
|
||||
is_new_context = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_new_context) contexts.push_back(new_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MatchType
|
||||
CompoundProtection::Impl::getMatch(const set<PMPattern> &matched) const
|
||||
{
|
||||
switch (operation) {
|
||||
case Operation::OR: return getMatchOr(matched);
|
||||
case Operation::AND: return getMatchAnd(matched);
|
||||
case Operation::ORDERED_AND: return getMatchOrderedAnd(matched);
|
||||
}
|
||||
|
||||
dbgAssert(false) << "Unknown compound operation: " << static_cast<uint>(operation);
|
||||
return MatchType::NO_MATCH;
|
||||
}
|
||||
|
||||
set<PMPattern>
|
||||
CompoundProtection::Impl::patternsInSignature() const
|
||||
{
|
||||
set<PMPattern> res;
|
||||
|
||||
for (auto &sig : sub_signatures) {
|
||||
const auto &sub_sig_patterns = sig->patternsInSignature();
|
||||
for (auto &pat : sub_sig_patterns) {
|
||||
res.insert(pat);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
MatchType
|
||||
CompoundProtection::Impl::getMatchOr(const set<PMPattern> &matched) const
|
||||
{
|
||||
MatchType res = MatchType::NO_MATCH;
|
||||
for (auto &sig : sub_signatures) {
|
||||
switch (getSubMatch(sig, matched)) {
|
||||
case MatchType::NO_MATCH: break;
|
||||
case MatchType::CACHE_MATCH: {
|
||||
res = MatchType::CACHE_MATCH;
|
||||
break;
|
||||
}
|
||||
case MatchType::MATCH: return MatchType::MATCH;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
MatchType
|
||||
CompoundProtection::Impl::getMatchAnd(const set<PMPattern> &matched) const
|
||||
{
|
||||
MatchType res = MatchType::CACHE_MATCH;
|
||||
for (auto &sig : sub_signatures) {
|
||||
switch (getSubMatch(sig, matched)) {
|
||||
case MatchType::NO_MATCH: {
|
||||
res = MatchType::NO_MATCH;
|
||||
break;
|
||||
}
|
||||
case MatchType::CACHE_MATCH: break;
|
||||
case MatchType::MATCH: {
|
||||
if (res == MatchType::CACHE_MATCH) res = MatchType::MATCH;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
MatchType
|
||||
CompoundProtection::Impl::getMatchOrderedAnd(const set<PMPattern> &matched) const
|
||||
{
|
||||
MatchType res = MatchType::CACHE_MATCH;
|
||||
for (auto &sig : sub_signatures) {
|
||||
switch (getSubMatch(sig, matched)) {
|
||||
case MatchType::NO_MATCH: return MatchType::NO_MATCH;
|
||||
case MatchType::CACHE_MATCH: break;
|
||||
case MatchType::MATCH: {
|
||||
res = MatchType::MATCH;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool
|
||||
isStringInVector(const Maybe<string, Context::Error> &str, const vector<string> &vec)
|
||||
{
|
||||
if (!str.ok()) return false;
|
||||
return find(vec.begin(), vec.end(), *str) != vec.end();
|
||||
}
|
||||
|
||||
MatchType
|
||||
CompoundProtection::Impl::getSubMatch(
|
||||
const std::shared_ptr<IPSSignatureSubTypes::BaseSignature> &sub_sig,
|
||||
const set<PMPattern> &matched
|
||||
) const
|
||||
{
|
||||
if (isFlagSet(sub_sig->getSigId())) return MatchType::CACHE_MATCH;
|
||||
|
||||
auto env = Singleton::Consume<I_Environment>::by<IPSComp>();
|
||||
auto curr_ctx = env->get<string>(I_KeywordsRule::getKeywordsRuleTag());
|
||||
if (!isStringInVector(curr_ctx, sub_sig->getContext())) return MatchType::NO_MATCH;
|
||||
|
||||
auto res = sub_sig->getMatch(matched);
|
||||
if (res != MatchType::NO_MATCH) setFlag(sub_sig->getSigId());
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
CompoundProtection::Impl::isFlagSet(const std::string &id) const
|
||||
{
|
||||
if (!table->hasState<IPSEntry>()) {
|
||||
dbgWarning(D_IPS) << "No entry was found, limited compound functionality";
|
||||
return false;
|
||||
}
|
||||
return table->getState<IPSEntry>().isFlagSet(id);
|
||||
}
|
||||
|
||||
void
|
||||
CompoundProtection::Impl::setFlag(const std::string &id) const
|
||||
{
|
||||
if (!table->hasState<IPSEntry>()) {
|
||||
dbgWarning(D_IPS) << "No entry was found, limited compound functionality";
|
||||
return;
|
||||
}
|
||||
table->getState<IPSEntry>().setFlag(id);
|
||||
}
|
||||
|
||||
class OperandsReader
|
||||
{
|
||||
public:
|
||||
OperandsReader(const string &sig_name) : base_sig_name(sig_name) {}
|
||||
|
||||
void
|
||||
load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
cereal::size_type cereal_size;
|
||||
ar(cereal::make_size_tag(cereal_size));
|
||||
rules.resize(static_cast<size_t>(cereal_size));
|
||||
|
||||
uint index = 0;
|
||||
for (auto &rule : rules) {
|
||||
stringstream ss;
|
||||
ss << base_sig_name << "##" << index;
|
||||
++index;
|
||||
RuleDetection detection(ss.str());
|
||||
ar(detection);
|
||||
rule = detection.getRule();
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<IPSSignatureSubTypes::BaseSignature>> && extrackRules() { return move(rules); }
|
||||
|
||||
private:
|
||||
string base_sig_name;
|
||||
vector<shared_ptr<IPSSignatureSubTypes::BaseSignature>> rules;
|
||||
};
|
||||
|
||||
shared_ptr<IPSSignatureSubTypes::BaseSignature>
|
||||
CompoundProtection::get(const string &sig_name, cereal::JSONInputArchive &ar)
|
||||
{
|
||||
string operation;
|
||||
OperandsReader operands(sig_name);
|
||||
|
||||
ar(
|
||||
cereal::make_nvp("operation", operation),
|
||||
cereal::make_nvp("operands", operands)
|
||||
);
|
||||
|
||||
return make_shared<Impl>(sig_name, operands.extrackRules(), getOperation(operation));
|
||||
}
|
||||
|
||||
CompoundProtection::Operation
|
||||
CompoundProtection::getOperation(const string &operation)
|
||||
{
|
||||
if (operation == "or") return Operation::OR;
|
||||
if (operation == "and") return Operation::AND;
|
||||
if (operation == "ordered_and") return Operation::ORDERED_AND;
|
||||
|
||||
reportConfigurationError("Unknown compound operation: " + operation);
|
||||
return Operation::OR;
|
||||
}
|
24
components/security_apps/ips/helper_open_source.cc
Normal file
24
components/security_apps/ips/helper_open_source.cc
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "helper.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace IPSHelper
|
||||
{
|
||||
|
||||
string
|
||||
deobfuscateString(const string &str)
|
||||
{
|
||||
if (str.substr(0, 7) == "M^AGI$C") reportConfigurationError("Deobfuscation isn't available in open-source mode");
|
||||
return str;
|
||||
}
|
||||
|
||||
string
|
||||
deobfuscateKeyword(const string &str)
|
||||
{
|
||||
if (str.substr(0, 7) == "M^AGI$C") reportConfigurationError("Deobfuscation isn't available in open-source mode");
|
||||
return str;
|
||||
}
|
||||
|
||||
} // IPSHelper
|
49
components/security_apps/ips/include/compound_protection.h
Normal file
49
components/security_apps/ips/include/compound_protection.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef __COMPOUND_PROTECTION_H__
|
||||
#define __COMPOUND_PROTECTION_H__
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ips_signatures.h"
|
||||
#include "i_table.h"
|
||||
|
||||
class CompoundProtection
|
||||
{
|
||||
enum class Operation { OR, AND, ORDERED_AND };
|
||||
using BaseSignature = IPSSignatureSubTypes::BaseSignature;
|
||||
|
||||
class Impl : public IPSSignatureSubTypes::BaseSignature
|
||||
{
|
||||
using SignaturesVector = std::vector<std::shared_ptr<BaseSignature>>;
|
||||
|
||||
public:
|
||||
Impl(const std::string &sig_name, SignaturesVector &&sig_vec, Operation oper);
|
||||
|
||||
const std::string & getSigId() const override { return sig_name; }
|
||||
MatchType getMatch(const std::set<PMPattern> &matched) const override;
|
||||
std::set<PMPattern> patternsInSignature() const override;
|
||||
const std::vector<std::string> & getContext() const override { return contexts; }
|
||||
|
||||
private:
|
||||
MatchType getMatchOr(const std::set<PMPattern> &matched) const;
|
||||
MatchType getMatchAnd(const std::set<PMPattern> &matched) const;
|
||||
MatchType getMatchOrderedAnd(const std::set<PMPattern> &matched) const;
|
||||
|
||||
MatchType getSubMatch(const std::shared_ptr<BaseSignature> &sub_sig, const std::set<PMPattern> &matched) const;
|
||||
bool isFlagSet(const std::string &id) const;
|
||||
void setFlag(const std::string &id) const;
|
||||
|
||||
std::string sig_name;
|
||||
SignaturesVector sub_signatures;
|
||||
std::vector<std::string> contexts;
|
||||
Operation operation;
|
||||
I_Table *table;
|
||||
};
|
||||
|
||||
public:
|
||||
static std::shared_ptr<BaseSignature> get(const std::string &sig_name, cereal::JSONInputArchive &ar);
|
||||
|
||||
private:
|
||||
static Operation getOperation(const std::string &operation);
|
||||
};
|
||||
|
||||
#endif // __COMPOUND_PROTECTION_H__
|
14
components/security_apps/ips/include/helper.h
Normal file
14
components/security_apps/ips/include/helper.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef __HELPER_H__
|
||||
#define __HELPER_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace IPSHelper
|
||||
{
|
||||
|
||||
std::string deobfuscateString(const std::string &str);
|
||||
std::string deobfuscateKeyword(const std::string &str);
|
||||
|
||||
} // IPSHelper
|
||||
|
||||
#endif // __HELPER_H__
|
19
components/security_apps/ips/include/i_first_tier_agg.h
Normal file
19
components/security_apps/ips/include/i_first_tier_agg.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef __I_FIRST_TIER_AGG_H__
|
||||
#define __I_FIRST_TIER_AGG_H__
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "pm_hook.h"
|
||||
|
||||
class I_FirstTierAgg
|
||||
{
|
||||
public:
|
||||
virtual std::shared_ptr<PMHook> getHook(const std::string &context_name, const std::set<PMPattern> &patterns) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~I_FirstTierAgg() {}
|
||||
};
|
||||
|
||||
#endif // __I_FIRST_TIER_AGG_H__
|
58
components/security_apps/ips/include/ips_basic_policy.h
Normal file
58
components/security_apps/ips/include/ips_basic_policy.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef __IPS_BASIC_POLICY_H__
|
||||
#define __IPS_BASIC_POLICY_H__
|
||||
|
||||
#include <cereal/archives/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "ips_enums.h"
|
||||
#include "debug.h"
|
||||
#include "maybe_res.h"
|
||||
#include "ips_signatures.h"
|
||||
|
||||
class RuleSelector
|
||||
{
|
||||
public:
|
||||
class Rule
|
||||
{
|
||||
public:
|
||||
void serialize(cereal::JSONInputArchive &ar);
|
||||
|
||||
bool isSignaturedMatched(const IPSSignatureSubTypes::CompleteSignature &signature) const;
|
||||
const IPSSignatureSubTypes::SignatureAction & getAction() const { return action; };
|
||||
void readAction(cereal::JSONInputArchive &ar, const std::string &action_type);
|
||||
void print(std::ostream &os) const;
|
||||
|
||||
private:
|
||||
void readPerformanceImpact(cereal::JSONInputArchive &ar);
|
||||
void readSeverityLevel(cereal::JSONInputArchive &ar);
|
||||
void readConfidenceLevel(cereal::JSONInputArchive &ar);
|
||||
void readServerProtections(cereal::JSONInputArchive &ar);
|
||||
void readClientProtections(cereal::JSONInputArchive &ar);
|
||||
void readProtectionsFromYear(cereal::JSONInputArchive &ar);
|
||||
void readProtectionTags(cereal::JSONInputArchive &ar);
|
||||
void readProtectionIds(cereal::JSONInputArchive &ar);
|
||||
|
||||
IPSSignatureSubTypes::SignatureAction action = IPSSignatureSubTypes::SignatureAction::IGNORE;
|
||||
Maybe<IPSSignatureSubTypes::IPSLevel> performance_impact = genError("undefined");
|
||||
Maybe<IPSSignatureSubTypes::IPSLevel> severity_level = genError("undefined");
|
||||
Maybe<IPSSignatureSubTypes::IPSLevel> confidence_level = genError("undefined");
|
||||
Maybe<bool> server_protections = genError("undefined");
|
||||
Maybe<bool> client_protections = genError("undefined");
|
||||
Maybe<int> protections_from_year = genError("undefined");
|
||||
Maybe<std::vector<std::string>> protection_tags = genError("undefined");
|
||||
Maybe<std::vector<std::string>> protection_ids = genError("undefined");
|
||||
};
|
||||
|
||||
public:
|
||||
std::vector<IPSSignatureSubTypes::SignatureAndAction> selectSignatures() const;
|
||||
void print(std::ostream &os) const;
|
||||
void load(cereal::JSONInputArchive &ar);
|
||||
|
||||
private:
|
||||
void readRules(cereal::JSONInputArchive &ar);
|
||||
void readDefaultAction(cereal::JSONInputArchive &ar);
|
||||
|
||||
std::vector<Rule> rules;
|
||||
};
|
||||
|
||||
#endif // __IPS_BASIC_POLICY_H__
|
11
components/security_apps/ips/include/ips_common_types.h
Normal file
11
components/security_apps/ips/include/ips_common_types.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef __IPS_COMMON_TYPES__
|
||||
#define __IPS_COMMON_TYPES__
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
struct IPSCommonTypes
|
||||
{
|
||||
static const Buffer requests_header_for_log;
|
||||
};
|
||||
|
||||
#endif //__IPS_COMMON_TYPES__
|
36
components/security_apps/ips/include/ips_configuration.h
Normal file
36
components/security_apps/ips/include/ips_configuration.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef __IPS_CONFIGURATION_H__
|
||||
#define __IPS_CONFIGURATION_H__
|
||||
|
||||
#include "config.h"
|
||||
|
||||
class IPSConfiguration
|
||||
{
|
||||
public:
|
||||
enum class ContextType { NORMAL, KEEP, HISTORY };
|
||||
|
||||
class Context {
|
||||
public:
|
||||
Context() : type(ContextType::NORMAL), history_size(0) {}
|
||||
Context(ContextType type, uint history);
|
||||
|
||||
ContextType getType() const { return type; }
|
||||
uint getHistorySize() const;
|
||||
|
||||
private:
|
||||
ContextType type;
|
||||
uint history_size;
|
||||
};
|
||||
|
||||
IPSConfiguration() {}
|
||||
IPSConfiguration(const std::map<std::string, Context> &initial_conf) : context_config(initial_conf) {}
|
||||
|
||||
void load(cereal::JSONInputArchive &ar);
|
||||
|
||||
Context getContext(const std::string &name) const;
|
||||
uint getHistorySize(const std::string &name) const;
|
||||
|
||||
private:
|
||||
std::map<std::string, Context> context_config;
|
||||
};
|
||||
|
||||
#endif // __IPS_CONFIGURATION_H__
|
56
components/security_apps/ips/include/ips_entry.h
Normal file
56
components/security_apps/ips/include/ips_entry.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef __IPS_ENTRY_H__
|
||||
#define __IPS_ENTRY_H__
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "table_opaque.h"
|
||||
#include "parsed_context.h"
|
||||
#include "buffer.h"
|
||||
#include "context.h"
|
||||
|
||||
class IPSEntry : public TableOpaqueSerialize<IPSEntry>, public Listener<ParsedContext>
|
||||
{
|
||||
public:
|
||||
IPSEntry();
|
||||
|
||||
void upon(const ParsedContext &) override;
|
||||
ParsedContextReply respond(const ParsedContext &ctx) override;
|
||||
std::string getListenerName() const override { return name(); }
|
||||
|
||||
template <typename T>
|
||||
void serialize(T &, uint32_t) {}
|
||||
static std::string name();
|
||||
static std::unique_ptr<TableOpaqueBase> prototype();
|
||||
static uint currVer();
|
||||
static uint minVer();
|
||||
|
||||
void uponEnteringContext() override { registerListener(); }
|
||||
void uponLeavingContext() override { unregisterListener(); }
|
||||
|
||||
void setFlag(const std::string &flag) { flags.insert(flag); }
|
||||
void unsetFlag(const std::string &flag) { flags.erase(flag); }
|
||||
bool isFlagSet(const std::string &flag) const { return flags.count(flag) != 0; }
|
||||
|
||||
Buffer getBuffer(const std::string &name) const;
|
||||
void setTransactionData(const Buffer &key, const Buffer &value);
|
||||
Maybe<Buffer> getTransactionData(const Buffer &key) const;
|
||||
|
||||
void addPendingContext(const std::string &name, const Buffer &buffer);
|
||||
const std::vector<std::pair<std::string, Buffer>> getPendingContexts() const { return pending_contexts; }
|
||||
void clearPendingContexts() { pending_contexts.clear(); }
|
||||
|
||||
void setDrop() { is_drop = true; }
|
||||
bool isDrop() const { return is_drop; }
|
||||
|
||||
private:
|
||||
std::map<std::string, Buffer> past_contexts;
|
||||
std::set<std::string> flags;
|
||||
Context ctx;
|
||||
std::map<Buffer, Buffer> transaction_data;
|
||||
std::vector<std::pair<std::string, Buffer>> pending_contexts;
|
||||
|
||||
bool is_drop = false;
|
||||
};
|
||||
|
||||
#endif // __IPS_ENTRY_H__
|
26
components/security_apps/ips/include/ips_enums.h
Normal file
26
components/security_apps/ips/include/ips_enums.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef __IPS_ENUMS_H__
|
||||
#define __IPS_ENUMS_H__
|
||||
|
||||
namespace IPSSignatureSubTypes
|
||||
{
|
||||
|
||||
enum class SignatureAction
|
||||
{
|
||||
PREVENT,
|
||||
DETECT,
|
||||
IGNORE
|
||||
};
|
||||
|
||||
enum class IPSLevel
|
||||
{
|
||||
VERY_LOW,
|
||||
LOW,
|
||||
MEDIUM_LOW,
|
||||
MEDIUM,
|
||||
MEDIUM_HIGH,
|
||||
HIGH,
|
||||
CRITICAL
|
||||
};
|
||||
|
||||
} // IPSSignatureSubTypes
|
||||
#endif // __IPS_ENUMS_H__
|
35
components/security_apps/ips/include/ips_metric.h
Normal file
35
components/security_apps/ips/include/ips_metric.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef __IPS_METRIC_H__
|
||||
#define __IPS_METRIC_H__
|
||||
|
||||
#include "ips_signatures.h"
|
||||
#include "generic_metric.h"
|
||||
|
||||
namespace IPSSignatureSubTypes
|
||||
{
|
||||
|
||||
class MatchEvent : public Event<MatchEvent>
|
||||
{
|
||||
public:
|
||||
MatchEvent(const std::shared_ptr<CompleteSignature> &sig, SignatureAction act) : signature(sig), action(act) {}
|
||||
|
||||
const SignatureAction & getAction() const { return action; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<CompleteSignature> signature;
|
||||
SignatureAction action;
|
||||
};
|
||||
|
||||
class IPSMetric : public GenericMetric, public Listener<MatchEvent>
|
||||
{
|
||||
public:
|
||||
void upon(const MatchEvent &event) override;
|
||||
|
||||
private:
|
||||
MetricCalculations::Counter prevented{this, "preventEngineMatchesSample"};
|
||||
MetricCalculations::Counter detected{this, "detectEngineMatchesSample"};
|
||||
MetricCalculations::Counter ignored{this, "ignoreEngineMatchesSample"};
|
||||
};
|
||||
|
||||
} // IPSSignatureSubTypes
|
||||
|
||||
#endif // __IPS_METRIC_H__
|
238
components/security_apps/ips/include/ips_signatures.h
Normal file
238
components/security_apps/ips/include/ips_signatures.h
Normal file
@@ -0,0 +1,238 @@
|
||||
#ifndef __IPS_SIGNATURES_H__
|
||||
#define __IPS_SIGNATURES_H__
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "config.h"
|
||||
#include "parsed_context.h"
|
||||
#include "log_generator.h"
|
||||
#include "pm_hook.h"
|
||||
#include "ips_enums.h"
|
||||
#include "ips_entry.h"
|
||||
#include "i_first_tier_agg.h"
|
||||
|
||||
namespace IPSSignatureSubTypes
|
||||
{
|
||||
using ActionResults = std::tuple<IPSSignatureSubTypes::SignatureAction, std::string, std::vector<std::string>>;
|
||||
|
||||
class BaseSignature
|
||||
{
|
||||
public:
|
||||
enum class MatchType { NO_MATCH, CACHE_MATCH, MATCH };
|
||||
|
||||
virtual const std::string & getSigId() const = 0;
|
||||
virtual MatchType getMatch(const std::set<PMPattern> &matched) const = 0;
|
||||
virtual std::set<PMPattern> patternsInSignature() const = 0;
|
||||
virtual const std::vector<std::string> & getContext() const = 0;
|
||||
};
|
||||
|
||||
class IPSSignatureMetaData
|
||||
{
|
||||
public:
|
||||
void load(cereal::JSONInputArchive &ar);
|
||||
void setIndicators(const std::string &source, const std::string &version);
|
||||
|
||||
const std::string & getId() const { return protection_id; }
|
||||
const std::string & getName() const { return sig_name; }
|
||||
const std::string & getUpdateVersion() const { return update; }
|
||||
const std::string & getLogTitle() const { return event_log; }
|
||||
const std::string & getSource() const { return source; }
|
||||
const std::string & getFeedVersion() const { return version; }
|
||||
const std::vector<std::string> & getCveList() const { return cve_list; }
|
||||
IPSLevel getSeverity() const { return severity; }
|
||||
std::string getSeverityString() const;
|
||||
IPSLevel getConfidence() const { return confidence; }
|
||||
std::string getConfidenceString() const;
|
||||
IPSLevel getPerformance() const { return performance; }
|
||||
std::string getPerformanceString() const;
|
||||
bool isSilent() const { return is_silent; }
|
||||
std::string getIncidentType() const;
|
||||
bool isYearAtLeast(const Maybe<int> &year) const;
|
||||
Maybe<int> getYear() const;
|
||||
|
||||
private:
|
||||
std::string protection_id;
|
||||
std::string sig_name;
|
||||
std::string event_log;
|
||||
std::string update;
|
||||
std::string source;
|
||||
std::string version;
|
||||
std::vector<std::string> cve_list;
|
||||
std::vector<std::string> tag_list;
|
||||
IPSLevel severity;
|
||||
IPSLevel confidence;
|
||||
IPSLevel performance;
|
||||
bool is_silent = false;
|
||||
};
|
||||
|
||||
class CompleteSignature
|
||||
{
|
||||
public:
|
||||
void load(cereal::JSONInputArchive &ar);
|
||||
BaseSignature::MatchType getMatch(const std::set<PMPattern> &matches) const;
|
||||
std::set<PMPattern> patternsInSignature() const;
|
||||
void setIndicators(const std::string &source, const std::string &version);
|
||||
|
||||
const std::vector<std::string> & getContext() const { return rule->getContext(); }
|
||||
const std::string & getId() const { return metadata.getId(); }
|
||||
const std::string & getLogTitle() const { return metadata.getLogTitle(); }
|
||||
const std::string & getName() const { return metadata.getName(); }
|
||||
const std::string & getUpdateVersion() const { return metadata.getUpdateVersion(); }
|
||||
const std::string & getSource() const { return metadata.getSource(); }
|
||||
const std::string & getFeedVersion() const { return metadata.getFeedVersion(); }
|
||||
const std::vector<std::string> & getCveList() const { return metadata.getCveList(); }
|
||||
IPSLevel getSeverity() const { return metadata.getSeverity(); }
|
||||
std::string getSeverityString() const { return metadata.getSeverityString(); }
|
||||
IPSLevel getConfidence() const { return metadata.getConfidence(); }
|
||||
std::string getConfidenceString() const { return metadata.getConfidenceString(); }
|
||||
IPSLevel getPerformance() const { return metadata.getPerformance(); }
|
||||
std::string getPerformanceString() const { return metadata.getPerformanceString(); }
|
||||
bool isSilent() const { return metadata.isSilent(); }
|
||||
std::string getIncidentType() const { return metadata.getIncidentType(); }
|
||||
|
||||
bool isYearAtLeast(const Maybe<int> &year) const { return metadata.isYearAtLeast(year); }
|
||||
Maybe<int> getYear() const { return metadata.getYear(); }
|
||||
|
||||
private:
|
||||
IPSSignatureMetaData metadata;
|
||||
std::shared_ptr<BaseSignature> rule;
|
||||
};
|
||||
|
||||
class SignatureAndAction
|
||||
{
|
||||
public:
|
||||
SignatureAndAction(std::shared_ptr<CompleteSignature> _signature, SignatureAction _action)
|
||||
:
|
||||
signature(_signature),
|
||||
action(_action)
|
||||
{
|
||||
}
|
||||
|
||||
bool isMatchedPrevent(const Buffer &context_buffer, const std::set<PMPattern> &pattern) const;
|
||||
bool matchSilent(const Buffer &context_buffer) const;
|
||||
std::set<PMPattern> patternsInSignature() const { return signature->patternsInSignature(); }
|
||||
const std::vector<std::string> & getContext() const { return signature->getContext(); }
|
||||
|
||||
private:
|
||||
ActionResults getAction(const IPSEntry &ips_state) const;
|
||||
std::shared_ptr<CompleteSignature> signature;
|
||||
SignatureAction action;
|
||||
};
|
||||
} // IPSSignatureSubTypes
|
||||
|
||||
class IPSSignaturesPerContext : public Singleton::Consume<I_FirstTierAgg>
|
||||
{
|
||||
public:
|
||||
void addSignature(const IPSSignatureSubTypes::SignatureAndAction &sig);
|
||||
bool isMatchedPrevent(const Buffer &context_buffer) const;
|
||||
void calcFirstTier(const std::string &ctx_name);
|
||||
|
||||
private:
|
||||
std::set<PMPattern> getFirstTierMatches(const Buffer &buffer) const;
|
||||
|
||||
std::map<PMPattern, std::vector<IPSSignatureSubTypes::SignatureAndAction>> signatures_per_lss;
|
||||
std::vector<IPSSignatureSubTypes::SignatureAndAction> signatures_without_lss;
|
||||
std::shared_ptr<PMHook> first_tier;
|
||||
};
|
||||
|
||||
class IPSSignaturesResource
|
||||
{
|
||||
public:
|
||||
void load(cereal::JSONInputArchive &ar);
|
||||
|
||||
const std::vector<std::shared_ptr<IPSSignatureSubTypes::CompleteSignature>> &
|
||||
getSignatures() const
|
||||
{
|
||||
return all_signatures;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<IPSSignatureSubTypes::CompleteSignature>> all_signatures;
|
||||
};
|
||||
|
||||
class SnortSignaturesResourceFile
|
||||
{
|
||||
public:
|
||||
void load(cereal::JSONInputArchive &ar);
|
||||
bool isFile(const std::string &file_name) const { return file_name == name; }
|
||||
const std::vector<std::shared_ptr<IPSSignatureSubTypes::CompleteSignature>> &
|
||||
getSignatures() const
|
||||
{
|
||||
return all_signatures;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
std::vector<std::shared_ptr<IPSSignatureSubTypes::CompleteSignature>> all_signatures;
|
||||
};
|
||||
|
||||
class SnortSignaturesResource
|
||||
{
|
||||
public:
|
||||
void load(cereal::JSONInputArchive &ar);
|
||||
|
||||
const std::vector<std::shared_ptr<IPSSignatureSubTypes::CompleteSignature>> &
|
||||
getSignatures(const std::string &file_name) const
|
||||
{
|
||||
for (auto &file : files) {
|
||||
if (file.isFile(file_name)) return file.getSignatures();
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<IPSSignatureSubTypes::CompleteSignature>> empty;
|
||||
std::vector<SnortSignaturesResourceFile> files;
|
||||
};
|
||||
|
||||
class IPSSignatures
|
||||
{
|
||||
std::set<PMPattern> getFirstTier(const ParsedContext &context);
|
||||
|
||||
public:
|
||||
void load(cereal::JSONInputArchive &ar);
|
||||
bool isMatchedPrevent(const std::string &context_name, const Buffer &context_buffer) const;
|
||||
bool isEmpty() const { return signatures_per_context.empty(); }
|
||||
bool isEmpty(const std::string &context) const;
|
||||
|
||||
const std::string & getAsset() const { return asset_name; }
|
||||
const std::string & getAssetId() const { return asset_id; }
|
||||
const std::string & getPractice() const { return practice_name; }
|
||||
const std::string & getPracticeId() const { return practice_id; }
|
||||
const std::string & getSourceIdentifier() const { return source_id; }
|
||||
|
||||
private:
|
||||
std::map<std::string, IPSSignaturesPerContext> signatures_per_context;
|
||||
std::string asset_name;
|
||||
std::string asset_id;
|
||||
std::string practice_name;
|
||||
std::string practice_id;
|
||||
std::string source_id;
|
||||
};
|
||||
|
||||
class SnortSignatures
|
||||
{
|
||||
std::set<PMPattern> getFirstTier(const ParsedContext &context);
|
||||
|
||||
public:
|
||||
void load(cereal::JSONInputArchive &ar);
|
||||
bool isMatchedPrevent(const std::string &context_name, const Buffer &context_buffer) const;
|
||||
bool isEmpty() const { return signatures_per_context.empty(); }
|
||||
bool isEmpty(const std::string &context) const;
|
||||
|
||||
const std::string & getAsset() const { return asset_name; }
|
||||
const std::string & getAssetId() const { return asset_id; }
|
||||
const std::string & getPractice() const { return practice_name; }
|
||||
const std::string & getPracticeId() const { return practice_id; }
|
||||
const std::string & getSourceIdentifier() const { return source_id; }
|
||||
|
||||
private:
|
||||
std::map<std::string, IPSSignaturesPerContext> signatures_per_context;
|
||||
std::string asset_name;
|
||||
std::string asset_id;
|
||||
std::string practice_name;
|
||||
std::string practice_id;
|
||||
std::string source_id;
|
||||
};
|
||||
|
||||
#endif // __IPS_SIGNATURES_H__
|
30
components/security_apps/ips/include/rule_detection.h
Normal file
30
components/security_apps/ips/include/rule_detection.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef __RULE_DETECTION_H__
|
||||
#define __RULE_DETECTION_H__
|
||||
|
||||
#include "simple_protection.h"
|
||||
#include "compound_protection.h"
|
||||
|
||||
class RuleDetection
|
||||
{
|
||||
public:
|
||||
RuleDetection(const std::string &_sig_name) : sig_name(_sig_name) {}
|
||||
|
||||
template <typename T>
|
||||
void serialize(T &ar)
|
||||
{
|
||||
std::string type;
|
||||
ar(cereal::make_nvp("type", type));
|
||||
|
||||
if (type == "simple") rule = SimpleProtection::get(sig_name, ar);
|
||||
else if (type == "compound") rule = CompoundProtection::get(sig_name, ar);
|
||||
else reportConfigurationError("Unknown rule type: " + type);
|
||||
};
|
||||
|
||||
std::shared_ptr<IPSSignatureSubTypes::BaseSignature> getRule() { return rule; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<IPSSignatureSubTypes::BaseSignature> rule;
|
||||
std::string sig_name;
|
||||
};
|
||||
|
||||
#endif // __RULE_DETECTION_H__
|
49
components/security_apps/ips/include/simple_protection.h
Normal file
49
components/security_apps/ips/include/simple_protection.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef __SIMPLE_PROTECTION_H__
|
||||
#define __SIMPLE_PROTECTION_H__
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ips_signatures.h"
|
||||
#include "i_keywords_rule.h"
|
||||
|
||||
class SimpleProtection
|
||||
{
|
||||
class Impl : public IPSSignatureSubTypes::BaseSignature
|
||||
{
|
||||
public:
|
||||
Impl(
|
||||
const std::string &sig_name,
|
||||
const std::string &ssm,
|
||||
const std::string &keyword,
|
||||
const std::vector<std::string> &context
|
||||
);
|
||||
|
||||
const std::string & getSigId() const override { return sig_name; }
|
||||
MatchType getMatch(const std::set<PMPattern> &matched) const override;
|
||||
std::set<PMPattern> patternsInSignature() const override;
|
||||
const std::vector<std::string> & getContext() const override { return context; }
|
||||
|
||||
private:
|
||||
std::string sig_name;
|
||||
std::vector<std::string> context;
|
||||
std::shared_ptr<I_KeywordsRule::VirtualRule> rule;
|
||||
PMPattern pattern;
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename Archive>
|
||||
static std::shared_ptr<IPSSignatureSubTypes::BaseSignature> get(const std::string &sig_name, Archive &ar)
|
||||
{
|
||||
std::string ssm, keyword;
|
||||
std::vector<std::string> context;
|
||||
|
||||
ar(
|
||||
cereal::make_nvp("SSM", ssm),
|
||||
cereal::make_nvp("keywords", keyword),
|
||||
cereal::make_nvp("context", context)
|
||||
);
|
||||
|
||||
return std::make_shared<Impl>(sig_name, ssm, keyword, context);
|
||||
}
|
||||
};
|
||||
#endif // __SIMPLE_PROTECTION_H__
|
22
components/security_apps/ips/include/snort_basic_policy.h
Normal file
22
components/security_apps/ips/include/snort_basic_policy.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef __SNORT_BASIC_POLICY_H__
|
||||
#define __SNORT_BASIC_POLICY_H__
|
||||
|
||||
#include <cereal/archives/json.hpp>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "ips_enums.h"
|
||||
#include "ips_signatures.h"
|
||||
|
||||
class SnortRuleSelector
|
||||
{
|
||||
public:
|
||||
std::vector<IPSSignatureSubTypes::SignatureAndAction> selectSignatures() const;
|
||||
void load(cereal::JSONInputArchive &ar);
|
||||
|
||||
private:
|
||||
IPSSignatureSubTypes::SignatureAction action = IPSSignatureSubTypes::SignatureAction::IGNORE;
|
||||
std::vector<std::string> file_names;
|
||||
};
|
||||
|
||||
#endif // __SNORT_BASIC_POLICY_H__
|
254
components/security_apps/ips/ips_basic_policy.cc
Normal file
254
components/security_apps/ips/ips_basic_policy.cc
Normal file
@@ -0,0 +1,254 @@
|
||||
#include "ips_basic_policy.h"
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <iomanip>
|
||||
#include "ips_signatures.h"
|
||||
#include "helper.h"
|
||||
#include "config.h"
|
||||
#include "common.h"
|
||||
|
||||
USE_DEBUG_FLAG(D_IPS);
|
||||
|
||||
using namespace std;
|
||||
|
||||
void
|
||||
RuleSelector::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
readRules(ar);
|
||||
readDefaultAction(ar);
|
||||
}
|
||||
|
||||
vector<IPSSignatureSubTypes::SignatureAndAction>
|
||||
RuleSelector::selectSignatures() const
|
||||
{
|
||||
vector<IPSSignatureSubTypes::SignatureAndAction> res;
|
||||
|
||||
auto all_signatures = getResource<IPSSignaturesResource>("IPS", "protections");
|
||||
if (!all_signatures.ok()) return res;
|
||||
auto signatures_version = getResourceWithDefault<string>("", "IPS", "VersionId");
|
||||
|
||||
for (auto &signature : (*all_signatures).getSignatures()) {
|
||||
for (auto &rule : rules) {
|
||||
if (rule.isSignaturedMatched(*signature)) {
|
||||
if (rule.getAction() != IPSSignatureSubTypes::SignatureAction::IGNORE) {
|
||||
signature->setIndicators("Check Point", signatures_version);
|
||||
res.emplace_back(signature, rule.getAction());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::readRules(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
ar(cereal::make_nvp("rules", rules));
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::readDefaultAction(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
Rule rule;
|
||||
rule.readAction(ar, "defaultAction");
|
||||
rules.push_back(rule);
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::serialize(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
readAction(ar, "action");
|
||||
readPerformanceImpact(ar);
|
||||
readSeverityLevel(ar);
|
||||
readConfidenceLevel(ar);
|
||||
readServerProtections(ar);
|
||||
readClientProtections(ar);
|
||||
readProtectionsFromYear(ar);
|
||||
readProtectionTags(ar);
|
||||
readProtectionIds(ar);
|
||||
}
|
||||
|
||||
bool
|
||||
RuleSelector::Rule::isSignaturedMatched(const IPSSignatureSubTypes::CompleteSignature &signature) const
|
||||
{
|
||||
if (confidence_level.ok() && signature.getConfidence() != confidence_level.unpack()) return false;
|
||||
if (severity_level.ok() && signature.getSeverity() < severity_level.unpack()) return false;
|
||||
if (performance_impact.ok() && signature.getPerformance() > performance_impact.unpack()) return false;
|
||||
if (!signature.isYearAtLeast(protections_from_year)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static ostream &
|
||||
operator<<(ostream &os, const RuleSelector::Rule & rule)
|
||||
{
|
||||
rule.print(os);
|
||||
return os;
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::print(ostream &os) const
|
||||
{
|
||||
os << "[Rule] " << "action: " << static_cast<int>(action);
|
||||
if (performance_impact.ok()) {
|
||||
os << " performanceImpact: " << static_cast<int>(performance_impact.unpack());
|
||||
}
|
||||
if (severity_level.ok()) {
|
||||
os << " severityLevel: " << static_cast<int>(severity_level.unpack());
|
||||
}
|
||||
if (confidence_level.ok()) {
|
||||
os << " confidenceLevel: " << static_cast<int>(confidence_level.unpack());
|
||||
}
|
||||
if (server_protections.ok()) {
|
||||
os << boolalpha << " serverProtections: " << server_protections.unpack();
|
||||
}
|
||||
if (client_protections.ok()) {
|
||||
os << boolalpha << " clientProtections: " << client_protections.unpack();
|
||||
}
|
||||
if (protections_from_year.ok()) {
|
||||
os << " protectionsFromYear: " << protections_from_year.unpack();
|
||||
}
|
||||
if (protection_ids.ok()) {
|
||||
os << " protectionIds: " << makeSeparatedStr(protection_ids.unpack(), ", ");
|
||||
}
|
||||
if (protection_tags.ok()) {
|
||||
os << " protectionTags: " << makeSeparatedStr(protection_tags.unpack(), ", ");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::readAction(cereal::JSONInputArchive &ar, const string &action_type)
|
||||
{
|
||||
string str;
|
||||
ar(cereal::make_nvp(action_type, str));
|
||||
|
||||
if (str == "Inactive") action = IPSSignatureSubTypes::SignatureAction::IGNORE;
|
||||
else if (str == "Detect") action = IPSSignatureSubTypes::SignatureAction::DETECT;
|
||||
else if (str == "Prevent") action = IPSSignatureSubTypes::SignatureAction::PREVENT;
|
||||
else reportConfigurationError("invalid action value " + str);
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::readPerformanceImpact(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
try {
|
||||
string str;
|
||||
ar(cereal::make_nvp("performanceImpact", str));
|
||||
|
||||
if (str == "Very low") performance_impact = IPSSignatureSubTypes::IPSLevel::VERY_LOW;
|
||||
else if (str == "Low or lower") performance_impact = IPSSignatureSubTypes::IPSLevel::LOW;
|
||||
else if (str == "Medium or lower") performance_impact = IPSSignatureSubTypes::IPSLevel::MEDIUM;
|
||||
else if (str == "High or lower") performance_impact = IPSSignatureSubTypes::IPSLevel::HIGH;
|
||||
else reportConfigurationError("invalid performanceImpact value " + str);
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::readSeverityLevel(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
try {
|
||||
string str;
|
||||
ar(cereal::make_nvp("severityLevel", str));
|
||||
|
||||
if (str == "Critical") severity_level = IPSSignatureSubTypes::IPSLevel::CRITICAL;
|
||||
else if (str == "High or above") severity_level = IPSSignatureSubTypes::IPSLevel::HIGH;
|
||||
else if (str == "Medium or above") severity_level = IPSSignatureSubTypes::IPSLevel::MEDIUM;
|
||||
else if (str == "Low or above") severity_level = IPSSignatureSubTypes::IPSLevel::LOW;
|
||||
else reportConfigurationError("invalid severityLevel value " + str);
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::readConfidenceLevel(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
try {
|
||||
string str;
|
||||
ar(cereal::make_nvp("confidenceLevel", str));
|
||||
|
||||
if (str == "Low") confidence_level = IPSSignatureSubTypes::IPSLevel::LOW;
|
||||
else if (str == "Medium") confidence_level = IPSSignatureSubTypes::IPSLevel::MEDIUM;
|
||||
else if (str == "High") confidence_level = IPSSignatureSubTypes::IPSLevel::HIGH;
|
||||
else reportConfigurationError("invalid confidenceLevel value " + str);
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::readServerProtections(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
try {
|
||||
bool _server_protections;
|
||||
ar(cereal::make_nvp("serverProtections", _server_protections));
|
||||
server_protections = _server_protections;
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::readClientProtections(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
try {
|
||||
bool _client_protections;
|
||||
ar(cereal::make_nvp("clientProtections", _client_protections));
|
||||
client_protections = _client_protections;
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::readProtectionsFromYear(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
try {
|
||||
int year;
|
||||
ar(cereal::make_nvp("protectionsFromYear", year));
|
||||
if (year < 1999 || year > 2021) {
|
||||
reportConfigurationError("invalid protectionsFromYear value " + to_string(year));
|
||||
}
|
||||
else {
|
||||
protections_from_year = year;
|
||||
}
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::readProtectionTags(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
try {
|
||||
vector<string> _protection_tags;
|
||||
ar(cereal::make_nvp("protectionTags", _protection_tags));
|
||||
protection_tags = _protection_tags;
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::Rule::readProtectionIds(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
try {
|
||||
vector<string> _protection_ids;
|
||||
ar(cereal::make_nvp("protectionIds", _protection_ids));
|
||||
protection_ids = _protection_ids;
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RuleSelector::print(ostream &os) const
|
||||
{
|
||||
os << makeSeparatedStr(rules, ";");
|
||||
}
|
4
components/security_apps/ips/ips_common_types.cc
Normal file
4
components/security_apps/ips/ips_common_types.cc
Normal file
@@ -0,0 +1,4 @@
|
||||
#include "ips_common_types.h"
|
||||
|
||||
const Buffer IPSCommonTypes::requests_header_for_log =
|
||||
Buffer("agent-found-Requests-header-for-log", 35, Buffer::MemoryType::STATIC);
|
392
components/security_apps/ips/ips_comp.cc
Normal file
392
components/security_apps/ips/ips_comp.cc
Normal file
@@ -0,0 +1,392 @@
|
||||
#include "ips_comp.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#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<I_FirstTierAgg>::SelfInterface,
|
||||
public Listener<NewTableEntry>,
|
||||
public Listener<NewHttpTransactionEvent>,
|
||||
public Listener<HttpRequestHeaderEvent>,
|
||||
public Listener<HttpRequestBodyEvent>,
|
||||
public Listener<EndRequestEvent>,
|
||||
public Listener<ResponseCodeEvent>,
|
||||
public Listener<HttpResponseHeaderEvent>,
|
||||
public Listener<HttpResponseBodyEvent>,
|
||||
public Listener<EndTransactionEvent>
|
||||
{
|
||||
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<PMHook> &
|
||||
getHook(const set<PMPattern> &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<PMPattern> pats;
|
||||
shared_ptr<PMHook> hook = make_shared<PMHook>();
|
||||
};
|
||||
|
||||
public:
|
||||
void
|
||||
preload()
|
||||
{
|
||||
function<void()> 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<I_Table>::by<IPSComp>();
|
||||
env = Singleton::Consume<I_Environment>::by<IPSComp>();
|
||||
}
|
||||
|
||||
void
|
||||
fini()
|
||||
{
|
||||
unregisterListener();
|
||||
}
|
||||
|
||||
void
|
||||
upon(const NewTableEntry &) override
|
||||
{
|
||||
if (isSignatureListsEmpty()) return;
|
||||
auto table = Singleton::Consume<I_Table>::by<IPSComp>();
|
||||
table->createState<IPSEntry>();
|
||||
table->getState<IPSEntry>().uponEnteringContext();
|
||||
}
|
||||
|
||||
string getListenerName() const override { return "ips application"; }
|
||||
|
||||
EventVerdict
|
||||
respond(const NewHttpTransactionEvent &event) override
|
||||
{
|
||||
if (isSignatureListsEmpty()) return ACCEPT;
|
||||
table->createState<IPSEntry>();
|
||||
auto &ips_state = table->getState<IPSEntry>();
|
||||
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<HexDecoder<'%'>>(event.getURI());
|
||||
vector<u_char> decoded_url(decoder.begin(), decoder.end());
|
||||
auto start = find(decoded_url.begin(), decoded_url.end(), static_cast<u_char>('?'));
|
||||
|
||||
if (start != decoded_url.end()) {
|
||||
vector<u_char> query(start + 1, decoded_url.end());
|
||||
ips_state.addPendingContext("HTTP_QUERY_DECODED", Buffer(move(query)));
|
||||
}
|
||||
vector<u_char> 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<IPSEntry>()) return ACCEPT;
|
||||
|
||||
auto &ips_state = table->getState<IPSEntry>();
|
||||
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<uint>(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<IPSEntry>()) return ACCEPT;
|
||||
|
||||
auto &ips_state = table->getState<IPSEntry>();
|
||||
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<IPSEntry>()) return ACCEPT;
|
||||
|
||||
auto &ips_state = table->getState<IPSEntry>();
|
||||
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<IPSEntry>()) return ACCEPT;
|
||||
|
||||
auto &ips_state = table->getState<IPSEntry>();
|
||||
ips_state.uponEnteringContext();
|
||||
auto leave_context = make_scope_exit([&ips_state] () { ips_state.uponLeavingContext(); });
|
||||
|
||||
Buffer buf(reinterpret_cast<const char *>(&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<IPSEntry>()) return ACCEPT;
|
||||
|
||||
auto &ips_state = table->getState<IPSEntry>();
|
||||
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<IPSEntry>()) return ACCEPT;
|
||||
|
||||
auto &ips_state = table->getState<IPSEntry>();
|
||||
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<IPSEntry>();
|
||||
ips_state.setTransactionData(name, value);
|
||||
}
|
||||
|
||||
shared_ptr<PMHook>
|
||||
getHook(const string &context_name, const set<PMPattern> &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<string, SigsFirstTierAgg> tier_aggs;
|
||||
};
|
||||
|
||||
IPSComp::IPSComp() : Component("IPSComp"), pimpl(make_unique<Impl>()) {}
|
||||
|
||||
IPSComp::~IPSComp() {}
|
||||
|
||||
void
|
||||
IPSComp::preload()
|
||||
{
|
||||
registerExpectedResource<IPSSignaturesResource>("IPS", "protections");
|
||||
registerExpectedResource<string>("IPS", "VersionId");
|
||||
registerExpectedResource<SnortSignaturesResource>("IPSSnortSigs", "protections");
|
||||
registerExpectedConfiguration<IPSConfiguration>("IPS", "IpsConfigurations");
|
||||
registerExpectedConfiguration<uint>("IPS", "Max Field Size");
|
||||
registerExpectedConfiguration<IPSSignatures>("IPS", "IpsProtections");
|
||||
registerExpectedConfiguration<SnortSignatures>("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();
|
||||
}
|
74
components/security_apps/ips/ips_configuration.cc
Normal file
74
components/security_apps/ips/ips_configuration.cc
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "ips_configuration.h"
|
||||
#include "debug.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
IPSConfiguration::Context::Context(ContextType _type, uint history) : type(_type), history_size(history) {}
|
||||
|
||||
uint
|
||||
IPSConfiguration::Context::getHistorySize() const
|
||||
{
|
||||
dbgAssert(type == ContextType::HISTORY) << "Try to access history size for non-history context";
|
||||
return history_size;
|
||||
}
|
||||
|
||||
static const map<string, IPSConfiguration::ContextType> type_convertor = {
|
||||
{ "normal", IPSConfiguration::ContextType::NORMAL },
|
||||
{ "keep", IPSConfiguration::ContextType::KEEP },
|
||||
{ "history", IPSConfiguration::ContextType::HISTORY }
|
||||
};
|
||||
|
||||
class ContextConfigurationJSON
|
||||
{
|
||||
public:
|
||||
void
|
||||
load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
string type_name;
|
||||
ar(
|
||||
cereal::make_nvp("name", name),
|
||||
cereal::make_nvp("type", type_name)
|
||||
);
|
||||
|
||||
auto type_pointer = type_convertor.find(type_name);
|
||||
if (type_pointer == type_convertor.end()) reportConfigurationError("Unknown IPS context type: " + type_name);
|
||||
type = type_pointer->second;
|
||||
|
||||
if (type == IPSConfiguration::ContextType::HISTORY) ar(cereal::make_nvp("historySize", size));
|
||||
}
|
||||
|
||||
string getName() const { return name; }
|
||||
IPSConfiguration::Context getContext() const { return IPSConfiguration::Context(type, size); }
|
||||
|
||||
private:
|
||||
string name;
|
||||
IPSConfiguration::ContextType type;
|
||||
uint size = 0;
|
||||
};
|
||||
|
||||
void
|
||||
IPSConfiguration::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
vector<ContextConfigurationJSON> config;
|
||||
ar(cereal::make_nvp("contextsConfiguration", config));
|
||||
|
||||
for (auto &context : config) {
|
||||
context_config.emplace(context.getName(), context.getContext());
|
||||
}
|
||||
}
|
||||
|
||||
IPSConfiguration::Context
|
||||
IPSConfiguration::getContext(const string &name) const
|
||||
{
|
||||
auto context = context_config.find(name);
|
||||
if (context == context_config.end()) return IPSConfiguration::Context();
|
||||
return context->second;
|
||||
}
|
||||
|
||||
uint
|
||||
IPSConfiguration::getHistorySize(const string &name) const
|
||||
{
|
||||
auto context = context_config.find(name);
|
||||
dbgAssert(context != context_config.end()) << "Try to access history size for non-exiting context";
|
||||
return context->second.getHistorySize();
|
||||
}
|
143
components/security_apps/ips/ips_entry.cc
Normal file
143
components/security_apps/ips/ips_entry.cc
Normal file
@@ -0,0 +1,143 @@
|
||||
#include "ips_entry.h"
|
||||
#include "ips_signatures.h"
|
||||
#include "ips_configuration.h"
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
#include "common.h"
|
||||
#include "i_keywords_rule.h"
|
||||
#include "helper.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace IPSHelper;
|
||||
|
||||
USE_DEBUG_FLAG(D_IPS);
|
||||
|
||||
static const map<string, IPSConfiguration::Context> default_conf_mapping = {
|
||||
{ "HTTP_METHOD", IPSConfiguration::Context(IPSConfiguration::ContextType::KEEP, 0) },
|
||||
{ "HTTP_COMPLETE_URL_DECODED", IPSConfiguration::Context(IPSConfiguration::ContextType::KEEP, 0) },
|
||||
{ "HTTP_PATH_DECODED", IPSConfiguration::Context(IPSConfiguration::ContextType::KEEP, 0) },
|
||||
{ "HTTP_QUERY_DECODED", IPSConfiguration::Context(IPSConfiguration::ContextType::KEEP, 0) },
|
||||
{ "HTTP_PROTOCOL", IPSConfiguration::Context(IPSConfiguration::ContextType::KEEP, 0) },
|
||||
{ "HTTP_REQUEST_HEADER", IPSConfiguration::Context(IPSConfiguration::ContextType::KEEP, 0) },
|
||||
{ "HTTP_REQUEST_BODY", IPSConfiguration::Context(IPSConfiguration::ContextType::HISTORY, 1000) },
|
||||
{ "HTTP_RESPONSE_CODE", IPSConfiguration::Context(IPSConfiguration::ContextType::KEEP, 0) },
|
||||
{ "HTTP_RESPONSE_HEADER", IPSConfiguration::Context(IPSConfiguration::ContextType::KEEP, 0) },
|
||||
{ "HTTP_RESPONSE_BODY", IPSConfiguration::Context(IPSConfiguration::ContextType::HISTORY, 1000) }
|
||||
};
|
||||
|
||||
static const IPSConfiguration default_conf(default_conf_mapping);
|
||||
|
||||
IPSEntry::IPSEntry() : TableOpaqueSerialize<IPSEntry>(this) {}
|
||||
|
||||
void
|
||||
IPSEntry::upon(const ParsedContext &)
|
||||
{
|
||||
}
|
||||
|
||||
ParsedContextReply
|
||||
IPSEntry::respond(const ParsedContext &parsed)
|
||||
{
|
||||
const auto &name = parsed.getName();
|
||||
auto buf = parsed.getBuffer();
|
||||
|
||||
dbgDebug(D_IPS) << "Entrying context " << name;
|
||||
dbgTrace(D_IPS) << "Context Content " << dumpHex(buf);
|
||||
|
||||
auto config = getConfigurationWithDefault(default_conf, "IPS", "IpsConfigurations").getContext(name);
|
||||
if (config.getType() == IPSConfiguration::ContextType::HISTORY) {
|
||||
buf = past_contexts[name] + buf;
|
||||
}
|
||||
ctx.registerValue(I_KeywordsRule::getKeywordsRuleTag(), name);
|
||||
ctx.registerValue(name, buf);
|
||||
|
||||
ctx.activate();
|
||||
auto &signatures = getConfigurationWithDefault(IPSSignatures(), "IPS", "IpsProtections");
|
||||
bool should_drop = signatures.isMatchedPrevent(parsed.getName(), buf);
|
||||
auto &snort_signatures = getConfigurationWithDefault(SnortSignatures(), "IPSSnortSigs", "SnortProtections");
|
||||
should_drop |= snort_signatures.isMatchedPrevent(parsed.getName(), buf);
|
||||
ctx.deactivate();
|
||||
|
||||
switch(config.getType()) {
|
||||
case IPSConfiguration::ContextType::NORMAL: {
|
||||
ctx.unregisterKey<Buffer>(name);
|
||||
break;
|
||||
}
|
||||
case IPSConfiguration::ContextType::KEEP: {
|
||||
past_contexts[name] += buf;
|
||||
ctx.registerValue(name, past_contexts[name]);
|
||||
break;
|
||||
}
|
||||
case IPSConfiguration::ContextType::HISTORY: {
|
||||
if (buf.size() > config.getHistorySize()) buf.keepTail(config.getHistorySize());
|
||||
ctx.registerValue(name, buf);
|
||||
past_contexts[name] = buf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dbgDebug(D_IPS) << "Return " << (should_drop ? "drop" : "continue");
|
||||
|
||||
return should_drop ? ParsedContextReply::DROP : ParsedContextReply::ACCEPT;
|
||||
}
|
||||
|
||||
Buffer
|
||||
IPSEntry::getBuffer(const string &name) const
|
||||
{
|
||||
auto elem = past_contexts.find(name);
|
||||
if (elem != past_contexts.end()) return elem->second;
|
||||
|
||||
for (auto &p : pending_contexts) {
|
||||
if (p.first == name) return p.second;
|
||||
}
|
||||
|
||||
return Buffer();
|
||||
}
|
||||
|
||||
void
|
||||
IPSEntry::setTransactionData(const Buffer &key, const Buffer &value)
|
||||
{
|
||||
transaction_data[key] = value;
|
||||
}
|
||||
|
||||
Maybe<Buffer>
|
||||
IPSEntry::getTransactionData(const Buffer &key) const
|
||||
{
|
||||
map<Buffer, Buffer>::const_iterator iter = transaction_data.find(key);
|
||||
|
||||
if (iter == transaction_data.end()) {
|
||||
return genError("Http header value not found");
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
|
||||
}
|
||||
|
||||
string
|
||||
IPSEntry::name()
|
||||
{
|
||||
return "IPS";
|
||||
}
|
||||
|
||||
unique_ptr<TableOpaqueBase>
|
||||
IPSEntry::prototype()
|
||||
{
|
||||
return make_unique<IPSEntry>();
|
||||
}
|
||||
|
||||
uint
|
||||
IPSEntry::currVer()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint
|
||||
IPSEntry::minVer()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
IPSEntry::addPendingContext(const std::string &name, const Buffer &buffer)
|
||||
{
|
||||
pending_contexts.emplace_back(name, buffer);
|
||||
}
|
20
components/security_apps/ips/ips_metric.cc
Normal file
20
components/security_apps/ips/ips_metric.cc
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "ips_metric.h"
|
||||
|
||||
void
|
||||
IPSSignatureSubTypes::IPSMetric::upon(const MatchEvent &event)
|
||||
{
|
||||
switch (event.getAction()) {
|
||||
case SignatureAction::PREVENT: {
|
||||
prevented.report(1);
|
||||
break;
|
||||
}
|
||||
case SignatureAction::DETECT: {
|
||||
detected.report(1);
|
||||
break;
|
||||
}
|
||||
case SignatureAction::IGNORE: {
|
||||
ignored.report(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
809
components/security_apps/ips/ips_signatures.cc
Normal file
809
components/security_apps/ips/ips_signatures.cc
Normal file
@@ -0,0 +1,809 @@
|
||||
#include "ips_signatures.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ips_comp.h"
|
||||
#include "ips_basic_policy.h"
|
||||
#include "snort_basic_policy.h"
|
||||
#include "generic_rulebase/parameters_config.h"
|
||||
#include "generic_rulebase/triggers_config.h"
|
||||
#include "rule_detection.h"
|
||||
#include "helper.h"
|
||||
#include "config.h"
|
||||
#include "context.h"
|
||||
#include "ips_entry.h"
|
||||
#include "ips_metric.h"
|
||||
#include "ips_common_types.h"
|
||||
|
||||
USE_DEBUG_FLAG(D_IPS);
|
||||
|
||||
using namespace IPSSignatureSubTypes;
|
||||
using namespace ReportIS;
|
||||
using namespace std;
|
||||
using MatchType = BaseSignature::MatchType;
|
||||
|
||||
static const map<IPSLevel, Severity> severities = {
|
||||
{ IPSLevel::CRITICAL, Severity::CRITICAL },
|
||||
{ IPSLevel::HIGH, Severity::HIGH },
|
||||
{ IPSLevel::MEDIUM_HIGH, Severity::HIGH },
|
||||
{ IPSLevel::MEDIUM, Severity::MEDIUM },
|
||||
{ IPSLevel::MEDIUM_LOW, Severity::LOW },
|
||||
{ IPSLevel::LOW, Severity::LOW },
|
||||
{ IPSLevel::VERY_LOW, Severity::INFO }
|
||||
};
|
||||
|
||||
static const map<string, IPSLevel> levels = {
|
||||
{ "Critical", IPSLevel::CRITICAL },
|
||||
{ "High", IPSLevel::HIGH },
|
||||
{ "Medium High", IPSLevel::MEDIUM_HIGH },
|
||||
{ "Medium", IPSLevel::MEDIUM },
|
||||
{ "Medium Low", IPSLevel::MEDIUM_LOW },
|
||||
{ "Low", IPSLevel::LOW },
|
||||
{ "Very Low", IPSLevel::VERY_LOW }
|
||||
};
|
||||
|
||||
static IPSLevel
|
||||
getLevel(const string &level_string, const string &attr_name)
|
||||
{
|
||||
auto index = levels.find(level_string);
|
||||
if (index == levels.end()) {
|
||||
reportConfigurationError(
|
||||
"Unknown level: '" + level_string + "' in attribute " + attr_name
|
||||
);
|
||||
}
|
||||
return index->second;
|
||||
}
|
||||
|
||||
void
|
||||
IPSSignatureMetaData::setIndicators(const string &_source, const string &_version)
|
||||
{
|
||||
source = _source;
|
||||
version = _version;
|
||||
}
|
||||
|
||||
string
|
||||
IPSSignatureMetaData::getSeverityString() const
|
||||
{
|
||||
switch (severity) {
|
||||
case IPSLevel::VERY_LOW:
|
||||
return "Very Low";
|
||||
case IPSLevel::LOW:
|
||||
return "Low";
|
||||
case IPSLevel::MEDIUM_LOW:
|
||||
return "Medium Low";
|
||||
case IPSLevel::MEDIUM:
|
||||
return "Medium";
|
||||
case IPSLevel::MEDIUM_HIGH:
|
||||
return "Medium High";
|
||||
case IPSLevel::HIGH:
|
||||
return "High";
|
||||
case IPSLevel::CRITICAL:
|
||||
return "Critical";
|
||||
}
|
||||
|
||||
dbgAssert(false) << "Illegal severity value: " << static_cast<uint>(severity);
|
||||
return "Critical";
|
||||
}
|
||||
|
||||
string
|
||||
IPSSignatureMetaData::getConfidenceString() const
|
||||
{
|
||||
if (confidence <= IPSLevel::LOW) return "Low";
|
||||
if (confidence >= IPSLevel::HIGH) return "High";
|
||||
return "Medium";
|
||||
}
|
||||
|
||||
string
|
||||
IPSSignatureMetaData::getPerformanceString() const
|
||||
{
|
||||
switch (performance) {
|
||||
case IPSLevel::VERY_LOW:
|
||||
return "Very Low";
|
||||
case IPSLevel::LOW:
|
||||
return "Low";
|
||||
case IPSLevel::MEDIUM_LOW:
|
||||
return "Medium Low";
|
||||
case IPSLevel::MEDIUM:
|
||||
return "Medium";
|
||||
case IPSLevel::MEDIUM_HIGH:
|
||||
return "Medium High";
|
||||
case IPSLevel::HIGH:
|
||||
return "High";
|
||||
case IPSLevel::CRITICAL:
|
||||
return "Critical";
|
||||
}
|
||||
|
||||
dbgAssert(false) << "Illegal performance value: " << static_cast<uint>(performance);
|
||||
return "Critical";
|
||||
}
|
||||
|
||||
void
|
||||
IPSSignatureMetaData::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
string severity_string, confidence_string, performance_string;
|
||||
ar(
|
||||
cereal::make_nvp("maintrainId", protection_id),
|
||||
cereal::make_nvp("protectionName", sig_name),
|
||||
cereal::make_nvp("severity", severity_string),
|
||||
cereal::make_nvp("lastUpdate", update),
|
||||
cereal::make_nvp("confidenceLevel", confidence_string),
|
||||
cereal::make_nvp("performanceImpact", performance_string),
|
||||
cereal::make_nvp("cveList", cve_list),
|
||||
cereal::make_nvp("tags", tag_list)
|
||||
);
|
||||
|
||||
severity = getLevel(severity_string, "severity");
|
||||
confidence = getLevel(confidence_string, "confidence");
|
||||
performance = getLevel(performance_string, "performance");
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("logAttackName", event_log));
|
||||
} catch (cereal::Exception &) {
|
||||
event_log = "IPS Signature '" + sig_name + "' Found";
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("silent", is_silent));
|
||||
} catch (cereal::Exception &) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
static const size_t protection_type_pos = strlen("Protection_Type_");
|
||||
static const size_t vul_type_pos = strlen("Vul_Type_");
|
||||
|
||||
string
|
||||
IPSSignatureSubTypes::IPSSignatureMetaData::getIncidentType() const
|
||||
{
|
||||
for (auto &tag : tag_list) {
|
||||
if (tag.compare(0, vul_type_pos, "Vul_Type_") == 0) {
|
||||
auto incident_type = tag.substr(vul_type_pos);
|
||||
replace(incident_type.begin(), incident_type.end(), '_', ' ');
|
||||
if (incident_type == "Vulnerability") return "Vulnerability exploit attempt";
|
||||
return incident_type;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &tag : tag_list) {
|
||||
if (tag.compare(0, protection_type_pos, "Protection_Type_") == 0) {
|
||||
auto incident_type = tag.substr(protection_type_pos);
|
||||
replace(incident_type.begin(), incident_type.end(), '_', ' ');
|
||||
if (incident_type == "Vulnerability") return "Vulnerability exploit attempt";
|
||||
return incident_type;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static const size_t year_start_pos = strlen("Threat_Year_");
|
||||
|
||||
bool
|
||||
IPSSignatureMetaData::isYearAtLeast(const Maybe<int> &year) const
|
||||
{
|
||||
if (!year.ok()) return true;
|
||||
auto protection_year = getYear();
|
||||
if (!protection_year.ok()) return true;
|
||||
|
||||
return *protection_year >= *year;
|
||||
}
|
||||
|
||||
Maybe<int>
|
||||
IPSSignatureMetaData::getYear() const
|
||||
{
|
||||
for (auto &tag : tag_list) {
|
||||
if (tag.compare(0, year_start_pos, "Threat_Year_") == 0) {
|
||||
if (tag.size() != year_start_pos + 4) {
|
||||
dbgWarning(D_IPS) << "Threat year tag (" << tag << ") doen't meet expected format";
|
||||
return false;
|
||||
}
|
||||
int protection_year =
|
||||
(tag[year_start_pos] - '0') * 1000 +
|
||||
(tag[year_start_pos + 1] - '0') * 100 +
|
||||
(tag[year_start_pos + 2] - '0') * 10 +
|
||||
(tag[year_start_pos + 3] - '0');
|
||||
return protection_year;
|
||||
}
|
||||
}
|
||||
|
||||
return genError("Year not found");
|
||||
}
|
||||
|
||||
void
|
||||
CompleteSignature::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
ar(cereal::make_nvp("protectionMetadata", metadata));
|
||||
RuleDetection rule_detection(metadata.getName());
|
||||
ar(cereal::make_nvp("detectionRules", rule_detection));
|
||||
rule = rule_detection.getRule();
|
||||
}
|
||||
|
||||
MatchType
|
||||
CompleteSignature::getMatch(const set<PMPattern> &matches) const
|
||||
{
|
||||
return rule->getMatch(matches);
|
||||
}
|
||||
|
||||
set<PMPattern>
|
||||
CompleteSignature::patternsInSignature() const
|
||||
{
|
||||
return rule->patternsInSignature();
|
||||
}
|
||||
|
||||
void
|
||||
CompleteSignature::setIndicators(const string &source, const string &version)
|
||||
{
|
||||
metadata.setIndicators(source, version);
|
||||
}
|
||||
|
||||
template <typename ErrorType>
|
||||
static string
|
||||
getSubString(const Maybe<Buffer, ErrorType> &buf, uint max_size = 0)
|
||||
{
|
||||
if (max_size == 0) max_size = buf.unpack().size();
|
||||
const Buffer &real_buf = buf.unpack();
|
||||
auto res = real_buf.size() <= max_size ? real_buf : real_buf.getSubBuffer(0, max_size);
|
||||
return static_cast<string>(res);
|
||||
}
|
||||
|
||||
ActionResults
|
||||
SignatureAndAction::getAction(const IPSEntry &ips_state) const
|
||||
{
|
||||
dbgDebug(D_IPS) << "matching exceptions";
|
||||
|
||||
unordered_map<string, set<string>> exceptions_dict;
|
||||
exceptions_dict["protectionName"].insert(signature->getName());
|
||||
|
||||
ScopedContext ctx;
|
||||
ctx.registerValue<string>("protectionName", signature->getName());
|
||||
|
||||
auto env = Singleton::Consume<I_Environment>::by<IPSComp>();
|
||||
auto host = env->get<string>(HttpTransactionData::host_name_ctx);
|
||||
if (host.ok()) exceptions_dict["hostName"].insert(*host);
|
||||
|
||||
auto client_ip = env->get<IPAddr>(HttpTransactionData::client_ip_ctx);
|
||||
if (client_ip.ok()) {
|
||||
stringstream client_ip_str;
|
||||
client_ip_str << client_ip.unpack();
|
||||
exceptions_dict["sourceIP"].insert(client_ip_str.str());
|
||||
}
|
||||
|
||||
auto path = ips_state.getBuffer("HTTP_PATH_DECODED");
|
||||
if (path.size()) exceptions_dict["url"].insert(static_cast<string>(path));
|
||||
|
||||
auto env_source_identifier = env->get<string>(HttpTransactionData::source_identifier);
|
||||
if (env_source_identifier.ok()) {
|
||||
exceptions_dict["sourceIdentifier"].insert(*env_source_identifier);
|
||||
}
|
||||
|
||||
I_GenericRulebase *i_rulebase = Singleton::Consume<I_GenericRulebase>::by<IPSComp>();
|
||||
auto behaviors = i_rulebase->getBehavior(exceptions_dict);
|
||||
|
||||
set<BehaviorValue> override_actions;
|
||||
vector<string> override_ids;
|
||||
for (auto const &behavior : behaviors) {
|
||||
if (behavior.getKey() == BehaviorKey::ACTION) {
|
||||
override_actions.insert(behavior.getValue());
|
||||
const string &override_id = behavior.getId();
|
||||
if (!override_id.empty()) override_ids.push_back(override_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (override_actions.find(BehaviorValue::IGNORE) != override_actions.end()) {
|
||||
dbgDebug(D_IPS) << "Exception matched - action=Detect";
|
||||
return make_tuple(IPSSignatureSubTypes::SignatureAction::DETECT, string("Skip"), override_ids);
|
||||
}
|
||||
if (override_actions.find(BehaviorValue::ACCEPT) != override_actions.end()) {
|
||||
dbgDebug(D_IPS) << "Exception matched - action=Detect";
|
||||
return make_tuple(IPSSignatureSubTypes::SignatureAction::DETECT, string("Accept"), override_ids);
|
||||
}
|
||||
if (override_actions.find(BehaviorValue::REJECT) != override_actions.end()) {
|
||||
dbgDebug(D_IPS) << "Exception matched - action=Prevent";
|
||||
return make_tuple(IPSSignatureSubTypes::SignatureAction::PREVENT, string("Drop"), override_ids);
|
||||
}
|
||||
return make_tuple(action, string("None"), override_ids);
|
||||
}
|
||||
|
||||
static const auto req_body = LogTriggerConf::WebLogFields::webBody;
|
||||
static const auto headers = LogTriggerConf::WebLogFields::webHeaders;
|
||||
static const auto url_path = LogTriggerConf::WebLogFields::webUrlPath;
|
||||
static const auto url_query = LogTriggerConf::WebLogFields::webUrlQuery;
|
||||
static const auto res_body = LogTriggerConf::WebLogFields::responseBody;
|
||||
static const auto res_code = LogTriggerConf::WebLogFields::responseCode;
|
||||
|
||||
bool
|
||||
SignatureAndAction::matchSilent(const Buffer &sample) const
|
||||
{
|
||||
dbgTrace(D_IPS) << "Matched silent signature";
|
||||
MatchEvent(signature, IPSSignatureSubTypes::SignatureAction::IGNORE).notify();
|
||||
|
||||
ScopedContext ctx;
|
||||
ctx.registerValue("Audience Team", AudienceTeam::SIGNATURE_DEVELOPERS);
|
||||
|
||||
LogGen log(
|
||||
"Silent Protection",
|
||||
Audience::INTERNAL,
|
||||
Severity::INFO,
|
||||
Priority::MEDIUM,
|
||||
LogField("practiceType", "Threat Prevention"),
|
||||
Tags::IPS,
|
||||
StreamType::JSON_FOG
|
||||
);
|
||||
log << LogField("signatureVersion", signature->getUpdateVersion())
|
||||
<< LogField("protectionId", signature->getName())
|
||||
<< LogField("indicatorsSource", signature->getSource())
|
||||
<< LogField("indicatorsVersion", signature->getFeedVersion())
|
||||
<< LogField("incidentType", signature->getIncidentType())
|
||||
<< LogField("matchedSample", static_cast<string>(sample), LogFieldOption::XORANDB64);
|
||||
|
||||
auto env = Singleton::Consume<I_Environment>::by<IPSComp>();
|
||||
auto table = Singleton::Consume<I_Table>::by<IPSComp>();
|
||||
auto &ips_state = table->getState<IPSEntry>();
|
||||
|
||||
auto method = env->get<string>(HttpTransactionData::method_ctx);
|
||||
if (method.ok()) log << LogField("httpMethod", method.unpack());
|
||||
|
||||
auto path = env->get<Buffer>("HTTP_PATH_DECODED");
|
||||
if (path.ok()) log << LogField("httpUriPath", getSubString(path, 1536), LogFieldOption::XORANDB64);
|
||||
|
||||
auto req_header = ips_state.getTransactionData(IPSCommonTypes::requests_header_for_log);
|
||||
if (req_header.ok()) log << LogField("httpRequestHeaders", getSubString(req_header), LogFieldOption::XORANDB64);
|
||||
|
||||
auto res_code = env->get<Buffer>("HTTP_RESPONSE_CODE");
|
||||
if (res_code.ok()) log << LogField("httpResponseCode", static_cast<string>(res_code.unpack()));
|
||||
|
||||
auto req_body = env->get<Buffer>("HTTP_REQUEST_BODY");
|
||||
auto res_body = env->get<Buffer>("HTTP_RESPONSE_BODY");
|
||||
uint req_size = req_body.ok() ? req_body.unpack().size() : 0;
|
||||
uint res_size = res_body.ok() ? res_body.unpack().size() : 0;
|
||||
if (req_size + res_size > 1536) {
|
||||
if (req_size + 500 > 1536) {
|
||||
res_size = std::min(500u, res_size);
|
||||
req_size = 1536 - res_size;
|
||||
} else {
|
||||
res_size = 1536 - req_size;
|
||||
}
|
||||
}
|
||||
if (req_size) log << LogField("httpRequestBody", getSubString(req_body, req_size), LogFieldOption::XORANDB64);
|
||||
if (res_size) log << LogField("httpResponseBody", getSubString(res_body, res_size), LogFieldOption::XORANDB64);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
SignatureAndAction::isMatchedPrevent(const Buffer &context_buffer, const set<PMPattern> &pattern) const
|
||||
{
|
||||
if (signature->getMatch(pattern) != MatchType::MATCH) {
|
||||
dbgTrace(D_IPS) << "Signature doesn't match";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (signature->isSilent()) return matchSilent(context_buffer);
|
||||
|
||||
auto table = Singleton::Consume<I_Table>::by<IPSComp>();
|
||||
auto &ips_state = table->getState<IPSEntry>();
|
||||
|
||||
auto override_action = getAction(ips_state);
|
||||
|
||||
MatchEvent(signature, get<0>(override_action)).notify();
|
||||
|
||||
if (get<0>(override_action) == IPSSignatureSubTypes::SignatureAction::IGNORE) {
|
||||
dbgDebug(D_IPS) << "Ignored signature";
|
||||
return false;
|
||||
}
|
||||
|
||||
dbgDebug(D_IPS) << "Signature matched - sending log";
|
||||
|
||||
auto &trigger = getConfigurationWithDefault(LogTriggerConf(), "rulebase", "log");
|
||||
bool is_prevent = get<0>(override_action) == IPSSignatureSubTypes::SignatureAction::PREVENT;
|
||||
|
||||
auto severity = signature->getSeverity() < IPSLevel::HIGH ? Severity::HIGH : Severity::CRITICAL;
|
||||
if (get<0>(override_action) == IPSSignatureSubTypes::SignatureAction::DETECT) severity = Severity::INFO;
|
||||
|
||||
LogGen log = trigger(
|
||||
"Web Request",
|
||||
LogTriggerConf::SecurityType::ThreatPrevention,
|
||||
severity,
|
||||
Priority::HIGH,
|
||||
is_prevent,
|
||||
LogField("practiceType", "Threat Prevention"),
|
||||
Tags::IPS
|
||||
);
|
||||
log
|
||||
<< LogField("matchedSignatureConfidence", signature->getConfidenceString())
|
||||
<< LogField("matchedSignaturePerformance", signature->getPerformanceString())
|
||||
<< LogField("matchedSignatureSeverity", signature->getSeverityString())
|
||||
<< LogField("matchedSignatureCVE", makeSeparatedStr(signature->getCveList(), ", "))
|
||||
<< LogField("signatureVersion", signature->getUpdateVersion())
|
||||
<< LogField("protectionId", signature->getName())
|
||||
<< LogField("indicatorsSource", signature->getSource())
|
||||
<< LogField("indicatorsVersion", signature->getFeedVersion())
|
||||
<< LogField("waapIncidentType", signature->getIncidentType());
|
||||
|
||||
if (context_buffer.size() < 1024) {
|
||||
log << LogField("matchedSample", static_cast<string>(context_buffer), LogFieldOption::XORANDB64);
|
||||
} else {
|
||||
auto sample = context_buffer;
|
||||
sample.keepHead(1024);
|
||||
log << LogField("matchedSample", static_cast<string>(sample), LogFieldOption::XORANDB64);
|
||||
}
|
||||
|
||||
auto year = signature->getYear();
|
||||
if (year.ok()) log << LogField("matchedSignatureYear", to_string(*year));
|
||||
|
||||
auto env = Singleton::Consume<I_Environment>::by<IPSComp>();
|
||||
auto host = env->get<string>(HttpTransactionData::host_name_ctx);
|
||||
if (host.ok()) log << LogField("httpHostName", host.unpack());
|
||||
auto client_ip = env->get<IPAddr>(HttpTransactionData::client_ip_ctx);
|
||||
|
||||
if (client_ip.ok()) {
|
||||
stringstream client_ip_str;
|
||||
client_ip_str << client_ip.unpack();
|
||||
log << LogField("sourceIP", client_ip_str.str());
|
||||
}
|
||||
|
||||
auto proxy_ip = env->get<string>(HttpTransactionData::proxy_ip_ctx);
|
||||
if (proxy_ip.ok()) {
|
||||
log << LogField("proxyIP", static_cast<string>(proxy_ip.unpack()));
|
||||
}
|
||||
|
||||
auto source_identifier = env->get<string>(HttpTransactionData::source_identifier);
|
||||
if (source_identifier.ok()) {
|
||||
log << LogField("httpSourceId", static_cast<string>(source_identifier.unpack()));
|
||||
}
|
||||
|
||||
auto req_header = ips_state.getTransactionData(IPSCommonTypes::requests_header_for_log);
|
||||
if (req_header.ok() && trigger.isWebLogFieldActive(headers)) {
|
||||
log << LogField("httpRequestHeaders", static_cast<string>(req_header.unpack()), LogFieldOption::XORANDB64);
|
||||
}
|
||||
|
||||
auto client_port = env->get<uint16_t>(HttpTransactionData::client_port_ctx);
|
||||
if (client_port.ok()) log << LogField("sourcePort", client_port.unpack());
|
||||
auto method = env->get<string>(HttpTransactionData::method_ctx);
|
||||
if (method.ok()) log << LogField("httpMethod", method.unpack());
|
||||
uint max_size = getConfigurationWithDefault<uint>(1536, "IPS", "Max Field Size");
|
||||
auto path = env->get<Buffer>("HTTP_PATH_DECODED");
|
||||
if (path.ok() && trigger.isWebLogFieldActive(url_path)) {
|
||||
log << LogField("httpUriPath", getSubString(path, max_size), LogFieldOption::XORANDB64);
|
||||
}
|
||||
auto query = env->get<Buffer>("HTTP_QUERY_DECODED");
|
||||
if (query.ok() && trigger.isWebLogFieldActive(url_query)) {
|
||||
log << LogField("httpUriQuery", getSubString(query, max_size), LogFieldOption::XORANDB64);
|
||||
}
|
||||
|
||||
auto res_code = env->get<Buffer>("HTTP_RESPONSE_CODE");
|
||||
if (res_code.ok() && trigger.isWebLogFieldActive(::res_code)) {
|
||||
log << LogField("httpResponseCode", static_cast<string>(res_code.unpack()));
|
||||
}
|
||||
|
||||
auto req_body = env->get<Buffer>("HTTP_REQUEST_BODY");
|
||||
auto res_body = env->get<Buffer>("HTTP_RESPONSE_BODY");
|
||||
uint req_size = req_body.ok() && trigger.isWebLogFieldActive(::req_body) ? req_body.unpack().size() : 0;
|
||||
uint res_size = res_body.ok() && trigger.isWebLogFieldActive(::res_body) ? res_body.unpack().size() : 0;
|
||||
if (req_size + res_size > max_size) {
|
||||
if (req_size + 500 > max_size) {
|
||||
res_size = std::min(500u, res_size);
|
||||
req_size = max_size - res_size;
|
||||
} else {
|
||||
res_size = max_size - req_size;
|
||||
}
|
||||
}
|
||||
if (req_size) log << LogField("httpRequestBody", getSubString(req_body, req_size), LogFieldOption::XORANDB64);
|
||||
if (res_size) log << LogField("httpResponseBody", getSubString(res_body, res_size), LogFieldOption::XORANDB64);
|
||||
|
||||
log << LogField("waapOverride", get<1>(override_action));
|
||||
|
||||
if (!get<2>(override_action).empty()) log.addToOrigin(LogField("exceptionIdList", get<2>(override_action)));
|
||||
|
||||
log << LogField("securityAction", is_prevent ? "Prevent" : "Detect");
|
||||
|
||||
return is_prevent;
|
||||
}
|
||||
|
||||
void
|
||||
IPSSignaturesResource::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
vector<CompleteSignature> sigs;
|
||||
cereal::load(ar, sigs);
|
||||
|
||||
all_signatures.reserve(sigs.size());
|
||||
for (auto &sig : sigs) {
|
||||
all_signatures.emplace_back(make_shared<CompleteSignature>(move(sig)));
|
||||
}
|
||||
}
|
||||
|
||||
class CompleteSignatureWrapper
|
||||
{
|
||||
public:
|
||||
void
|
||||
load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
try {
|
||||
sig.load(ar);
|
||||
is_loaded = true;
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.finishNode();
|
||||
reportError(e.what());
|
||||
} catch (const Config::ConfigException &e) {
|
||||
ar.finishNode();
|
||||
reportError(e.getError());
|
||||
}
|
||||
}
|
||||
|
||||
bool isOk() const { return is_loaded; }
|
||||
void setIndicators(const string &source, const string &version) { sig.setIndicators(source, version); }
|
||||
shared_ptr<CompleteSignature> getPtr() { return make_shared<CompleteSignature>(move(sig)); }
|
||||
|
||||
private:
|
||||
void
|
||||
reportError(const string &err)
|
||||
{
|
||||
dbgError(D_IPS) << "Failed to load signature due to: " << err;
|
||||
|
||||
if (sig.getName() != "") {
|
||||
string remediation =
|
||||
"Verify the validity of the '" +
|
||||
sig.getName() +
|
||||
"' signature.";
|
||||
|
||||
LogGen(
|
||||
"Could not load a Snort signature from configured file",
|
||||
ReportIS::Level::ACTION,
|
||||
ReportIS::Audience::SECURITY,
|
||||
ReportIS::Severity::CRITICAL,
|
||||
ReportIS::Priority::URGENT,
|
||||
LogField("EventTopic", "Snort Signatures"),
|
||||
ReportIS::Tags::POLICY_INSTALLATION
|
||||
) << LogField("EventRemediation", remediation);
|
||||
}
|
||||
}
|
||||
|
||||
CompleteSignature sig;
|
||||
bool is_loaded = false;
|
||||
};
|
||||
|
||||
void
|
||||
SnortSignaturesResourceFile::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
string time;
|
||||
vector<CompleteSignatureWrapper> sigs;
|
||||
ar(
|
||||
cereal::make_nvp("modificationTime", time),
|
||||
cereal::make_nvp("name", name),
|
||||
cereal::make_nvp("protections", sigs)
|
||||
);
|
||||
|
||||
all_signatures.reserve(sigs.size());
|
||||
for (auto &sig : sigs) {
|
||||
if (sig.isOk()) {
|
||||
sig.setIndicators(name, time);
|
||||
all_signatures.emplace_back(sig.getPtr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SnortSignaturesResource::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
cereal::load(ar, files);
|
||||
}
|
||||
|
||||
void
|
||||
IPSSignaturesPerContext::addSignature(const IPSSignatureSubTypes::SignatureAndAction &sig)
|
||||
{
|
||||
auto patterns = sig.patternsInSignature();
|
||||
|
||||
if (patterns.empty()) {
|
||||
signatures_without_lss.push_back(sig);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &pat : patterns) {
|
||||
signatures_per_lss[pat].push_back(sig);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IPSSignaturesPerContext::calcFirstTier(const string &ctx_name)
|
||||
{
|
||||
std::set<PMPattern> patterns;
|
||||
for (const auto &lss_to_sig : signatures_per_lss) {
|
||||
patterns.emplace(lss_to_sig.first);
|
||||
}
|
||||
|
||||
first_tier = Singleton::Consume<I_FirstTierAgg>::by<IPSSignaturesPerContext>()->getHook(ctx_name, patterns);
|
||||
}
|
||||
|
||||
set<PMPattern>
|
||||
IPSSignaturesPerContext::getFirstTierMatches(const Buffer &buffer) const
|
||||
{
|
||||
return first_tier->ok() ? first_tier->scanBuf(buffer) : set<PMPattern>();
|
||||
}
|
||||
|
||||
bool
|
||||
IPSSignaturesPerContext::isMatchedPrevent(const Buffer &context_buffer) const
|
||||
{
|
||||
auto first_tier_res = getFirstTierMatches(context_buffer);
|
||||
|
||||
for (auto &pat : first_tier_res) {
|
||||
auto find = signatures_per_lss.find(pat);
|
||||
if (find == signatures_per_lss.end()) continue;
|
||||
for (auto &sig : find->second) {
|
||||
if (sig.isMatchedPrevent(context_buffer, first_tier_res)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &sig : signatures_without_lss) {
|
||||
if (sig.isMatchedPrevent(context_buffer, first_tier_res)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
IPSSignatures::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
ar(
|
||||
cereal::make_nvp("assetName", asset_name),
|
||||
cereal::make_nvp("practiceName", practice_name)
|
||||
);
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("assetId", asset_id));
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
asset_id = "";
|
||||
}
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("practiceId", practice_id));
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
practice_id = "";
|
||||
}
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("sourceIdentifier", source_id));
|
||||
for (auto &ch : source_id) {
|
||||
ch = tolower(ch);
|
||||
}
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
source_id = "";
|
||||
}
|
||||
|
||||
RuleSelector ruleSelector;
|
||||
ruleSelector.load(ar);
|
||||
std::vector<IPSSignatureSubTypes::SignatureAndAction> signatures = ruleSelector.selectSignatures();
|
||||
|
||||
if (signatures.empty()) {
|
||||
dbgDebug(D_IPS) << "[IPS] Could not find any match between rules and signatures.";
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &sig : signatures) {
|
||||
auto &sig_contexts = sig.getContext();
|
||||
for (auto &sig_context : sig_contexts) {
|
||||
signatures_per_context[sig_context].addSignature(sig);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &sig_per_ctx : signatures_per_context) {
|
||||
sig_per_ctx.second.calcFirstTier(sig_per_ctx.first);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
IPSSignatures::isMatchedPrevent(const string &context_name, const Buffer &context_buffer) const
|
||||
{
|
||||
auto curr_sig = signatures_per_context.find(context_name);
|
||||
|
||||
if (curr_sig == signatures_per_context.end()) {
|
||||
dbgDebug(D_IPS) << "[IPS] No signatures for " << context_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &config = getConfiguration<IPSSignatures>("IPS", "IpsProtections");
|
||||
ScopedContext ctx;
|
||||
auto SOURCE = EnvKeyAttr::LogSection::SOURCE;
|
||||
if (config.ok()) {
|
||||
ctx.registerValue<string>("practiceName", (*config).getPractice(), SOURCE);
|
||||
ctx.registerValue<string>("practiceId", (*config).getPracticeId(), SOURCE);
|
||||
}
|
||||
ctx.registerValue<string>("practiceSubType", "Web IPS", SOURCE);
|
||||
auto is_matched = curr_sig->second.isMatchedPrevent(context_buffer);
|
||||
|
||||
return is_matched;
|
||||
}
|
||||
|
||||
bool
|
||||
IPSSignatures::isEmpty(const std::string &context) const
|
||||
{
|
||||
return signatures_per_context.find(context) == signatures_per_context.end();
|
||||
}
|
||||
|
||||
void
|
||||
SnortSignatures::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
ar(
|
||||
cereal::make_nvp("assetName", asset_name),
|
||||
cereal::make_nvp("practiceName", practice_name)
|
||||
);
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("assetId", asset_id));
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
asset_id = "";
|
||||
}
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("practiceId", practice_id));
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
practice_id = "";
|
||||
}
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("sourceIdentifier", source_id));
|
||||
for (auto &ch : source_id) {
|
||||
ch = tolower(ch);
|
||||
}
|
||||
} catch (const cereal::Exception &e) {
|
||||
ar.setNextName(nullptr);
|
||||
source_id = "";
|
||||
}
|
||||
|
||||
SnortRuleSelector ruleSelector;
|
||||
ruleSelector.load(ar);
|
||||
std::vector<IPSSignatureSubTypes::SignatureAndAction> signatures = ruleSelector.selectSignatures();
|
||||
|
||||
if (signatures.empty()) {
|
||||
dbgDebug(D_IPS) << "[Snort] Could not find any match between rules and signatures.";
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &sig : signatures) {
|
||||
auto &sig_contexts = sig.getContext();
|
||||
for (auto &sig_context : sig_contexts) {
|
||||
signatures_per_context[sig_context].addSignature(sig);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &sig_per_ctx: signatures_per_context) {
|
||||
sig_per_ctx.second.calcFirstTier(sig_per_ctx.first);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SnortSignatures::isMatchedPrevent(const string &context_name, const Buffer &context_buffer) const
|
||||
{
|
||||
auto curr_sig = signatures_per_context.find(context_name);
|
||||
|
||||
if (curr_sig == signatures_per_context.end()) {
|
||||
dbgDebug(D_IPS) << "[Snort] No signatures for " << context_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &config = getConfiguration<SnortSignatures>("IPSSnortSigs", "SnortProtections");
|
||||
ScopedContext ctx;
|
||||
auto SOURCE = EnvKeyAttr::LogSection::SOURCE;
|
||||
if (config.ok()) {
|
||||
ctx.registerValue<string>("assetName", (*config).getAsset(), SOURCE);
|
||||
ctx.registerValue<string>("assetId", (*config).getAssetId(), SOURCE);
|
||||
ctx.registerValue<string>("practiceName", (*config).getPractice(), SOURCE);
|
||||
ctx.registerValue<string>("practiceId", (*config).getPracticeId(), SOURCE);
|
||||
}
|
||||
ctx.registerValue<string>("practiceSubType", "Web Snort", SOURCE);
|
||||
auto is_matched = curr_sig->second.isMatchedPrevent(context_buffer);
|
||||
|
||||
return is_matched;
|
||||
}
|
||||
|
||||
bool
|
||||
SnortSignatures::isEmpty(const std::string &context) const
|
||||
{
|
||||
return signatures_per_context.find(context) == signatures_per_context.end();
|
||||
}
|
7
components/security_apps/ips/ips_ut/CMakeLists.txt
Normal file
7
components/security_apps/ips/ips_ut/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
link_directories(${CMAKE_BINARY_DIR}/core/shmem_ipc)
|
||||
|
||||
add_unit_test(
|
||||
ips_ut
|
||||
"signatures_ut.cc;entry_ut.cc;component_ut.cc;configuration.cc;rule_selector_ut.cc;compound_ut.cc;resource_ut.cc"
|
||||
"ips;keywords;pcre2-8;intelligence_is_v2;logging;compression_utils;agent_details;time_proxy;event_is;table;http_transaction_data;nginx_attachment;connkey;pm;metric;encryptor;generic_rulebase;generic_rulebase_evaluators;compression_utils;ip_utilities;-lboost_regex;-lcrypto;-lz"
|
||||
)
|
913
components/security_apps/ips/ips_ut/component_ut.cc
Normal file
913
components/security_apps/ips/ips_ut/component_ut.cc
Normal file
@@ -0,0 +1,913 @@
|
||||
#include "ips_comp.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "cptest.h"
|
||||
#include "ips_entry.h"
|
||||
#include "new_table_entry.h"
|
||||
#include "keyword_comp.h"
|
||||
#include "environment.h"
|
||||
#include "mock/mock_table.h"
|
||||
#include "config.h"
|
||||
#include "http_manager.h"
|
||||
#include "config_component.h"
|
||||
#include "agent_details.h"
|
||||
#include "mock/mock_logging.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "encryptor.h"
|
||||
#include "generic_rulebase/generic_rulebase.h"
|
||||
#include "generic_rulebase/triggers_config.h"
|
||||
|
||||
using namespace testing;
|
||||
using namespace std;
|
||||
|
||||
class ComponentTest : public Test
|
||||
{
|
||||
public:
|
||||
ComponentTest()
|
||||
{
|
||||
comp.preload();
|
||||
comp.init();
|
||||
}
|
||||
|
||||
~ComponentTest()
|
||||
{
|
||||
comp.fini();
|
||||
}
|
||||
|
||||
void
|
||||
loadPolicy(const string &config)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << config;
|
||||
Singleton::Consume<Config::I_Config>::from(conf)->loadConfiguration(ss);
|
||||
}
|
||||
|
||||
void
|
||||
setTrigger()
|
||||
{
|
||||
string log_trigger(
|
||||
"{"
|
||||
" \"context\": \"triggerId(5eaeefde6765c30010bae8b6)\","
|
||||
" \"triggerName\": \"Logging Trigger\","
|
||||
" \"triggerType\": \"log\","
|
||||
" \"urlForSyslog\": \"\","
|
||||
" \"urlForCef\": \"128.1.1.1:333\","
|
||||
" \"acAllow\": false,"
|
||||
" \"acDrop\": true,"
|
||||
" \"complianceViolations\": true,"
|
||||
" \"complianceWarnings\": true,"
|
||||
" \"logToAgent\": true,"
|
||||
" \"logToCloud\": true,"
|
||||
" \"logToSyslog\": false,"
|
||||
" \"logToCef\": true,"
|
||||
" \"tpDetect\": true,"
|
||||
" \"tpPrevent\": true,"
|
||||
" \"verbosity\": \"Standard\","
|
||||
" \"webBody\": true,"
|
||||
" \"webHeaders\": true,"
|
||||
" \"webRequests\": true,"
|
||||
" \"webUrlPath\": true,"
|
||||
" \"webUrlQuery\": true"
|
||||
"}"
|
||||
);
|
||||
|
||||
stringstream ss(log_trigger);
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
LogTriggerConf trigger;
|
||||
trigger.load(ar);
|
||||
|
||||
setConfiguration(trigger, "rulebase", "log");
|
||||
}
|
||||
|
||||
IPSComp comp;
|
||||
StrictMock<MockTable> table;
|
||||
IPSEntry entry;
|
||||
|
||||
GenericRulebase generic_rulebase;
|
||||
ConfigComponent conf;
|
||||
Encryptor encryptor;
|
||||
KeywordComp keywords;
|
||||
::Environment env;
|
||||
AgentDetails details;
|
||||
NiceMock<MockLogging> logs;
|
||||
NiceMock<MockTimeGet> time;
|
||||
NiceMock<MockMainLoop> mainloop;
|
||||
static const EventVerdict inspect;
|
||||
static const EventVerdict accept;
|
||||
static const EventVerdict drop;
|
||||
|
||||
HttpHeader end_headers{Buffer(""), Buffer(""), 0, true};
|
||||
};
|
||||
|
||||
static bool
|
||||
operator ==(const EventVerdict &first, const EventVerdict &second)
|
||||
{
|
||||
return first.getVerdict() == second.getVerdict();
|
||||
}
|
||||
|
||||
const EventVerdict ComponentTest::inspect(ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INSPECT);
|
||||
|
||||
const EventVerdict ComponentTest::accept(ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT);
|
||||
|
||||
const EventVerdict ComponentTest::drop(ngx_http_cp_verdict_e::TRAFFIC_VERDICT_DROP);
|
||||
|
||||
TEST_F(ComponentTest, check_init_fini_do_not_crush)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, new_table_entry_with_empty_configuration)
|
||||
{
|
||||
NewTableEntry().notify();
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, new_table_entry_with_configuration)
|
||||
{
|
||||
string config =
|
||||
"{"
|
||||
"\"IPS\": {"
|
||||
"\"configurations\": ["
|
||||
"{"
|
||||
"\"context\": \"\","
|
||||
"\"contextsConfiguration\": ["
|
||||
"{"
|
||||
"\"type\": \"keep\","
|
||||
"\"name\": \"HTTP_REQUEST_BODY\""
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"],"
|
||||
"\"protections\": ["
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"ddd\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\"]"
|
||||
"}"
|
||||
"}"
|
||||
"],"
|
||||
"\"IpsProtections\": ["
|
||||
"{"
|
||||
"\"context\": \"\","
|
||||
"\"ruleName\": \"rule1\","
|
||||
"\"assetName\": \"asset1\","
|
||||
"\"assetId\": \"1-1-1\","
|
||||
"\"practiceId\": \"2-2-2\","
|
||||
"\"practiceName\": \"practice1\","
|
||||
"\"defaultAction\": \"Detect\","
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Prevent\","
|
||||
"\"severityLevel\": \"Low or above\","
|
||||
"\"performanceImpact\": \"High or lower\","
|
||||
"\"confidenceLevel\": \"Low\""
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
loadPolicy(config);
|
||||
EXPECT_CALL(table, createStateRValueRemoved(_, _));
|
||||
EXPECT_CALL(table, getState(_)).WillOnce(Return(&entry));
|
||||
NewTableEntry().notify();
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, events)
|
||||
{
|
||||
EXPECT_CALL(table, hasState(_)).WillRepeatedly(Return(true));
|
||||
IPSEntry entry;
|
||||
EXPECT_CALL(table, getState(_)).WillRepeatedly(Return(&entry));
|
||||
|
||||
HttpTransactionData transaction;
|
||||
EXPECT_THAT(NewHttpTransactionEvent(transaction).query(), ElementsAre(accept));
|
||||
HttpHeader header_req(Buffer("key"), Buffer("val"), 1);
|
||||
EXPECT_THAT(HttpRequestHeaderEvent(header_req).query(), ElementsAre(inspect));
|
||||
HttpBody body_req(Buffer("data"), 0, true);
|
||||
EXPECT_THAT(HttpRequestBodyEvent(body_req, Buffer()).query(), ElementsAre(inspect));
|
||||
EXPECT_THAT(EndRequestEvent().query(), ElementsAre(accept));
|
||||
|
||||
EXPECT_THAT(ResponseCodeEvent(200).query(), ElementsAre(inspect));
|
||||
HttpHeader header_res(Buffer("key"), Buffer("val"), 2);
|
||||
EXPECT_THAT(HttpResponseHeaderEvent(header_res).query(), ElementsAre(inspect));
|
||||
HttpBody body_res(Buffer("data"), true, 0);
|
||||
EXPECT_THAT(HttpResponseBodyEvent(body_res, Buffer()).query(), ElementsAre(accept));
|
||||
EXPECT_THAT(EndTransactionEvent().performNamedQuery(), ElementsAre(Pair("ips application", accept)));
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, check_url_decoding)
|
||||
{
|
||||
string config =
|
||||
"{"
|
||||
"\"IPS\": {"
|
||||
"\"protections\": ["
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"d d\\\";\","
|
||||
"\"context\": [\"HTTP_COMPLETE_URL_DECODED\"]"
|
||||
"}"
|
||||
"}"
|
||||
"],"
|
||||
"\"IpsProtections\": ["
|
||||
"{"
|
||||
"\"context\": \"\","
|
||||
"\"ruleName\": \"rule1\","
|
||||
"\"assetName\": \"asset1\","
|
||||
"\"assetId\": \"1-1-1\","
|
||||
"\"practiceId\": \"2-2-2\","
|
||||
"\"practiceName\": \"practice1\","
|
||||
"\"defaultAction\": \"Detect\","
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Prevent\","
|
||||
"\"severityLevel\": \"Low or above\","
|
||||
"\"performanceImpact\": \"High or lower\","
|
||||
"\"confidenceLevel\": \"Low\""
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"}";
|
||||
loadPolicy(config);
|
||||
|
||||
EXPECT_CALL(table, createStateRValueRemoved(_, _));
|
||||
EXPECT_CALL(table, getState(_)).WillRepeatedly(Return(&entry));
|
||||
EXPECT_CALL(table, hasState(_)).WillRepeatedly(Return(true));
|
||||
|
||||
HttpTransactionData new_transaction(
|
||||
"1.1",
|
||||
"GET",
|
||||
"ffff",
|
||||
IPAddr::createIPAddr("0.0.0.0").unpack(),
|
||||
80,
|
||||
"d%20d",
|
||||
IPAddr::createIPAddr("1.1.1.1").unpack(),
|
||||
5428
|
||||
);
|
||||
|
||||
EXPECT_THAT(NewHttpTransactionEvent(new_transaction).query(), ElementsAre(inspect));
|
||||
EXPECT_THAT(HttpRequestHeaderEvent(end_headers).query(), ElementsAre(drop));
|
||||
EXPECT_THAT(EndRequestEvent().query(), ElementsAre(drop));
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, check_query)
|
||||
{
|
||||
string config =
|
||||
"{"
|
||||
"\"IPS\": {"
|
||||
"\"protections\": ["
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"g=#\\\";\","
|
||||
"\"context\": [\"HTTP_QUERY_DECODED\"]"
|
||||
"}"
|
||||
"}"
|
||||
"],"
|
||||
"\"IpsProtections\": ["
|
||||
"{"
|
||||
"\"context\": \"\","
|
||||
"\"ruleName\": \"rule1\","
|
||||
"\"assetName\": \"asset1\","
|
||||
"\"assetId\": \"1-1-1\","
|
||||
"\"practiceId\": \"2-2-2\","
|
||||
"\"practiceName\": \"practice1\","
|
||||
"\"defaultAction\": \"Detect\","
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Prevent\","
|
||||
"\"severityLevel\": \"Low or above\","
|
||||
"\"performanceImpact\": \"High or lower\","
|
||||
"\"confidenceLevel\": \"Low\""
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"}";
|
||||
loadPolicy(config);
|
||||
|
||||
EXPECT_CALL(table, createStateRValueRemoved(_, _));
|
||||
EXPECT_CALL(table, getState(_)).WillRepeatedly(Return(&entry));
|
||||
EXPECT_CALL(table, hasState(_)).WillRepeatedly(Return(true));
|
||||
|
||||
HttpTransactionData new_transaction(
|
||||
"1.1",
|
||||
"GET",
|
||||
"ffff",
|
||||
IPAddr::createIPAddr("0.0.0.0").unpack(),
|
||||
80,
|
||||
"d%20d?g=%23",
|
||||
IPAddr::createIPAddr("1.1.1.1").unpack(),
|
||||
5428
|
||||
);
|
||||
|
||||
EXPECT_THAT(NewHttpTransactionEvent(new_transaction).query(), ElementsAre(inspect));
|
||||
EXPECT_THAT(HttpRequestHeaderEvent(end_headers).query(), ElementsAre(drop));
|
||||
EXPECT_THAT(EndRequestEvent().query(), ElementsAre(drop));
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, check_query_detect_mode)
|
||||
{
|
||||
string config =
|
||||
"{"
|
||||
"\"IPS\": {"
|
||||
"\"protections\": ["
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"d d\\\";\","
|
||||
"\"context\": [\"HTTP_QUERY_DECODED\"]"
|
||||
"}"
|
||||
"}"
|
||||
"],"
|
||||
"\"IpsProtections\": ["
|
||||
"{"
|
||||
"\"context\": \"\","
|
||||
"\"ruleName\": \"rule1\","
|
||||
"\"assetName\": \"asset1\","
|
||||
"\"assetId\": \"1-1-1\","
|
||||
"\"practiceId\": \"2-2-2\","
|
||||
"\"practiceName\": \"practice1\","
|
||||
"\"defaultAction\": \"Detect\","
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Detect\","
|
||||
"\"severityLevel\": \"Low or above\","
|
||||
"\"performanceImpact\": \"High or lower\","
|
||||
"\"confidenceLevel\": \"Low\""
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"}";
|
||||
loadPolicy(config);
|
||||
|
||||
EXPECT_CALL(table, createStateRValueRemoved(_, _));
|
||||
EXPECT_CALL(table, getState(_)).WillOnce(Return(&entry));
|
||||
|
||||
HttpTransactionData new_transaction(
|
||||
"1.1",
|
||||
"GET",
|
||||
"ffff",
|
||||
IPAddr::createIPAddr("0.0.0.0").unpack(),
|
||||
80,
|
||||
"d%20d",
|
||||
IPAddr::createIPAddr("1.1.1.1").unpack(),
|
||||
5428
|
||||
);
|
||||
|
||||
EXPECT_THAT(NewHttpTransactionEvent(new_transaction).query(), ElementsAre(inspect));
|
||||
EXPECT_THAT(EndTransactionEvent().query(), ElementsAre(accept));
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, check_query_inactive_mode)
|
||||
{
|
||||
string config =
|
||||
"{"
|
||||
"\"IPS\": {"
|
||||
"\"protections\": ["
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"g=#\\\";\","
|
||||
"\"context\": [\"HTTP_QUERY_DECODED\"]"
|
||||
"}"
|
||||
"}"
|
||||
"],"
|
||||
"\"IpsProtections\": ["
|
||||
"{"
|
||||
"\"context\": \"\","
|
||||
"\"ruleName\": \"rule1\","
|
||||
"\"assetName\": \"asset1\","
|
||||
"\"assetId\": \"1-1-1\","
|
||||
"\"practiceId\": \"2-2-2\","
|
||||
"\"practiceName\": \"practice1\","
|
||||
"\"defaultAction\": \"Prevent\","
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Inactive\","
|
||||
"\"severityLevel\": \"Low or above\","
|
||||
"\"performanceImpact\": \"High or lower\","
|
||||
"\"confidenceLevel\": \"Low\""
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"}";
|
||||
loadPolicy(config);
|
||||
|
||||
HttpTransactionData new_transaction(
|
||||
"1.1",
|
||||
"GET",
|
||||
"ffff",
|
||||
IPAddr::createIPAddr("0.0.0.0").unpack(),
|
||||
80,
|
||||
"d%20d?g=%23",
|
||||
IPAddr::createIPAddr("1.1.1.1").unpack(),
|
||||
5428
|
||||
);
|
||||
|
||||
EXPECT_THAT(NewHttpTransactionEvent(new_transaction).query(), ElementsAre(accept));
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, check_query_silent_mode)
|
||||
{
|
||||
string config =
|
||||
"{"
|
||||
"\"IPS\": {"
|
||||
"\"protections\": ["
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"silent\": true,"
|
||||
"\"protectionName\": \"Test\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"g=#\\\";\","
|
||||
"\"context\": [\"HTTP_QUERY_DECODED\"]"
|
||||
"}"
|
||||
"}"
|
||||
"],"
|
||||
"\"IpsProtections\": ["
|
||||
"{"
|
||||
"\"context\": \"\","
|
||||
"\"ruleName\": \"rule1\","
|
||||
"\"assetName\": \"asset1\","
|
||||
"\"assetId\": \"1-1-1\","
|
||||
"\"practiceId\": \"2-2-2\","
|
||||
"\"practiceName\": \"practice1\","
|
||||
"\"defaultAction\": \"Prevent\","
|
||||
"\"rules\": []"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"}";
|
||||
loadPolicy(config);
|
||||
|
||||
HttpTransactionData new_transaction(
|
||||
"1.1",
|
||||
"GET",
|
||||
"ffff",
|
||||
IPAddr::createIPAddr("0.0.0.0").unpack(),
|
||||
80,
|
||||
"d%20d?g=%23",
|
||||
IPAddr::createIPAddr("1.1.1.1").unpack(),
|
||||
5428
|
||||
);
|
||||
|
||||
EXPECT_CALL(table, createStateRValueRemoved(_, _));
|
||||
EXPECT_CALL(table, getState(_)).WillRepeatedly(Return(&entry));
|
||||
EXPECT_CALL(table, hasState(_)).WillRepeatedly(Return(true));
|
||||
|
||||
EXPECT_THAT(NewHttpTransactionEvent(new_transaction).query(), ElementsAre(inspect));
|
||||
EXPECT_THAT(HttpRequestHeaderEvent(end_headers).query(), ElementsAre(inspect));
|
||||
EXPECT_THAT(EndRequestEvent().query(), ElementsAre(accept));
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, check_filtering_by_year)
|
||||
{
|
||||
string config =
|
||||
"{"
|
||||
"\"IPS\": {"
|
||||
"\"protections\": ["
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [ \"ggg\", \"Threat_Year_2014\", \"hhh\" ],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"g=#\\\";\","
|
||||
"\"context\": [\"HTTP_QUERY_DECODED\"]"
|
||||
"}"
|
||||
"}"
|
||||
"],"
|
||||
"\"IpsProtections\": ["
|
||||
"{"
|
||||
"\"context\": \"\","
|
||||
"\"ruleName\": \"rule1\","
|
||||
"\"assetName\": \"asset1\","
|
||||
"\"assetId\": \"1-1-1\","
|
||||
"\"practiceId\": \"2-2-2\","
|
||||
"\"practiceName\": \"practice1\","
|
||||
"\"defaultAction\": \"Prevent\","
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Inactive\","
|
||||
"\"protectionsFromYear\": 2013"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"}";
|
||||
loadPolicy(config);
|
||||
|
||||
HttpTransactionData new_transaction(
|
||||
"1.1",
|
||||
"GET",
|
||||
"ffff",
|
||||
IPAddr::createIPAddr("0.0.0.0").unpack(),
|
||||
80,
|
||||
"d%20d?g=%23",
|
||||
IPAddr::createIPAddr("1.1.1.1").unpack(),
|
||||
5428
|
||||
);
|
||||
|
||||
EXPECT_THAT(NewHttpTransactionEvent(new_transaction).query(), ElementsAre(accept));
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, log_fields)
|
||||
{
|
||||
string config =
|
||||
"{"
|
||||
"\"IPS\": {"
|
||||
"\"Max Field Size\": ["
|
||||
"{"
|
||||
"\"value\": 25"
|
||||
"}"
|
||||
"],"
|
||||
"\"protections\": ["
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"ddd\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\"]"
|
||||
"}"
|
||||
"}"
|
||||
"],"
|
||||
"\"IpsProtections\": ["
|
||||
"{"
|
||||
"\"context\": \"\","
|
||||
"\"ruleName\": \"rule1\","
|
||||
"\"assetName\": \"asset1\","
|
||||
"\"assetId\": \"1-1-1\","
|
||||
"\"practiceId\": \"2-2-2\","
|
||||
"\"practiceName\": \"practice1\","
|
||||
"\"defaultAction\": \"Detect\","
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Prevent\","
|
||||
"\"severityLevel\": \"Low or above\","
|
||||
"\"performanceImpact\": \"High or lower\","
|
||||
"\"confidenceLevel\": \"Low\""
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"}";
|
||||
loadPolicy(config);
|
||||
setTrigger();
|
||||
|
||||
EXPECT_CALL(table, createStateRValueRemoved(_, _));
|
||||
EXPECT_CALL(table, getState(_)).WillRepeatedly(Return(&entry));
|
||||
EXPECT_CALL(table, hasState(_)).WillRepeatedly(Return(true));
|
||||
|
||||
Report report;
|
||||
EXPECT_CALL(logs, sendLog(_)).WillOnce(SaveArg<0>(&report));
|
||||
|
||||
HttpTransactionData new_transaction(
|
||||
"1.1",
|
||||
"POST",
|
||||
"ffff",
|
||||
IPAddr::createIPAddr("0.0.0.0").unpack(),
|
||||
80,
|
||||
"d%20d?g=%23",
|
||||
IPAddr::createIPAddr("1.1.1.1").unpack(),
|
||||
5428
|
||||
);
|
||||
|
||||
EXPECT_THAT(NewHttpTransactionEvent(new_transaction).query(), ElementsAre(inspect));
|
||||
HttpHeader header_req1(Buffer("key1"), Buffer("val1"), 1);
|
||||
EXPECT_THAT(HttpRequestHeaderEvent(header_req1).query(), ElementsAre(inspect));
|
||||
HttpHeader header_req2(Buffer("key2"), Buffer("val2"), 2);
|
||||
EXPECT_THAT(HttpRequestHeaderEvent(header_req2).query(), ElementsAre(inspect));
|
||||
HttpHeader header_req3(Buffer("key3"), Buffer("val3"), 3);
|
||||
EXPECT_THAT(HttpRequestHeaderEvent(header_req3).query(), ElementsAre(inspect));
|
||||
string body_str("data: ddd");
|
||||
HttpBody body_req(Buffer(body_str), 0, true);
|
||||
EXPECT_THAT(HttpRequestBodyEvent(body_req, Buffer()).query(), ElementsAre(inspect));
|
||||
EXPECT_THAT(EndRequestEvent().query(), ElementsAre(drop));
|
||||
|
||||
EXPECT_THAT(report.getSyslog(), HasSubstr("httpRequestHeaders=\"key1: val1, key2: val2\""));
|
||||
EXPECT_THAT(report.getSyslog(), HasSubstr("httpRequestBody=\"" + body_str + "\""));
|
||||
EXPECT_THAT(report.getSyslog(), HasSubstr("signatureVersion=\"20210420\""));
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, log_field_httpRequestHeader)
|
||||
{
|
||||
string config =
|
||||
"{"
|
||||
"\"IPS\": {"
|
||||
"\"Max Field Size\": ["
|
||||
"{"
|
||||
"\"value\": 25"
|
||||
"}"
|
||||
"],"
|
||||
"\"protections\": ["
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"ddd\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\"]"
|
||||
"}"
|
||||
"}"
|
||||
"],"
|
||||
"\"IpsProtections\": ["
|
||||
"{"
|
||||
"\"context\": \"\","
|
||||
"\"ruleName\": \"rule1\","
|
||||
"\"assetName\": \"asset1\","
|
||||
"\"assetId\": \"1-1-1\","
|
||||
"\"practiceId\": \"2-2-2\","
|
||||
"\"practiceName\": \"practice1\","
|
||||
"\"defaultAction\": \"Detect\","
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Prevent\","
|
||||
"\"severityLevel\": \"Low or above\","
|
||||
"\"performanceImpact\": \"High or lower\","
|
||||
"\"confidenceLevel\": \"Low\""
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"}";
|
||||
loadPolicy(config);
|
||||
setTrigger();
|
||||
|
||||
EXPECT_CALL(table, createStateRValueRemoved(_, _));
|
||||
IPSEntry entry;
|
||||
EXPECT_CALL(table, getState(_)).WillRepeatedly(Return(&entry));
|
||||
EXPECT_CALL(table, hasState(_)).WillRepeatedly(Return(true));
|
||||
|
||||
Report report;
|
||||
EXPECT_CALL(logs, sendLog(_)).WillOnce(SaveArg<0>(&report));
|
||||
|
||||
HttpTransactionData new_transaction(
|
||||
"1.1",
|
||||
"POST",
|
||||
"ffff",
|
||||
IPAddr::createIPAddr("0.0.0.0").unpack(),
|
||||
80,
|
||||
"d%20d?g=%23",
|
||||
IPAddr::createIPAddr("1.1.1.1").unpack(),
|
||||
5428
|
||||
);
|
||||
|
||||
EXPECT_THAT(NewHttpTransactionEvent(new_transaction).query(), ElementsAre(inspect));
|
||||
HttpHeader header_req1(Buffer("key1"), Buffer("val1"), 1);
|
||||
EXPECT_THAT(HttpRequestHeaderEvent(header_req1).query(), ElementsAre(inspect));
|
||||
HttpBody body_req(Buffer("data: ddd"), 0, true);
|
||||
EXPECT_THAT(HttpRequestBodyEvent(body_req, Buffer()).query(), ElementsAre(inspect));
|
||||
EXPECT_THAT(EndRequestEvent().query(), ElementsAre(drop));
|
||||
|
||||
EXPECT_THAT(report.getSyslog(), HasSubstr("httpRequestHeaders=\"key1: val1\""));
|
||||
|
||||
EXPECT_CALL(table, createStateRValueRemoved(_, _));
|
||||
IPSEntry entry1;
|
||||
EXPECT_CALL(table, getState(_)).WillRepeatedly(Return(&entry1));
|
||||
Report report1;
|
||||
EXPECT_CALL(logs, sendLog(_)).WillOnce(SaveArg<0>(&report1));
|
||||
|
||||
HttpTransactionData new_transaction2(
|
||||
"1.1",
|
||||
"POST",
|
||||
"ffff",
|
||||
IPAddr::createIPAddr("0.0.0.0").unpack(),
|
||||
80,
|
||||
"d%20d?g=%23",
|
||||
IPAddr::createIPAddr("1.1.1.1").unpack(),
|
||||
5428
|
||||
);
|
||||
|
||||
EXPECT_THAT(NewHttpTransactionEvent(new_transaction2).query(), ElementsAre(inspect));
|
||||
HttpHeader header_req2(Buffer("key2"), Buffer("val2"), 1);
|
||||
EXPECT_THAT(HttpRequestHeaderEvent(header_req2).query(), ElementsAre(inspect));
|
||||
|
||||
HttpBody body_req2(Buffer("data: ddd"), 0, true);
|
||||
EXPECT_THAT(HttpRequestBodyEvent(body_req2, Buffer()).query(), ElementsAre(inspect));
|
||||
EXPECT_THAT(EndRequestEvent().query(), ElementsAre(drop));
|
||||
|
||||
EXPECT_THAT(report1.getSyslog(), HasSubstr("httpRequestHeaders=\"key2: val2\""));
|
||||
}
|
||||
|
||||
TEST_F(ComponentTest, prxeem_exception_bug)
|
||||
{
|
||||
generic_rulebase.preload();
|
||||
generic_rulebase.init();
|
||||
string config =
|
||||
"{"
|
||||
" \"IPS\": {"
|
||||
" \"protections\": ["
|
||||
" {"
|
||||
" \"protectionMetadata\": {"
|
||||
" \"protectionName\": \"Null HTTP Encodings\","
|
||||
" \"maintrainId\": \"101\","
|
||||
" \"severity\": \"Low\","
|
||||
" \"confidenceLevel\": \"Low\","
|
||||
" \"performanceImpact\": \"Medium High\","
|
||||
" \"lastUpdate\": \"20210420\","
|
||||
" \"tags\": [],"
|
||||
" \"cveList\": []"
|
||||
" },"
|
||||
" \"detectionRules\": {"
|
||||
" \"type\": \"simple\","
|
||||
" \"SSM\": \"\","
|
||||
" \"keywords\": \"data: \\\"|25|00\\\"; data: \\\"?\\\";\","
|
||||
" \"context\": [\"HTTP_COMPLETE_URL_ENCODED\"]"
|
||||
" }"
|
||||
" }"
|
||||
" ],"
|
||||
" \"IpsProtections\": ["
|
||||
" {"
|
||||
" \"context\": \"\","
|
||||
" \"ruleName\": \"rule1\","
|
||||
" \"assetName\": \"asset1\","
|
||||
" \"assetId\": \"1-1-1\","
|
||||
" \"practiceId\": \"2-2-2\","
|
||||
" \"practiceName\": \"practice1\","
|
||||
" \"defaultAction\": \"Prevent\","
|
||||
" \"rules\": []"
|
||||
" }"
|
||||
" ]"
|
||||
" },"
|
||||
" \"rulebase\": {"
|
||||
" \"rulesConfig\": ["
|
||||
" {"
|
||||
" \"context\": \"All()\","
|
||||
" \"priority\": 1,"
|
||||
" \"ruleId\": \"5eaef0726765c30010bae8bb\","
|
||||
" \"ruleName\": \"Acme web API\","
|
||||
" \"assetId\": \"5e243effd858007660b758ad\","
|
||||
" \"assetName\": \"Acme Power API\","
|
||||
" \"parameters\": ["
|
||||
" {"
|
||||
" \"parameterId\": \"6c3867be-4da5-42c2-93dc-8f509a764003\","
|
||||
" \"parameterType\": \"exceptions\","
|
||||
" \"parameterName\": \"exception\""
|
||||
" }"
|
||||
" ],"
|
||||
" \"zoneId\": \"\","
|
||||
" \"zoneName\": \"\""
|
||||
" }"
|
||||
" ],"
|
||||
" \"exception\": ["
|
||||
" {"
|
||||
" \"context\": \"parameterId(6c3867be-4da5-42c2-93dc-8f509a764003)\","
|
||||
" \"match\": {"
|
||||
" \"type\": \"operator\","
|
||||
" \"op\": \"and\","
|
||||
" \"items\": [{"
|
||||
" \"type\": \"condition\","
|
||||
" \"op\": \"equals\","
|
||||
" \"key\": \"url\","
|
||||
" \"value\": [\"(/en|/de)?/admin/helpdesk/dashboard/operator/advanced_search.*\"]"
|
||||
" }, {"
|
||||
" \"type\": \"operator\","
|
||||
" \"op\": \"or\","
|
||||
" \"items\": [{"
|
||||
" \"type\": \"condition\","
|
||||
" \"op\": \"equals\","
|
||||
" \"key\": \"protectionName\","
|
||||
" \"value\": [\"Null HTTP Encodings\"]"
|
||||
" }, {"
|
||||
" \"type\": \"condition\","
|
||||
" \"op\": \"equals\","
|
||||
" \"key\": \"parameterName\","
|
||||
" \"value\": [\"op\\\\.submit\\\\.reset\"]"
|
||||
" }]"
|
||||
" }]"
|
||||
" },"
|
||||
" \"behavior\": {"
|
||||
" \"key\": \"action\","
|
||||
" \"value\": \"accept\""
|
||||
" }"
|
||||
" }"
|
||||
" ]"
|
||||
" }"
|
||||
"}";
|
||||
loadPolicy(config);
|
||||
|
||||
|
||||
EXPECT_CALL(table, createStateRValueRemoved(_, _));
|
||||
IPSEntry entry;
|
||||
EXPECT_CALL(table, getState(_)).WillRepeatedly(Return(&entry));
|
||||
EXPECT_CALL(table, hasState(_)).WillRepeatedly(Return(true));
|
||||
|
||||
HttpTransactionData new_transaction(
|
||||
"1.1",
|
||||
"POST",
|
||||
"ffff",
|
||||
IPAddr::createIPAddr("0.0.0.0").unpack(),
|
||||
80,
|
||||
"/admin/helpdesk/dashboard/operator/advanced_search?order=created&stuff=%00",
|
||||
IPAddr::createIPAddr("1.1.1.1").unpack(),
|
||||
5428
|
||||
);
|
||||
|
||||
EXPECT_THAT(NewHttpTransactionEvent(new_transaction).query(), ElementsAre(inspect));
|
||||
HttpHeader header_req1(Buffer("key1"), Buffer("val1"), 0, true);
|
||||
EXPECT_THAT(HttpRequestHeaderEvent(header_req1).query(), ElementsAre(inspect));
|
||||
}
|
155
components/security_apps/ips/ips_ut/compound_ut.cc
Normal file
155
components/security_apps/ips/ips_ut/compound_ut.cc
Normal file
@@ -0,0 +1,155 @@
|
||||
#include "compound_protection.h"
|
||||
|
||||
#include "cptest.h"
|
||||
#include "ips_entry.h"
|
||||
#include "mock/mock_table.h"
|
||||
#include "environment.h"
|
||||
#include "i_keywords_rule.h"
|
||||
|
||||
using namespace testing;
|
||||
using namespace std;
|
||||
|
||||
class CompoundTest : public Test
|
||||
{
|
||||
public:
|
||||
CompoundTest()
|
||||
{
|
||||
ON_CALL(table, hasState(_)).WillByDefault(Return(true));
|
||||
ON_CALL(table, getState(_)).WillByDefault(Return(&ips_state));
|
||||
}
|
||||
|
||||
template <typename ... PatternContextPair>
|
||||
shared_ptr<IPSSignatureSubTypes::BaseSignature>
|
||||
loadSig(const string &name, const string &operation, PatternContextPair ... pat_ctx)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "{"
|
||||
<< "\"type\": \"compound\","
|
||||
<< "\"operation\": \"" << operation << "\","
|
||||
<< "\"operands\": [";
|
||||
getSginatureStream(ss, pat_ctx ...);
|
||||
ss << "]" << "}";
|
||||
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
|
||||
return CompoundProtection::get(name, ar);
|
||||
}
|
||||
|
||||
template <typename ... Strings>
|
||||
set<PMPattern>
|
||||
turnToPatternSet(const Strings & ... strings)
|
||||
{
|
||||
pat_set.clear();
|
||||
populatePatternSet(strings ...);
|
||||
return pat_set;
|
||||
}
|
||||
|
||||
void setActiveContext(const string &name) { ctx.registerValue(I_KeywordsRule::getKeywordsRuleTag(), name); }
|
||||
|
||||
private:
|
||||
void populatePatternSet() {}
|
||||
|
||||
template <typename ... Strings>
|
||||
void
|
||||
populatePatternSet(const string &pat, const Strings & ... strings)
|
||||
{
|
||||
pat_set.emplace(pat, false, false);
|
||||
populatePatternSet(strings ...);
|
||||
}
|
||||
|
||||
ostream &
|
||||
getSginatureStream(ostream &ss, const string &pattern, const string &context)
|
||||
{
|
||||
ss << "{"
|
||||
<< "\"type\": \"simple\","
|
||||
<< "\"SSM\": \"" << pattern << "\","
|
||||
<< "\"keywords\": \"\","
|
||||
<< "\"context\": ["
|
||||
<< "\"" << context << "\""
|
||||
<< "]"
|
||||
<< "}";
|
||||
return ss;
|
||||
}
|
||||
|
||||
template <typename ... PatternContextPair>
|
||||
ostream &
|
||||
getSginatureStream(ostream &ss, const string &pattern, const string &context, PatternContextPair ... pat_ctx)
|
||||
{
|
||||
return getSginatureStream(getSginatureStream(ss, pattern, context) << ",", pat_ctx ...);
|
||||
}
|
||||
|
||||
NiceMock<MockTable> table;
|
||||
IPSEntry ips_state;
|
||||
set<PMPattern> pat_set;
|
||||
::Environment env;
|
||||
ScopedContext ctx;
|
||||
};
|
||||
|
||||
TEST_F(CompoundTest, BasicLoading)
|
||||
{
|
||||
auto sig = loadSig("Test", "and", "aaa", "HTTP_REQUEST_DATA", "bbb", "HTTP_RESPONSE_DATA");
|
||||
EXPECT_NE(sig, nullptr);
|
||||
EXPECT_EQ(sig->getSigId(), "Test");
|
||||
EXPECT_THAT(sig->getContext(), ElementsAre("HTTP_REQUEST_DATA", "HTTP_RESPONSE_DATA"));
|
||||
EXPECT_EQ(sig->patternsInSignature(), turnToPatternSet("aaa", "bbb"));
|
||||
}
|
||||
|
||||
TEST_F(CompoundTest, BasicOrTest)
|
||||
{
|
||||
auto sig = loadSig("Test", "or", "aaa", "HTTP_REQUEST_DATA", "bbb", "HTTP_RESPONSE_DATA");
|
||||
|
||||
setActiveContext("NO_CONTEXT");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("aaa")), IPSSignatureSubTypes::BaseSignature::MatchType::NO_MATCH);
|
||||
setActiveContext("HTTP_REQUEST_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("aaa")), IPSSignatureSubTypes::BaseSignature::MatchType::MATCH);
|
||||
setActiveContext("HTTP_REQUEST_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("ddd")), IPSSignatureSubTypes::BaseSignature::MatchType::CACHE_MATCH);
|
||||
}
|
||||
|
||||
TEST_F(CompoundTest, BasicOrOrderTest)
|
||||
{
|
||||
auto sig = loadSig("Test", "or", "aaa", "HTTP_REQUEST_DATA", "bbb", "HTTP_RESPONSE_DATA");
|
||||
|
||||
setActiveContext("HTTP_RESPONSE_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("bbb")), IPSSignatureSubTypes::BaseSignature::MatchType::MATCH);
|
||||
}
|
||||
|
||||
TEST_F(CompoundTest, BasicAndTest)
|
||||
{
|
||||
auto sig = loadSig("Test", "and", "aaa", "HTTP_REQUEST_DATA", "bbb", "HTTP_RESPONSE_DATA");
|
||||
|
||||
setActiveContext("HTTP_REQUEST_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("aaa")), IPSSignatureSubTypes::BaseSignature::MatchType::NO_MATCH);
|
||||
setActiveContext("HTTP_RESPONSE_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("bbb")), IPSSignatureSubTypes::BaseSignature::MatchType::MATCH);
|
||||
}
|
||||
|
||||
TEST_F(CompoundTest, BasicAndOrderTest)
|
||||
{
|
||||
auto sig = loadSig("Test", "and", "aaa", "HTTP_REQUEST_DATA", "bbb", "HTTP_RESPONSE_DATA");
|
||||
|
||||
setActiveContext("HTTP_RESPONSE_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("bbb")), IPSSignatureSubTypes::BaseSignature::MatchType::NO_MATCH);
|
||||
setActiveContext("HTTP_REQUEST_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("aaa")), IPSSignatureSubTypes::BaseSignature::MatchType::MATCH);
|
||||
}
|
||||
|
||||
TEST_F(CompoundTest, BasicOrderedAndTest)
|
||||
{
|
||||
auto sig = loadSig("Test", "ordered_and", "aaa", "HTTP_REQUEST_DATA", "bbb", "HTTP_RESPONSE_DATA");
|
||||
|
||||
setActiveContext("HTTP_REQUEST_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("aaa")), IPSSignatureSubTypes::BaseSignature::MatchType::NO_MATCH);
|
||||
setActiveContext("HTTP_RESPONSE_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("bbb")), IPSSignatureSubTypes::BaseSignature::MatchType::MATCH);
|
||||
}
|
||||
|
||||
TEST_F(CompoundTest, BasicOrderedAndOrderTest)
|
||||
{
|
||||
auto sig = loadSig("Test", "ordered_and", "aaa", "HTTP_REQUEST_DATA", "bbb", "HTTP_RESPONSE_DATA");
|
||||
|
||||
setActiveContext("HTTP_RESPONSE_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("bbb")), IPSSignatureSubTypes::BaseSignature::MatchType::NO_MATCH);
|
||||
setActiveContext("HTTP_REQUEST_DATA");
|
||||
EXPECT_EQ(sig->getMatch(turnToPatternSet("aaa")), IPSSignatureSubTypes::BaseSignature::MatchType::NO_MATCH);
|
||||
}
|
55
components/security_apps/ips/ips_ut/configuration.cc
Normal file
55
components/security_apps/ips/ips_ut/configuration.cc
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "ips_configuration.h"
|
||||
#include "cptest.h"
|
||||
|
||||
TEST(configuration, basic_context)
|
||||
{
|
||||
cptestPrepareToDie();
|
||||
|
||||
IPSConfiguration::Context ctx1(IPSConfiguration::ContextType::HISTORY, 254);
|
||||
EXPECT_EQ(ctx1.getType(), IPSConfiguration::ContextType::HISTORY);
|
||||
EXPECT_EQ(ctx1.getHistorySize(), 254);
|
||||
|
||||
IPSConfiguration::Context ctx2(IPSConfiguration::ContextType::NORMAL, 0);
|
||||
EXPECT_EQ(ctx2.getType(), IPSConfiguration::ContextType::NORMAL);
|
||||
EXPECT_DEATH(ctx2.getHistorySize(), "Try to access history size for non-history context");
|
||||
}
|
||||
|
||||
|
||||
TEST(configuration, read_configuration)
|
||||
{
|
||||
cptestPrepareToDie();
|
||||
|
||||
std::stringstream conf_str;
|
||||
conf_str <<
|
||||
"{"
|
||||
"\"contextsConfiguration\": ["
|
||||
"{"
|
||||
"\"name\": \"HTTP_REQUEST_BODY\","
|
||||
"\"type\": \"history\","
|
||||
"\"historySize\": 100"
|
||||
"},"
|
||||
"{"
|
||||
"\"name\": \"HTTP_REQUEST_HEADER\","
|
||||
"\"type\": \"keep\""
|
||||
"}"
|
||||
"]"
|
||||
"}";
|
||||
|
||||
cereal::JSONInputArchive ar(conf_str);
|
||||
|
||||
IPSConfiguration conf;
|
||||
conf.load(ar);
|
||||
|
||||
auto body = conf.getContext("HTTP_REQUEST_BODY");
|
||||
EXPECT_EQ(body.getType(), IPSConfiguration::ContextType::HISTORY);
|
||||
EXPECT_EQ(conf.getHistorySize("HTTP_REQUEST_BODY"), 100);
|
||||
|
||||
auto header = conf.getContext("HTTP_REQUEST_HEADER");
|
||||
EXPECT_EQ(header.getType(), IPSConfiguration::ContextType::KEEP);
|
||||
EXPECT_DEATH(conf.getHistorySize("HTTP_REQUEST_HEADER"), "Try to access history size for non-history context");
|
||||
|
||||
auto line = conf.getContext("HTTP_REQUEST_LINE");
|
||||
EXPECT_EQ(line.getType(), IPSConfiguration::ContextType::NORMAL);
|
||||
|
||||
EXPECT_DEATH(conf.getHistorySize("NO_CONTEXT"), "Try to access history size for non-exiting context");
|
||||
}
|
268
components/security_apps/ips/ips_ut/entry_ut.cc
Normal file
268
components/security_apps/ips/ips_ut/entry_ut.cc
Normal file
@@ -0,0 +1,268 @@
|
||||
#include "ips_entry.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "ips_signatures.h"
|
||||
#include "cptest.h"
|
||||
#include "keyword_comp.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "environment.h"
|
||||
#include "agent_details.h"
|
||||
#include "mock/mock_logging.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_table.h"
|
||||
#include "generic_rulebase/generic_rulebase.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
ostream &
|
||||
operator<<(ostream &os, const ParsedContextReply &action)
|
||||
{
|
||||
return os << (action==ParsedContextReply::ACCEPT ? "ACCEPT" : "DROP");
|
||||
}
|
||||
|
||||
class MockAgg : Singleton::Provide<I_FirstTierAgg>::SelfInterface
|
||||
{
|
||||
shared_ptr<PMHook>
|
||||
getHook(const string &, const set<PMPattern> &pats) override
|
||||
{
|
||||
auto hook = make_shared<PMHook>();
|
||||
hook->prepare(pats);
|
||||
return hook;
|
||||
}
|
||||
};
|
||||
|
||||
class EntryTest : public Test
|
||||
{
|
||||
public:
|
||||
EntryTest()
|
||||
{
|
||||
ON_CALL(table, getState(_)).WillByDefault(Return(ptr));
|
||||
}
|
||||
|
||||
void
|
||||
loadSignatures(const string &sigs)
|
||||
{
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "[" << sigs << "]";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
IPSSignaturesResource resource;
|
||||
resource.load(ar);
|
||||
setResource(resource, "IPS", "protections");
|
||||
}
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "{";
|
||||
ss << "\"context\": \"\",";
|
||||
ss << "\"ruleName\": \"rule1\",";
|
||||
ss << "\"assetName\": \"asset1\",";
|
||||
ss << "\"assetId\": \"1-1-1\",";
|
||||
ss << "\"practiceId\": \"2-2-2\",";
|
||||
ss << "\"practiceName\": \"practice1\",";
|
||||
ss << "\"defaultAction\": \"Detect\",";
|
||||
ss << "\"rules\": [";
|
||||
ss << "{";
|
||||
ss << "\"action\": \"Prevent\",";
|
||||
ss << "\"performanceImpact\": \"High or lower\",";
|
||||
ss << "\"severityLevel\": \"Low or above\",";
|
||||
ss << "\"confidenceLevel\": \"Low\"";
|
||||
ss << "}";
|
||||
ss << "]";
|
||||
ss << "}";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
IPSSignatures signatures;
|
||||
signatures.load(ar);
|
||||
setConfiguration(signatures, "IPS", "IpsProtections");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
loadSnortSignatures(const string &sigs)
|
||||
{
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "[{ \"modificationTime\": \"22/02/08\", \"name\": \"rules1\", \"protections\": [" << sigs << "] }]";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
SnortSignaturesResource resource;
|
||||
resource.load(ar);
|
||||
setResource(resource, "IPSSnortSigs", "protections");
|
||||
}
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "{";
|
||||
ss << "\"context\": \"\",";
|
||||
ss << "\"assetName\": \"asset1\",";
|
||||
ss << "\"assetId\": \"1-1-1\",";
|
||||
ss << "\"practiceId\": \"2-2-2\",";
|
||||
ss << "\"practiceName\": \"practice1\",";
|
||||
ss << "\"files\": [ \"rules1\" ],";
|
||||
ss << "\"mode\": \"Prevent\"";
|
||||
ss << "}";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
SnortSignatures signatures;
|
||||
signatures.load(ar);
|
||||
setConfiguration(signatures, "IPSSnortSigs", "SnortProtections");
|
||||
}
|
||||
}
|
||||
|
||||
ParsedContextReply
|
||||
repondToContext(const string &buf_str, const string &name)
|
||||
{
|
||||
Buffer buf(buf_str);
|
||||
ScopedContext ctx;
|
||||
ctx.registerValue(name, buf);
|
||||
return entry.respond(ParsedContext(buf, name, 0));
|
||||
}
|
||||
|
||||
IPSEntry entry;
|
||||
TableOpaqueBase *ptr = &entry;
|
||||
|
||||
private:
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
NiceMock<MockTimeGet> time;
|
||||
::Environment env;
|
||||
GenericRulebase generic_rulebase;
|
||||
ConfigComponent conf;
|
||||
KeywordComp keywords;
|
||||
AgentDetails details;
|
||||
NiceMock<MockLogging> logs;
|
||||
NiceMock<MockTable> table;
|
||||
MockAgg mock_agg;
|
||||
};
|
||||
|
||||
TEST_F(EntryTest, basic_inherited_functions)
|
||||
{
|
||||
EXPECT_EQ(IPSEntry::name(), "IPS");
|
||||
EXPECT_EQ(IPSEntry::currVer(), 0);
|
||||
EXPECT_EQ(IPSEntry::minVer(), 0);
|
||||
EXPECT_NE(IPSEntry::prototype(), nullptr);
|
||||
EXPECT_EQ(entry.getListenerName(), IPSEntry::name());
|
||||
|
||||
stringstream ss;
|
||||
{
|
||||
cereal::JSONOutputArchive ar(ss);
|
||||
entry.serialize(ar, 0);
|
||||
}
|
||||
EXPECT_EQ(ss.str(), "");
|
||||
|
||||
// Just make sure it doesn't crush
|
||||
entry.upon(ParsedContext(Buffer(), "Nothing", 0));
|
||||
}
|
||||
|
||||
TEST_F(EntryTest, check_listenning)
|
||||
{
|
||||
EXPECT_TRUE(Listener<ParsedContext>::empty());
|
||||
ptr->uponEnteringContext();
|
||||
EXPECT_FALSE(Listener<ParsedContext>::empty());
|
||||
ptr->uponLeavingContext();
|
||||
EXPECT_TRUE(Listener<ParsedContext>::empty());
|
||||
}
|
||||
|
||||
TEST_F(EntryTest, check_signature_invoking)
|
||||
{
|
||||
EXPECT_EQ(repondToContext("ddd", "HTTP_REQUEST_BODY"), ParsedContextReply::ACCEPT);
|
||||
EXPECT_EQ(repondToContext("ddd", "HTTP_RESPONSE_BODY"), ParsedContextReply::ACCEPT);
|
||||
|
||||
string signature =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test1\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Medium High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"ddd\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
loadSignatures(signature);
|
||||
|
||||
EXPECT_EQ(repondToContext("ddd", "HTTP_REQUEST_BODY"), ParsedContextReply::DROP);
|
||||
EXPECT_EQ(repondToContext("ddd", "HTTP_RESPONSE_BODY"), ParsedContextReply::ACCEPT);
|
||||
}
|
||||
|
||||
TEST_F(EntryTest, check_snort_signature_invoking)
|
||||
{
|
||||
EXPECT_EQ(repondToContext("ddd", "HTTP_REQUEST_BODY"), ParsedContextReply::ACCEPT);
|
||||
EXPECT_EQ(repondToContext("ddd", "HTTP_RESPONSE_BODY"), ParsedContextReply::ACCEPT);
|
||||
|
||||
string signature =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test1\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Medium High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"ddd\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\"]"
|
||||
"}"
|
||||
"},"
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Bad sig\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Medium High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: jjjj;\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
loadSnortSignatures(signature);
|
||||
|
||||
EXPECT_EQ(repondToContext("ddd", "HTTP_REQUEST_BODY"), ParsedContextReply::DROP);
|
||||
EXPECT_EQ(repondToContext("ddd", "HTTP_RESPONSE_BODY"), ParsedContextReply::ACCEPT);
|
||||
}
|
||||
|
||||
TEST_F(EntryTest, flags_test)
|
||||
{
|
||||
EXPECT_FALSE(entry.isFlagSet("CONTEXT_A"));
|
||||
EXPECT_FALSE(entry.isFlagSet("CONTEXT_B"));
|
||||
entry.setFlag("CONTEXT_A");
|
||||
EXPECT_TRUE(entry.isFlagSet("CONTEXT_A"));
|
||||
EXPECT_FALSE(entry.isFlagSet("CONTEXT_B"));
|
||||
entry.unsetFlag("CONTEXT_A");
|
||||
EXPECT_FALSE(entry.isFlagSet("CONTEXT_A"));
|
||||
EXPECT_FALSE(entry.isFlagSet("CONTEXT_B"));
|
||||
}
|
||||
|
||||
TEST_F(EntryTest, get_buffer_test)
|
||||
{
|
||||
repondToContext("ddd", "HTTP_REQUEST_BODY");
|
||||
EXPECT_EQ(entry.getBuffer("HTTP_REQUEST_BODY"), Buffer("ddd"));
|
||||
EXPECT_EQ(entry.getBuffer("HTTP_REQUEST_HEADER"), Buffer());
|
||||
}
|
||||
|
||||
TEST_F(EntryTest, get_and_set_transaction_data)
|
||||
{
|
||||
EXPECT_FALSE(entry.getTransactionData(Buffer("transaction_key")).ok());
|
||||
entry.setTransactionData(Buffer("transaction_key"), Buffer("transaction_value"));
|
||||
ASSERT_TRUE(entry.getTransactionData(Buffer("transaction_key")).ok());
|
||||
EXPECT_EQ(entry.getTransactionData(Buffer("transaction_key")).unpack(), Buffer("transaction_value"));
|
||||
}
|
77
components/security_apps/ips/ips_ut/resource_ut.cc
Normal file
77
components/security_apps/ips/ips_ut/resource_ut.cc
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "ips_signatures.h"
|
||||
#include "cptest.h"
|
||||
#include "environment.h"
|
||||
#include "config_component.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
static const string basic_resource =
|
||||
"{"
|
||||
"\"IPS\": {"
|
||||
"\"VersionId\": \"1234567\","
|
||||
"\"protections\": ["
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Null HTTP Encodings\","
|
||||
"\"severity\": \"Medium\","
|
||||
"\"confidenceLevel\": \"High\","
|
||||
"\"performanceImpact\": \"Medium\","
|
||||
"\"lastUpdate\": \"20130101\","
|
||||
"\"maintrainId\": \"8576967832\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": [],"
|
||||
"\"silent\": false"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"aaaa\","
|
||||
"\"keywords\": \"\","
|
||||
"\"context\": ["
|
||||
"\"HTTP_COMPLETE_URL_ENCODED\""
|
||||
"]"
|
||||
"}"
|
||||
"},"
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Null HTTP Encodings\","
|
||||
"\"severity\": \"Medium\","
|
||||
"\"confidenceLevel\": \"High\","
|
||||
"\"performanceImpact\": \"Medium\","
|
||||
"\"lastUpdate\": \"20130101\","
|
||||
"\"maintrainId\": \"8576967832\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": [],"
|
||||
"\"silent\": false"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"bbbbb\","
|
||||
"\"keywords\": \"\","
|
||||
"\"context\": ["
|
||||
"\"HTTP_COMPLETE_URL_ENCODED\""
|
||||
"]"
|
||||
"}"
|
||||
"}"
|
||||
"]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
TEST(resources, basic_resource)
|
||||
{
|
||||
ConfigComponent conf;
|
||||
::Environment env;
|
||||
|
||||
conf.preload();
|
||||
|
||||
registerExpectedSetting<IPSSignaturesResource>("IPS", "protections");
|
||||
registerExpectedSetting<string>("IPS", "VersionId");
|
||||
stringstream resource;
|
||||
resource << basic_resource;
|
||||
Singleton::Consume<Config::I_Config>::from(conf)->loadConfiguration(resource);
|
||||
|
||||
auto loaded_resources = getSettingWithDefault(IPSSignaturesResource(), "IPS", "protections");
|
||||
EXPECT_EQ(loaded_resources.getSignatures().size(), 2);
|
||||
auto version = getSettingWithDefault<string>("", "IPS", "VersionId");
|
||||
EXPECT_EQ(version, "1234567");
|
||||
}
|
163
components/security_apps/ips/ips_ut/rule_selector_ut.cc
Normal file
163
components/security_apps/ips/ips_ut/rule_selector_ut.cc
Normal file
@@ -0,0 +1,163 @@
|
||||
#include <sstream>
|
||||
#include "ips_enums.h"
|
||||
#include "ips_basic_policy.h"
|
||||
#include "cptest.h"
|
||||
#include "config.h"
|
||||
|
||||
using namespace testing;
|
||||
using namespace std;
|
||||
|
||||
ostream &
|
||||
operator<<(ostream &os, const RuleSelector &selector)
|
||||
{
|
||||
selector.print(os);
|
||||
return os;
|
||||
}
|
||||
|
||||
class RuleSelectorTest : public Test
|
||||
{
|
||||
public:
|
||||
void
|
||||
load(const string &config)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << config;
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
|
||||
ruleSelector.load(ar);
|
||||
}
|
||||
|
||||
string protection =
|
||||
"{"
|
||||
"\"defaultAction\": \"Prevent\","
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Detect\","
|
||||
"\"performanceImpact\": \"Medium or lower\","
|
||||
"\"severityLevel\": \"Low or above\","
|
||||
"\"confidenceLevel\": \"Medium\","
|
||||
"\"serverProtections\": false,"
|
||||
"\"clientProtections\": true,"
|
||||
"\"protectionsFromYear\": 2020,"
|
||||
"\"protectionTags\": ["
|
||||
"\"tag1\","
|
||||
"\"tag2\""
|
||||
"],"
|
||||
"\"protectionIds\": ["
|
||||
"\"id1\","
|
||||
"\"id2\""
|
||||
"]"
|
||||
"},"
|
||||
"{"
|
||||
"\"action\": \"Prevent\","
|
||||
"\"performanceImpact\": \"Very low\","
|
||||
"\"severityLevel\": \"Medium or above\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"serverProtections\": true,"
|
||||
"\"clientProtections\": false,"
|
||||
"\"protectionsFromYear\": 1999,"
|
||||
"\"protectionTags\": ["
|
||||
"\"tag11\","
|
||||
"\"tag22\""
|
||||
"],"
|
||||
"\"protectionIds\": ["
|
||||
"\"id11\","
|
||||
"\"id22\""
|
||||
"]"
|
||||
"}"
|
||||
"]"
|
||||
"}";
|
||||
|
||||
string protection2 =
|
||||
"{"
|
||||
"\"defaultAction\": \"Inactive\","
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Detect\","
|
||||
"\"performanceImpact\": \"Medium or lower\","
|
||||
"\"severityLevel\": \"Low or above\","
|
||||
"\"confidenceLevel\": \"Medium\""
|
||||
"},"
|
||||
"{"
|
||||
"\"action\": \"Prevent\""
|
||||
"}"
|
||||
"]"
|
||||
"}";
|
||||
|
||||
string protection3 =
|
||||
"{"
|
||||
"\"defaultAction\": \"Prevent\","
|
||||
"\"rules\": []"
|
||||
"}";
|
||||
|
||||
string protection4 =
|
||||
"{"
|
||||
"\"rules\": ["
|
||||
"{"
|
||||
"\"action\": \"Detect\","
|
||||
"\"performanceImpact\": \"Medium or lower\","
|
||||
"\"severityLevel\": \"Low or above\","
|
||||
"\"confidenceLevel\": \"Medium\""
|
||||
"},"
|
||||
"{"
|
||||
"\"action\": \"Prevent\""
|
||||
"}"
|
||||
"]"
|
||||
"}";
|
||||
|
||||
RuleSelector ruleSelector;
|
||||
};
|
||||
|
||||
TEST_F(RuleSelectorTest, read_rules)
|
||||
{
|
||||
load(protection);
|
||||
ostringstream stream;
|
||||
stream << ruleSelector;
|
||||
string str = stream.str();
|
||||
string result =
|
||||
"[Rule] action: 1 performanceImpact: 3 severityLevel: 1 confidenceLevel: 3 serverProtections: false"
|
||||
" clientProtections: true protectionsFromYear: 2020 protectionIds: id1, id2 protectionTags: tag1, tag2;"
|
||||
"[Rule] action: 0 performanceImpact: 0 severityLevel: 3 confidenceLevel: 1 serverProtections: true"
|
||||
" clientProtections: false protectionsFromYear: 1999 protectionIds: id11, id22 protectionTags: tag11, tag22;"
|
||||
"[Rule] action: 0";
|
||||
|
||||
EXPECT_EQ(result, str);
|
||||
}
|
||||
|
||||
TEST_F(RuleSelectorTest, read_semi_rules)
|
||||
{
|
||||
load(protection2);
|
||||
ostringstream stream;
|
||||
stream << ruleSelector;
|
||||
string str = stream.str();
|
||||
string result =
|
||||
"[Rule] action: 1 performanceImpact: 3 severityLevel: 1 confidenceLevel: 3;"
|
||||
"[Rule] action: 0;"
|
||||
"[Rule] action: 2";
|
||||
|
||||
EXPECT_EQ(result, str);
|
||||
}
|
||||
|
||||
TEST_F(RuleSelectorTest, read_empty_rules)
|
||||
{
|
||||
try
|
||||
{
|
||||
load(protection3);
|
||||
}
|
||||
catch(const Config::ConfigException &e)
|
||||
{
|
||||
EXPECT_EQ("rules array is empty", e.getError());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RuleSelectorTest, read_no_default_action)
|
||||
{
|
||||
try
|
||||
{
|
||||
load(protection4);
|
||||
}
|
||||
catch(const cereal::Exception &e)
|
||||
{
|
||||
EXPECT_EQ("JSON Parsing failed - provided NVP (defaultAction) not found", string(e.what()));
|
||||
}
|
||||
}
|
661
components/security_apps/ips/ips_ut/signatures_ut.cc
Normal file
661
components/security_apps/ips/ips_ut/signatures_ut.cc
Normal file
@@ -0,0 +1,661 @@
|
||||
#include "ips_signatures.h"
|
||||
#include "ips_common_types.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include "cptest.h"
|
||||
|
||||
#include "keyword_comp.h"
|
||||
#include "environment.h"
|
||||
#include "agent_details.h"
|
||||
#include "mock/mock_logging.h"
|
||||
#include "time_proxy.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "i_keywords_rule.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "generic_rulebase/generic_rulebase.h"
|
||||
#include "generic_rulebase/parameters_config.h"
|
||||
#include "generic_rulebase/generic_rulebase_context.h"
|
||||
#include "encryptor.h"
|
||||
#include "mock/mock_table.h"
|
||||
|
||||
using namespace testing;
|
||||
using namespace std;
|
||||
|
||||
MATCHER_P(IsLog, IteratableFields, "")
|
||||
{
|
||||
stringstream ss;
|
||||
{
|
||||
cereal::JSONOutputArchive ar(ss);
|
||||
ar(arg);
|
||||
}
|
||||
for (const auto &field : IteratableFields) {
|
||||
if (ss.str().find(field) == string::npos) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class MockAgg : Singleton::Provide<I_FirstTierAgg>::SelfInterface
|
||||
{
|
||||
shared_ptr<PMHook>
|
||||
getHook(const string &, const set<PMPattern> &pats) override
|
||||
{
|
||||
auto hook = make_shared<PMHook>();
|
||||
hook->prepare(pats);
|
||||
return hook;
|
||||
}
|
||||
};
|
||||
|
||||
class SignatureTest : public Test
|
||||
{
|
||||
public:
|
||||
SignatureTest()
|
||||
{
|
||||
generic_rulebase.preload();
|
||||
EXPECT_CALL(logs, getCurrentLogId()).Times(AnyNumber());
|
||||
ON_CALL(table, getState(_)).WillByDefault(Return(&ips_state));
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "[" << signature1 << "]";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
single_signature.load(ar);
|
||||
}
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "[" << signature3 << "]";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
single_signature2.load(ar);
|
||||
}
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "[" << signature1 << ", " << signature2 << ", " << signature3 << "]";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
multiple_signatures.load(ar);
|
||||
}
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "[" << signature_performance_very_low << ", " << signature_performance_low << "]";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
performance_signatures1.load(ar);
|
||||
}
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "[" << signature_performance_medium_low << ", " << signature_performance_medium << "]";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
performance_signatures2.load(ar);
|
||||
}
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "[" << signature_performance_medium_high << ", " << signature_performance_high << "]";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
performance_signatures3.load(ar);
|
||||
}
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "[" << signature_high_confidance << ", " << signature_medium_confidance << "]";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
high_medium_confidance_signatures.load(ar);
|
||||
}
|
||||
}
|
||||
|
||||
~SignatureTest()
|
||||
{
|
||||
if (gen_ctx != nullptr) {
|
||||
gen_ctx->deactivate();
|
||||
gen_ctx.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
loadExceptions()
|
||||
{
|
||||
env.preload();
|
||||
env.init();
|
||||
|
||||
BasicRuleConfig::preload();
|
||||
registerExpectedConfiguration<ParameterException>("rulebase", "exception");
|
||||
|
||||
string test_config(
|
||||
"{"
|
||||
" \"rulebase\": {"
|
||||
" \"rulesConfig\": ["
|
||||
" {"
|
||||
" \"context\": \"All()\","
|
||||
" \"priority\": 1,"
|
||||
" \"ruleId\": \"5eaef0726765c30010bae8bb\","
|
||||
" \"ruleName\": \"Acme web API\","
|
||||
" \"assetId\": \"5e243effd858007660b758ad\","
|
||||
" \"assetName\": \"Acme Power API\","
|
||||
" \"parameters\": ["
|
||||
" {"
|
||||
" \"parameterId\": \"6c3867be-4da5-42c2-93dc-8f509a764003\","
|
||||
" \"parameterType\": \"exceptions\","
|
||||
" \"parameterName\": \"exception\""
|
||||
" }"
|
||||
" ],"
|
||||
" \"zoneId\": \"\","
|
||||
" \"zoneName\": \"\""
|
||||
" }"
|
||||
" ],"
|
||||
" \"exception\": ["
|
||||
" {"
|
||||
" \"context\": \"parameterId(6c3867be-4da5-42c2-93dc-8f509a764003)\","
|
||||
" \"match\": {"
|
||||
" \"type\": \"operator\","
|
||||
" \"op\": \"or\","
|
||||
" \"items\": [{"
|
||||
" \"type\": \"condition\","
|
||||
" \"op\": \"equals\","
|
||||
" \"key\": \"protectionName\","
|
||||
" \"value\": [\"Test1\"]"
|
||||
" }, {"
|
||||
" \"type\": \"condition\","
|
||||
" \"op\": \"equals\","
|
||||
" \"key\": \"protectionName\","
|
||||
" \"value\": [\"Test2\"]"
|
||||
" }, {"
|
||||
" \"type\": \"condition\","
|
||||
" \"op\": \"equals\","
|
||||
" \"key\": \"sourceIdentifier\","
|
||||
" \"value\": [\"1.1.1.1\"]"
|
||||
" }]"
|
||||
" },"
|
||||
" \"behavior\": {"
|
||||
" \"key\": \"action\","
|
||||
" \"value\": \"accept\""
|
||||
" }"
|
||||
" }"
|
||||
" ]"
|
||||
" }"
|
||||
"}"
|
||||
);
|
||||
|
||||
istringstream ss(test_config);
|
||||
auto i_config = Singleton::Consume<Config::I_Config>::from(config);
|
||||
i_config->loadConfiguration(ss);
|
||||
|
||||
gen_ctx = make_unique<GenericRulebaseContext>();
|
||||
gen_ctx->activate();
|
||||
}
|
||||
|
||||
void
|
||||
load(const IPSSignaturesResource &policy, const string &severity, const string &confidence)
|
||||
{
|
||||
setResource(policy, "IPS", "protections");
|
||||
stringstream ss;
|
||||
ss << "{";
|
||||
ss << "\"ruleName\": \"rule1\", \"assetName\": \"asset1\", \"practiceName\": \"practice1\",";
|
||||
ss << "\"assetId\": \"1-1-1\", \"practiceId\": \"2-2-2\",";
|
||||
ss << "\"defaultAction\" : " << "\"Detect\",";
|
||||
ss << "\"rules\": [";
|
||||
ss << "{";
|
||||
ss << "\"action\": \"Prevent\",";
|
||||
ss << "\"performanceImpact\": \"High or lower\",";
|
||||
ss << "\"severityLevel\": \"" << severity << "\",";
|
||||
ss << "\"confidenceLevel\": \"" << confidence << "\"";
|
||||
ss << "}";
|
||||
ss << "]";
|
||||
ss << "}";
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
sigs.load(ar);
|
||||
}
|
||||
|
||||
bool
|
||||
checkData(const string &data, const string &ctx_name = "HTTP_REQUEST_BODY")
|
||||
{
|
||||
ParsedContext body(data, ctx_name, 0);
|
||||
ScopedContext ctx;
|
||||
ctx.registerValue<string>(I_KeywordsRule::getKeywordsRuleTag(), ctx_name);
|
||||
ctx.registerValue(body.getName(), body.getBuffer());
|
||||
return sigs.isMatchedPrevent(body.getName(), body.getBuffer());
|
||||
}
|
||||
|
||||
template <typename ...Strings>
|
||||
void
|
||||
expectLog(const string &field, Strings ...more_fields)
|
||||
{
|
||||
vector<string> all_fields;
|
||||
all_fields.push_back(field);
|
||||
expectLog(all_fields, more_fields...);
|
||||
}
|
||||
|
||||
template <typename ...Strings>
|
||||
void
|
||||
expectLog(vector<string> all_fields, const string &field, Strings ...more_fields)
|
||||
{
|
||||
all_fields.push_back(field);
|
||||
expectLog(all_fields, more_fields...);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
expectLog(vector<string> all_fields)
|
||||
{
|
||||
EXPECT_CALL(logs, sendLog(IsLog(all_fields)));
|
||||
}
|
||||
|
||||
IPSSignatures sigs;
|
||||
IPSSignaturesResource single_signature;
|
||||
IPSSignaturesResource single_signature2;
|
||||
IPSSignaturesResource multiple_signatures;
|
||||
IPSSignaturesResource high_medium_confidance_signatures;
|
||||
IPSSignaturesResource performance_signatures1;
|
||||
IPSSignaturesResource performance_signatures2;
|
||||
IPSSignaturesResource performance_signatures3;
|
||||
NiceMock<MockTable> table;
|
||||
MockAgg mock_agg;
|
||||
|
||||
private:
|
||||
GenericRulebase generic_rulebase;
|
||||
unique_ptr<GenericRulebaseContext> gen_ctx;
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
KeywordComp keywords;
|
||||
TimeProxyComponent time;
|
||||
::Environment env;
|
||||
ConfigComponent config;
|
||||
Encryptor encryptor;
|
||||
AgentDetails details;
|
||||
StrictMock<MockLogging> logs;
|
||||
IPSEntry ips_state;
|
||||
|
||||
string signature1 =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test1\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Medium High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [\"Protection_Type_Scanning_Tool\"],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"fff\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\", \"HTTP_RESPONSE_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
string signature2 =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test2\","
|
||||
"\"maintrainId\": \"102\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Low\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [\"Vul_Type_SQL_Injection\"],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"ddd\","
|
||||
"\"keywords\": \"data: \\\"ddd\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
string signature3 =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test3\","
|
||||
"\"maintrainId\": \"102\","
|
||||
"\"severity\": \"High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Low\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [\"Protection_Type_Scanning_Tool\", \"Vul_Type_SQL_Injection\"],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"ggg\","
|
||||
"\"keywords\": \"\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
string signature_high_confidance =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test3\","
|
||||
"\"maintrainId\": \"103\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"High\","
|
||||
"\"performanceImpact\": \"Low\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"hhh\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
string signature_medium_confidance =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test4\","
|
||||
"\"maintrainId\": \"104\","
|
||||
"\"severity\": \"Low\","
|
||||
"\"confidenceLevel\": \"Medium\","
|
||||
"\"performanceImpact\": \"Low\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"mmm\","
|
||||
"\"keywords\": \"data: \\\"mmm\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
string signature_performance_very_low =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test1\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Medium High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Very Low\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"aaa\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\", \"HTTP_RESPONSE_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
string signature_performance_low =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test1\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Medium High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Low\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"bbb\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\", \"HTTP_RESPONSE_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
string signature_performance_medium_low =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test1\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Medium High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium Low\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"ccc\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\", \"HTTP_RESPONSE_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
string signature_performance_medium =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test1\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Medium High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"ddd\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\", \"HTTP_RESPONSE_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
string signature_performance_medium_high =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test1\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Medium High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"Medium High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"eee\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\", \"HTTP_RESPONSE_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
string signature_performance_high =
|
||||
"{"
|
||||
"\"protectionMetadata\": {"
|
||||
"\"protectionName\": \"Test1\","
|
||||
"\"maintrainId\": \"101\","
|
||||
"\"severity\": \"Medium High\","
|
||||
"\"confidenceLevel\": \"Low\","
|
||||
"\"performanceImpact\": \"High\","
|
||||
"\"lastUpdate\": \"20210420\","
|
||||
"\"tags\": [],"
|
||||
"\"cveList\": []"
|
||||
"},"
|
||||
"\"detectionRules\": {"
|
||||
"\"type\": \"simple\","
|
||||
"\"SSM\": \"\","
|
||||
"\"keywords\": \"data: \\\"fff\\\";\","
|
||||
"\"context\": [\"HTTP_REQUEST_BODY\", \"HTTP_RESPONSE_BODY\"]"
|
||||
"}"
|
||||
"}";
|
||||
};
|
||||
|
||||
TEST_F(SignatureTest, basic_load_of_signatures)
|
||||
{
|
||||
EXPECT_TRUE(sigs.isEmpty());
|
||||
load(single_signature, "Low or above", "Low");
|
||||
EXPECT_FALSE(sigs.isEmpty());
|
||||
EXPECT_TRUE(sigs.isEmpty("NO_CONTEXT"));
|
||||
EXPECT_FALSE(sigs.isEmpty("HTTP_REQUEST_BODY"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, single_signature_matching_override)
|
||||
{
|
||||
load(single_signature, "Low or above", "Low");
|
||||
|
||||
expectLog("\"protectionId\": \"Test1\"", "\"eventSeverity\": \"High\"");
|
||||
|
||||
EXPECT_TRUE(checkData("fffddd"));
|
||||
|
||||
loadExceptions();
|
||||
|
||||
expectLog("\"protectionId\": \"Test1\"", "\"eventSeverity\": \"Info\"");
|
||||
|
||||
EXPECT_FALSE(checkData("fffddd"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, source_idetifier_exception)
|
||||
{
|
||||
load(single_signature2, "Low or above", "Low");
|
||||
|
||||
loadExceptions();
|
||||
|
||||
expectLog("\"protectionId\": \"Test3\"", "\"eventSeverity\": \"Critical\"");
|
||||
|
||||
EXPECT_TRUE(checkData("gggddd"));
|
||||
|
||||
ScopedContext ctx;
|
||||
ctx.registerValue<string>("sourceIdentifiers", "1.1.1.1");
|
||||
|
||||
expectLog("\"protectionId\": \"Test3\"", "\"eventSeverity\": \"Info\"");
|
||||
|
||||
EXPECT_FALSE(checkData("gggddd"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, single_signature_matching)
|
||||
{
|
||||
load(single_signature, "Low or above", "Low");
|
||||
|
||||
EXPECT_FALSE(checkData("ggg"));
|
||||
|
||||
EXPECT_FALSE(checkData("ddd"));
|
||||
|
||||
expectLog("\"protectionId\": \"Test1\"", "\"eventSeverity\": \"High\"");
|
||||
|
||||
EXPECT_TRUE(checkData("fffddd"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, context_signature_matching)
|
||||
{
|
||||
load(single_signature, "Low or above", "Low");
|
||||
|
||||
expectLog("\"protectionId\": \"Test1\"", "\"eventSeverity\": \"High\"");
|
||||
EXPECT_TRUE(checkData("fff", "HTTP_REQUEST_BODY"));
|
||||
|
||||
expectLog("\"protectionId\": \"Test1\"", "\"eventSeverity\": \"High\"");
|
||||
EXPECT_TRUE(checkData("fff", "HTTP_RESPONSE_BODY"));
|
||||
|
||||
EXPECT_FALSE(checkData("fff", "HTTP_COMPLETE_URL_DECODED"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, id_to_log_test)
|
||||
{
|
||||
load(single_signature, "Low or above", "Low");
|
||||
|
||||
expectLog("\"protectionId\": \"Test1\"");
|
||||
|
||||
EXPECT_TRUE(checkData("fffddd"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, multiple_signatures_matching)
|
||||
{
|
||||
load(multiple_signatures, "Low or above", "Low");
|
||||
EXPECT_FALSE(checkData("hhh"));
|
||||
|
||||
expectLog("\"protectionId\": \"Test2\"", "\"eventSeverity\": \"High\"");
|
||||
EXPECT_TRUE(checkData("ddd"));
|
||||
|
||||
expectLog("\"protectionId\": \"Test1\"", "\"eventSeverity\": \"High\"");
|
||||
EXPECT_TRUE(checkData("fff"));
|
||||
|
||||
expectLog("\"protectionId\": \"Test3\"", "\"eventSeverity\": \"Critical\"");
|
||||
EXPECT_TRUE(checkData("ggg"));
|
||||
|
||||
// Only one signature is caught
|
||||
expectLog("\"protectionId\": \"Test2\"", "\"eventSeverity\": \"High\"");
|
||||
EXPECT_TRUE(checkData("fffdddggg"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, severity_to_log_test)
|
||||
{
|
||||
load(multiple_signatures, "Low or above", "Low");
|
||||
|
||||
expectLog("\"matchedSignatureSeverity\": \"Medium High\"");
|
||||
EXPECT_TRUE(checkData("fff"));
|
||||
|
||||
expectLog("\"matchedSignatureSeverity\": \"Low\"");
|
||||
EXPECT_TRUE(checkData("ddd"));
|
||||
|
||||
expectLog("\"matchedSignatureSeverity\": \"High\"");
|
||||
EXPECT_TRUE(checkData("ggg"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, incident_type)
|
||||
{
|
||||
load(multiple_signatures, "Low or above", "Low");
|
||||
|
||||
expectLog("\"waapIncidentType\": \"Scanning Tool\"");
|
||||
EXPECT_TRUE(checkData("fff"));
|
||||
|
||||
expectLog("\"waapIncidentType\": \"SQL Injection\"");
|
||||
EXPECT_TRUE(checkData("ddd"));
|
||||
|
||||
expectLog("\"waapIncidentType\": \"SQL Injection\"");
|
||||
EXPECT_TRUE(checkData("ggg"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, performance_to_log_very_low)
|
||||
{
|
||||
load(performance_signatures1, "Low or above", "Low");
|
||||
|
||||
EXPECT_FALSE(checkData("ggg"));
|
||||
|
||||
expectLog("\"matchedSignaturePerformance\": \"Very Low\"");
|
||||
|
||||
EXPECT_TRUE(checkData("aaa"));
|
||||
|
||||
expectLog("\"matchedSignaturePerformance\": \"Low\"");
|
||||
|
||||
EXPECT_TRUE(checkData("bbb"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, performance_to_log_medium_low)
|
||||
{
|
||||
load(performance_signatures2, "Low or above", "Low");
|
||||
|
||||
EXPECT_FALSE(checkData("ggg"));
|
||||
|
||||
expectLog("\"matchedSignaturePerformance\": \"Medium Low\"");
|
||||
|
||||
EXPECT_TRUE(checkData("ccc"));
|
||||
|
||||
expectLog("\"matchedSignaturePerformance\": \"Medium\"");
|
||||
|
||||
EXPECT_TRUE(checkData("ddd"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, performance_to_log_medium_high)
|
||||
{
|
||||
load(performance_signatures3, "Low or above", "Low");
|
||||
|
||||
EXPECT_FALSE(checkData("ggg"));
|
||||
|
||||
expectLog("\"matchedSignaturePerformance\": \"Medium High\"");
|
||||
|
||||
EXPECT_TRUE(checkData("eee"));
|
||||
|
||||
expectLog("\"matchedSignaturePerformance\": \"High\"");
|
||||
|
||||
EXPECT_TRUE(checkData("fff"));
|
||||
}
|
||||
|
||||
TEST_F(SignatureTest, high_confidance_signatures_matching)
|
||||
{
|
||||
load(high_medium_confidance_signatures, "Low or above", "High");
|
||||
EXPECT_FALSE(checkData("ggg"));
|
||||
|
||||
expectLog("\"protectionId\": \"Test3\"", "\"matchedSignatureConfidence\": \"High\"");
|
||||
EXPECT_TRUE(checkData("hhh"));
|
||||
|
||||
expectLog("\"protectionId\": \"Test4\"", "\"matchedSignatureConfidence\": \"Medium\"");
|
||||
EXPECT_FALSE(checkData("mmm"));
|
||||
}
|
64
components/security_apps/ips/simple_protection.cc
Normal file
64
components/security_apps/ips/simple_protection.cc
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "simple_protection.h"
|
||||
#include "ips_comp.h"
|
||||
#include "debug.h"
|
||||
#include "helper.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_IPS);
|
||||
|
||||
SimpleProtection::Impl::Impl(
|
||||
const string &_sig_name,
|
||||
const string &ssm,
|
||||
const string &keyword,
|
||||
const vector<string> &_context
|
||||
)
|
||||
:
|
||||
sig_name(_sig_name),
|
||||
context(_context)
|
||||
{
|
||||
string deobfuscated_keyword = IPSHelper::deobfuscateKeyword(keyword);
|
||||
if (deobfuscated_keyword != "") {
|
||||
auto compiled = Singleton::Consume<I_KeywordsRule>::by<IPSComp>()->genRule(deobfuscated_keyword);
|
||||
if (!compiled.ok()) {
|
||||
reportConfigurationError(
|
||||
"Failed to complie keywords '" + keyword + "' in signature " + sig_name + ": " + compiled.getErr()
|
||||
);
|
||||
}
|
||||
rule = compiled.unpackMove();
|
||||
}
|
||||
|
||||
auto deobfuscated_ssm = IPSHelper::deobfuscateString(ssm);
|
||||
if (deobfuscated_ssm != "") {
|
||||
auto temp_pattern = PMHook::lineToPattern(deobfuscated_ssm);
|
||||
if (!temp_pattern.ok()) reportConfigurationError("Failed first tier pattern: " + temp_pattern.getErr());
|
||||
pattern = temp_pattern.unpackMove();
|
||||
}
|
||||
|
||||
if (deobfuscated_keyword == "" && deobfuscated_ssm == "") {
|
||||
reportConfigurationError("Both Simple String and Keyword are empty in a simple protection " + sig_name);
|
||||
}
|
||||
}
|
||||
|
||||
using MatchType = IPSSignatureSubTypes::BaseSignature::MatchType;
|
||||
|
||||
MatchType
|
||||
SimpleProtection::Impl::getMatch(const set<PMPattern> &matches) const
|
||||
{
|
||||
dbgTrace(D_IPS) << "Entering signature";
|
||||
if (!pattern.empty() && matches.find(pattern) == matches.end()) return MatchType::NO_MATCH;
|
||||
|
||||
dbgTrace(D_IPS) << "Checking for rule";
|
||||
if (!rule) return MatchType::MATCH;
|
||||
|
||||
dbgTrace(D_IPS) << "Running keywords";
|
||||
return rule->isMatch() ? MatchType::MATCH : MatchType::NO_MATCH;
|
||||
}
|
||||
|
||||
set<PMPattern>
|
||||
SimpleProtection::Impl::patternsInSignature() const
|
||||
{
|
||||
set<PMPattern> res;
|
||||
if (!pattern.empty()) res.insert(pattern);
|
||||
return res;
|
||||
}
|
45
components/security_apps/ips/snort_basic_policy.cc
Normal file
45
components/security_apps/ips/snort_basic_policy.cc
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "snort_basic_policy.h"
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <iomanip>
|
||||
#include "ips_signatures.h"
|
||||
#include "helper.h"
|
||||
#include "config.h"
|
||||
#include "common.h"
|
||||
|
||||
USE_DEBUG_FLAG(D_IPS);
|
||||
|
||||
using namespace std;
|
||||
|
||||
void
|
||||
SnortRuleSelector::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
string mode;
|
||||
ar(cereal::make_nvp("mode", mode), cereal::make_nvp("files", file_names));
|
||||
|
||||
if (mode == "Inactive") action = IPSSignatureSubTypes::SignatureAction::IGNORE;
|
||||
else if (mode == "Disabled") action = IPSSignatureSubTypes::SignatureAction::IGNORE;
|
||||
else if (mode == "Detect") action = IPSSignatureSubTypes::SignatureAction::DETECT;
|
||||
else if (mode == "Prevent") action = IPSSignatureSubTypes::SignatureAction::PREVENT;
|
||||
else reportConfigurationError("invalid action value " + mode);
|
||||
}
|
||||
|
||||
vector<IPSSignatureSubTypes::SignatureAndAction>
|
||||
SnortRuleSelector::selectSignatures() const
|
||||
{
|
||||
vector<IPSSignatureSubTypes::SignatureAndAction> res;
|
||||
|
||||
if (action == IPSSignatureSubTypes::SignatureAction::IGNORE) return res;
|
||||
|
||||
auto signatures = getResource<SnortSignaturesResource>("IPSSnortSigs", "protections");
|
||||
if (!signatures.ok()) return res;
|
||||
|
||||
for (auto &file : file_names) {
|
||||
for (auto &signature : (*signatures).getSignatures(file)) {
|
||||
res.emplace_back(signature, action);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
Reference in New Issue
Block a user