diff --git a/CHANGELOG.md b/CHANGELOG.md index bd2d2831e..40e80e29f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ # libddwaf release +### v1.17.0-alpha3 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +#### Changes +- Action semantics and related improvements ([#277](https://github.com/DataDog/libddwaf/pull/277)) + +#### Miscellaneous +- LFI detector fuzzer ([#274](https://github.com/DataDog/libddwaf/pull/274)) ### v1.17.0-alpha2 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Changes diff --git a/CMakeLists.txt b/CMakeLists.txt index 680e03c56..0d34722e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,7 @@ if (LIBDDWAF_TESTING) add_subdirectory(benchmark EXCLUDE_FROM_ALL) add_subdirectory(fuzzer EXCLUDE_FROM_ALL) add_subdirectory(tools EXCLUDE_FROM_ALL) + add_subdirectory(examples EXCLUDE_FROM_ALL) include(cmake/clang-tidy.cmake) include(cmake/clang-format.cmake) diff --git a/README.md b/README.md index eabb43200..e5cc41598 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Build](https://github.com/DataDog/libddwaf/actions/workflows/build.yml/badge.svg)](https://github.com/DataDog/libddwaf/actions/workflows/build.yml) -# Datadog's WAF +# Datadog's WAF & RASP Engine ``libddwaf`` is Datadog's implementation of a Web Application Firewall (WAF) engine, with a goal of low performance and memory overhead, and embeddability in a wide variety of language runtimes through a C API. @@ -65,116 +65,231 @@ The general process is as follows: ### Example -```c +The full example can be found [here](examples/example.cpp). + +```cpp #include #include "ddwaf.h" -int main(void) +constexpr std::string_view waf_rule = R"( +version: "2.1" +rules: + - id: "1" + name: rule 1 + tags: + type: flow1 + category: test + conditions: + - operator: match_regex + parameters: + inputs: + - address: arg1 + regex: .* + - operator: match_regex + parameters: + inputs: + - address: arg2 + regex: .* + on_match: [ block ] +)"; + +int main() { + YAML::Node doc = YAML::Load(waf_rule.data()); - YAML::Node doc = YAML::Load(R"({version: '0.1', events: [{id: 1, tags: {type: flow1}, conditions: [{operation: match_regex, parameters: {inputs: [arg1], regex: .*}},{operation: match_regex, parameters: {inputs: [arg2], regex: .*}}], action: record}]})"); - - ddwaf_object rule = doc.as();//= convert_yaml_to_args(doc); + auto rule = doc.as();//= convert_yaml_to_args(doc); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ddwaf_object_free(&rule); if (handle == nullptr) { - exit(EXIT_FAILURE); + return EXIT_FAILURE; } ddwaf_context context = ddwaf_context_init(handle); - if (handle == nullptr) { + if (context == nullptr) { ddwaf_destroy(handle); - exit(EXIT_FAILURE); + return EXIT_FAILURE; } - ddwaf_object param1, param2, tmp; - ddwaf_object_map(¶m1); - ddwaf_object_map(¶m2); - ddwaf_object_map_add(¶m1, "arg1", ddwaf_object_string(&tmp, "string 1")); - ddwaf_object_map_add(¶m2, "arg2", ddwaf_object_string(&tmp, "string 2")); + ddwaf_object root, tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "arg1", ddwaf_object_string(&tmp, "string 1")); + ddwaf_object_map_add(&root, "arg2", ddwaf_object_string(&tmp, "string 2")); ddwaf_result ret; - auto code = ddwaf_run(context, ¶m1, nullptr, &ret, LONG_TIME); - printf("Output first run: %d\n", code); - ddwaf_result_free(&ret); + auto code = ddwaf_run(context, &root, nullptr, &ret, 1000000 /* microseconds */); + std::cout << "Output second run: " << code << '\n'; + if (code == DDWAF_MATCH) { + YAML::Emitter out(std::cout); + out.SetIndent(2); + out.SetMapFormat(YAML::Block); + out.SetSeqFormat(YAML::Block); + out << object_to_yaml(ret.events); + out << object_to_yaml(ret.actions); + } - code = ddwaf_run(context, ¶m2, nullptr, &ret, LONG_TIME); - printf("Output second run: %d - %s\n", ret.action, ret.data); ddwaf_result_free(&ret); ddwaf_context_destroy(context); ddwaf_destroy(handle); + + return EXIT_SUCCESS; } ``` -### YAML to ddwaf::object converter example +#### YAML to ddwaf::object converter example ```cpp -namespace YAML -{ - -class parsing_error : public std::exception -{ -public: - parsing_error(const std::string& what) : what_(what) {} - const char* what() { return what_.c_str(); } -protected: - const std::string what_; -}; +namespace YAML { -ddwaf_object node_to_arg(const Node& node) +template <> +struct as_if { - switch (node.Type()) + explicit as_if(const Node& node_) : node(node_) {} + + static ddwaf_object yaml_to_object_helper(const Node& node) { - case NodeType::Sequence: + ddwaf_object arg; + switch (node.Type()) { - ddwaf_object arg; - ddwaf_object_array(&arg); - for (auto it = node.begin(); it != node.end(); ++it) - { - ddwaf_object child = node_to_arg(*it); - ddwaf_object_array_add(&arg, &child); - } - return arg; + case NodeType::Sequence: + ddwaf_object_array(&arg); + break; + case NodeType::Map: + ddwaf_object_map(&arg); + break; + case NodeType::Scalar: + ddwaf_object_string(&arg, node.Scalar().c_str()); + break; + case NodeType::Null: + ddwaf_object_null(&arg); + break; + case NodeType::Undefined: + default: + ddwaf_object_invalid(&arg); + break; } - case NodeType::Map: - { - ddwaf_object arg; - ddwaf_object_map(&arg); - for (auto it = node.begin(); it != node.end(); ++it) - { - std::string key = it->first.as(); - ddwaf_object child = node_to_arg(it->second); - ddwaf_object_map_addl(&arg, key.c_str(), key.size(), &child); - } - return arg; + return arg; + } + + ddwaf_object operator()() const { + std::list> stack; + + ddwaf_object root = yaml_to_object_helper(node); + if (root.type == DDWAF_OBJ_MAP || root.type == DDWAF_OBJ_ARRAY) { + stack.emplace_back(root, node, node.begin()); } - case NodeType::Scalar: - { - const std::string& value = node.Scalar(); - ddwaf_object arg; - ddwaf_object_stringl(&arg, value.c_str(), value.size()); - return arg; + + while (!stack.empty()) { + auto current_depth = stack.size(); + auto &[parent_obj, parent_node, it] = stack.back(); + + for (;it != parent_node.end(); ++it) { + YAML::Node child_node = parent_node.IsMap() ? it->second : *it; + auto child_obj = yaml_to_object_helper(child_node); + if (parent_obj.type == DDWAF_OBJ_MAP) { + auto key = it->first.as(); + ddwaf_object_map_add(&parent_obj, key.c_str(), &child_obj); + } else if (parent_obj.type == DDWAF_OBJ_ARRAY) { + ddwaf_object_array_add(&parent_obj, &child_obj); + } + + if (child_obj.type == DDWAF_OBJ_MAP || child_obj.type == DDWAF_OBJ_ARRAY) { + auto &child_ptr = parent_obj.array[parent_obj.nbEntries - 1]; + stack.emplace_back(child_ptr, child_node, child_node.begin()); + ++it; + break; + } + } + + if (current_depth == stack.size()) { stack.pop_back(); } } - case NodeType::Null: - case NodeType::Undefined: - // Perhaps this should return an invalid pwarg - ddwaf_object arg; - ddwaf_object_invalid(&arg); - return arg; + return root; } - throw parsing_error("Invalid YAML node type"); -} - -template <> -struct as_if { - explicit as_if(const Node& node_) : node(node_) {} - ddwaf_object operator()() const { return node_to_arg(node); } const Node& node; }; +} // namespace YAML + +``` + +#### ddwaf::object to YAML converter example + +```cpp +namespace { + +YAML::Node object_to_yaml_helper(const ddwaf_object &obj) +{ + YAML::Node output; + switch (obj.type) { + case DDWAF_OBJ_BOOL: + output = obj.boolean; + break; + case DDWAF_OBJ_SIGNED: + output = obj.intValue; + break; + case DDWAF_OBJ_UNSIGNED: + output = obj.uintValue; + break; + case DDWAF_OBJ_FLOAT: + output = obj.f64; + break; + case DDWAF_OBJ_STRING: + output = std::string{obj.stringValue, obj.nbEntries}; + break; + case DDWAF_OBJ_MAP: + output = YAML::Load("{}"); + break; + case DDWAF_OBJ_ARRAY: + output = YAML::Load("[]"); + break; + case DDWAF_OBJ_INVALID: + case DDWAF_OBJ_NULL: + output = YAML::Null; + break; + }; + return output; +} + +} // namespace + +YAML::Node object_to_yaml(const ddwaf_object &obj) +{ + std::list> stack; + + YAML::Node root = object_to_yaml_helper(obj); + if (obj.type == DDWAF_OBJ_MAP || obj.type == DDWAF_OBJ_ARRAY) { + stack.emplace_back(obj, root, 0); + } + + while (!stack.empty()) { + auto current_depth = stack.size(); + auto &[parent_obj, parent_node, index] = stack.back(); + + for (;index ", + "type": "", + "parameters": { "" } + }] +} +``` + +Secondly, since the definition of each action is now available internally, the schema of `ddwaf_result.actions` has been updated from an array of IDs to a map of action types, each containing its own set of parameters: + +```json +{ + "block_request": { + "status_code": 403, + "type": "auto" + } +} +``` + +This means the caller no longer has to translate action IDs to their relevant definition and any blocking action conflicts are resolved internally following a simple set of rules: +- When multiple actions of the same type are present, the first one produced has precedence. +- An action of type`redirect_request` has priority over an action of type `block_request`. + +In addition, specific action types can now have dynamic parameters, such as the `generate_stack` action type, which requires the inclusion of a stack trace UUID in both the action parameters and the relevant event: + +```json +{ + "generate_stack": { + "stack_id": "f96a33a2-f5c1-11ee-99aa-9bdcccee26aa" + } +} +``` + +Finally the following set of default actions are included: +- `block`: of type `block_request`, requires the caller to block the request and provides the following default parameters: + - `status_code`: `403` + - `type`: `auto` + - `grpc_status_code`: `10` +- `stack_trace`: of type `generate_stack`, requires the user to generate a stack trace with the UUID provided in the `stack_id` parameter. +- `extract_schema`: of type `generate_schema`, instructs the user to call the WAF again with the relevant parameters required for schema generation. +- `monitor`: an internal reserved action. + ## Upgrading from `1.14.0` to `1.15.0` ### Interface changes diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..3ee5eec7e --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,15 @@ +file(GLOB LIBDDWAF_EXAMPLE_SOURCE *.cpp) + +foreach(EXAMPLE ${LIBDDWAF_EXAMPLE_SOURCE}) + get_filename_component(EXAMPLE_NAME ${EXAMPLE} NAME_WLE) + + add_executable(${EXAMPLE_NAME} ${EXAMPLE}) + target_link_libraries(${EXAMPLE_NAME} PRIVATE libddwaf_objects lib_yamlcpp lib_rapidjson) + target_include_directories(${EXAMPLE_NAME} PRIVATE ${LIBDDWAF_PRIVATE_INCLUDES}) + + set_target_properties(${EXAMPLE_NAME} PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO) +endforeach() + diff --git a/examples/example.cpp b/examples/example.cpp new file mode 100644 index 000000000..37c83399c --- /dev/null +++ b/examples/example.cpp @@ -0,0 +1,216 @@ +#include "ddwaf.h" +#include +#include + +#define LONG_TIME 1000000 + +namespace YAML { + +template <> struct as_if { + explicit as_if(const Node &node_) : node(node_) {} + + static ddwaf_object yaml_to_object_helper(const Node &node) + { + ddwaf_object arg; + switch (node.Type()) { + case NodeType::Sequence: + ddwaf_object_array(&arg); + break; + case NodeType::Map: + ddwaf_object_map(&arg); + break; + case NodeType::Scalar: + ddwaf_object_string(&arg, node.Scalar().c_str()); + break; + case NodeType::Null: + ddwaf_object_null(&arg); + break; + case NodeType::Undefined: + default: + ddwaf_object_invalid(&arg); + break; + } + return arg; + } + + ddwaf_object operator()() const + { + std::list> stack; + + ddwaf_object root = yaml_to_object_helper(node); + if (root.type == DDWAF_OBJ_MAP || root.type == DDWAF_OBJ_ARRAY) { + stack.emplace_back(root, node, node.begin()); + } + + while (!stack.empty()) { + auto current_depth = stack.size(); + auto &[parent_obj, parent_node, it] = stack.back(); + + for (; it != parent_node.end(); ++it) { + YAML::Node child_node = parent_node.IsMap() ? it->second : *it; + auto child_obj = yaml_to_object_helper(child_node); + if (parent_obj.type == DDWAF_OBJ_MAP) { + auto key = it->first.as(); + ddwaf_object_map_add(&parent_obj, key.c_str(), &child_obj); + } else if (parent_obj.type == DDWAF_OBJ_ARRAY) { + ddwaf_object_array_add(&parent_obj, &child_obj); + } + + if (child_obj.type == DDWAF_OBJ_MAP || child_obj.type == DDWAF_OBJ_ARRAY) { + auto &child_ptr = parent_obj.array[parent_obj.nbEntries - 1]; + stack.emplace_back(child_ptr, child_node, child_node.begin()); + ++it; + break; + } + } + + if (current_depth == stack.size()) { + stack.pop_back(); + } + } + return root; + } + + const Node &node; +}; + +} // namespace YAML + +namespace { + +YAML::Node object_to_yaml_helper(const ddwaf_object &obj) +{ + YAML::Node output; + switch (obj.type) { + case DDWAF_OBJ_BOOL: + output = obj.boolean; + break; + case DDWAF_OBJ_SIGNED: + output = obj.intValue; + break; + case DDWAF_OBJ_UNSIGNED: + output = obj.uintValue; + break; + case DDWAF_OBJ_FLOAT: + output = obj.f64; + break; + case DDWAF_OBJ_STRING: + output = std::string{obj.stringValue, obj.nbEntries}; + break; + case DDWAF_OBJ_MAP: + output = YAML::Load("{}"); + break; + case DDWAF_OBJ_ARRAY: + output = YAML::Load("[]"); + break; + case DDWAF_OBJ_INVALID: + case DDWAF_OBJ_NULL: + output = YAML::Null; + break; + }; + return output; +} + +} // namespace + +YAML::Node object_to_yaml(const ddwaf_object &obj) +{ + std::list> stack; + + YAML::Node root = object_to_yaml_helper(obj); + if (obj.type == DDWAF_OBJ_MAP || obj.type == DDWAF_OBJ_ARRAY) { + stack.emplace_back(obj, root, 0); + } + + while (!stack.empty()) { + auto current_depth = stack.size(); + auto &[parent_obj, parent_node, index] = stack.back(); + + for (; index < parent_obj.nbEntries; ++index) { + auto &child_obj = parent_obj.array[index]; + auto child_node = object_to_yaml_helper(child_obj); + + if (parent_obj.type == DDWAF_OBJ_MAP) { + std::string key{child_obj.parameterName, child_obj.parameterNameLength}; + parent_node[key] = child_node; + } else if (parent_obj.type == DDWAF_OBJ_ARRAY) { + parent_node.push_back(child_node); + } + + if (child_obj.type == DDWAF_OBJ_MAP || child_obj.type == DDWAF_OBJ_ARRAY) { + stack.emplace_back(child_obj, child_node, 0); + ++index; + break; + } + } + + if (current_depth == stack.size()) { + stack.pop_back(); + } + } + return root; +} + +constexpr std::string_view waf_rule = R"( +version: "2.1" +rules: + - id: "1" + name: rule 1 + tags: + type: flow1 + category: test + conditions: + - operator: match_regex + parameters: + inputs: + - address: arg1 + regex: .* + - operator: match_regex + parameters: + inputs: + - address: arg2 + regex: .* + on_match: [ block ] +)"; + +int main() +{ + YAML::Node doc = YAML::Load(waf_rule.data()); + + auto rule = doc.as(); //= convert_yaml_to_args(doc); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ddwaf_object_free(&rule); + if (handle == nullptr) { + return EXIT_FAILURE; + } + + ddwaf_context context = ddwaf_context_init(handle); + if (context == nullptr) { + ddwaf_destroy(handle); + return EXIT_FAILURE; + } + + ddwaf_object root, tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "arg1", ddwaf_object_string(&tmp, "string 1")); + ddwaf_object_map_add(&root, "arg2", ddwaf_object_string(&tmp, "string 2")); + + ddwaf_result ret; + auto code = ddwaf_run(context, &root, nullptr, &ret, LONG_TIME); + std::cout << "Output second run: " << code << '\n'; + if (code == DDWAF_MATCH) { + YAML::Emitter out(std::cout); + out.SetIndent(2); + out.SetMapFormat(YAML::Block); + out.SetSeqFormat(YAML::Block); + out << object_to_yaml(ret.events); + out << object_to_yaml(ret.actions); + } + + ddwaf_result_free(&ret); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); + + return EXIT_SUCCESS; +} diff --git a/include/ddwaf.h b/include/ddwaf.h index b52b6028d..cfad7f935 100644 --- a/include/ddwaf.h +++ b/include/ddwaf.h @@ -162,7 +162,9 @@ struct _ddwaf_result bool timeout; /** Array of events generated, this is guaranteed to be an array **/ ddwaf_object events; - /** Array of actions generated, this is guaranteed to be an array **/ + /** Map of actions generated, this is guaranteed to be a map in the format: + * {action type: { }, ...} + **/ ddwaf_object actions; /** Map containing all derived objects in the format (address, value) **/ ddwaf_object derivatives; diff --git a/src/condition/structured_condition.hpp b/src/condition/structured_condition.hpp index 94a0fee6d..0522b8239 100644 --- a/src/condition/structured_condition.hpp +++ b/src/condition/structured_condition.hpp @@ -76,7 +76,7 @@ template std::optional convert(const ddwaf_object *obj) if constexpr (std::is_same_v) { if (obj->type == DDWAF_OBJ_BOOL) { - return static_cast(obj->intValue); + return static_cast(obj->boolean); } } diff --git a/src/event.cpp b/src/event.cpp index f7b60b792..875ec89e1 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -63,7 +63,7 @@ void serialize_match(const condition_match &match, ddwaf_object &match_map, auto } // Scalar case - if (match.args.size() == 1 || match.args[0].name == "input") { + if (match.args.size() == 1 && match.args[0].name == "input") { const auto &arg = match.args[0]; ddwaf_object key_path; diff --git a/version b/version index c8ec401e8..d339f7df0 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.17.0-alpha2 \ No newline at end of file +1.17.0-alpha3 \ No newline at end of file