Skip to content

Commit

Permalink
Support min_version and max_version on evaluation primitives and …
Browse files Browse the repository at this point in the history
…RASP operator versioning (#343)
  • Loading branch information
Anilm3 authored Oct 11, 2024
1 parent 851a05d commit 609eaf1
Show file tree
Hide file tree
Showing 33 changed files with 1,489 additions and 41 deletions.
1 change: 1 addition & 0 deletions src/condition/lfi_detector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace ddwaf {

class lfi_detector : public base_impl<lfi_detector> {
public:
static constexpr unsigned version = 1;
static constexpr std::array<std::string_view, 2> param_names{"resource", "params"};

explicit lfi_detector(std::vector<condition_parameter> args, const object_limits &limits = {})
Expand Down
1 change: 1 addition & 0 deletions src/condition/shi_detector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace ddwaf {

class shi_detector : public base_impl<shi_detector> {
public:
static constexpr unsigned version = 1;
static constexpr std::array<std::string_view, 2> param_names{"resource", "params"};

explicit shi_detector(std::vector<condition_parameter> args, const object_limits &limits = {});
Expand Down
1 change: 1 addition & 0 deletions src/condition/sqli_detector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace ddwaf {

class sqli_detector : public base_impl<sqli_detector> {
public:
static constexpr unsigned version = 2;
static constexpr std::array<std::string_view, 3> param_names{"resource", "params", "db_type"};

explicit sqli_detector(std::vector<condition_parameter> args, const object_limits &limits = {})
Expand Down
1 change: 1 addition & 0 deletions src/condition/ssrf_detector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace ddwaf {

class ssrf_detector : public base_impl<ssrf_detector> {
public:
static constexpr unsigned version = 1;
static constexpr std::array<std::string_view, 2> param_names{"resource", "params"};

explicit ssrf_detector(std::vector<condition_parameter> args, const object_limits &limits = {});
Expand Down
11 changes: 11 additions & 0 deletions src/exception.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <string>
#include <utility>

#include "fmt/format.h"

namespace ddwaf {

class exception : public std::exception {
Expand All @@ -27,6 +29,15 @@ class unsupported_version : public exception {
unsupported_version() : exception(std::string()){};
};

class unsupported_operator_version : public exception {
public:
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
unsupported_operator_version(std::string_view name, unsigned expected, unsigned current)
: exception(ddwaf::fmt::format(
"unsupported operator version {}@{}, current {}@{}", name, expected, name, current))
{}
};

class parsing_error : public exception {
public:
explicit parsing_error(const std::string &what) : exception(what) {}
Expand Down
2 changes: 1 addition & 1 deletion src/interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ void ddwaf_context_destroy(ddwaf_context context)
}
}

const char *ddwaf_get_version() { return LIBDDWAF_VERSION; }
const char *ddwaf_get_version() { return ddwaf::current_version.cstring(); }

bool ddwaf_set_log_cb(ddwaf_log_cb cb, DDWAF_LOG_LEVEL min_level)
{
Expand Down
10 changes: 10 additions & 0 deletions src/parameter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "ddwaf.h"
#include "exception.hpp"
#include "parameter.hpp"
#include "semver.hpp"
#include "utils.hpp"

namespace {
Expand Down Expand Up @@ -306,4 +307,13 @@ parameter::operator std::unordered_map<std::string, std::string>() const
return data;
}

parameter::operator semantic_version() const
{
if (type != DDWAF_OBJ_STRING || stringValue == nullptr) {
throw bad_cast("string", strtype(type));
}

return semantic_version{{stringValue, static_cast<size_t>(nbEntries)}};
}

} // namespace ddwaf
6 changes: 6 additions & 0 deletions src/parameter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "ddwaf.h"
#include "exception.hpp"
#include "semver.hpp"

namespace ddwaf {

Expand Down Expand Up @@ -47,6 +48,7 @@ class parameter : public ddwaf_object {
explicit operator std::vector<std::string>() const;
explicit operator std::vector<std::string_view>() const;
explicit operator std::unordered_map<std::string, std::string>() const;
explicit operator semantic_version() const;

~parameter() = default;
};
Expand Down Expand Up @@ -87,4 +89,8 @@ template <> struct parameter_traits<std::unordered_map<std::string, std::string>
static const char *name() { return "std::unordered_map<std::string, std::string>"; }
};

template <> struct parameter_traits<semantic_version> {
static const char *name() { return "semantic_version"; }
};

} // namespace ddwaf
15 changes: 15 additions & 0 deletions src/parser/exclusion_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
#include "parser/common.hpp"
#include "parser/parser.hpp"
#include "parser/specification.hpp"
#include "semver.hpp"
#include "utils.hpp"
#include "version.hpp"

namespace ddwaf::parser::v2 {

Expand Down Expand Up @@ -124,6 +126,16 @@ filter_spec_container parse_filters(parameter::vector &filter_array, base_sectio
continue;
}

// Check version compatibility and fail without diagnostic
auto min_version{at<semantic_version>(node, "min_version", semantic_version::min())};
auto max_version{at<semantic_version>(node, "max_version", semantic_version::max())};
if (min_version > current_version || max_version < current_version) {
DDWAF_DEBUG("Skipping filter '{}': version required between [{}, {}], current {}",
id, min_version, max_version, current_version);
info.add_skipped(id);
continue;
}

if (node.find("inputs") != node.end()) {
auto filter = parse_input_filter(node, addresses, filter_data_ids, limits);
filters.ids.emplace(id);
Expand All @@ -137,6 +149,9 @@ filter_spec_container parse_filters(parameter::vector &filter_array, base_sectio

info.add_loaded(id);
add_addresses_to_info(addresses, info);
} catch (const unsupported_operator_version &e) {
DDWAF_WARN("Skipping filter '{}': {}", id, e.what());
info.add_skipped(id);
} catch (const std::exception &e) {
if (id.empty()) {
id = index_to_id(i);
Expand Down
52 changes: 38 additions & 14 deletions src/parser/expression_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,10 @@ std::vector<condition_parameter> parse_arguments(const parameter::map &params, d
}

template <typename T, typename... Matchers>
auto build_condition(auto operator_name, auto &params, auto &data_ids_to_type, auto source,
auto &transformers, auto &addresses, auto &limits)
auto build_condition(std::string_view operator_name, const parameter::map &params,
std::unordered_map<std::string, std::string> &data_ids_to_type, data_source source,
const std::vector<transformer_id> &transformers, address_container &addresses,
const object_limits &limits)
{
auto [data_id, matcher] = parse_matcher<Matchers...>(operator_name, params);

Expand All @@ -124,6 +126,20 @@ auto build_condition(auto operator_name, auto &params, auto &data_ids_to_type, a
return std::make_unique<T>(std::move(matcher), data_id, std::move(arguments), limits);
}

template <typename Condition>
auto build_versioned_condition(std::string_view operator_name, unsigned version,
const parameter::map &params, data_source source,
const std::vector<transformer_id> &transformers, address_container &addresses,
const object_limits &limits)
{
if (version > Condition::version) {
throw unsupported_operator_version(operator_name, version, Condition::version);
}

auto arguments = parse_arguments<Condition>(params, source, transformers, addresses, limits);
return std::make_unique<Condition>(std::move(arguments), limits);
}

} // namespace

std::shared_ptr<expression> parse_expression(const parameter::vector &conditions_array,
Expand All @@ -138,22 +154,30 @@ std::shared_ptr<expression> parse_expression(const parameter::vector &conditions
auto operator_name = at<std::string_view>(root, "operator");
auto params = at<parameter::map>(root, "parameters");

// Exploit Prevention Operators may have a single-digit version
unsigned version = 0;
auto version_idx = operator_name.find("@v");
if (version_idx != std::string_view::npos) {
auto version_str = operator_name.substr(version_idx + 2);
auto [res, value] = from_string<unsigned>(version_str);
if (res) {
version = value;
}
operator_name = operator_name.substr(0, version_idx);
}

if (operator_name == "lfi_detector") {
auto arguments =
parse_arguments<lfi_detector>(params, source, transformers, addresses, limits);
conditions.emplace_back(std::make_unique<lfi_detector>(std::move(arguments), limits));
conditions.emplace_back(build_versioned_condition<lfi_detector>(
operator_name, version, params, source, transformers, addresses, limits));
} else if (operator_name == "ssrf_detector") {
auto arguments =
parse_arguments<ssrf_detector>(params, source, transformers, addresses, limits);
conditions.emplace_back(std::make_unique<ssrf_detector>(std::move(arguments), limits));
conditions.emplace_back(build_versioned_condition<ssrf_detector>(
operator_name, version, params, source, transformers, addresses, limits));
} else if (operator_name == "sqli_detector") {
auto arguments =
parse_arguments<sqli_detector>(params, source, transformers, addresses, limits);
conditions.emplace_back(std::make_unique<sqli_detector>(std::move(arguments), limits));
conditions.emplace_back(build_versioned_condition<sqli_detector>(
operator_name, version, params, source, transformers, addresses, limits));
} else if (operator_name == "shi_detector") {
auto arguments =
parse_arguments<shi_detector>(params, source, transformers, addresses, limits);
conditions.emplace_back(std::make_unique<shi_detector>(std::move(arguments), limits));
conditions.emplace_back(build_versioned_condition<shi_detector>(
operator_name, version, params, source, transformers, addresses, limits));
} else if (operator_name == "exists") {
auto arguments =
parse_arguments<exists_condition>(params, source, transformers, addresses, limits);
Expand Down
17 changes: 16 additions & 1 deletion src/parser/processor_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
#include "processor/base.hpp"
#include "processor/extract_schema.hpp"
#include "processor/fingerprint.hpp"
#include "semver.hpp"
#include "utils.hpp"
#include "version.hpp"

namespace ddwaf::parser::v2 {

Expand Down Expand Up @@ -81,6 +83,17 @@ processor_container parse_processors(
continue;
}

// Check version compatibility and fail without diagnostic
auto min_version{at<semantic_version>(node, "min_version", semantic_version::min())};
auto max_version{at<semantic_version>(node, "max_version", semantic_version::max())};
if (min_version > current_version || max_version < current_version) {
DDWAF_DEBUG(
"Skipping processor '{}': version required between [{}, {}], current {}", id,
min_version, max_version, current_version);
info.add_skipped(id);
continue;
}

processor_type type;
auto generator_id = at<std::string>(node, "generator");
if (generator_id == "extract_schema") {
Expand Down Expand Up @@ -152,7 +165,9 @@ processor_container parse_processors(
processors.post.emplace_back(processor_builder{type, std::move(id), std::move(expr),
std::move(mappings), std::move(scanners), eval, output});
}

} catch (const unsupported_operator_version &e) {
DDWAF_WARN("Skipping processor '{}': {}", id, e.what());
info.add_skipped(id);
} catch (const std::exception &e) {
if (id.empty()) {
id = index_to_id(i);
Expand Down
21 changes: 18 additions & 3 deletions src/parser/rule_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
#include "parser/parser.hpp"
#include "parser/specification.hpp"
#include "rule.hpp"
#include "semver.hpp"
#include "transformer/base.hpp"
#include "utils.hpp"
#include "version.hpp"

namespace ddwaf::parser::v2 {

Expand Down Expand Up @@ -71,24 +73,37 @@ rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info
rule_spec_container rules;
for (unsigned i = 0; i < rule_array.size(); ++i) {
const auto &rule_param = rule_array[i];
auto rule_map = static_cast<parameter::map>(rule_param);
auto node = static_cast<parameter::map>(rule_param);
std::string id;
try {
address_container addresses;

id = at<std::string>(rule_map, "id");
id = at<std::string>(node, "id");
if (rules.find(id) != rules.end()) {
DDWAF_WARN("Duplicate rule {}", id);
info.add_failed(id, "duplicate rule");
continue;
}

auto rule = parse_rule(rule_map, rule_data_ids, limits, source, addresses);
// Check version compatibility and fail without diagnostic
auto min_version{at<semantic_version>(node, "min_version", semantic_version::min())};
auto max_version{at<semantic_version>(node, "max_version", semantic_version::max())};
if (min_version > current_version || max_version < current_version) {
DDWAF_DEBUG("Skipping rule '{}': version required between [{}, {}], current {}", id,
min_version, max_version, current_version);
info.add_skipped(id);
continue;
}

auto rule = parse_rule(node, rule_data_ids, limits, source, addresses);
DDWAF_DEBUG("Parsed rule {}", id);
info.add_loaded(id);
add_addresses_to_info(addresses, info);

rules.emplace(std::move(id), std::move(rule));
} catch (const unsupported_operator_version &e) {
DDWAF_WARN("Skipping rule '{}': {}", id, e.what());
info.add_skipped(id);
} catch (const std::exception &e) {
if (id.empty()) {
id = index_to_id(i);
Expand Down
12 changes: 12 additions & 0 deletions src/parser/scanner_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include "parser/matcher_parser.hpp"
#include "parser/parser.hpp"
#include "scanner.hpp"
#include "semver.hpp"
#include "version.hpp"

namespace ddwaf::parser::v2 {

Expand Down Expand Up @@ -55,6 +57,16 @@ indexer<const scanner> parse_scanners(parameter::vector &scanner_array, base_sec
continue;
}

// Check version compatibility and fail without diagnostic
auto min_version{at<semantic_version>(node, "min_version", semantic_version::min())};
auto max_version{at<semantic_version>(node, "max_version", semantic_version::max())};
if (min_version > current_version || max_version < current_version) {
DDWAF_DEBUG("Skipping scanner '{}': version required between [{}, {}], current {}",
id, min_version, max_version, current_version);
info.add_skipped(id);
continue;
}

std::unordered_map<std::string, std::string> tags;
for (auto &[key, value] : at<parameter::map>(node, "tags")) {
try {
Expand Down
7 changes: 7 additions & 0 deletions src/ruleset_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ void ruleset_info::section_info::add_failed(std::string_view id, std::string_vie
ddwaf_object_array_add(&failed_, &id_str);
}

void ruleset_info::section_info::add_skipped(std::string_view id)
{
ddwaf_object id_str;
ddwaf_object_stringl(&id_str, id.data(), id.size());
ddwaf_object_array_add(&skipped_, &id_str);
}

void ruleset_info::section_info::add_required_address(std::string_view address)
{
if (!required_addresses_set_.contains(address)) {
Expand Down
Loading

0 comments on commit 609eaf1

Please sign in to comment.