Skip to content

Commit

Permalink
Exists operator and waf.context.event virtual address (#321)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anilm3 authored Jul 17, 2024
1 parent 409abbe commit 3c57f42
Show file tree
Hide file tree
Showing 12 changed files with 338 additions and 50 deletions.
2 changes: 1 addition & 1 deletion src/collection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ std::optional<event> match_rule(rule *rule, const object_store &store,
}

template <typename Derived>
void base_collection<Derived>::match(std::vector<event> &events, const object_store &store,
void base_collection<Derived>::match(std::vector<event> &events, object_store &store,
collection_cache &cache, const exclusion::context_policy &exclusion,
const std::unordered_map<std::string, std::shared_ptr<matcher::base>> &dynamic_matchers,
ddwaf::timer &deadline) const
Expand Down
2 changes: 1 addition & 1 deletion src/collection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ template <typename Derived> class base_collection {

void insert(const std::shared_ptr<rule> &rule) { rules_.emplace_back(rule.get()); }

void match(std::vector<event> &events, const object_store &store, collection_cache &cache,
void match(std::vector<event> &events, object_store &store, collection_cache &cache,
const exclusion::context_policy &exclusion,
const std::unordered_map<std::string, std::shared_ptr<matcher::base>> &dynamic_matchers,
ddwaf::timer &deadline) const;
Expand Down
34 changes: 34 additions & 0 deletions src/condition/exists.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Unless explicitly stated otherwise all files in this repository are
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2021 Datadog, Inc.

#pragma once

#include "condition/structured_condition.hpp"

namespace ddwaf {

class exists_condition : public base_impl<exists_condition> {
public:
static constexpr std::array<std::string_view, 1> param_names{"inputs"};

explicit exists_condition(
std::vector<condition_parameter> args, const object_limits &limits = {})
: base_impl<exists_condition>(std::move(args), limits)
{}

protected:
[[nodiscard]] eval_result eval_impl(const unary_argument<const ddwaf_object *> &input,
condition_cache &cache, const exclusion::object_set_ref & /*objects_excluded*/,
ddwaf::timer & /*deadline*/) const
{
cache.match = {{{{"input", {}, input.address, {}}}, {}, "exists", {}, input.ephemeral}};
return {true, input.ephemeral};
}

friend class base_impl<exists_condition>;
};

} // namespace ddwaf
26 changes: 26 additions & 0 deletions src/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,31 @@

namespace ddwaf {

namespace {
using attribute = object_store::attribute;

// This function adds the waf.context.event "virtual" address, specifically
// meant to be used to tryigger post-processors when there has been an event
// during the lifecycle of the context.
// Since post-processors aren't typically used with ephemeral addresses or
// composite requests in general, we don't need to make this address dependent
// on whether the events were ephemeral or not.
void set_context_event_address(object_store &store)
{
static std::string_view event_addr = "waf.context.event";
static auto event_addr_idx = get_target_index(event_addr);

if (store.has_target(event_addr_idx)) {
return;
}

ddwaf_object true_obj;
ddwaf_object_bool(&true_obj, true);
store.insert(event_addr_idx, event_addr, true_obj, attribute::none);
}

} // namespace

// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
DDWAF_RET_CODE context::run(optional_ref<ddwaf_object> persistent,
optional_ref<ddwaf_object> ephemeral, optional_ref<ddwaf_result> res, uint64_t timeout)
Expand Down Expand Up @@ -81,6 +104,9 @@ DDWAF_RET_CODE context::run(optional_ref<ddwaf_object> persistent,

if (should_eval_rules) {
events = eval_rules(policy, deadline);
if (!events.empty()) {
set_context_event_address(store_);
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/parser/expression_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2021 Datadog, Inc.

#include "condition/exists.hpp"
#include "condition/lfi_detector.hpp"
#include "condition/scalar_condition.hpp"
#include "condition/shi_detector.hpp"
Expand Down Expand Up @@ -114,6 +115,11 @@ std::shared_ptr<expression> parse_expression(const parameter::vector &conditions
auto arguments =
parse_arguments<shi_detector>(params, source, transformers, addresses, limits);
conditions.emplace_back(std::make_unique<shi_detector>(std::move(arguments), limits));
} else if (operator_name == "exists") {
auto arguments =
parse_arguments<exists_condition>(params, source, transformers, addresses, limits);
conditions.emplace_back(
std::make_unique<exists_condition>(std::move(arguments), limits));
} else {
auto [data_id, matcher] = parse_matcher(operator_name, params);

Expand Down
76 changes: 76 additions & 0 deletions tests/integration/context/ruleset/context_event_address.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"version": "2.2",
"metadata": {
"rules_version": "1.8.0"
},
"rules": [
{
"id": "rule1",
"name": "rule1",
"tags": {
"type": "flow1",
"category": "category1"
},
"conditions": [
{
"parameters": {
"inputs": [
{
"address": "waf.trigger"
}
],
"regex": "rule"
},
"operator": "match_regex"
}
]
}
],
"processors": [
{
"id": "processor-001",
"generator": "http_endpoint_fingerprint",
"conditions": [
{
"operator": "exists",
"parameters": {
"inputs": [
{
"address": "waf.context.event"
}
]
}
}
],
"parameters": {
"mappings": [
{
"method": [
{
"address": "server.request.method"
}
],
"uri_raw": [
{
"address": "server.request.uri.raw"
}
],
"body": [
{
"address": "server.request.body"
}
],
"query": [
{
"address": "server.request.query"
}
],
"output": "_dd.appsec.fp.http.endpoint"
}
]
},
"evaluate": false,
"output": true
}
]
}
77 changes: 77 additions & 0 deletions tests/integration/context/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -862,4 +862,81 @@ TEST(TestContextIntegration, PersistentPriorityAndEphemeralNonPriority)
ddwaf_destroy(handle);
}

TEST(TestContextIntegration, WafContextEventAddress)
{
auto rule = read_json_file("context_event_address.json", base_dir);
ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID);
ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr);
ASSERT_NE(handle, nullptr);
ddwaf_object_free(&rule);

ddwaf_object tmp;

{
ddwaf_context context = ddwaf_context_init(handle);
ASSERT_NE(context, nullptr);

ddwaf_object map = DDWAF_OBJECT_MAP;

ddwaf_object body = DDWAF_OBJECT_MAP;
ddwaf_object_map_add(&body, "key", ddwaf_object_invalid(&tmp));
ddwaf_object_map_add(&map, "server.request.body", &body);

ddwaf_object query = DDWAF_OBJECT_MAP;
ddwaf_object_map_add(&query, "key", ddwaf_object_invalid(&tmp));
ddwaf_object_map_add(&map, "server.request.query", &query);

ddwaf_object_map_add(
&map, "server.request.uri.raw", ddwaf_object_string(&tmp, "/path/to/resource/?key="));
ddwaf_object_map_add(&map, "server.request.method", ddwaf_object_string(&tmp, "PuT"));

ddwaf_object_map_add(&map, "waf.trigger", ddwaf_object_string(&tmp, "irrelevant"));

ddwaf_result out;
ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK);
EXPECT_FALSE(out.timeout);

EXPECT_EQ(ddwaf_object_size(&out.derivatives), 0);

ddwaf_result_free(&out);
ddwaf_context_destroy(context);
}

{
ddwaf_context context = ddwaf_context_init(handle);
ASSERT_NE(context, nullptr);

ddwaf_object map = DDWAF_OBJECT_MAP;

ddwaf_object body = DDWAF_OBJECT_MAP;
ddwaf_object_map_add(&body, "key", ddwaf_object_invalid(&tmp));
ddwaf_object_map_add(&map, "server.request.body", &body);

ddwaf_object query = DDWAF_OBJECT_MAP;
ddwaf_object_map_add(&query, "key", ddwaf_object_invalid(&tmp));
ddwaf_object_map_add(&map, "server.request.query", &query);

ddwaf_object_map_add(
&map, "server.request.uri.raw", ddwaf_object_string(&tmp, "/path/to/resource/?key="));
ddwaf_object_map_add(&map, "server.request.method", ddwaf_object_string(&tmp, "PuT"));

ddwaf_object_map_add(&map, "waf.trigger", ddwaf_object_string(&tmp, "rule"));

ddwaf_result out;
ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH);
EXPECT_FALSE(out.timeout);

EXPECT_EQ(ddwaf_object_size(&out.derivatives), 1);

auto json = test::object_to_json(out.derivatives);
EXPECT_STR(
json, R"({"_dd.appsec.fp.http.endpoint":"http-put-729d56c3-2c70e12b-2c70e12b"})");

ddwaf_result_free(&out);
ddwaf_context_destroy(context);
}

ddwaf_destroy(handle);
}

} // namespace
56 changes: 56 additions & 0 deletions tests/operator/exists_condition_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Unless explicitly stated otherwise all files in this repository are
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2021 Datadog, Inc.

#include "../test.hpp"
#include "condition/exists.hpp"

using namespace ddwaf;
using namespace std::literals;

namespace {

template <typename... Args> std::vector<condition_parameter> gen_param_def(Args... addresses)
{
return {{{{std::string{addresses}, get_target_index(addresses)}}}...};
}

TEST(TestExistsCondition, AddressAvailable)
{
exists_condition cond{{gen_param_def("server.request.uri_raw")}};

ddwaf_object tmp;
ddwaf_object root;
ddwaf_object_map(&root);
ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_invalid(&tmp));

object_store store;
store.insert(root);

ddwaf::timer deadline{2s};
condition_cache cache;
auto res = cond.eval(cache, store, {}, {}, deadline);
ASSERT_TRUE(res.outcome);
}

TEST(TestExistsCondition, AddressNotAvaialble)
{
exists_condition cond{{gen_param_def("server.request.uri_raw")}};

ddwaf_object tmp;
ddwaf_object root;
ddwaf_object_map(&root);
ddwaf_object_map_add(&root, "server.request.query", ddwaf_object_invalid(&tmp));

object_store store;
store.insert(root);

ddwaf::timer deadline{2s};
condition_cache cache;
auto res = cond.eval(cache, store, {}, {}, deadline);
ASSERT_FALSE(res.outcome);
}

} // namespace
Loading

0 comments on commit 3c57f42

Please sign in to comment.