mirror of
https://github.com/openappsec/openappsec.git
synced 2025-06-28 16:41:02 +03:00
380 lines
14 KiB
C++
Executable File
380 lines
14 KiB
C++
Executable File
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "http_manager.h"
|
|
|
|
#include <string>
|
|
#include <map>
|
|
#include <sys/stat.h>
|
|
#include <climits>
|
|
#include <unordered_map>
|
|
#include <boost/range/iterator_range.hpp>
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
|
|
#include "common.h"
|
|
#include "config.h"
|
|
#include "table_opaque.h"
|
|
#include "http_manager_opaque.h"
|
|
#include "log_generator.h"
|
|
#include "sasal.h"
|
|
#include "http_inspection_events.h"
|
|
|
|
SASAL_START // HTTP Manager
|
|
|
|
USE_DEBUG_FLAG(D_HTTP_MANAGER);
|
|
|
|
using namespace std;
|
|
|
|
static ostream &
|
|
operator<<(ostream &os, const EventVerdict &event)
|
|
{
|
|
switch (event.getVerdict()) {
|
|
case ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INSPECT: return os << "Inspect";
|
|
case ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT: return os << "Accept";
|
|
case ngx_http_cp_verdict_e::TRAFFIC_VERDICT_DROP: return os << "Drop";
|
|
case ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INJECT: return os << "Inject";
|
|
case ngx_http_cp_verdict_e::TRAFFIC_VERDICT_IRRELEVANT: return os << "Irrelevant";
|
|
case ngx_http_cp_verdict_e::TRAFFIC_VERDICT_RECONF: return os << "Reconf";
|
|
case ngx_http_cp_verdict_e::TRAFFIC_VERDICT_WAIT: return os << "Wait";
|
|
}
|
|
|
|
dbgAssert(false) << "Illegal Event Verdict value: " << static_cast<uint>(event.getVerdict());
|
|
return os;
|
|
}
|
|
|
|
class HttpManager::Impl
|
|
:
|
|
Singleton::Provide<I_HttpManager>::From<HttpManager>
|
|
{
|
|
public:
|
|
void
|
|
init()
|
|
{
|
|
dbgFlow(D_HTTP_MANAGER);
|
|
|
|
i_transaction_table = Singleton::Consume<I_Table>::by<HttpManager>();
|
|
|
|
Singleton::Consume<I_Logging>::by<HttpManager>()->addGeneralModifier(compressAppSecLogs);
|
|
}
|
|
|
|
FilterVerdict
|
|
inspect(const HttpTransactionData &event) override
|
|
{
|
|
if (!i_transaction_table->createState<HttpManagerOpaque>()) {
|
|
dbgWarning(D_HTTP_MANAGER) << "Failed to create new transaction table state - Returning default verdict.";
|
|
return FilterVerdict(default_verdict);
|
|
}
|
|
|
|
ScopedContext ctx;
|
|
ctx.registerValue(app_sec_marker_key, i_transaction_table->keyToString(), EnvKeyAttr::LogSection::MARKER);
|
|
|
|
return handleEvent(NewHttpTransactionEvent(event).performNamedQuery());
|
|
}
|
|
|
|
FilterVerdict
|
|
inspect(const HttpHeader &event, bool is_request) override
|
|
{
|
|
if (!i_transaction_table->hasState<HttpManagerOpaque>()) {
|
|
dbgWarning(D_HTTP_MANAGER) << "Transaction state was not found - Returning default verdict.";
|
|
return FilterVerdict(default_verdict);
|
|
}
|
|
|
|
ScopedContext ctx;
|
|
ctx.registerValue(app_sec_marker_key, i_transaction_table->keyToString(), EnvKeyAttr::LogSection::MARKER);
|
|
|
|
auto event_responds =
|
|
is_request ?
|
|
HttpRequestHeaderEvent(event).performNamedQuery() :
|
|
HttpResponseHeaderEvent(event).performNamedQuery();
|
|
FilterVerdict verdict = handleEvent(event_responds);
|
|
if (verdict.getVerdict() == ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INJECT) {
|
|
applyInjectionModifications(verdict, event_responds, event.getHeaderIndex());
|
|
}
|
|
return verdict;
|
|
}
|
|
|
|
FilterVerdict
|
|
inspect(const HttpBody &event, bool is_request) override
|
|
{
|
|
if (!i_transaction_table->hasState<HttpManagerOpaque>()) {
|
|
dbgWarning(D_HTTP_MANAGER) << "Transaction state was not found - Returning default verdict.";
|
|
return FilterVerdict(default_verdict);
|
|
}
|
|
|
|
ngx_http_cp_verdict_e body_size_limit_verdict = handleBodySizeLimit(is_request, event);
|
|
if (body_size_limit_verdict != ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INSPECT) {
|
|
return FilterVerdict(body_size_limit_verdict);
|
|
}
|
|
|
|
HttpManagerOpaque &state = i_transaction_table->getState<HttpManagerOpaque>();
|
|
|
|
ScopedContext ctx;
|
|
ctx.registerValue(app_sec_marker_key, i_transaction_table->keyToString(), EnvKeyAttr::LogSection::MARKER);
|
|
|
|
FilterVerdict verdict(ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INSPECT);
|
|
if (!is_request && event.getData().size() == 0 && !event.isLastChunk()) {
|
|
dbgDebug(D_HTTP_MANAGER) << "Skipping inspection of first empty chunk for respond body";
|
|
return verdict;
|
|
}
|
|
|
|
auto event_responds =
|
|
is_request ?
|
|
HttpRequestBodyEvent(event, state.getPreviousDataCache()).performNamedQuery() :
|
|
HttpResponseBodyEvent(event, state.getPreviousDataCache()).performNamedQuery();
|
|
verdict = handleEvent(event_responds);
|
|
state.saveCurrentDataToCache(event.getData());
|
|
if (verdict.getVerdict() == ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INJECT) {
|
|
applyInjectionModifications(verdict, event_responds, event.getBodyChunkIndex());
|
|
}
|
|
return verdict;
|
|
}
|
|
|
|
FilterVerdict
|
|
inspect(const ResponseCode &event) override
|
|
{
|
|
if (!i_transaction_table->hasState<HttpManagerOpaque>()) {
|
|
dbgWarning(D_HTTP_MANAGER) << "Transaction state was not found - Returning default verdict.";
|
|
return FilterVerdict(default_verdict);
|
|
}
|
|
|
|
ScopedContext ctx;
|
|
ctx.registerValue(app_sec_marker_key, i_transaction_table->keyToString(), EnvKeyAttr::LogSection::MARKER);
|
|
|
|
return handleEvent(ResponseCodeEvent(event).performNamedQuery());
|
|
}
|
|
|
|
FilterVerdict
|
|
inspectEndRequest() override
|
|
{
|
|
if (!i_transaction_table->hasState<HttpManagerOpaque>()) {
|
|
dbgWarning(D_HTTP_MANAGER) << "Transaction state was not found - Returning default verdict.";
|
|
return FilterVerdict(default_verdict);
|
|
}
|
|
|
|
HttpManagerOpaque &state = i_transaction_table->getState<HttpManagerOpaque>();
|
|
state.resetPayloadSize();
|
|
|
|
ScopedContext ctx;
|
|
ctx.registerValue(app_sec_marker_key, i_transaction_table->keyToString(), EnvKeyAttr::LogSection::MARKER);
|
|
|
|
return handleEvent(EndRequestEvent().performNamedQuery());
|
|
}
|
|
|
|
FilterVerdict
|
|
inspectEndTransaction() override
|
|
{
|
|
if (!i_transaction_table->hasState<HttpManagerOpaque>()) {
|
|
dbgWarning(D_HTTP_MANAGER) << "Transaction state was not found - Returning default verdict.";
|
|
return FilterVerdict(default_verdict);
|
|
}
|
|
|
|
HttpManagerOpaque &state = i_transaction_table->getState<HttpManagerOpaque>();
|
|
state.resetPayloadSize();
|
|
|
|
ScopedContext ctx;
|
|
ctx.registerValue(app_sec_marker_key, i_transaction_table->keyToString(), EnvKeyAttr::LogSection::MARKER);
|
|
|
|
return handleEvent(EndTransactionEvent().performNamedQuery());
|
|
}
|
|
|
|
FilterVerdict
|
|
inspectDelayedVerdict() override
|
|
{
|
|
if (!i_transaction_table->hasState<HttpManagerOpaque>()) {
|
|
dbgWarning(D_HTTP_MANAGER) << "Transaction state was not found - Returning default verdict.";
|
|
return FilterVerdict(default_verdict);
|
|
}
|
|
|
|
ScopedContext ctx;
|
|
ctx.registerValue(app_sec_marker_key, i_transaction_table->keyToString(), EnvKeyAttr::LogSection::MARKER);
|
|
|
|
return handleEvent(WaitTransactionEvent().performNamedQuery());
|
|
}
|
|
|
|
void
|
|
sendPolicyLog()
|
|
{
|
|
LogGen(
|
|
"Web AppSec Policy Loaded Successfully",
|
|
ReportIS::Audience::SECURITY,
|
|
ReportIS::Severity::LOW,
|
|
ReportIS::Priority::LOW,
|
|
ReportIS::Tags::THREAT_PREVENTION
|
|
);
|
|
}
|
|
|
|
private:
|
|
ngx_http_cp_verdict_e
|
|
handleBodySizeLimit(bool is_request_body_type, const HttpBody &event)
|
|
{
|
|
HttpManagerOpaque &state = i_transaction_table->getState<HttpManagerOpaque>();
|
|
state.updatePayloadSize(event.getData().size());
|
|
|
|
auto size_limit = getConfiguration<uint>(
|
|
"HTTP manager",
|
|
is_request_body_type ? "Max Request Body Size" : "Max Response Body Size"
|
|
);
|
|
|
|
string size_limit_verdict = getConfigurationWithDefault<string>(
|
|
"Accept",
|
|
"HTTP manager",
|
|
is_request_body_type ? "Request Size Limit Verdict" : "Response Size Limit Verdict"
|
|
);
|
|
|
|
if (!size_limit.ok() || state.getAggeregatedPayloadSize() < size_limit.unpack()) {
|
|
return ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INSPECT;
|
|
}
|
|
|
|
ngx_http_cp_verdict_e verdict = size_limit_verdict == "Drop" ?
|
|
ngx_http_cp_verdict_e::TRAFFIC_VERDICT_DROP :
|
|
ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT;
|
|
|
|
dbgDebug(D_HTTP_MANAGER)
|
|
<< "Transaction body size is over the limit. Max body size: "
|
|
<< size_limit.unpack()
|
|
<< ", Returned verdict: "
|
|
<< size_limit_verdict
|
|
<< ".";
|
|
|
|
state.setManagerVerdict(verdict);
|
|
return verdict;
|
|
}
|
|
|
|
static void
|
|
applyInjectionModifications(
|
|
FilterVerdict &verdict,
|
|
const vector<pair<string, EventVerdict>> &event_responds,
|
|
ModifiedChunkIndex event_idx)
|
|
{
|
|
for (const pair<string, EventVerdict> &respond : event_responds) {
|
|
if (respond.second.getVerdict() == ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INJECT) {
|
|
dbgTrace(D_HTTP_MANAGER)
|
|
<< "Applying inject verdict modifications for security App: "
|
|
<< respond.first;
|
|
verdict.addModifications(respond.second.getModifications(), event_idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
FilterVerdict
|
|
handleEvent(const vector<pair<string, EventVerdict>> &event_responds)
|
|
{
|
|
HttpManagerOpaque &state = i_transaction_table->getState<HttpManagerOpaque>();
|
|
|
|
for (const pair<string, EventVerdict> &respond : event_responds) {
|
|
if (state.getApplicationsVerdict(respond.first) == ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT) {
|
|
dbgTrace(D_HTTP_MANAGER)
|
|
<< "Skipping event verdict for app that already accepted traffic. App: "
|
|
<< respond.first;
|
|
continue;
|
|
}
|
|
|
|
dbgTrace(D_HTTP_MANAGER)
|
|
<< "Security app "
|
|
<< respond.first
|
|
<< " returned verdict "
|
|
<< respond.second.getVerdict();
|
|
|
|
state.setApplicationVerdict(respond.first, respond.second.getVerdict());
|
|
}
|
|
|
|
return state.getCurrVerdict();
|
|
}
|
|
|
|
static void
|
|
compressAppSecLogs(LogBulkRest &bulk)
|
|
{
|
|
dbgTrace(D_HTTP_MANAGER) << "Starting to reduce logs";
|
|
|
|
map<string, uint> app_sec_logs_by_key;
|
|
|
|
for (const auto &log : bulk) {
|
|
auto &markers = log.getMarkers();
|
|
auto appsec_marker = markers.find(app_sec_marker_key);
|
|
if (appsec_marker != markers.end()) app_sec_logs_by_key[appsec_marker->second]++;
|
|
}
|
|
|
|
for (const auto &specific_set_of_logs : app_sec_logs_by_key) {
|
|
if (specific_set_of_logs.second > 1) reduceLogs(bulk, specific_set_of_logs.first);
|
|
}
|
|
|
|
dbgTrace(D_HTTP_MANAGER) << "Finished logs reduction";
|
|
}
|
|
|
|
static void
|
|
reduceLogs(LogBulkRest &bulk, const string ¤t_id)
|
|
{
|
|
dbgTrace(D_HTTP_MANAGER) << "Reducing logs for marker " << current_id;
|
|
|
|
vector<vector<Report>::iterator> relevent_logs;
|
|
vector<Report>::iterator keep_log = bulk.end();
|
|
for (auto curr_log = bulk.begin(); curr_log != bulk.end(); ++curr_log) {
|
|
if (isRelevantLog(curr_log, current_id)) {
|
|
relevent_logs.push_back(curr_log);
|
|
if (keep_log == bulk.end() || (isPreventLog(curr_log) && !isPreventLog(keep_log))) keep_log = curr_log;
|
|
}
|
|
}
|
|
|
|
dbgTrace(D_HTTP_MANAGER) << "Found " << relevent_logs.size() << " logs that match marker " << current_id;
|
|
|
|
// Reverse iteration to avoid iterator invalidation
|
|
for (auto iter = relevent_logs.rbegin(); iter != relevent_logs.rend(); ++iter) {
|
|
if (*iter != keep_log) bulk.erase(*iter);
|
|
}
|
|
|
|
dbgTrace(D_HTTP_MANAGER) << "Finished going over maker " << current_id;
|
|
}
|
|
|
|
static bool
|
|
isRelevantLog(const vector<Report>::iterator &log, const string ¤t_id)
|
|
{
|
|
const auto &markers = log->getMarkers();
|
|
auto app_sec_marker = markers.find(app_sec_marker_key);
|
|
if (app_sec_marker == markers.end()) return false;
|
|
return app_sec_marker->second == current_id;
|
|
}
|
|
|
|
static bool
|
|
isPreventLog(const vector<Report>::iterator &log)
|
|
{
|
|
auto res = log->getStringData("securityAction");
|
|
return res.ok() && *res == "Prevent";
|
|
}
|
|
|
|
I_Table *i_transaction_table;
|
|
static const ngx_http_cp_verdict_e default_verdict;
|
|
static const string app_sec_marker_key;
|
|
};
|
|
|
|
const ngx_http_cp_verdict_e HttpManager::Impl::default_verdict(ngx_http_cp_verdict_e::TRAFFIC_VERDICT_DROP);
|
|
const string HttpManager::Impl::app_sec_marker_key = "app_sec_marker";
|
|
|
|
HttpManager::HttpManager() : Component("HttpManager"), pimpl(make_unique<Impl>()) {}
|
|
HttpManager::~HttpManager() {}
|
|
|
|
void HttpManager::init() { pimpl->init(); }
|
|
|
|
void
|
|
HttpManager::preload()
|
|
{
|
|
registerExpectedConfiguration<uint>("HTTP manager", "Previous Buffer Cache size");
|
|
registerExpectedConfiguration<uint>("HTTP manager", "Max Request Body Size");
|
|
registerExpectedConfiguration<uint>("HTTP manager", "Max Response Body Size");
|
|
registerExpectedConfiguration<string>("HTTP manager", "Request Size Limit Verdict");
|
|
registerExpectedConfiguration<string>("HTTP manager", "Response Size Limit Verdict");
|
|
registerConfigLoadCb([this] () { pimpl->sendPolicyLog(); });
|
|
}
|
|
|
|
SASAL_END
|