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