My 11th 2023 update

This commit is contained in:
Ned Wright
2023-05-11 18:54:44 +00:00
parent 240f58217a
commit 29bd82d125
92 changed files with 9301 additions and 135 deletions

View 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)

View 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;
}

View 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

View 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__

View 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__

View 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__

View 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__

View 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__

View 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__

View 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__

View 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__

View 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__

View 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__

View 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__

View 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__

View 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__

View 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, ";");
}

View 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);

View 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();
}

View 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();
}

View 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);
}

View 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;
}
}
}

View 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();
}

View 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"
)

View 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));
}

View 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);
}

View 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");
}

View 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"));
}

View 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");
}

View 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()));
}
}

View 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"));
}

View 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;
}

View 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;
}