mirror of
https://github.com/openappsec/openappsec.git
synced 2025-06-28 16:41:02 +03:00
457 lines
16 KiB
C++
Executable File
457 lines
16 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.
|
|
|
|
#pragma once
|
|
#include <cereal/types/vector.hpp>
|
|
#include <boost/regex.hpp>
|
|
#include <boost/regex/pattern_except.hpp>
|
|
#include <boost/algorithm/string/case_conv.hpp>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <memory>
|
|
#include "debug.h"
|
|
#include "CidrMatch.h"
|
|
#include "RegexComparator.h"
|
|
|
|
USE_DEBUG_FLAG(D_WAAP_OVERRIDE);
|
|
|
|
namespace Waap {
|
|
namespace Override {
|
|
using boost::algorithm::to_lower_copy;
|
|
|
|
class Match {
|
|
public:
|
|
bool operator==(const Match &other) const;
|
|
|
|
template <typename _A>
|
|
void serialize(_A &ar) {
|
|
// Read the value of "op"
|
|
ar(cereal::make_nvp("operator", m_op));
|
|
m_op = to_lower_copy(m_op);
|
|
m_isCidr = false;
|
|
m_value = "";
|
|
m_isValid = true;
|
|
|
|
if (m_op == "basic") {
|
|
// If op == "BASIC" - read numeric value
|
|
ar(cereal::make_nvp("tag", m_tag));
|
|
m_tag = to_lower_copy(m_tag);
|
|
|
|
if (m_tag != "sourceip" && m_tag != "sourceidentifier" && m_tag != "url" && m_tag != "hostname" &&
|
|
m_tag != "keyword" && m_tag != "paramname" && m_tag != "paramvalue" && m_tag != "paramlocation" &&
|
|
m_tag != "responsebody" && m_tag != "headername" && m_tag != "headervalue" && m_tag != "method") {
|
|
m_isValid = false;
|
|
dbgDebug(D_WAAP_OVERRIDE) << "Invalid override tag: " << m_tag;
|
|
}
|
|
|
|
try {
|
|
ar(cereal::make_nvp("values", m_values));
|
|
dbgDebug(D_WAAP_OVERRIDE) << "Values list is missing, using single value instead.";
|
|
} catch (const cereal::Exception &e) {
|
|
// The name "value" here is misleading. The real meaning is "regex pattern string"
|
|
ar(cereal::make_nvp("value", m_value));
|
|
m_values.insert(m_value);
|
|
}
|
|
|
|
if (m_tag == "sourceip" || m_tag == "sourceidentifier") {
|
|
m_isCidr = true;
|
|
m_ip_addr_values.resize(m_values.size());
|
|
|
|
int val_idx = 0;
|
|
for (const auto &cur_val : m_values) {
|
|
if (!Waap::Util::isCIDR(cur_val, m_ip_addr_values[val_idx])) {
|
|
dbgDebug(D_WAAP_OVERRIDE) << "Invalid value in list of IP addresses: " << cur_val;
|
|
m_isValid = false;
|
|
break;
|
|
}
|
|
val_idx++;
|
|
}
|
|
sortAndMergeCIDRs();
|
|
dbgTrace(D_WAAP_OVERRIDE) << "CIDR list: " << cidrsToString(m_ip_addr_values);
|
|
}
|
|
m_isOverrideResponse = (m_tag == "responsebody" || m_tag == "responseBody");
|
|
|
|
if (!m_isCidr) {
|
|
for (const auto &cur_val : m_values) {
|
|
try {
|
|
m_valuesRegex.emplace(std::make_shared<boost::regex>(cur_val));
|
|
}
|
|
catch (const boost::regex_error &err) {
|
|
dbgDebug(D_WAAP_OVERRIDE)
|
|
<< "Waap::Override::Match(): Failed to compile regex pattern '"
|
|
<< cur_val
|
|
<< "' on position "
|
|
<< err.position()
|
|
<< ". Reason: '"
|
|
<< err.what()
|
|
<< "'";
|
|
m_isValid = false;
|
|
m_valuesRegex.clear();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// If op is "AND" or "OR" - get two operands
|
|
if (m_op == "and" || m_op == "or") {
|
|
m_operand1 = std::make_shared<Match>();
|
|
ar(cereal::make_nvp("operand1", *m_operand1));
|
|
m_operand2 = std::make_shared<Match>();
|
|
ar(cereal::make_nvp("operand2", *m_operand2));
|
|
m_isOverrideResponse = m_operand1->m_isOverrideResponse || m_operand2->m_isOverrideResponse;
|
|
m_isValid = m_operand1->m_isValid && m_operand2->m_isValid;
|
|
}
|
|
else if (m_op == "not") {
|
|
// If op is "NOT" get one operand
|
|
m_operand1 = std::make_shared<Match>();
|
|
ar(cereal::make_nvp("operand1", *m_operand1));
|
|
m_isOverrideResponse = m_operand1->m_isOverrideResponse;
|
|
m_isValid = m_operand1->m_isValid;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename TestFunctor>
|
|
bool match(TestFunctor testFunctor) const {
|
|
if (m_op == "basic" && m_isCidr) {
|
|
bool result = testFunctor(m_tag, m_ip_addr_values);
|
|
dbgTrace(D_WAAP_OVERRIDE)
|
|
<< "Override matching CIDR list: "
|
|
<< cidrsToString(m_ip_addr_values)
|
|
<< " result: "
|
|
<< result;
|
|
return result;
|
|
}
|
|
else if (m_op == "basic" && !m_valuesRegex.empty()) {
|
|
bool result = testFunctor(m_tag, m_valuesRegex);
|
|
dbgTrace(D_WAAP_OVERRIDE)
|
|
<< "Override matching regex list: "
|
|
<< regexSetToString(m_valuesRegex)
|
|
<< " result: "
|
|
<< result;
|
|
return result;
|
|
}
|
|
if (m_op == "and") {
|
|
bool result = m_operand1->match(testFunctor) && m_operand2->match(testFunctor);
|
|
dbgTrace(D_WAAP_OVERRIDE) << "Override matching logical AND result: " << result;
|
|
return result;
|
|
}
|
|
if (m_op == "or") {
|
|
bool result = m_operand1->match(testFunctor) || m_operand2->match(testFunctor);
|
|
dbgTrace(D_WAAP_OVERRIDE) << "Override matching logical OR result: " << result;
|
|
return result;
|
|
}
|
|
if (m_op == "not") {
|
|
bool result = !m_operand1->match(testFunctor);
|
|
dbgTrace(D_WAAP_OVERRIDE) << "Override matching logical NOT result: " << result;
|
|
return result;
|
|
}
|
|
|
|
// unknown operator. this should not occur
|
|
dbgDebug(D_WAAP_OVERRIDE) << "Invalid override operator " << m_op;
|
|
return false;
|
|
}
|
|
|
|
bool isOverrideResponse() const { return m_isOverrideResponse; }
|
|
|
|
bool isValidMatch() const { return m_isValid; }
|
|
|
|
private:
|
|
void sortAndMergeCIDRs() {
|
|
if (m_ip_addr_values.empty()) return;
|
|
std::sort(m_ip_addr_values.begin(), m_ip_addr_values.end());
|
|
|
|
size_t mergedIndex = 0;
|
|
for (size_t i = 1; i < m_ip_addr_values.size(); ++i) {
|
|
Waap::Util::CIDRData ¤t = m_ip_addr_values[mergedIndex];
|
|
Waap::Util::CIDRData &next = m_ip_addr_values[i];
|
|
|
|
if (!doesFirstCidrContainSecond(current, next)) {
|
|
++mergedIndex;
|
|
if (i != mergedIndex) m_ip_addr_values[mergedIndex] = next;
|
|
}
|
|
}
|
|
|
|
m_ip_addr_values.resize(mergedIndex + 1);
|
|
}
|
|
|
|
std::string m_op;
|
|
std::shared_ptr<Match> m_operand1;
|
|
std::shared_ptr<Match> m_operand2;
|
|
std::string m_tag;
|
|
std::string m_value;
|
|
std::set<std::string> m_values;
|
|
std::vector<Waap::Util::CIDRData> m_ip_addr_values;
|
|
std::set<std::shared_ptr<boost::regex>, Waap::Util::RegexComparator> m_valuesRegex;
|
|
bool m_isCidr;
|
|
bool m_isOverrideResponse;
|
|
bool m_isValid;
|
|
};
|
|
|
|
class Behavior
|
|
{
|
|
public:
|
|
Behavior();
|
|
bool operator==(const Behavior &other) const;
|
|
|
|
template <typename _A>
|
|
void serialize(_A &ar) {
|
|
try
|
|
{
|
|
ar(cereal::make_nvp("action", m_action));
|
|
m_action = to_lower_copy(m_action);
|
|
}
|
|
catch (std::runtime_error& e) {
|
|
ar.setNextName(nullptr);
|
|
m_action = "";
|
|
}
|
|
|
|
try
|
|
{
|
|
ar(cereal::make_nvp("log", m_log));
|
|
m_log = to_lower_copy(m_log);
|
|
}
|
|
catch (const std::runtime_error& e) {
|
|
ar.setNextName(nullptr);
|
|
m_log = "";
|
|
}
|
|
|
|
try
|
|
{
|
|
ar(cereal::make_nvp("httpSourceId", m_sourceIdentifier));
|
|
}
|
|
catch (const std::runtime_error & e) {
|
|
ar.setNextName(nullptr);
|
|
m_sourceIdentifier = "";
|
|
}
|
|
|
|
if (!m_log.size() && !m_action.size() && !m_sourceIdentifier.size())
|
|
{
|
|
dbgDebug(D_WAAP_OVERRIDE) << "Override does not contain any relevant action";
|
|
}
|
|
}
|
|
|
|
const std::string &getParentId() const;
|
|
const std::string &getAction() const;
|
|
const std::string &getLog() const;
|
|
const std::string &getSourceIdentifier() const;
|
|
void setParentId(const std::string& id);
|
|
private:
|
|
std::string m_id;
|
|
std::string m_action;
|
|
std::string m_log;
|
|
std::string m_sourceIdentifier;
|
|
};
|
|
|
|
class Rule {
|
|
public:
|
|
|
|
Rule(): m_match(), m_isChangingRequestData(false), isValid(true){}
|
|
|
|
bool operator==(const Rule &other) const;
|
|
|
|
template <typename _A>
|
|
void serialize(_A &ar) {
|
|
try {
|
|
ar(cereal::make_nvp("id", m_id));
|
|
}
|
|
catch (const cereal::Exception &e)
|
|
{
|
|
dbgDebug(D_WAAP_OVERRIDE) << "An override rule has no id.";
|
|
m_id.clear();
|
|
}
|
|
ar(cereal::make_nvp("parsedMatch", m_match));
|
|
if (!m_match.isValidMatch()) {
|
|
dbgDebug(D_WAAP_OVERRIDE) << "An override rule was not load";
|
|
isValid = false;
|
|
}
|
|
|
|
ar(cereal::make_nvp("parsedBehavior", m_behaviors));
|
|
|
|
m_isChangingRequestData = false;
|
|
|
|
for (std::vector<Waap::Override::Behavior>::iterator it = m_behaviors.begin();
|
|
it != m_behaviors.end();
|
|
++it)
|
|
{
|
|
Behavior& behavior = *it;
|
|
behavior.setParentId(m_id);
|
|
if (!behavior.getSourceIdentifier().empty()) // this rule changes data in request itself
|
|
{
|
|
m_isChangingRequestData = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename TestFunctor>
|
|
void match(TestFunctor testFunctor, std::vector<Behavior> &matchedBehaviors,
|
|
std::set<std::string> &matchedOverrideIds) const
|
|
{
|
|
if (m_match.match(testFunctor)) {
|
|
// extend matchedBehaviors list with all behaviors on this rule
|
|
std::string overrideId = getId();
|
|
dbgTrace(D_WAAP_OVERRIDE) << "Override rule matched id: " << overrideId <<
|
|
". Adding " << m_behaviors.size() << " new behaviors:";
|
|
if (!overrideId.empty()) {
|
|
matchedOverrideIds.insert(overrideId);
|
|
}
|
|
for (const Behavior &behavior : m_behaviors) {
|
|
dbgTrace(D_WAAP_OVERRIDE) << "Behavior: action='" << behavior.getAction() << "', log='" <<
|
|
behavior.getLog() << "', sourceIdentifier='" << behavior.getSourceIdentifier() << "'";
|
|
matchedBehaviors.push_back(behavior);
|
|
}
|
|
return;
|
|
}
|
|
dbgTrace(D_WAAP_OVERRIDE) << "Rule not matched";
|
|
}
|
|
|
|
|
|
bool isChangingRequestData() const {
|
|
return m_isChangingRequestData;
|
|
}
|
|
bool isOverrideResponse() const {
|
|
return m_match.isOverrideResponse();
|
|
}
|
|
|
|
const std::string &getId() const {
|
|
return m_id;
|
|
}
|
|
|
|
bool isValidRule() const {
|
|
return isValid;
|
|
}
|
|
|
|
private:
|
|
Match m_match;
|
|
bool m_isChangingRequestData;
|
|
std::vector<Behavior> m_behaviors;
|
|
std::string m_id;
|
|
bool isValid;
|
|
};
|
|
|
|
class Policy {
|
|
public:
|
|
template <typename _A>
|
|
Policy(_A &ar) {
|
|
std::vector<Waap::Override::Rule> rules;
|
|
ar(cereal::make_nvp("overrides", rules));
|
|
m_isOverrideResponse = false;
|
|
|
|
for (std::vector<Waap::Override::Rule>::const_iterator it = rules.begin(); it != rules.end(); ++it) {
|
|
const Waap::Override::Rule& rule = *it;
|
|
if (!rule.isValidRule()) {
|
|
dbgWarning(D_WAAP_OVERRIDE) << "rule is not valid";
|
|
continue;
|
|
}
|
|
if (rule.isChangingRequestData())
|
|
{
|
|
m_RequestOverrides.push_back(rule);
|
|
}
|
|
else
|
|
{
|
|
m_ResponseOverrides.push_back(rule);
|
|
}
|
|
m_isOverrideResponse |= rule.isOverrideResponse();
|
|
}
|
|
}
|
|
|
|
bool operator==(const Policy &other) const;
|
|
|
|
template <typename TestFunctor>
|
|
void match(TestFunctor &testFunctor, std::vector<Behavior> &matchedBehaviors, bool requestOverrides,
|
|
std::set<std::string> &matchedOverrideIds) const
|
|
{
|
|
// Run all rules and collect matched behaviors
|
|
|
|
const std::vector<Waap::Override::Rule>& rules = requestOverrides ? m_RequestOverrides : m_ResponseOverrides;
|
|
dbgTrace(D_WAAP_OVERRIDE) << "Start matching override rules ...";
|
|
for (const Waap::Override::Rule &rule : rules) {
|
|
dbgTrace(D_WAAP_OVERRIDE) << "Matching override rule ...";
|
|
rule.match(testFunctor, matchedBehaviors, matchedOverrideIds);
|
|
}
|
|
dbgTrace(D_WAAP_OVERRIDE) << "Finished matching override rules.";
|
|
}
|
|
|
|
bool isOverrideResponse() const {
|
|
return m_isOverrideResponse;
|
|
}
|
|
|
|
private:
|
|
std::vector<Waap::Override::Rule> m_RequestOverrides; //overrides that change request data
|
|
std::vector<Waap::Override::Rule> m_ResponseOverrides; //overrides that change response/log data
|
|
bool m_isOverrideResponse;
|
|
};
|
|
|
|
struct State {
|
|
// whether to force block regardless of stage2 response (and even if bSendRequest and/or bSendResponse are false)
|
|
bool bForceBlock;
|
|
std::set<std::string> forceBlockIds;
|
|
// exception (allow) was matched, so this request won't be blocked.
|
|
bool bForceException;
|
|
std::set<std::string> forceExceptionIds;
|
|
// overrides decision in case log should be ignored
|
|
bool bIgnoreLog;
|
|
// user identfier override to be applied
|
|
bool bSourceIdentifierOverride;
|
|
std::string sSourceIdentifierMatch;
|
|
|
|
State();
|
|
|
|
// Compute overrides from override policy
|
|
template<typename Functor>
|
|
void applyOverride(const Waap::Override::Policy &policy, Functor functor,
|
|
std::set<std::string> &matchedOverrideIds, bool requestOverrides)
|
|
{
|
|
// Collect all behaviors from matched rules
|
|
std::vector<Waap::Override::Behavior> matchedBehaviors;
|
|
policy.match(functor, matchedBehaviors, requestOverrides, matchedOverrideIds);
|
|
|
|
dbgTrace(D_WAAP_OVERRIDE) << "applyOverride(): " << matchedBehaviors.size() << " detected override actions";
|
|
|
|
// Apply all detected behaviors
|
|
for (auto &matchedBehavior : matchedBehaviors) {
|
|
dbgTrace(D_WAAP_OVERRIDE) << "applyOverride(): found override action: " << matchedBehavior.getAction();
|
|
if (matchedBehavior.getAction() == "accept") {
|
|
dbgTrace(D_WAAP_OVERRIDE) << "applyOverride(): setting bForceException due to override behavior.";
|
|
bForceException = true;
|
|
forceExceptionIds.insert(matchedBehavior.getParentId());
|
|
}
|
|
else if (matchedBehavior.getAction() == "reject") {
|
|
dbgTrace(D_WAAP_OVERRIDE) << "applyOverride(): setting bForceBlock due to override behavior.";
|
|
bForceBlock = true;
|
|
forceBlockIds.insert(matchedBehavior.getParentId());
|
|
}
|
|
|
|
if (matchedBehavior.getLog() == "ignore")
|
|
{
|
|
dbgTrace(D_WAAP_OVERRIDE) << "applyOverride(): setting bIgnoreLog due to override behavior.";
|
|
bIgnoreLog = true;
|
|
}
|
|
|
|
sSourceIdentifierMatch = matchedBehavior.getSourceIdentifier();
|
|
if (sSourceIdentifierMatch.size())
|
|
{
|
|
dbgTrace(D_WAAP_OVERRIDE) << "applyOverride(): setting bSourceIdentifier -"
|
|
<< "Override due to override behavior: "
|
|
<< sSourceIdentifierMatch.c_str();
|
|
bSourceIdentifierOverride = true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
}
|
|
}
|