From a4254149e1efa128cf6d0b4f01961628f40255cc Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Mon, 17 Jun 2024 19:16:21 +0100 Subject: [PATCH 01/17] Upgrade to macos-12 (#312) --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1e267008..dbfc897e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,7 @@ jobs: ${{ github.workspace }}/packages/*.sha256 macos-build: - runs-on: macos-11 + runs-on: macos-12 steps: - uses: actions/checkout@v4 with: @@ -87,7 +87,7 @@ jobs: ${{ github.workspace }}/packages/*.sha256 macos-cross-build: - runs-on: macos-11 + runs-on: macos-12 steps: - uses: actions/checkout@v4 with: @@ -117,7 +117,7 @@ jobs: ${{ github.workspace }}/packages/*.sha256 macos-universal-package: - runs-on: macos-11 + runs-on: macos-12 needs: [macos-build, macos-cross-build] steps: - uses: actions/checkout@v4 From 6c23797d00357f25287f8089ac982c3fc29b3aa0 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Wed, 19 Jun 2024 15:35:22 +0100 Subject: [PATCH 02/17] SHA256 hash based on OpenSSL (#304) --- .github/workflows/fuzz.yml | 2 + LICENSE-3rdparty.csv | 1 + cmake/objects.cmake | 1 + fuzzer/sha256/corpus/.gitignore | 4 + fuzzer/sha256/src/main.cpp | 25 +++ src/sha256.cpp | 300 ++++++++++++++++++++++++++++++++ src/sha256.hpp | 49 ++++++ tests/sha256_test.cpp | 172 ++++++++++++++++++ 8 files changed, 554 insertions(+) create mode 100644 fuzzer/sha256/corpus/.gitignore create mode 100644 fuzzer/sha256/src/main.cpp create mode 100644 src/sha256.cpp create mode 100644 src/sha256.hpp create mode 100644 tests/sha256_test.cpp diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 4fb6d92c4..2263c7523 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -86,6 +86,8 @@ jobs: params: "--dialect=sqlite" - fuzzer: sqli_detector params: "--dialect=standard" + - fuzzer: sha256 + params: "" steps: - uses: actions/checkout@v4 diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 0d9b2ddef..5715ff459 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -8,5 +8,6 @@ core,https://github.com/llvm/llvm-project/libcxxabi,Apache-2.0,"Copyright (c) 20 core,https://github.com/llvm/llvm-project/libunwind,Apache-2.0,"Copyright (c) 2009-2019 by the contributors listed in CREDITS.TXT" core,https://github.com/JuliaStrings/utf8proc,MIT,"Copyright (c) 2014-2021 by Steven G. Johnson, Jiahao Chen, Tony Kelman, Jonas Fonseca, and other contributors listed in the git history" core,https://github.com/fmtlib/fmt,MIT,"Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors" +core,https://github.com/openssl/openssl,Apache-2.0,"Copyright © 1999-2024, OpenSSL Project Authors. All Rights Reserved." dev,https://github.com/jbeder/yaml-cpp,MIT,Copyright (c) 2008-2015 Jesse Beder dev,https://github.com/Tencent/rapidjson,MIT,"Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip" diff --git a/cmake/objects.cmake b/cmake/objects.cmake index b89f46146..a10ee021c 100644 --- a/cmake/objects.cmake +++ b/cmake/objects.cmake @@ -19,6 +19,7 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/utils.cpp ${libddwaf_SOURCE_DIR}/src/waf.cpp ${libddwaf_SOURCE_DIR}/src/platform.cpp + ${libddwaf_SOURCE_DIR}/src/sha256.cpp ${libddwaf_SOURCE_DIR}/src/uuid.cpp ${libddwaf_SOURCE_DIR}/src/action_mapper.cpp ${libddwaf_SOURCE_DIR}/src/builder/processor_builder.cpp diff --git a/fuzzer/sha256/corpus/.gitignore b/fuzzer/sha256/corpus/.gitignore new file mode 100644 index 000000000..5e7d2734c --- /dev/null +++ b/fuzzer/sha256/corpus/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/fuzzer/sha256/src/main.cpp b/fuzzer/sha256/src/main.cpp new file mode 100644 index 000000000..010750a7d --- /dev/null +++ b/fuzzer/sha256/src/main.cpp @@ -0,0 +1,25 @@ +// 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 + +#include "sha256.hpp" + +extern "C" int LLVMFuzzerInitialize(const int * /*argc*/, char *** /*argv*/) { return 0; } + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) +{ + ddwaf::sha256_hash hasher; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + hasher << std::string_view{reinterpret_cast(bytes), size}; + auto str = hasher.digest(); + + // Force the compiler to not optimize away str + // NOLINTNEXTLINE(hicpp-no-assembler) + asm volatile("" : "+m"(str) : : "memory"); + + return 0; +} diff --git a/src/sha256.cpp b/src/sha256.cpp new file mode 100644 index 000000000..7ef19a7ee --- /dev/null +++ b/src/sha256.cpp @@ -0,0 +1,300 @@ +// 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. +// +// This code has been adapted from OpenSSL. +// +// Copyright 2004-2023 The OpenSSL Project Authors. All Rights Reserved. +// +// Licensed under the Apache License 2.0 (the "License"). You may not use +// this file except in compliance with the License. You can obtain a copy +// in the file LICENSE in the source distribution or at +// https://www.openssl.org/source/license.html +// + +#include "sha256.hpp" + +namespace ddwaf { +namespace { + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +/* + * Note that FIPS180-2 discusses "Truncation of the Hash Function Output." + * default: case below covers for it. It's not clear however if it's + * permitted to truncate to amount of bytes not divisible by 4. I bet not, + * but if it is, then default: case shall be extended. For reference. + * Idea behind separate cases for pre-defined lengths is to let the + * compiler decide if it's appropriate to unroll small loops. + */ +#define ROTATE(a, n) (((a) << (n)) | (((a) & 0xffffffff) >> (32 - (n)))) + +#define CHAR_TO_UINT32(c, l) \ + { \ + (l) = ((static_cast(*((c)++))) << 24); \ + (l) |= ((static_cast(*((c)++))) << 16); \ + (l) |= ((static_cast(*((c)++))) << 8); \ + (l) |= ((static_cast(*((c)++)))); \ + } + +#define UINT8_TO_HEX_CHAR(u) static_cast((u) < 10 ? (u) + '0' : (u)-10 + 'a') + +/* + * FIPS specification refers to right rotations, while our ROTATE macro + * is left one. This is why you might notice that rotation coefficients + * differ from those observed in FIPS document by 32-N... + */ +#define Sigma0(x) (ROTATE((x), 30) ^ ROTATE((x), 19) ^ ROTATE((x), 10)) +#define Sigma1(x) (ROTATE((x), 26) ^ ROTATE((x), 21) ^ ROTATE((x), 7)) +#define sigma0(x) (ROTATE((x), 25) ^ ROTATE((x), 14) ^ ((x) >> 3)) +#define sigma1(x) (ROTATE((x), 15) ^ ROTATE((x), 13) ^ ((x) >> 10)) +#define Ch(x, y, z) (((x) & (y)) ^ ((~(x)) & (z))) +#define Maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) + +#define ROUND_00_15(i, a, b, c, d, e, f, g, h) \ + { \ + T1 += (h) + Sigma1(e) + Ch(e, f, g) + K256[i]; \ + (h) = Sigma0(a) + Maj(a, b, c); \ + (d) += T1; \ + (h) += T1; \ + } + +#define ROUND_16_63(i, a, b, c, d, e, f, g, h, X) \ + { \ + s0 = (X)[((i) + 1) & 0x0f]; \ + s0 = sigma0(s0); \ + s1 = (X)[((i) + 14) & 0x0f]; \ + s1 = sigma1(s1); \ + T1 = (X)[(i) & 0x0f] += s0 + s1 + (X)[((i) + 9) & 0x0f]; \ + ROUND_00_15(i, a, b, c, d, e, f, g, h); \ + } + +// NOLINTEND(cppcoreguidelines-macro-usage) + +constexpr std::array K256 = {0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, + 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, + 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, + 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, + 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, + 0x4d2c6dfcUL, 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, + 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, + 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, + 0x84c87814UL, 0x8cc70208UL, 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL}; + +// Length of the string representation of the digest +constexpr std::size_t sha_digest_length = 64; + +} // namespace + +sha256_hash &sha256_hash::operator<<(std::string_view str) +{ + uint8_t *p; + uint32_t l; + size_t n; + + if (str.length() == 0) { + return *this; + } + auto len = static_cast(str.length()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + const auto *data = reinterpret_cast(str.data()); + + l = (length_low + ((len) << 3)) & 0xffffffffUL; + if (l < length_low) { /* overflow */ + length_high++; + } + length_high += (len >> 29); /* might cause compiler warning on + * 16-bit */ + length_low = l; + + n = num; + if (n != 0) { + p = buffer.data(); + + if (len >= block_size || len + n >= block_size) { + memcpy(p + n, data, block_size - n); + sha_block_data_order(p, 1); + n = block_size - n; + data += n; + len -= n; + num = 0; + memset(p, 0, block_size); /* keep it zeroed */ + } else { + memcpy(p + n, data, len); + num += len; + return *this; + } + } + + n = len / block_size; + if (n > 0) { + sha_block_data_order(data, n); + n *= block_size; + data += n; + len -= n; + } + + if (len != 0) { + p = buffer.data(); + num = len; + memcpy(p, data, len); + } + return *this; +} + +std::string sha256_hash::digest() +{ + auto *p = buffer.data(); + size_t n = num; + + p[n] = 0x80; /* there is always room for one */ + n++; + + if (n > (block_size - 8)) { + memset(p + n, 0, block_size - n); + n = 0; + sha_block_data_order(p, 1); + } + memset(p + n, 0, block_size - 8 - n); + + p += block_size - 8; + + *(p++) = static_cast((length_high >> 24) & 0xff); + *(p++) = static_cast((length_high >> 16) & 0xff); + *(p++) = static_cast((length_high >> 8) & 0xff); + *(p++) = static_cast(length_high & 0xff); + + *(p++) = static_cast((length_low >> 24) & 0xff); + *(p++) = static_cast((length_low >> 16) & 0xff); + *(p++) = static_cast((length_low >> 8) & 0xff); + *(p++) = static_cast(length_low & 0xff); + + p -= block_size; + + sha_block_data_order(p, 1); + num = 0; + memset(p, 0, block_size); + + std::array final_digest{0}; + for (unsigned int nn = 0; nn < sha_digest_length; nn += 8) { + uint32_t ll = hash[nn >> 3]; + final_digest[nn + 0] = UINT8_TO_HEX_CHAR(static_cast((ll >> 28) & 0x0f)); + final_digest[nn + 1] = UINT8_TO_HEX_CHAR(static_cast((ll >> 24) & 0x0f)); + final_digest[nn + 2] = UINT8_TO_HEX_CHAR(static_cast((ll >> 20) & 0x0f)); + final_digest[nn + 3] = UINT8_TO_HEX_CHAR(static_cast((ll >> 16) & 0x0f)); + final_digest[nn + 4] = UINT8_TO_HEX_CHAR(static_cast((ll >> 12) & 0x0f)); + final_digest[nn + 5] = UINT8_TO_HEX_CHAR(static_cast((ll >> 8) & 0x0f)); + final_digest[nn + 6] = UINT8_TO_HEX_CHAR(static_cast((ll >> 4) & 0x0f)); + final_digest[nn + 7] = UINT8_TO_HEX_CHAR(static_cast(ll & 0x0f)); + } + + // Reset the hasher and return + reset(); + + return std::string{final_digest.data(), 64}; +} + +void sha256_hash::sha_block_data_order(const uint8_t *data, size_t len) +{ + unsigned int a; + unsigned int b; + unsigned int c; + unsigned int d; + unsigned int e; + unsigned int f; + unsigned int g; + unsigned int h; + unsigned int s0; + unsigned int s1; + unsigned int T1; + std::array X{0}; + int i; + + while ((len--) != 0) { + + a = hash[0]; + b = hash[1]; + c = hash[2]; + d = hash[3]; + e = hash[4]; + f = hash[5]; + g = hash[6]; + h = hash[7]; + + uint32_t l; + + CHAR_TO_UINT32(data, l); + T1 = X[0] = l; + ROUND_00_15(0, a, b, c, d, e, f, g, h); + CHAR_TO_UINT32(data, l); + T1 = X[1] = l; + ROUND_00_15(1, h, a, b, c, d, e, f, g); + CHAR_TO_UINT32(data, l); + T1 = X[2] = l; + ROUND_00_15(2, g, h, a, b, c, d, e, f); + CHAR_TO_UINT32(data, l); + T1 = X[3] = l; + ROUND_00_15(3, f, g, h, a, b, c, d, e); + CHAR_TO_UINT32(data, l); + T1 = X[4] = l; + ROUND_00_15(4, e, f, g, h, a, b, c, d); + CHAR_TO_UINT32(data, l); + T1 = X[5] = l; + ROUND_00_15(5, d, e, f, g, h, a, b, c); + CHAR_TO_UINT32(data, l); + T1 = X[6] = l; + ROUND_00_15(6, c, d, e, f, g, h, a, b); + CHAR_TO_UINT32(data, l); + T1 = X[7] = l; + ROUND_00_15(7, b, c, d, e, f, g, h, a); + CHAR_TO_UINT32(data, l); + T1 = X[8] = l; + ROUND_00_15(8, a, b, c, d, e, f, g, h); + CHAR_TO_UINT32(data, l); + T1 = X[9] = l; + ROUND_00_15(9, h, a, b, c, d, e, f, g); + CHAR_TO_UINT32(data, l); + T1 = X[10] = l; + ROUND_00_15(10, g, h, a, b, c, d, e, f); + CHAR_TO_UINT32(data, l); + T1 = X[11] = l; + ROUND_00_15(11, f, g, h, a, b, c, d, e); + CHAR_TO_UINT32(data, l); + T1 = X[12] = l; + ROUND_00_15(12, e, f, g, h, a, b, c, d); + CHAR_TO_UINT32(data, l); + T1 = X[13] = l; + ROUND_00_15(13, d, e, f, g, h, a, b, c); + CHAR_TO_UINT32(data, l); + T1 = X[14] = l; + ROUND_00_15(14, c, d, e, f, g, h, a, b); + CHAR_TO_UINT32(data, l); + T1 = X[15] = l; + ROUND_00_15(15, b, c, d, e, f, g, h, a); + + for (i = 16; i < 64; i += 8) { + ROUND_16_63(i + 0, a, b, c, d, e, f, g, h, X); + ROUND_16_63(i + 1, h, a, b, c, d, e, f, g, X); + ROUND_16_63(i + 2, g, h, a, b, c, d, e, f, X); + ROUND_16_63(i + 3, f, g, h, a, b, c, d, e, X); + ROUND_16_63(i + 4, e, f, g, h, a, b, c, d, X); + ROUND_16_63(i + 5, d, e, f, g, h, a, b, c, X); + ROUND_16_63(i + 6, c, d, e, f, g, h, a, b, X); + ROUND_16_63(i + 7, b, c, d, e, f, g, h, a, X); + } + + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; + hash[4] += e; + hash[5] += f; + hash[6] += g; + hash[7] += h; + } +} + +} // namespace ddwaf diff --git a/src/sha256.hpp b/src/sha256.hpp new file mode 100644 index 000000000..fe03e7c36 --- /dev/null +++ b/src/sha256.hpp @@ -0,0 +1,49 @@ +// 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 +#include +#include +#include + +namespace ddwaf { + +class sha256_hash { +public: + sha256_hash() = default; + ~sha256_hash() = default; + sha256_hash(const sha256_hash &) = delete; + sha256_hash(sha256_hash &&) = delete; + sha256_hash &operator=(const sha256_hash &) = delete; + sha256_hash &operator=(sha256_hash &&) noexcept = delete; + + sha256_hash &operator<<(std::string_view str); + [[nodiscard]] std::string digest(); + + void reset() + { + hash = initial_hash_values; + length_low = 0; + length_high = 0; + buffer = {0}; + num = {0}; + } + +protected: + static constexpr std::size_t block_size = 64; + static constexpr std::array initial_hash_values{0x6a09e667, 0xbb67ae85, 0x3c6ef372, + 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; + + void sha_block_data_order(const uint8_t *data, size_t len); + + std::array hash{initial_hash_values}; + uint32_t length_low{0}; + uint32_t length_high{0}; + std::array buffer{0}; + uint32_t num{0}; +}; + +} // namespace ddwaf diff --git a/tests/sha256_test.cpp b/tests/sha256_test.cpp new file mode 100644 index 000000000..2ad84f383 --- /dev/null +++ b/tests/sha256_test.cpp @@ -0,0 +1,172 @@ +// 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 "sha256.hpp" +#include "test.hpp" + +#include +#include + +TEST(TestSha256, RandomInputTest) +{ + std::vector> samples{ + {"YqJwhSIzGlqqehMom6Z3Ok4bqLFN2Kpn", + "403b62faf37de5e5e6e02a300a2ed1a42e2dbf9d83951ec8b288bf026b6f79a2"}, + {"3f3qLGvqHleNTAWS7MxB0QGXLoS1IMk1", + "535a34d235d1a0914d49abd6d9e6ceb8cd55ce4948214e7805e5d829616c1a3b"}, + {"QZly1PUSiJ5sxknDjzh5ZKOmam9gGSnl", + "2af42b61fc7b75cbfcc2fb7aaee9ba287e0fcdcae284f8498cdcf97bdfa8ad6c"}, + {"np1WXOU7JTH1zd3N3eEEVpojfCSG07qN", + "3fadb64fe82e69e500aeba66aaa4042dcf1e33f7636b3f826bb13651de8c3c6e"}, + {"kZCS8KnHLcBGyJvqXKVMTYZyQvKdXONI", + "34990ff286e09cb6c88ca4db69143de0ef495820ecbc1b874e1171ce3038015c"}, + {"zmZO2KUR3KKa2llyA8vdQuLSjEIHRGDx", + "4ce5dcf2b57e70660cae49d5e9747f0b6e6f3d0a43173858ced3543735474a37"}, + {"xcd5YpxGerWhw6B0lR4G7duiMftNSu8V", + "06784e3810f127b2e1cb1c37b184ed7723a19cd4ba5f3afc01730646b20db89a"}, + {"PAIbUxTu9WiHNUXxlzwrb6E4oEkJqwM8", + "87a5aefe7cc55c91f556d2e37b0c3871b0792db570ec57abe6b07a0d96b9ca94"}, + {"tFUFGUSS6GXa6IQwTXHULFPt4GeeaKnR", + "505349119e977eefdd0cb24715a9133e0c4b93597cb4e94f953f3b4eadd63725"}, + {"WfUaDo5mEMZIH15gfIxBh3mwfJDGNL6h", + "dce8225f0559d76a1928c004a6a55887293761c40623cdbbe7d31c97326d8c8b"}, + {"auTy0yqqU9QvVGjhCGCx2C7Zx5wCE61P", + "5af6e6fbaf3a551abb60c0a366668581a42584dd1c3ae5756d25172e6f0a8554"}, + {"ue30YmBaXqXejTRwWHOPABDVo0NCOydy", + "0002bef6617f034614d9444f6a74b86136037534a162837639d8c4d8d81844ac"}, + {"m7UkVhIDVOtqXaVvkFbGoc6WUwG4s8D6", + "4e9d3107abf6d9128de5a12d9ce2bbd5c61a12e993c01832ce531f728ee5c21a"}, + {"PqT7NuFLaTlHQulpoccYzAgvDRJHbp3M", + "37187a7c80428feb5c0976b3f32d44616a52799ebfc7f31f95613f4e5d938678"}, + {"RgbxLhrZFbrYv5XykdSBlU537fDYkW1g", + "34144fc83c319f8ceff879d67531d7b9aed67bc81a2ec02f9ad401b52fe217b6"}, + {"wngyajXn1cSOaczvTomAAItUXwGZOEoz", + "4a1e660e9b4f63fa28d489f025f04deed37045a051b590e8ab36617bcc307e81"}, + {"Esyl9moe4yShgxayVGaGTP4nP0cZCoVC", + "a4d0f3b09d6ade659061fb1d21a5a5ef07d9b71bdc0ca2f389466d8a4ac53527"}, + {"qkXBYY4xnD1dGLthAYZCo2me3c5MG58q", + "ab5af4c9a196e775fd25e84e33a118d422d8ed983f6bb4b9e676fc028723648b"}, + {"XPhl2droso1w5qdf9kt7ztzuL0a3T8zi", + "48f76b330cf0d34a1942956af302f6b85d6806eb52f8a736a057744298be58e2"}, + {"uVF4yUAufiREPkPFwlUOrEsQ10duGD0L", + "e61c936a0d8c85f4bc9d22e25bca6cea9ac6b2097fd567f60d5efa38c34cfb30"}, + {"JaoF4myRTg4TCMm", "a1f9adb16b52ef2c845e85bc552a86c6426cb2780bb061e34c8982e41c2e36af"}, + {"1sj5xeZxRF623qV", "97cc8c0f72ed810be1f3d9653372926e6e5884f341e58f4edfb2f875b097655e"}, + {"ipsEfuH3nmPqzcp", "32adf694cb470c2ffd50b26fb684510510b0d4b227f89bd2ae5ba49d2a6e4c3a"}, + {"Biy1I5idi5TQK9e", "76d42814fecde876121bbf8c73721893578077092eb30aa072a78679f80a48a8"}, + {"dE8BpndRLevSL81", "6d7218b399480aed8afb21194f57daf1767c4ac20ff5edcd2c757e7c214d9ef7"}, + {"92eB4V0FBDivRwB", "ff7e8ec21651f29cfeb692318b506db7b09be207765ff4eae000a8a2f9b579cc"}, + {"we37ZqY2Swj5hrf", "da1134a980af5aa6a74d2cd09f6478ada8cfd69e4385a66bf8393345629db865"}, + {"YmmkJi9JmR5F4uK", "6eb6206a23058f0c8c945b79dab52e374a7cccfde17928312d7dd1156ec0b013"}, + {"xVdC0kucB0ykKd3", "065093b4389e81c2235ccf57c4f7d9916e478ece5209aac5d850ae82d7c1106b"}, + {"kovdtReY2LmgFn8", "d51e607c19b019efe4d3768bb9e0202c0b439c0c556bd4ba87e2e05417f54c3f"}, + }; + + for (auto &[original, hash] : samples) { + ddwaf::sha256_hash hasher; + hasher << original; + + EXPECT_STR(hasher.digest(), hash.c_str()); + } +} + +TEST(TestSha256, StreamedTest) +{ + std::string blob = "Ai2KO2HKBOXdwJRlrxbNrDXQus8Slx9G" + "DW7DU9Uptyr1TLZM4msEuP0qRXZqbIcl" + "9IdxP1H909eeH09vC7fM6zGY5OGh9oIZ" + "hlHjjC2cLC47vOpthrUQTwKaEs06uwnt" + "4haHvH3Y9znnEkFz9iEVMtvpO3Apxtyh" + "lwXNcvrj56UKTz2KHPHQV2GdrRRrDDWt" + "4mjiQTX8ozDyQO1mN8WZapm1F16Fj538" + "IenCvtermrzDYIFXsBe1tovNIatThxVS" + "euVkn2oijl7XgT1dmOnWkaFGjer8Ic38" + "itIcWeNzqID6mUQxoYual6Qkl7OqD8jb" + "WpkOshSHB2y1hkRUnSub5rCFwh2nTvsp" + "F0oH1FOIn7gU6OI24DWgPsxJrz18Teht" + "EhGQUBcVxBZiNaVV87VniRbUT6kkGKKN" + "F1pGrWGSDSwTL1AEXuhpSXv1Q705kGzi" + "TExoakd2wOUsordxOv3bF2Um1EKfFGHN" + "FTFTph7B49WdhxTXDF0JLHdHYCkNS6oc" + "MB6D2hmZAEuNPp3sO7YDXnGCI0lk2mEK" + "IHk17j3jBIEI6KJOHHErtZiE3i2g2j9a" + "dWetVl6ElvfTewSHByuLSAkYjfEhtAct" + "EmHBedfeQ8mLiKd2fcRzItB4DtZRlRGw" + "1eHlCZFFciwxTPxUL30ornn1sDdHa7N7" + "TewPCQiljeRilUK4rzl24toKBrF3FheK" + "JPfUlmOV8T6rrqpq8iDX4fMVwkHcyyNZ" + "JD6n3TIfDnZcsurqjywtp9PbWuvB0FxP" + "Ujm4U3Nn68kIQs326iLrpy1tDwu1e7Io" + "iRyA3xpLIKvnIAabgXggUzL6hTd2QbRZ" + "4QwRvJoHwZjjei08baJqrAL5CiytVsMW" + "gIqWp8Wq4HmlOhJpLxuLHn6xPoCsuaPh" + "0yFqjZ33cmZJ3M2jAG2Fwnx7kObsOO2E" + "ENUR6iVXLUQJeDGep1JA7tupGQtynAmL" + "kmM82VgKlKx3tMsWEU1xZXM6oJeeRKPm" + "KSfe8ivjQBb9YaLfcuidxhmoWCu0vn9H" + "hGQaeHhqlbZ0CVDR1R4wPeF4cGr1AaFJ" + "E10KDeGJaMiagBMOT5o8h4K7PvkBpnDv" + "U1a55iWhxPf9w952Si0EsFLRJBjb5QYn" + "sqdhqeCe3DyPQbCOGRITYdKuxQBcBL7W" + "PdtHrWSqEkwOL6Mc1HLmEQALr3uSgvA7" + "wxP6WN3A05T0v9w6UkEWlwspX4nd96Xj" + "zZKgKEwsEjJ7bL2Lo0T0BokSjA2n5pTn" + "1iw0quvM9wiPk2FKmXV0IMWI9oHBlwmu" + "FVzE023CSNiN0UQ4fOklUPwOZ2ugdNeI" + "AGPppJa6LYPdR1vg7G1BbDg7gR74XjxU" + "eKQFDGMTELYhloXFC9J5U9oAwxKwcSWK" + "wjIKFWazNYXDVKJWN4BlutUOE6MqvPaD" + "COk1r4LiWCI88CvvoTleQMtDbeO4dYHp" + "cLiXmG57FrGEu9NhaPkFPe9Qn4CaBmq8" + "Ivq7RDYnQnbEnvuQXsiL9bXnL8gwR5Ws" + "y9OqKZSeuDfiwUAgWXbFNI7QTxsPzy12" + "L2CyAIGsYvwEpzIBKu2ZrD7eKTBUnjhZ" + "w5jZADzh4dO79PZ5kyzPEyK56TF2KSb6" + "ZT3bZhnruaHMnQnOG8bk" + "WPFXaHtDusILTT6xrv4S" + "1GGIuIloQRt7CwM7aA5i" + "uSLS8ZwmeVW9nyHhKPEF" + "ERH0zHH7Camp2sZ621Sz" + "DcDZQkxGoixYaFqemYpD" + "zxo0s1rNTYzUWtDIxfFK" + "UkX9j8XOTepq0s9PmBks" + "lAGJhcPvO8kFojBJyubu" + "Kq72twgeJpablacHOtfD" + "JKfkR8ALfqaEI7ubAv9C" + "AuiUMCcOuNdAqWNbjybN" + "XM7ScXx9yvPvdiMeY1ma" + "XZXPw6yL3V6ufx20ZLVT" + "VKoYSrCVLGpEyRLaMz3e" + "RAGX5eKp6S6XBKUe0iwx" + "D22oOuVl6R78MgvGWsoS" + "YG1qxg4dJayp5O4IRAUy" + "KNWlAmcXjspkFDYOFRk2" + "cxvLG2mrBgXZkBe7tqVw" + "FkNsRWwswSJvT7aP1MpI" + "t6k4rhJLholcpQCTN2Vi" + "50fM7yVWNBCIOrdyF0R9" + "HE2Zd3BDVPl9dhb5oYDN"; + + // Test whole blob first + { + ddwaf::sha256_hash hasher; + hasher << blob; + EXPECT_STR( + hasher.digest(), "a198619280b5107d1fde448e4098d5b527a216a4bc41df70d24ae33fc2dac2da"); + } + + { + ddwaf::sha256_hash hasher; + + for (std::size_t i = 0, bytes = 1; i < blob.size(); i += bytes, ++bytes) { + auto sub = blob.substr(i, (i + bytes) >= blob.size() ? std::string::npos : bytes); + hasher << sub; + } + + EXPECT_STR( + hasher.digest(), "a198619280b5107d1fde448e4098d5b527a216a4bc41df70d24ae33fc2dac2da"); + } +} From 2fbc6766c8a44de31507ac05b3d46b4394581caf Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 21 Jun 2024 21:23:43 +0100 Subject: [PATCH 03/17] Skip disabled rules when generating ruleset (#314) --- src/indexer.hpp | 10 +- src/mkmap.hpp | 8 ++ src/ruleset_builder.cpp | 20 +++ tests/interface_test.cpp | 60 ++++++++ tests/mkmap_test.cpp | 158 +++++++++++++++++++++ tests/waf_test.cpp | 17 +-- tests/yaml/ruleset_with_disabled_rule.yaml | 25 ++++ 7 files changed, 284 insertions(+), 14 deletions(-) create mode 100644 tests/yaml/ruleset_with_disabled_rule.yaml diff --git a/src/indexer.hpp b/src/indexer.hpp index ab5397c9a..5568a58fd 100644 --- a/src/indexer.hpp +++ b/src/indexer.hpp @@ -27,6 +27,14 @@ template class indexer { by_tags_.insert(item->get_tags(), item.get()); } + iterator erase(iterator &it) + { + auto &item = *it; + by_tags_.erase(item->get_tags(), item.get()); + by_id_.erase(item->get_id()); + return items_.erase(it); + } + T *find_by_id(std::string_view id) const { auto it = by_id_.find(id); @@ -58,7 +66,7 @@ template class indexer { protected: std::vector> items_; std::unordered_map by_id_; - multi_key_map by_tags_; + multi_key_map by_tags_; }; } // namespace ddwaf diff --git a/src/mkmap.hpp b/src/mkmap.hpp index 8f434632a..fc352a2d6 100644 --- a/src/mkmap.hpp +++ b/src/mkmap.hpp @@ -27,6 +27,14 @@ class multi_key_map { for (const auto &key : keys) { data_[key.first][key.second].emplace(value); } } + template ::value, + typename U::iterator>> + void erase(const U &keys, const T &value) + { + for (const auto &key : keys) { data_[key.first][key.second].erase(value); } + } + template std::set find(const std::pair &key) const { auto first_it = data_.find(key.first); diff --git a/src/ruleset_builder.cpp b/src/ruleset_builder.cpp index b238486ae..1d5e58a15 100644 --- a/src/ruleset_builder.cpp +++ b/src/ruleset_builder.cpp @@ -112,6 +112,15 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules } } } + + // Remove any disabled rules + for (auto it = final_base_rules_.begin(); it != final_base_rules_.end();) { + if (!(*it)->is_enabled()) { + it = final_base_rules_.erase(it); + } else { + ++it; + } + } } if ((state & change_state::custom_rules) != change_state::none) { @@ -121,6 +130,10 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules for (const auto &[id, spec] : user_rules_) { auto rule_ptr = std::make_shared( id, spec.name, spec.tags, spec.expr, spec.actions, spec.enabled, spec.source); + if (!rule_ptr->is_enabled()) { + // Skip disabled rules + continue; + } final_user_rules_.emplace(rule_ptr); } } @@ -180,6 +193,13 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules rs->free_fn = free_fn_; rs->event_obfuscator = event_obfuscator_; + // Since disabled rules aren't added to the final ruleset, we must check + // again that there are rules available. + if (rs->rules.empty()) { + DDWAF_WARN("No valid rules found"); + throw ddwaf::parsing_error("no valid or enabled rules found"); + } + return rs; } diff --git a/tests/interface_test.cpp b/tests/interface_test.cpp index 45f9f9ff7..639bea110 100644 --- a/tests/interface_test.cpp +++ b/tests/interface_test.cpp @@ -1848,4 +1848,64 @@ TEST(TestInterface, UpdateEverything) ddwaf_destroy(handle1); } +TEST(TestInterface, KnownAddressesDisabledRule) +{ + auto rule = read_file("ruleset_with_disabled_rule.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_handle handle2; + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{rule_id: id-rule-1}], enabled: true}]})"); + handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + ddwaf_handle handle3; + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{rule_id: id-rule-1}], enabled: false}]})"); + handle3 = ddwaf_update(handle2, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + { + uint32_t size; + const auto *addresses = ddwaf_known_addresses(handle1, &size); + std::set available_addresses{"value2"}; + while ((size--) != 0U) { + EXPECT_NE(available_addresses.find(addresses[size]), available_addresses.end()); + } + } + + { + uint32_t size; + const auto *addresses = ddwaf_known_addresses(handle2, &size); + + std::set available_addresses{"value1", "value2"}; + while ((size--) != 0U) { + EXPECT_NE(available_addresses.find(addresses[size]), available_addresses.end()); + } + } + + { + uint32_t size; + const auto *addresses = ddwaf_known_addresses(handle3, &size); + std::set available_addresses{"value2"}; + while ((size--) != 0U) { + EXPECT_NE(available_addresses.find(addresses[size]), available_addresses.end()); + } + } + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + } // namespace diff --git a/tests/mkmap_test.cpp b/tests/mkmap_test.cpp index 8dabf4df8..6de5be537 100644 --- a/tests/mkmap_test.cpp +++ b/tests/mkmap_test.cpp @@ -150,4 +150,162 @@ TEST(TestMultiKeyMap, Multifind) } } +TEST(TestMultiKeyMap, Erase) +{ + rule_tag_map ruledb; + + struct rule_spec { + std::string id; + std::string type; + std::string category; + std::unordered_map tags; + }; + + std::vector specs{{"id0", "type0", "category0", {{"key", "value0"}}}, + {"id1", "type0", "category0", {{"key", "value1"}}}, + {"id2", "type0", "category1", {{"key", "value0"}}}, + {"id3", "type0", "category1", {{"key", "value1"}}}, + {"id4", "type1", "category0", {{"key", "value0"}}}, + {"id5", "type1", "category0", {{"key", "value1"}}}, + {"id6", "type1", "category1", {{"key", "value0"}}}, + {"id7", "type1", "category1", {{"key", "value1"}}}}; + + std::vector> rules; + for (const auto &spec : specs) { + std::unordered_map tags = spec.tags; + tags.emplace("type", spec.type); + tags.emplace("category", spec.category); + + auto rule_ptr = std::make_shared( + std::string(spec.id), "name", decltype(tags)(tags), std::make_shared()); + rules.emplace_back(rule_ptr); + ruledb.insert(rule_ptr->get_tags(), rule_ptr.get()); + } + + using sv_pair = std::pair; + { + auto rules = ruledb.find(sv_pair{"type"sv, "type0"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(sv_pair{"category", "category0"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(sv_pair{"key"sv, "value0"sv}); + EXPECT_EQ(rules.size(), 4); + } + + using s_pair = std::pair; + { + auto rules = ruledb.find(s_pair{"type"sv, "type1"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(s_pair{"category", "category1"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(s_pair{"key"sv, "value1"sv}); + EXPECT_EQ(rules.size(), 4); + } + + ruledb.erase(rules[3]->get_tags(), rules[3].get()); + { + auto rules = ruledb.find(sv_pair{"type"sv, "type0"}); + EXPECT_EQ(rules.size(), 3); + } + + { + auto rules = ruledb.find(sv_pair{"category", "category0"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(sv_pair{"key"sv, "value0"sv}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(sv_pair{"type"sv, "type1"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(sv_pair{"category", "category1"}); + EXPECT_EQ(rules.size(), 3); + } + + { + auto rules = ruledb.find(sv_pair{"key"sv, "value1"sv}); + EXPECT_EQ(rules.size(), 3); + } + + ruledb.erase(rules[0]->get_tags(), rules[0].get()); + { + auto rules = ruledb.find(sv_pair{"type"sv, "type0"}); + EXPECT_EQ(rules.size(), 2); + } + + { + auto rules = ruledb.find(sv_pair{"category", "category0"}); + EXPECT_EQ(rules.size(), 3); + } + + { + auto rules = ruledb.find(sv_pair{"key"sv, "value0"sv}); + EXPECT_EQ(rules.size(), 3); + } + + { + auto rules = ruledb.find(sv_pair{"type"sv, "type1"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(sv_pair{"category", "category1"}); + EXPECT_EQ(rules.size(), 3); + } + + { + auto rules = ruledb.find(sv_pair{"key"sv, "value1"sv}); + EXPECT_EQ(rules.size(), 3); + } + + ruledb.erase(rules[7]->get_tags(), rules[7].get()); + { + auto rules = ruledb.find(sv_pair{"type"sv, "type0"}); + EXPECT_EQ(rules.size(), 2); + } + + { + auto rules = ruledb.find(sv_pair{"category", "category0"}); + EXPECT_EQ(rules.size(), 3); + } + + { + auto rules = ruledb.find(sv_pair{"key"sv, "value0"sv}); + EXPECT_EQ(rules.size(), 3); + } + + { + auto rules = ruledb.find(sv_pair{"type"sv, "type1"}); + EXPECT_EQ(rules.size(), 3); + } + + { + auto rules = ruledb.find(sv_pair{"category", "category1"}); + EXPECT_EQ(rules.size(), 2); + } + + { + auto rules = ruledb.find(sv_pair{"key"sv, "value1"sv}); + EXPECT_EQ(rules.size(), 2); + } +} + } // namespace diff --git a/tests/waf_test.cpp b/tests/waf_test.cpp index bbcb3fc88..55699258f 100644 --- a/tests/waf_test.cpp +++ b/tests/waf_test.cpp @@ -53,20 +53,11 @@ TEST(TestWaf, RuleDisabledInRuleset) ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); ddwaf::null_ruleset_info info; - ddwaf::waf instance{ - rule, info, ddwaf::object_limits(), ddwaf_object_free, std::make_shared()}; - ddwaf_object_free(&rule); + EXPECT_THROW((ddwaf::waf{rule, info, ddwaf::object_limits(), ddwaf_object_free, + std::make_shared()}), + ddwaf::parsing_error); - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); - - auto *ctx = instance.create_context(); - EXPECT_EQ(ctx->run(root, std::nullopt, std::nullopt, LONG_TIME), DDWAF_OK); - delete ctx; - } + ddwaf_object_free(&rule); } TEST(TestWaf, AddressUniqueness) diff --git a/tests/yaml/ruleset_with_disabled_rule.yaml b/tests/yaml/ruleset_with_disabled_rule.yaml new file mode 100644 index 000000000..3f450161b --- /dev/null +++ b/tests/yaml/ruleset_with_disabled_rule.yaml @@ -0,0 +1,25 @@ +version: '2.1' +rules: + - id: id-rule-1 + name: rule1 + enabled: false + tags: + type: flow1 + category: category1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value1 + regex: rule1 + - id: id-rule-2 + name: rule2 + tags: + type: flow2 + category: category2 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value2 + regex: rule2 From aaf4d2a2b9343fc903428294be1d3558393aa8af Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Mon, 24 Jun 2024 15:21:45 +0100 Subject: [PATCH 04/17] Custom rule filter actions (#303) --- src/collection.cpp | 11 +- src/context.cpp | 3 +- src/event.cpp | 29 +- src/event.hpp | 2 +- src/exclusion/common.hpp | 31 +- src/exclusion/rule_filter.cpp | 7 +- src/exclusion/rule_filter.hpp | 4 +- src/parser/exclusion_parser.cpp | 8 +- src/parser/specification.hpp | 1 + src/ruleset_builder.cpp | 2 +- tests/parser_v2_rule_filters_test.cpp | 46 ++- tests/rule_filter_test.cpp | 281 +++++++++++++++++- tests/yaml/bypass_custom_precedence.yaml | 31 ++ tests/yaml/exclude_with_custom_action.yaml | 19 ++ ...lude_with_custom_action_and_condition.yaml | 27 ++ .../yaml/exclude_with_nonblocking_action.yaml | 20 ++ tests/yaml/exclude_with_unknown_action.yaml | 19 ++ tests/yaml/monitor_custom_precedence.yaml | 31 ++ .../010_rule10_rule_monitored_by_tags.yaml | 2 +- .../011_rule11_rule_custom_action_by_id.yaml | 22 ++ .../012_rule11_rule_not_blocking.yaml | 11 + .../rule_filter/unconditional/ruleset.yaml | 22 ++ 22 files changed, 591 insertions(+), 38 deletions(-) create mode 100644 tests/yaml/bypass_custom_precedence.yaml create mode 100644 tests/yaml/exclude_with_custom_action.yaml create mode 100644 tests/yaml/exclude_with_custom_action_and_condition.yaml create mode 100644 tests/yaml/exclude_with_nonblocking_action.yaml create mode 100644 tests/yaml/exclude_with_unknown_action.yaml create mode 100644 tests/yaml/monitor_custom_precedence.yaml create mode 100644 validator/tests/exclusions/rule_filter/unconditional/011_rule11_rule_custom_action_by_id.yaml create mode 100644 validator/tests/exclusions/rule_filter/unconditional/012_rule11_rule_not_blocking.yaml diff --git a/src/collection.cpp b/src/collection.cpp index 41076ad98..69795b4ee 100644 --- a/src/collection.cpp +++ b/src/collection.cpp @@ -29,7 +29,7 @@ std::optional match_rule(rule *rule, const object_store &store, return std::nullopt; } - action_type action_override = action_type::none; + std::string_view action_override; auto exclusion = policy.find(rule); if (exclusion.mode == exclusion::filter_mode::bypass) { DDWAF_DEBUG("Bypassing rule '{}'", id); @@ -38,11 +38,14 @@ std::optional match_rule(rule *rule, const object_store &store, if (exclusion.mode == exclusion::filter_mode::monitor) { DDWAF_DEBUG("Monitoring rule '{}'", id); - action_override = action_type::monitor; + action_override = "monitor"; + } else if (exclusion.mode == exclusion::filter_mode::custom) { + action_override = exclusion.action_override; + DDWAF_DEBUG("Evaluating rule '{}' with custom action '{}'", id, action_override); + } else { + DDWAF_DEBUG("Evaluating rule '{}'", id); } - DDWAF_DEBUG("Evaluating rule '{}'", id); - try { auto it = cache.find(rule); if (it == cache.end()) { diff --git a/src/context.cpp b/src/context.cpp index 9e0617c7c..a3c97ba59 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -159,7 +159,8 @@ exclusion::context_policy &context::eval_filters(ddwaf::timer &deadline) auto exclusion = filter->match(store_, cache, deadline); if (exclusion.has_value()) { for (const auto &rule : exclusion->rules) { - exclusion_policy_.add_rule_exclusion(rule, exclusion->mode, exclusion->ephemeral); + exclusion_policy_.add_rule_exclusion( + rule, exclusion->mode, exclusion->action, exclusion->ephemeral); } } } diff --git a/src/event.cpp b/src/event.cpp index 6de443950..a4df749cb 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -178,10 +178,10 @@ void serialize_empty_rule(ddwaf_object &rule_map) } void serialize_and_consolidate_rule_actions(const ddwaf::rule &rule, ddwaf_object &rule_map, - action_type action_override, action_tracker &actions, ddwaf_object &stack_id) + std::string_view action_override, action_tracker &actions, ddwaf_object &stack_id) { const auto &rule_actions = rule.get_actions(); - if (rule_actions.empty()) { + if (rule_actions.empty() && action_override.empty()) { return; } @@ -189,15 +189,34 @@ void serialize_and_consolidate_rule_actions(const ddwaf::rule &rule, ddwaf_objec ddwaf_object actions_array; ddwaf_object_array(&actions_array); - if (action_override == action_type::monitor) { - ddwaf_object_array_add(&actions_array, to_object(tmp, "monitor")); + if (!action_override.empty()) { + auto action_it = actions.mapper.find(action_override); + if (action_it != actions.mapper.end()) { + const auto &[type, type_str, parameters] = action_it->second; + + // The action override must be either a blocking one or monitor + if (type == action_type::monitor || is_blocking_action(type)) { + add_action_to_tracker(actions, action_override, type); + } else { + // Clear the action override because it's not usable + action_override = {}; + } + } else { + // Without a definition, the override can't be applied + action_override = {}; + } + + // Tha override might have been clear if no definition was found + if (!action_override.empty()) { + ddwaf_object_array_add(&actions_array, to_object(tmp, action_override)); + } } for (const auto &action_id : rule_actions) { auto action_it = actions.mapper.find(action_id); if (action_it != actions.mapper.end()) { const auto &[type, type_str, parameters] = action_it->second; - if (action_override == action_type::monitor && + if (!action_override.empty() && (type == action_type::monitor || is_blocking_action(type))) { // If the rule was in monitor mode, ignore blocking and monitor actions continue; diff --git a/src/event.hpp b/src/event.hpp index 226c6b06d..1d1d34445 100644 --- a/src/event.hpp +++ b/src/event.hpp @@ -21,7 +21,7 @@ struct event { const ddwaf::rule *rule{nullptr}; std::vector matches; bool ephemeral{false}; - action_type action_override{action_type::none}; + std::string_view action_override; }; using optional_event = std::optional; diff --git a/src/exclusion/common.hpp b/src/exclusion/common.hpp index cf76d7701..aa4437c52 100644 --- a/src/exclusion/common.hpp +++ b/src/exclusion/common.hpp @@ -19,7 +19,7 @@ class rule; namespace exclusion { -enum class filter_mode : uint8_t { none = 0, monitor = 1, bypass = 2 }; +enum class filter_mode : uint8_t { none = 0, custom = 1, monitor = 2, bypass = 3 }; struct object_set { std::unordered_set persistent; @@ -35,7 +35,8 @@ struct object_set { struct rule_policy { filter_mode mode{filter_mode::none}; - std::unordered_set objects{}; + std::string_view action_override; + std::unordered_set objects; }; struct object_set_ref { @@ -63,6 +64,7 @@ struct object_set_ref { struct rule_policy_ref { filter_mode mode{filter_mode::none}; + std::string_view action_override; object_set_ref objects; }; @@ -86,38 +88,37 @@ struct context_policy { if (p_it == persistent.end()) { if (e_it == ephemeral.end()) { - return {filter_mode::none, {std::nullopt, std::nullopt}}; + return {filter_mode::none, {}, {std::nullopt, std::nullopt}}; } const auto &e_policy = e_it->second; - return {e_policy.mode, {std::nullopt, e_policy.objects}}; + return {e_policy.mode, e_policy.action_override, {std::nullopt, e_policy.objects}}; } if (e_it == ephemeral.end()) { const auto &p_policy = p_it->second; p_policy.objects.size(); - return {p_policy.mode, {p_policy.objects, std::nullopt}}; + return {p_policy.mode, p_policy.action_override, {p_policy.objects, std::nullopt}}; } const auto &p_policy = p_it->second; const auto &e_policy = e_it->second; - auto mode = p_policy.mode > e_policy.mode ? p_policy.mode : e_policy.mode; - return {mode, {p_policy.objects, e_policy.objects}}; + const auto &effective_policy = p_policy.mode > e_policy.mode ? p_policy : e_policy; + return {effective_policy.mode, effective_policy.action_override, + {p_policy.objects, e_policy.objects}}; } - void add_rule_exclusion(const ddwaf::rule *rule, filter_mode mode, bool ephemeral_exclusion) + void add_rule_exclusion(const ddwaf::rule *rule, filter_mode mode, std::string_view action, + bool ephemeral_exclusion) { auto &rule_policy = ephemeral_exclusion ? ephemeral : persistent; - auto it = rule_policy.find(rule); + auto &policy = rule_policy[rule]; // Bypass has precedence over monitor - if (it != rule_policy.end()) { - if (it->second.mode < mode) { - it->second.mode = mode; - } - } else { - rule_policy[rule].mode = mode; + if (policy.mode < mode) { + policy.mode = mode; + policy.action_override = action; } } diff --git a/src/exclusion/rule_filter.cpp b/src/exclusion/rule_filter.cpp index 19b4dae82..392430da9 100644 --- a/src/exclusion/rule_filter.cpp +++ b/src/exclusion/rule_filter.cpp @@ -6,14 +6,15 @@ #include #include +#include namespace ddwaf::exclusion { using excluded_set = rule_filter::excluded_set; rule_filter::rule_filter(std::string id, std::shared_ptr expr, - std::set rule_targets, filter_mode mode) - : id_(std::move(id)), expr_(std::move(expr)), mode_(mode) + std::set rule_targets, filter_mode mode, std::string action) + : id_(std::move(id)), expr_(std::move(expr)), mode_(mode), action_(std::move(action)) { if (!expr_) { throw std::invalid_argument("rule filter constructed with null expression"); @@ -40,7 +41,7 @@ std::optional rule_filter::match( return std::nullopt; } - return {{rule_targets_, res.ephemeral, mode_}}; + return {{rule_targets_, res.ephemeral, mode_, action_}}; } } // namespace ddwaf::exclusion diff --git a/src/exclusion/rule_filter.hpp b/src/exclusion/rule_filter.hpp index 508829d40..041f0f071 100644 --- a/src/exclusion/rule_filter.hpp +++ b/src/exclusion/rule_filter.hpp @@ -23,12 +23,13 @@ class rule_filter { const std::unordered_set &rules; bool ephemeral{false}; filter_mode mode{filter_mode::none}; + std::string_view action; }; using cache_type = expression::cache_type; rule_filter(std::string id, std::shared_ptr expr, std::set rule_targets, - filter_mode mode = filter_mode::bypass); + filter_mode mode = filter_mode::bypass, std::string action = {}); rule_filter(const rule_filter &) = delete; rule_filter &operator=(const rule_filter &) = delete; rule_filter(rule_filter &&) = default; @@ -50,6 +51,7 @@ class rule_filter { std::shared_ptr expr_; std::unordered_set rule_targets_; filter_mode mode_; + std::string action_{}; }; } // namespace ddwaf::exclusion diff --git a/src/parser/exclusion_parser.cpp b/src/parser/exclusion_parser.cpp index 6f68ed221..f56f1b4a4 100644 --- a/src/parser/exclusion_parser.cpp +++ b/src/parser/exclusion_parser.cpp @@ -73,19 +73,23 @@ rule_filter_spec parse_rule_filter( exclusion::filter_mode on_match; auto on_match_str = at(filter, "on_match", "bypass"); + std::string on_match_id; if (on_match_str == "bypass") { on_match = exclusion::filter_mode::bypass; } else if (on_match_str == "monitor") { on_match = exclusion::filter_mode::monitor; + } else if (!on_match_str.empty()) { + on_match = exclusion::filter_mode::custom; + on_match_id = on_match_str; } else { - throw ddwaf::parsing_error("unsupported on_match value: " + std::string(on_match_str)); + throw ddwaf::parsing_error("empty on_match value"); } if (expr->empty() && rules_target.empty()) { throw ddwaf::parsing_error("empty exclusion filter"); } - return {std::move(expr), std::move(rules_target), on_match}; + return {std::move(expr), std::move(rules_target), on_match, std::move(on_match_id)}; } } // namespace diff --git a/src/parser/specification.hpp b/src/parser/specification.hpp index 83b60bf06..b244dc968 100644 --- a/src/parser/specification.hpp +++ b/src/parser/specification.hpp @@ -46,6 +46,7 @@ struct rule_filter_spec { std::shared_ptr expr; std::vector targets; exclusion::filter_mode on_match; + std::string custom_action; }; struct input_filter_spec { diff --git a/src/ruleset_builder.cpp b/src/ruleset_builder.cpp index 1d5e58a15..35c8d11ec 100644 --- a/src/ruleset_builder.cpp +++ b/src/ruleset_builder.cpp @@ -149,7 +149,7 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules rule_targets.merge(references_to_rules(filter.targets, final_user_rules_)); auto filter_ptr = std::make_shared( - id, filter.expr, std::move(rule_targets), filter.on_match); + id, filter.expr, std::move(rule_targets), filter.on_match, filter.custom_action); rule_filters_.emplace(filter_ptr->get_id(), filter_ptr); } diff --git a/tests/parser_v2_rule_filters_test.cpp b/tests/parser_v2_rule_filters_test.cpp index f29c58ee5..563b8b9fc 100644 --- a/tests/parser_v2_rule_filters_test.cpp +++ b/tests/parser_v2_rule_filters_test.cpp @@ -635,7 +635,7 @@ TEST(TestParserV2RuleFilters, ParseOnMatchBypass) EXPECT_EQ(filter.on_match, exclusion::filter_mode::bypass); } -TEST(TestParserV2RuleFilters, ParseInvalidOnMatch) +TEST(TestParserV2RuleFilters, ParseCustomOnMatch) { ddwaf::object_limits limits; @@ -647,6 +647,47 @@ TEST(TestParserV2RuleFilters, ParseInvalidOnMatch) auto filters = parser::v2::parse_filters(filters_array, section, limits); ddwaf_object_free(&object); + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(filters.rule_filters.size(), 1); + EXPECT_EQ(filters.input_filters.size(), 0); + + const auto &filter_it = filters.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + const auto &filter = filter_it->second; + EXPECT_EQ(filter.on_match, exclusion::filter_mode::custom); + EXPECT_STR(filter.custom_action, "obliterate"); +} + +TEST(TestParserV2RuleFilters, ParseInvalidOnMatch) +{ + ddwaf::object_limits limits; + + auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: ""}])"); + + ddwaf::ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + auto filters = parser::v2::parse_filters(filters_array, section, limits); + ddwaf_object_free(&object); + { ddwaf::parameter root; section.to_object(root); @@ -662,7 +703,7 @@ TEST(TestParserV2RuleFilters, ParseInvalidOnMatch) auto errors = ddwaf::parser::at(root_map, "errors"); EXPECT_EQ(errors.size(), 1); - auto it = errors.find("unsupported on_match value: obliterate"); + auto it = errors.find("empty on_match value"); EXPECT_NE(it, errors.end()); auto error_rules = static_cast(it->second); @@ -675,5 +716,4 @@ TEST(TestParserV2RuleFilters, ParseInvalidOnMatch) EXPECT_EQ(filters.rule_filters.size(), 0); EXPECT_EQ(filters.input_filters.size(), 0); } - } // namespace diff --git a/tests/rule_filter_test.cpp b/tests/rule_filter_test.cpp index 0915eada3..8df318f1f 100644 --- a/tests/rule_filter_test.cpp +++ b/tests/rule_filter_test.cpp @@ -1029,7 +1029,7 @@ TEST(TestRuleFilter, AvoidHavingTwoMonitorOnActions) ddwaf_destroy(handle); } -TEST(TestRuleFilter, FilterModePrecedence) +TEST(TestRuleFilter, MonitorBypassFilterModePrecedence) { auto rule = read_file("monitor_bypass_precedence.yaml"); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -1050,4 +1050,283 @@ TEST(TestRuleFilter, FilterModePrecedence) ddwaf_context_destroy(context); ddwaf_destroy(handle); } + +TEST(TestRuleFilter, MonitorCustomFilterModePrecedence) +{ + auto rule = read_file("monitor_custom_precedence.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .actions = {"monitor"}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}); + EXPECT_ACTIONS(out, {}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestRuleFilter, BypassCustomFilterModePrecedence) +{ + auto rule = read_file("bypass_custom_precedence.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + EXPECT_EQ(ddwaf_run(context, &root, nullptr, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestRuleFilter, UnconditionalCustomFilterMode) +{ + auto rule = read_file("exclude_with_custom_action.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .actions = {"block"}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}); + EXPECT_ACTIONS(out, + {{"block_request", {{"status_code", "403"}, {"grpc_status_code", "10"}, {"type", "auto"}}}}) + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestRuleFilter, ConditionalCustomFilterMode) +{ + auto rule = read_file("exclude_with_custom_action_and_condition.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + { + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .actions = {"block"}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}); + EXPECT_ACTIONS(out, {{"block_request", {{"status_code", "403"}, {"grpc_status_code", "10"}, + {"type", "auto"}}}}) + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + { + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.2")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.2", + .args = {{ + .value = "192.168.0.2", + .address = "http.client_ip", + }}}}}); + EXPECT_ACTIONS(out, {}) + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + ddwaf_destroy(handle); +} + +TEST(TestRuleFilter, CustomFilterModeUnknownAction) +{ + auto rule = read_file("exclude_with_unknown_action.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + auto *handle1 = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + { + ddwaf_context context = ddwaf_context_init(handle1); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}); + EXPECT_EQ(out.actions.nbEntries, 0); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_handle handle2; + { + auto overrides = + yaml_to_object(R"({actions: [{id: block2, type: block_request, parameters: {}}]})"); + handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_object_free(&overrides); + ASSERT_NE(handle2, nullptr); + } + + { + ddwaf_context context = ddwaf_context_init(handle2); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .actions = {"block2"}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}); + EXPECT_ACTIONS(out, {{"block_request", {{"status_code", "403"}, {"grpc_status_code", "10"}, + {"type", "auto"}}}}) + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); +} + +TEST(TestRuleFilter, CustomFilterModeNonblockingAction) +{ + // In this test, the ruleset contains a rule filter with the action + // generate_stack, which is neither a blocking, redirecting or monitoring + // action, hence its ignored. + auto rule = read_file("exclude_with_nonblocking_action.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + auto *handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .actions = {"block"}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}); + EXPECT_ACTIONS(out, + {{"block_request", {{"status_code", "403"}, {"grpc_status_code", "10"}, {"type", "auto"}}}}) + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + + ddwaf_destroy(handle); +} + } // namespace diff --git a/tests/yaml/bypass_custom_precedence.yaml b/tests/yaml/bypass_custom_precedence.yaml new file mode 100644 index 000000000..29d5a8e8c --- /dev/null +++ b/tests/yaml/bypass_custom_precedence.yaml @@ -0,0 +1,31 @@ +version: '2.1' +exclusions: + - id: 1 + rules_target: + - rule_id: 1 + on_match: block + - id: 2 + rules_target: + - rule_id: 1 + on_match: bypass +rules: + - id: 1 + name: rule1 + tags: + type: type1 + category: category + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + list: + - 192.168.0.1 + on_match: [ redirect ] +actions: + - id: redirect + parameters: + status_code: 303 + location: google.com + type: redirect_request + diff --git a/tests/yaml/exclude_with_custom_action.yaml b/tests/yaml/exclude_with_custom_action.yaml new file mode 100644 index 000000000..536927704 --- /dev/null +++ b/tests/yaml/exclude_with_custom_action.yaml @@ -0,0 +1,19 @@ +version: '2.1' +exclusions: + - id: 1 + rules_target: + - rule_id: 1 + on_match: block +rules: + - id: 1 + name: rule1 + tags: + type: type1 + category: category + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + list: + - 192.168.0.1 diff --git a/tests/yaml/exclude_with_custom_action_and_condition.yaml b/tests/yaml/exclude_with_custom_action_and_condition.yaml new file mode 100644 index 000000000..6e395bf0f --- /dev/null +++ b/tests/yaml/exclude_with_custom_action_and_condition.yaml @@ -0,0 +1,27 @@ +version: '2.1' +exclusions: + - id: 1 + rules_target: + - rule_id: 1 + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + list: + - 192.168.0.1 + on_match: block +rules: + - id: 1 + name: rule1 + tags: + type: type1 + category: category + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + list: + - 192.168.0.1 + - 192.168.0.2 diff --git a/tests/yaml/exclude_with_nonblocking_action.yaml b/tests/yaml/exclude_with_nonblocking_action.yaml new file mode 100644 index 000000000..0c8112179 --- /dev/null +++ b/tests/yaml/exclude_with_nonblocking_action.yaml @@ -0,0 +1,20 @@ +version: '2.1' +exclusions: + - id: 1 + rules_target: + - rule_id: 1 + on_match: generate_stack +rules: + - id: 1 + name: rule1 + tags: + type: type1 + category: category + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + list: + - 192.168.0.1 + on_match: [ block ] diff --git a/tests/yaml/exclude_with_unknown_action.yaml b/tests/yaml/exclude_with_unknown_action.yaml new file mode 100644 index 000000000..2d31493c6 --- /dev/null +++ b/tests/yaml/exclude_with_unknown_action.yaml @@ -0,0 +1,19 @@ +version: '2.1' +exclusions: + - id: 1 + rules_target: + - rule_id: 1 + on_match: block2 +rules: + - id: 1 + name: rule1 + tags: + type: type1 + category: category + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + list: + - 192.168.0.1 diff --git a/tests/yaml/monitor_custom_precedence.yaml b/tests/yaml/monitor_custom_precedence.yaml new file mode 100644 index 000000000..82f29fbf1 --- /dev/null +++ b/tests/yaml/monitor_custom_precedence.yaml @@ -0,0 +1,31 @@ +version: '2.1' +exclusions: + - id: 1 + rules_target: + - rule_id: 1 + on_match: monitor + - id: 2 + rules_target: + - rule_id: 1 + on_match: block +rules: + - id: 1 + name: rule1 + tags: + type: type1 + category: category + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + list: + - 192.168.0.1 + on_match: [ redirect ] +actions: + - id: redirect + parameters: + status_code: 303 + location: google.com + type: redirect_request + diff --git a/validator/tests/exclusions/rule_filter/unconditional/010_rule10_rule_monitored_by_tags.yaml b/validator/tests/exclusions/rule_filter/unconditional/010_rule10_rule_monitored_by_tags.yaml index a8d426bf3..3955fda91 100644 --- a/validator/tests/exclusions/rule_filter/unconditional/010_rule10_rule_monitored_by_tags.yaml +++ b/validator/tests/exclusions/rule_filter/unconditional/010_rule10_rule_monitored_by_tags.yaml @@ -1,5 +1,5 @@ { - name: "Rule monitored by rags", + name: "Rule monitored by tags", runs: [ { persistent-input: { diff --git a/validator/tests/exclusions/rule_filter/unconditional/011_rule11_rule_custom_action_by_id.yaml b/validator/tests/exclusions/rule_filter/unconditional/011_rule11_rule_custom_action_by_id.yaml new file mode 100644 index 000000000..c74cbca28 --- /dev/null +++ b/validator/tests/exclusions/rule_filter/unconditional/011_rule11_rule_custom_action_by_id.yaml @@ -0,0 +1,22 @@ +{ + name: "Rule custom action by ID", + runs: [ + { + persistent-input: { + rule11-input: "rule11" + }, + rules: [ + { + 11: [ + { + address: rule11-input, + value: rule11, + on_match: [ block ] + } + ] + } + ], + code: match, + } + ] +} diff --git a/validator/tests/exclusions/rule_filter/unconditional/012_rule11_rule_not_blocking.yaml b/validator/tests/exclusions/rule_filter/unconditional/012_rule11_rule_not_blocking.yaml new file mode 100644 index 000000000..3fe811d75 --- /dev/null +++ b/validator/tests/exclusions/rule_filter/unconditional/012_rule11_rule_not_blocking.yaml @@ -0,0 +1,11 @@ +{ + name: "Rule doesn't block due to no match", + runs: [ + { + persistent-input: { + rule11-input: "rule12" + }, + code: ok, + } + ] +} diff --git a/validator/tests/exclusions/rule_filter/unconditional/ruleset.yaml b/validator/tests/exclusions/rule_filter/unconditional/ruleset.yaml index edeaad643..b646930ae 100644 --- a/validator/tests/exclusions/rule_filter/unconditional/ruleset.yaml +++ b/validator/tests/exclusions/rule_filter/unconditional/ruleset.yaml @@ -40,6 +40,17 @@ exclusions: rules_target: - rule_id: "1" on_match: monitor + # Test precedence of bypass over custom + - id: "9" + rules_target: + - rule_id: "1" + - rule_id: "9" + on_match: redirect + - id: "10" + rules_target: + - rule_id: "11" + on_match: block + rules: - id: "1" @@ -154,3 +165,14 @@ rules: - address: rule10-input regex: rule10 on_match: [ block ] + - id: "11" + name: rule11-custom-by-id + tags: + type: flow11 + category: category11 + conditions: + - operator: match_regex + parameters: + inputs: + - address: rule11-input + regex: rule11 From 15a358881bc51233c0983d4c35195234705f5dbc Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Mon, 24 Jun 2024 16:43:31 +0100 Subject: [PATCH 05/17] Limit the number of transformers per rule or input (#309) --- src/parser/expression_parser.cpp | 16 ++-- src/parser/parser_v1.cpp | 4 + src/parser/rule_parser.cpp | 4 + src/utils.hpp | 1 + tests/parser_v1_test.cpp | 39 +++++++++ tests/parser_v2_rules_test.cpp | 83 +++++++++++++++++++ .../invalid_too_many_transformers_v1.yaml | 29 +++++++ 7 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 tests/yaml/invalid_too_many_transformers_v1.yaml diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index f33966e92..d4a7458e2 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -19,7 +19,8 @@ namespace { template std::vector parse_arguments(const parameter::map ¶ms, data_source source, - const std::vector &transformers, address_container &addresses) + const std::vector &transformers, address_container &addresses, + const object_limits &limits) { const auto &specification = T::arguments(); std::vector definitions; @@ -67,6 +68,10 @@ std::vector parse_arguments(const parameter::map ¶ms, d address, get_target_index(address), std::move(kp), transformers, source}); } else { auto input_transformers = static_cast(it->second); + if (input_transformers.size() > limits.max_transformers_per_address) { + throw ddwaf::parsing_error("number of transformers beyond allowed limit"); + } + source = data_source::values; auto new_transformers = parse_transformers(input_transformers, source); targets.emplace_back(condition_target{address, get_target_index(address), @@ -93,15 +98,16 @@ std::shared_ptr parse_expression(const parameter::vector &conditions auto params = at(root, "parameters"); if (operator_name == "lfi_detector") { - auto arguments = parse_arguments(params, source, transformers, addresses); + auto arguments = + parse_arguments(params, source, transformers, addresses, limits); conditions.emplace_back(std::make_unique(std::move(arguments), limits)); } else if (operator_name == "ssrf_detector") { auto arguments = - parse_arguments(params, source, transformers, addresses); + parse_arguments(params, source, transformers, addresses, limits); conditions.emplace_back(std::make_unique(std::move(arguments), limits)); } else if (operator_name == "sqli_detector") { auto arguments = - parse_arguments(params, source, transformers, addresses); + parse_arguments(params, source, transformers, addresses, limits); conditions.emplace_back(std::make_unique(std::move(arguments), limits)); } else { auto [data_id, matcher] = parse_matcher(operator_name, params); @@ -111,7 +117,7 @@ std::shared_ptr parse_expression(const parameter::vector &conditions } auto arguments = - parse_arguments(params, source, transformers, addresses); + parse_arguments(params, source, transformers, addresses, limits); conditions.emplace_back(std::make_unique( std::move(matcher), data_id, std::move(arguments), limits)); diff --git a/src/parser/parser_v1.cpp b/src/parser/parser_v1.cpp index 13d3f1ad0..8286e5761 100644 --- a/src/parser/parser_v1.cpp +++ b/src/parser/parser_v1.cpp @@ -123,6 +123,10 @@ void parseRule(parameter::map &rule, base_section_info &info, try { std::vector rule_transformers; auto transformers = at(rule, "transformers", parameter::vector()); + if (transformers.size() > limits.max_transformers_per_address) { + throw ddwaf::parsing_error("number of transformers beyond allowed limit"); + } + for (const auto &transformer_param : transformers) { auto transformer = static_cast(transformer_param); auto id = transformer_from_string(transformer); diff --git a/src/parser/rule_parser.cpp b/src/parser/rule_parser.cpp index e351777cb..c9068ca1d 100644 --- a/src/parser/rule_parser.cpp +++ b/src/parser/rule_parser.cpp @@ -19,6 +19,10 @@ rule_spec parse_rule(parameter::map &rule, std::vector rule_transformers; auto data_source = ddwaf::data_source::values; auto transformers = at(rule, "transformers", {}); + if (transformers.size() > limits.max_transformers_per_address) { + throw ddwaf::parsing_error("number of transformers beyond allowed limit"); + } + rule_transformers = parse_transformers(transformers, data_source); auto conditions_array = at(rule, "conditions"); diff --git a/src/utils.hpp b/src/utils.hpp index 204b0ff72..12dbe090e 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -52,6 +52,7 @@ struct object_limits { uint32_t max_container_depth{DDWAF_MAX_CONTAINER_DEPTH}; uint32_t max_container_size{DDWAF_MAX_CONTAINER_SIZE}; uint32_t max_string_length{DDWAF_MAX_STRING_LENGTH}; + uint32_t max_transformers_per_address{10}; // can't be overridden for now }; using target_index = std::size_t; diff --git a/tests/parser_v1_test.cpp b/tests/parser_v1_test.cpp index 9af962211..a975b5c9f 100644 --- a/tests/parser_v1_test.cpp +++ b/tests/parser_v1_test.cpp @@ -324,4 +324,43 @@ TEST(TestParserV1, TestInvalidDuplicate) ddwaf_destroy(handle); } +TEST(TestParserV1, TestInvalidTooManyTransformers) +{ + auto rule = read_file("invalid_too_many_transformers_v1.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &diagnostics); + ASSERT_EQ(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::parser::at(root_map, "ruleset_version", ""); + EXPECT_STREQ(version.c_str(), ""); + + auto rules = ddwaf::parser::at(root_map, "rules"); + + auto loaded = ddwaf::parser::at(rules, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(rules, "failed"); + EXPECT_EQ(failed.size(), 1); + + auto errors = ddwaf::parser::at(rules, "errors"); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("number of transformers beyond allowed limit"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&diagnostics); + ddwaf_destroy(handle); +} + } // namespace diff --git a/tests/parser_v2_rules_test.cpp b/tests/parser_v2_rules_test.cpp index 07f9ca937..53d5d79b5 100644 --- a/tests/parser_v2_rules_test.cpp +++ b/tests/parser_v2_rules_test.cpp @@ -368,4 +368,87 @@ TEST(TestParserV2Rules, ParseMultipleRulesOneDuplicate) EXPECT_STR(rule.tags["category"], "category1"); } } + +TEST(TestParserV2Rules, ParseSingleRuleTooManyTransformers) +{ + ddwaf::object_limits limits; + ddwaf::ruleset_info::section_info section; + std::unordered_map rule_data_ids; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}], transformers: [base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("number of transformers beyond allowed limit"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } +} + +TEST(TestParserV2Rules, ParseSingleRuleTooManyInputTransformers) +{ + ddwaf::object_limits limits; + ddwaf::ruleset_info::section_info section; + std::unordered_map rule_data_ids; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y], transformers: [base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode]}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("number of transformers beyond allowed limit"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } +} + } // namespace diff --git a/tests/yaml/invalid_too_many_transformers_v1.yaml b/tests/yaml/invalid_too_many_transformers_v1.yaml new file mode 100644 index 000000000..a957b9189 --- /dev/null +++ b/tests/yaml/invalid_too_many_transformers_v1.yaml @@ -0,0 +1,29 @@ +version: '1.1' +events: + - id: 1 + name: rule1 + tags: + category: category1 + conditions: + - operation: match_regex + parameters: + inputs: + - arg1 + regex: .* + - operation: match_regex + parameters: + inputs: + - arg2 + regex: .* + transformers: + - base64_encode + - base64_encode + - base64_encode + - base64_encode + - base64_encode + - base64_encode + - base64_encode + - base64_encode + - base64_encode + - base64_encode + - base64_encode From 445c1a8f2471c0461aeace0ae868924c8af01133 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:15:40 +0100 Subject: [PATCH 06/17] Validate redirection location and restrict status codes (#310) --- src/parser/actions_parser.cpp | 18 ++- src/uri_utils.cpp | 41 ++++--- tests/parser_v2_actions_test.cpp | 177 +++++++++++++++++++++++++++- tests/uri_utils_test.cpp | 192 ++++++++++++++++++++++++++++++- 4 files changed, 398 insertions(+), 30 deletions(-) diff --git a/src/parser/actions_parser.cpp b/src/parser/actions_parser.cpp index 574a65962..a94f9ab30 100644 --- a/src/parser/actions_parser.cpp +++ b/src/parser/actions_parser.cpp @@ -7,6 +7,7 @@ #include "log.hpp" #include "parser/common.hpp" #include "parser/parser.hpp" +#include "uri_utils.hpp" namespace ddwaf::parser::v2 { @@ -31,10 +32,25 @@ void validate_and_add_redirect( return; } + // Validate the URL; + // - Check it's a valid URL + // - If it has a scheme, check it's http or https + // - If it doesn't have a scheme: + // - Check it also doesn't have an authority + // - Check it's a path starting with / + auto decomposed = uri_parse(it->second); + if (!decomposed.has_value() || + (!decomposed->scheme.empty() && decomposed->scheme != "http" && + decomposed->scheme != "https") || + (decomposed->scheme_and_authority.empty() && !decomposed->path.starts_with('/'))) { + builder.alias_default_action_to("block", id); + return; + } + it = parameters.find("status_code"); if (it != parameters.end()) { auto [res, code] = ddwaf::from_string(it->second); - if (!res || code < 300 || code > 399) { + if (!res || (code != 301 && code != 302 && code != 303 && code != 307)) { it->second = "303"; } } else { diff --git a/src/uri_utils.cpp b/src/uri_utils.cpp index bc1780ad6..e0108245a 100644 --- a/src/uri_utils.cpp +++ b/src/uri_utils.cpp @@ -14,6 +14,11 @@ / path-absolute / path-rootless / path-empty + relative-ref = relative-part [ "?" query ] [ "#" fragment ] + relative-part = "//" authority path-abempty + / path-absolute + / path-noscheme -> Not supported + / path-empty -> Not supported scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) authority = [ userinfo "@" ] host [ ":" port ] userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) @@ -59,6 +64,7 @@ namespace { enum class token_type { none, scheme, + scheme_authority_or_path, hierarchical_part, authority, userinfo, @@ -67,7 +73,6 @@ enum class token_type { ipv6address, regname_or_ipv4address, path, - path_no_authority, query, fragment, }; @@ -107,7 +112,7 @@ std::optional uri_parse(std::string_view uri) uri_decomposed decomposed; decomposed.raw = uri; - auto expected_token = token_type::scheme; + auto expected_token = token_type::scheme_authority_or_path; auto lookahead_token = token_type::none; // Authority helpers @@ -120,6 +125,20 @@ std::optional uri_parse(std::string_view uri) expected_token = token_type::none; switch (current_token) { + case token_type::scheme_authority_or_path: { + if (uri[i] == '/') { + // Path or authority + if ((i + 1) < uri.size() && uri[i + 1] == '/') { + expected_token = token_type::authority; + i += 2; + } else { + expected_token = token_type::path; + } + } else if (isalpha(uri[i])) { + expected_token = token_type::scheme; + } + break; + } case token_type::scheme: { auto token_begin = i; if (!isalpha(uri[i++])) { @@ -157,26 +176,10 @@ std::optional uri_parse(std::string_view uri) i += 2; } else { // Otherwise we expect a path (path-absolute, path-rootless, path-empty) - expected_token = token_type::path_no_authority; + expected_token = token_type::path; } break; } - case token_type::path_no_authority: { - auto token_begin = i; - // The path can be empty but we wouldn't be here... - while (i < uri.size()) { - const auto c = uri[i++]; - if (!is_path_char(c) && c != '/') { - return std::nullopt; - } - } - - decomposed.path_index = token_begin; - decomposed.path = uri.substr(token_begin, i - token_begin); - - // We're done, nothing else to parse - return decomposed; - } case token_type::authority: { auto token_begin = i; authority_end = uri.find_first_of("/?#", i); diff --git a/tests/parser_v2_actions_test.cpp b/tests/parser_v2_actions_test.cpp index ddd8a62a7..3bd20c57b 100644 --- a/tests/parser_v2_actions_test.cpp +++ b/tests/parser_v2_actions_test.cpp @@ -4,6 +4,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. +#include "fmt/core.h" #include "parser/common.hpp" #include "parser/parser.hpp" #include "test_utils.hpp" @@ -89,9 +90,79 @@ TEST(TestParserV2Actions, SingleAction) } TEST(TestParserV2Actions, RedirectAction) +{ + std::vector> redirections{ + {"redirect_301", "301", "http://www.datadoghq.com"}, + {"redirect_302", "302", "http://www.datadoghq.com"}, + {"redirect_303", "303", "http://www.datadoghq.com"}, + {"redirect_307", "307", "http://www.datadoghq.com"}, + {"redirect_https", "303", "https://www.datadoghq.com"}, + {"redirect_path", "303", "/security/appsec"}, + }; + + std::string yaml; + yaml.append("["); + for (auto &[name, status_code, url] : redirections) { + yaml += fmt::format("{{id: {}, parameters: {{location: \"{}\", status_code: {}}}, type: " + "redirect_request}},", + name, url, status_code); + } + yaml.append("]"); + auto object = yaml_to_object(yaml); + + ddwaf::ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + auto actions = parser::v2::parse_actions(actions_array, section); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 6); + EXPECT_NE(loaded.find("redirect_301"), loaded.end()); + EXPECT_NE(loaded.find("redirect_302"), loaded.end()); + EXPECT_NE(loaded.find("redirect_303"), loaded.end()); + EXPECT_NE(loaded.find("redirect_307"), loaded.end()); + EXPECT_NE(loaded.find("redirect_https"), loaded.end()); + EXPECT_NE(loaded.find("redirect_path"), loaded.end()); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(actions->size(), 10); + EXPECT_TRUE(actions->contains("block")); + EXPECT_TRUE(actions->contains("stack_trace")); + EXPECT_TRUE(actions->contains("extract_schema")); + EXPECT_TRUE(actions->contains("monitor")); + + for (auto &[name, status_code, url] : redirections) { + ASSERT_TRUE(actions->contains(name)); + + const auto &spec = actions->at(name); + EXPECT_EQ(spec.type, action_type::redirect_request) << name; + EXPECT_EQ(spec.type_str, "redirect_request"); + EXPECT_EQ(spec.parameters.size(), 2); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), status_code.c_str()); + EXPECT_STR(parameters.at("location"), url.c_str()); + } +} + +TEST(TestParserV2Actions, RedirectActionInvalidStatusCode) { auto object = yaml_to_object( - R"([{id: redirect, parameters: {location: "http://www.google.com", status_code: 302}, type: redirect_request}])"); + R"([{id: redirect, parameters: {location: "http://www.google.com", status_code: 404}, type: redirect_request}])"); ddwaf::ruleset_info::section_info section; auto actions_array = static_cast(parameter(object)); @@ -131,15 +202,15 @@ TEST(TestParserV2Actions, RedirectAction) EXPECT_EQ(spec.parameters.size(), 2); const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "302"); + EXPECT_STR(parameters.at("status_code"), "303"); EXPECT_STR(parameters.at("location"), "http://www.google.com"); } } -TEST(TestParserV2Actions, RedirectActionInvalidStatusCode) +TEST(TestParserV2Actions, RedirectActionInvalid300StatusCode) { auto object = yaml_to_object( - R"([{id: redirect, parameters: {location: "http://www.google.com", status_code: 404}, type: redirect_request}])"); + R"([{id: redirect, parameters: {location: "http://www.google.com", status_code: 304}, type: redirect_request}])"); ddwaf::ruleset_info::section_info section; auto actions_array = static_cast(parameter(object)); @@ -281,6 +352,104 @@ TEST(TestParserV2Actions, RedirectActionMissingLocation) } } +TEST(TestParserV2Actions, RedirectActionNonHttpURL) +{ + auto object = yaml_to_object( + R"([{id: redirect, parameters: {status_code: 303, location: ftp://myftp.mydomain.com}, type: redirect_request}])"); + + ddwaf::ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + auto actions = parser::v2::parse_actions(actions_array, section); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("redirect"), loaded.end()); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(actions->size(), 5); + EXPECT_TRUE(actions->contains("redirect")); + EXPECT_TRUE(actions->contains("block")); + EXPECT_TRUE(actions->contains("stack_trace")); + EXPECT_TRUE(actions->contains("extract_schema")); + EXPECT_TRUE(actions->contains("monitor")); + + { + const auto &spec = actions->at("redirect"); + EXPECT_EQ(spec.type, action_type::block_request); + EXPECT_EQ(spec.type_str, "block_request"); + EXPECT_EQ(spec.parameters.size(), 3); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "403"); + EXPECT_STR(parameters.at("grpc_status_code"), "10"); + EXPECT_STR(parameters.at("type"), "auto"); + } +} + +TEST(TestParserV2Actions, RedirectActionInvalidRelativePathURL) +{ + auto object = yaml_to_object( + R"([{id: redirect, parameters: {status_code: 303, location: ../../../etc/passwd}, type: redirect_request}])"); + + ddwaf::ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + auto actions = parser::v2::parse_actions(actions_array, section); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("redirect"), loaded.end()); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(actions->size(), 5); + EXPECT_TRUE(actions->contains("redirect")); + EXPECT_TRUE(actions->contains("block")); + EXPECT_TRUE(actions->contains("stack_trace")); + EXPECT_TRUE(actions->contains("extract_schema")); + EXPECT_TRUE(actions->contains("monitor")); + + { + const auto &spec = actions->at("redirect"); + EXPECT_EQ(spec.type, action_type::block_request); + EXPECT_EQ(spec.type_str, "block_request"); + EXPECT_EQ(spec.parameters.size(), 3); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "403"); + EXPECT_STR(parameters.at("grpc_status_code"), "10"); + EXPECT_STR(parameters.at("type"), "auto"); + } +} + TEST(TestParserV2Actions, OverrideDefaultBlockAction) { auto object = yaml_to_object( diff --git a/tests/uri_utils_test.cpp b/tests/uri_utils_test.cpp index 3ab965e7e..f4e968e62 100644 --- a/tests/uri_utils_test.cpp +++ b/tests/uri_utils_test.cpp @@ -140,12 +140,7 @@ TEST(TestURI, SchemeAndPath) } } -TEST(TestURI, SchemeMalformedPath) -{ - EXPECT_FALSE(ddwaf::uri_parse("file:[][][]")); - EXPECT_FALSE(ddwaf::uri_parse("file:?query")); - EXPECT_FALSE(ddwaf::uri_parse("file:#fragment")); -} +TEST(TestURI, SchemeMalformedPath) { EXPECT_FALSE(ddwaf::uri_parse("file:[][][]")); } TEST(TestURI, SchemeHost) { @@ -178,6 +173,40 @@ TEST(TestURI, SchemeHost) } } +TEST(TestURI, SchemeQuery) +{ + auto uri = ddwaf::uri_parse("http:?hello"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, ""); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->query, "hello"); +} + +TEST(TestURI, SchemeFragment) +{ + auto uri = ddwaf::uri_parse("http:#hello"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, ""); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->fragment, "hello"); +} + +TEST(TestURI, SchemeQueryFragment) +{ + auto uri = ddwaf::uri_parse("http:?hello#bye"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, ""); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->query, "hello"); + EXPECT_STRV(uri->fragment, "bye"); +} + TEST(TestURI, SchemeIPv4Host) { auto uri = ddwaf::uri_parse("http://1.2.3.4"); @@ -488,4 +517,155 @@ TEST(TestURI, Complete) } } +TEST(TestURI, RelativeRefHostPortQuery) +{ + auto uri = ddwaf::uri_parse("//authority:123?query"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_STRV(uri->authority.port, "123"); + EXPECT_STRV(uri->query, "query"); +} + +TEST(TestURI, RelativeRefHostPortPath) +{ + auto uri = ddwaf::uri_parse("//authority:12/path"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_STRV(uri->authority.port, "12"); + EXPECT_STRV(uri->path, "/path"); +} + +TEST(TestURI, RelativeRefHostPortFragment) +{ + auto uri = ddwaf::uri_parse("//authority:1#f"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_STRV(uri->authority.port, "1"); + EXPECT_STRV(uri->fragment, "f"); +} + +TEST(TestURI, RelativeRefUserHostQuery) +{ + auto uri = ddwaf::uri_parse("//user@authority?query"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_STRV(uri->authority.userinfo, "user"); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->query, "query"); +} + +TEST(TestURI, RelativeRefUserHostPath) +{ + auto uri = ddwaf::uri_parse("//us@authority/path"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_STRV(uri->authority.userinfo, "us"); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "/path"); +} + +TEST(TestURI, RelativeRefUserHostFragment) +{ + auto uri = ddwaf::uri_parse("//u@authority#f"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_STRV(uri->authority.userinfo, "u"); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->fragment, "f"); +} + +TEST(TestURI, RelativeRefAbsolutePath) +{ + auto uri = ddwaf::uri_parse("/path"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, ""); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_STRV(uri->authority.port, ""); + EXPECT_STRV(uri->path, "/path"); +} + +TEST(TestURI, RelativeRefAbsolutePathFragment) +{ + auto uri = ddwaf::uri_parse("/path#f"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, ""); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_STRV(uri->authority.port, ""); + EXPECT_STRV(uri->path, "/path"); + EXPECT_STRV(uri->fragment, "f"); +} + +TEST(TestURI, RelativeRefAbsolutePathQuery) +{ + auto uri = ddwaf::uri_parse("/path?query"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, ""); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_STRV(uri->authority.port, ""); + EXPECT_STRV(uri->path, "/path"); + EXPECT_STRV(uri->query, "query"); +} + +TEST(TestURI, RelativeRefAbsolutePathQueryFragment) +{ + auto uri = ddwaf::uri_parse("/path?query#f"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, ""); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_STRV(uri->authority.port, ""); + EXPECT_STRV(uri->path, "/path"); + EXPECT_STRV(uri->query, "query"); + EXPECT_STRV(uri->fragment, "f"); +} + +TEST(TestURI, RelativeRefQuery) +{ + auto uri = ddwaf::uri_parse("/?hello"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, ""); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "/"); + EXPECT_STRV(uri->query, "hello"); +} + +TEST(TestURI, RelativeRefFragment) +{ + auto uri = ddwaf::uri_parse("/#hello"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, ""); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "/"); + EXPECT_STRV(uri->fragment, "hello"); +} + +TEST(TestURI, RelativeRefQueryFragment) +{ + auto uri = ddwaf::uri_parse("/?hello#bye"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, ""); + EXPECT_STRV(uri->authority.host, ""); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "/"); + EXPECT_STRV(uri->query, "hello"); + EXPECT_STRV(uri->fragment, "bye"); +} + } // namespace From 7e6e5b433509ba879faf0dbe8092f774bcde86c2 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:06:32 +0100 Subject: [PATCH 07/17] Update default obfuscator regex (#317) --- src/obfuscator.hpp | 4 ++-- tests/obfuscator_test.cpp | 25 +++++++++++++++++++++++++ tools/waf_runner.cpp | 5 +++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/obfuscator.hpp b/src/obfuscator.hpp index b84e14b58..3dd01ad1d 100644 --- a/src/obfuscator.hpp +++ b/src/obfuscator.hpp @@ -28,10 +28,10 @@ class obfuscator { static constexpr std::string_view redaction_msg{""}; -protected: static constexpr std::string_view default_key_regex_str{ - R"((p(ass)?w(or)?d|pass(_?phrase)?|secret|(api_?|private_?|public_?)key)|token|consumer_?(id|key|secret)|sign(ed|ature)|bearer|authorization)"}; + R"((?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key)|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\.net[_-]sessionid|sid|jwt)"}; +protected: std::unique_ptr key_regex{nullptr}; std::unique_ptr value_regex{nullptr}; }; diff --git a/tests/obfuscator_test.cpp b/tests/obfuscator_test.cpp index f8cd636c2..bf24998d6 100644 --- a/tests/obfuscator_test.cpp +++ b/tests/obfuscator_test.cpp @@ -55,6 +55,31 @@ TEST(TestObfuscator, TestEmptyObfuscator) EXPECT_FALSE(event_obfuscator.is_sensitive_value("value"sv)); } +TEST(TestObfuscator, TestDefaultObfuscator) +{ + std::vector samples{"password", "pwd", "pword", "passwd", "pass", "passphrase", + "pass-phrase", "passphrase", "secret", "api_key", "api-key", "apikey", "private_key", + "private-key", "privatekey", "public-key", "public_key", "publickey", "secret_key", + "secret-key", "secretkey", "accesskey", "access_key", "access-key", "auth-token", + "auth_token", "authtoken", "access-token", "access_token", "accesstoken", "id-token", + "id_token", "idtoken", "refresh_token", "refresh-token", "refreshtoken", "consumer-id", + "consumer_id", "consumerid", "consumer-key", "consumer_key", "consumerkey", + "consumer-secret", "consumer_secret", "consumersecret", "signed", "signature", "bearer", + "authorization", "jsessionid", "phpsessid", "asp.net_sessionid", "asp.net-sessionid", "sid", + "jwt", "PASSWORD", "PWD", "PWORD", "PASSWD", "PASS", "PASSPHRASE", "PASS-PHRASE", + "PASSPHRASE", "SECRET", "API_KEY", "API-KEY", "APIKEY", "PRIVATE_KEY", "PRIVATE-KEY", + "PRIVATEKEY", "PUBLIC-KEY", "PUBLIC_KEY", "PUBLICKEY", "SECRET_KEY", "SECRET-KEY", + "SECRETKEY", "ACCESSKEY", "ACCESS_KEY", "ACCESS-KEY", "AUTH-TOKEN", "AUTH_TOKEN", + "AUTHTOKEN", "ACCESS-TOKEN", "ACCESS_TOKEN", "ACCESSTOKEN", "ID-TOKEN", "ID_TOKEN", + "IDTOKEN", "REFRESH_TOKEN", "REFRESH-TOKEN", "REFRESHTOKEN", "CONSUMER-ID", "CONSUMER_ID", + "CONSUMERID", "CONSUMER-KEY", "CONSUMER_KEY", "CONSUMERKEY", "CONSUMER-SECRET", + "CONSUMER_SECRET", "CONSUMERSECRET", "SIGNED", "SIGNATURE", "BEARER", "AUTHORIZATION", + "JSESSIONID", "PHPSESSID", "ASP.NET_SESSIONID", "ASP.NET-SESSIONID", "SID", "JWT"}; + + ddwaf::obfuscator event_obfuscator{ddwaf::obfuscator::default_key_regex_str, {}}; + for (auto &sample : samples) { EXPECT_TRUE(event_obfuscator.is_sensitive_key(sample)); } +} + TEST(TestObfuscator, TestConfigKeyValue) { auto rule = read_file("obfuscator.yaml"); diff --git a/tools/waf_runner.cpp b/tools/waf_runner.cpp index 72189b00b..bd073d5ae 100644 --- a/tools/waf_runner.cpp +++ b/tools/waf_runner.cpp @@ -47,8 +47,9 @@ auto parse_args(int argc, char *argv[]) } return args; } -const char *key_regex = "(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization"; -const char *value_regex = R"((?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:\s*=[^;]|"\s*:\s*"[^"]+")|bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,})"; +const char *key_regex = R"((?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key)|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\.net[_-]sessionid|sid|jwt)"; + +const char *value_regex = R"((?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key(?:[_-]?id)?|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|jsessionid|phpsessid|asp\.net(?:[_-]|-)sessionid|sid|jwt)(?:\s*=[^;]|"\s*:\s*"[^"]+")|bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,})"; int main(int argc, char *argv[]) { From 4aa0a7ab4ff156a4ee89c73a236312cf90e4c421 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:59:23 +0100 Subject: [PATCH 08/17] Rule override for adding tags (#313) --- src/event.cpp | 3 + src/parser/rule_override_parser.cpp | 8 +- src/parser/specification.hpp | 1 + src/rule.hpp | 32 +- src/ruleset_builder.cpp | 8 + tests/interface_test.cpp | 389 +++++++++++++++++++++++- tests/parser_v2_rules_override_test.cpp | 89 ++++++ 7 files changed, 511 insertions(+), 19 deletions(-) diff --git a/src/event.cpp b/src/event.cpp index a4df749cb..efd78ba8f 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -159,6 +159,9 @@ void serialize_rule(const ddwaf::rule &rule, ddwaf_object &rule_map) for (const auto &[key, value] : rule.get_tags()) { ddwaf_object_map_addl(&tags_map, key.c_str(), key.size(), to_object(tmp, value)); } + for (const auto &[key, value] : rule.get_ancillary_tags()) { + ddwaf_object_map_addl(&tags_map, key.c_str(), key.size(), to_object(tmp, value)); + } ddwaf_object_map_add(&rule_map, "tags", &tags_map); } diff --git a/src/parser/rule_override_parser.cpp b/src/parser/rule_override_parser.cpp index ca4fd4dc3..7f4bcd08e 100644 --- a/src/parser/rule_override_parser.cpp +++ b/src/parser/rule_override_parser.cpp @@ -28,6 +28,12 @@ std::pair parse_override(const parameter::map &no current.actions = std::move(actions); } + it = node.find("tags"); + if (it != node.end()) { + auto tags = static_cast>(it->second); + current.tags = std::move(tags); + } + reference_type type = reference_type::none; auto rules_target_array = at(node, "rules_target", {}); @@ -51,7 +57,7 @@ std::pair parse_override(const parameter::map &no type = reference_type::id; } - if (!current.actions.has_value() && !current.enabled.has_value()) { + if (!current.actions.has_value() && !current.enabled.has_value() && current.tags.empty()) { throw ddwaf::parsing_error("rule override without side-effects"); } diff --git a/src/parser/specification.hpp b/src/parser/specification.hpp index b244dc968..87e6ec941 100644 --- a/src/parser/specification.hpp +++ b/src/parser/specification.hpp @@ -40,6 +40,7 @@ struct override_spec { std::optional enabled; std::optional> actions; std::vector targets; + std::unordered_map tags; }; struct rule_filter_spec { diff --git a/src/rule.hpp b/src/rule.hpp index 50d1cdfdb..976ea84b5 100644 --- a/src/rule.hpp +++ b/src/rule.hpp @@ -42,23 +42,8 @@ class rule { rule(const rule &) = delete; rule &operator=(const rule &) = delete; - rule(rule &&rhs) noexcept - : enabled_(rhs.enabled_), source_(rhs.source_), id_(std::move(rhs.id_)), - name_(std::move(rhs.name_)), tags_(std::move(rhs.tags_)), expr_(std::move(rhs.expr_)), - actions_(std::move(rhs.actions_)) - {} - - rule &operator=(rule &&rhs) noexcept - { - enabled_ = rhs.enabled_; - source_ = rhs.source_; - id_ = std::move(rhs.id_); - name_ = std::move(rhs.name_); - tags_ = std::move(rhs.tags_); - expr_ = std::move(rhs.expr_); - actions_ = std::move(rhs.actions_); - return *this; - } + rule(rule &&rhs) noexcept = default; + rule &operator=(rule &&rhs) = default; virtual ~rule() = default; @@ -94,6 +79,18 @@ class rule { } const std::unordered_map &get_tags() const { return tags_; } + const std::unordered_map &get_ancillary_tags() const + { + return ancillary_tags_; + } + + void set_ancillary_tag(const std::string &key, const std::string &value) + { + // Ancillary tags aren't allowed to overlap with standard tags + if (!tags_.contains(key)) { + ancillary_tags_[key] = value; + } + } const std::vector &get_actions() const { return actions_; } @@ -110,6 +107,7 @@ class rule { std::string id_; std::string name_; std::unordered_map tags_; + std::unordered_map ancillary_tags_; std::shared_ptr expr_; std::vector actions_; }; diff --git a/src/ruleset_builder.cpp b/src/ruleset_builder.cpp index 35c8d11ec..ae7e4ec18 100644 --- a/src/ruleset_builder.cpp +++ b/src/ruleset_builder.cpp @@ -97,6 +97,10 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules if (ovrd.actions.has_value()) { rule_ptr->set_actions(*ovrd.actions); } + + for (const auto &[tag, value] : ovrd.tags) { + rule_ptr->set_ancillary_tag(tag, value); + } } } @@ -110,6 +114,10 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules if (ovrd.actions.has_value()) { rule_ptr->set_actions(*ovrd.actions); } + + for (const auto &[tag, value] : ovrd.tags) { + rule_ptr->set_ancillary_tag(tag, value); + } } } diff --git a/tests/interface_test.cpp b/tests/interface_test.cpp index 639bea110..bf79015da 100644 --- a/tests/interface_test.cpp +++ b/tests/interface_test.cpp @@ -731,6 +731,349 @@ TEST(TestInterface, UpdateActionsByTags) ddwaf_destroy(handle2); } +TEST(TestInterface, UpdateTagsByID) +{ + auto rule = read_file("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_handle handle2; + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{rule_id: 1}], tags: {category: new_category, confidence: 0, new_tag: value}}]})"); + handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + ddwaf_result result1; + ddwaf_result result2; + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, &result1, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, &result2, LONG_TIME), DDWAF_MATCH); + + EXPECT_EVENTS(result1, + {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}, {"confidence", "1"}}, + .matches = {{.op = "match_regex", + .op_value = "rule1", + .highlight = "rule1", + .args = { + {.name = "input", .value = "rule1", .address = "value1", .path = {}}}}}}); + + EXPECT_EVENTS(result2, + {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}, {"confidence", "1"}, + {"new_tag", "value"}}, + .matches = {{.op = "match_regex", + .op_value = "rule1", + .highlight = "rule1", + .args = { + {.name = "input", .value = "rule1", .address = "value1", .path = {}}}}}}); + + ddwaf_object_free(¶meter); + + ddwaf_result_free(&result1); + ddwaf_result_free(&result2); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + ddwaf_handle handle3; + { + auto overrides = yaml_to_object(R"({rules_override: []})"); + handle3 = ddwaf_update(handle2, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + { + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + ddwaf_result result3; + ddwaf_result result2; + + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, &result3, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, &result2, LONG_TIME), DDWAF_MATCH); + + EXPECT_EVENTS(result3, + {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}, {"confidence", "1"}}, + .matches = {{.op = "match_regex", + .op_value = "rule1", + .highlight = "rule1", + .args = { + {.name = "input", .value = "rule1", .address = "value1", .path = {}}}}}}); + + EXPECT_EVENTS(result2, + {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}, {"confidence", "1"}, + {"new_tag", "value"}}, + .matches = {{.op = "match_regex", + .op_value = "rule1", + .highlight = "rule1", + .args = { + {.name = "input", .value = "rule1", .address = "value1", .path = {}}}}}}); + + ddwaf_object_free(¶meter); + + ddwaf_result_free(&result3); + ddwaf_result_free(&result2); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context3); + } + + ddwaf_destroy(handle2); + ddwaf_destroy(handle1); + ddwaf_destroy(handle3); +} + +TEST(TestInterface, UpdateTagsByTags) +{ + auto rule = read_file("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_handle handle2; + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{tags: {confidence: 1}}], tags: {new_tag: value, confidence: 0}}]})"); + handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + ddwaf_result result1; + ddwaf_result result2; + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, &result1, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, &result2, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EVENTS(result1, + {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}, {"confidence", "1"}}, + .matches = {{.op = "match_regex", + .op_value = "rule1", + .highlight = "rule1", + .args = { + {.name = "input", .value = "rule1", .address = "value1", .path = {}}}}}}); + + EXPECT_EVENTS(result2, + {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}, {"confidence", "1"}, + {"new_tag", "value"}}, + .matches = {{.op = "match_regex", + .op_value = "rule1", + .highlight = "rule1", + .args = { + {.name = "input", .value = "rule1", .address = "value1", .path = {}}}}}}); + + ddwaf_result_free(&result1); + ddwaf_result_free(&result2); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule2")); + + ddwaf_result result1; + ddwaf_result result2; + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, &result1, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, &result2, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EVENTS(result1, + {.id = "2", + .name = "rule2", + .tags = {{"type", "flow2"}, {"category", "category2"}}, + .matches = {{.op = "match_regex", + .op_value = "rule2", + .highlight = "rule2", + .args = { + {.name = "input", .value = "rule2", .address = "value1", .path = {}}}}}}); + + EXPECT_EVENTS(result2, + {.id = "2", + .name = "rule2", + .tags = {{"type", "flow2"}, {"category", "category2"}}, + .matches = {{.op = "match_regex", + .op_value = "rule2", + .highlight = "rule2", + .args = { + {.name = "input", .value = "rule2", .address = "value1", .path = {}}}}}}); + + ddwaf_result_free(&result1); + ddwaf_result_free(&result2); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value2", ddwaf_object_string(&tmp, "rule3")); + + ddwaf_result result1; + ddwaf_result result2; + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, &result1, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, &result2, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EVENTS(result1, + {.id = "3", + .name = "rule3", + .tags = {{"type", "flow2"}, {"category", "category3"}, {"confidence", "1"}}, + .matches = {{.op = "match_regex", + .op_value = "rule3", + .highlight = "rule3", + .args = { + {.name = "input", .value = "rule3", .address = "value2", .path = {}}}}}}); + + EXPECT_EVENTS(result2, + {.id = "3", + .name = "rule3", + .tags = {{"type", "flow2"}, {"category", "category3"}, {"confidence", "1"}, + {"new_tag", "value"}}, + .matches = {{.op = "match_regex", + .op_value = "rule3", + .highlight = "rule3", + .args = { + {.name = "input", .value = "rule3", .address = "value2", .path = {}}}}}}); + + ddwaf_result_free(&result1); + ddwaf_result_free(&result2); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + ddwaf_handle handle3; + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{tags: {confidence: 0}}], tags: {should_not: exist}}]})"); + handle3 = ddwaf_update(handle2, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + { + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value2", ddwaf_object_string(&tmp, "rule3")); + + ddwaf_result result3; + ddwaf_result result2; + + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, &result3, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, &result2, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EVENTS(result3, + {.id = "3", + .name = "rule3", + .tags = {{"type", "flow2"}, {"category", "category3"}, {"confidence", "1"}}, + .matches = {{.op = "match_regex", + .op_value = "rule3", + .highlight = "rule3", + .args = { + {.name = "input", .value = "rule3", .address = "value2", .path = {}}}}}}); + + EXPECT_EVENTS(result2, + {.id = "3", + .name = "rule3", + .tags = {{"type", "flow2"}, {"category", "category3"}, {"confidence", "1"}, + {"new_tag", "value"}}, + .matches = {{.op = "match_regex", + .op_value = "rule3", + .highlight = "rule3", + .args = { + {.name = "input", .value = "rule3", .address = "value2", .path = {}}}}}}); + ddwaf_result_free(&result3); + ddwaf_result_free(&result2); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context3); + } + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + TEST(TestInterface, UpdateOverrideByIDAndTag) { auto rule = read_file("interface.yaml"); @@ -745,7 +1088,7 @@ TEST(TestInterface, UpdateOverrideByIDAndTag) ddwaf_handle handle2; { auto overrides = yaml_to_object( - R"({rules_override: [{rules_target: [{tags: {type: flow1}}], on_match: ["block"], enabled: false}, {rules_target: [{rule_id: 1}], enabled: true}]})"); + R"({rules_override: [{rules_target: [{tags: {type: flow1}}], tags: {new_tag: old_value}, on_match: ["block"], enabled: false}, {rules_target: [{rule_id: 1}], tags: {new_tag: new_value}, enabled: true}]})"); handle2 = ddwaf_update(handle1, &overrides, nullptr); ddwaf_object_free(&overrides); } @@ -767,6 +1110,28 @@ TEST(TestInterface, UpdateOverrideByIDAndTag) EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, &result1, LONG_TIME), DDWAF_MATCH); EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, &result2, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(result1, + {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}, {"confidence", "1"}}, + .matches = {{.op = "match_regex", + .op_value = "rule1", + .highlight = "rule1", + .args = { + {.name = "input", .value = "rule1", .address = "value1", .path = {}}}}}}); + + EXPECT_EVENTS(result2, + {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}, {"confidence", "1"}, + {"new_tag", "new_value"}}, + .actions = {"block"}, + .matches = {{.op = "match_regex", + .op_value = "rule1", + .highlight = "rule1", + .args = { + {.name = "input", .value = "rule1", .address = "value1", .path = {}}}}}}); + EXPECT_ACTIONS(result1, {}); EXPECT_ACTIONS( result2, {{"block_request", @@ -806,6 +1171,28 @@ TEST(TestInterface, UpdateOverrideByIDAndTag) EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, &result2, LONG_TIME), DDWAF_MATCH); EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, &result3, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(result2, + {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}, {"confidence", "1"}, + {"new_tag", "new_value"}}, + .actions = {"block"}, + .matches = {{.op = "match_regex", + .op_value = "rule1", + .highlight = "rule1", + .args = { + {.name = "input", .value = "rule1", .address = "value1", .path = {}}}}}}); + + EXPECT_EVENTS(result3, + {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}, {"confidence", "1"}}, + .matches = {{.op = "match_regex", + .op_value = "rule1", + .highlight = "rule1", + .args = { + {.name = "input", .value = "rule1", .address = "value1", .path = {}}}}}}); + EXPECT_ACTIONS( result2, {{"block_request", {{"status_code", "403"}, {"grpc_status_code", "10"}, {"type", "auto"}}}}); diff --git a/tests/parser_v2_rules_override_test.cpp b/tests/parser_v2_rules_override_test.cpp index 304bbb082..3f7a47627 100644 --- a/tests/parser_v2_rules_override_test.cpp +++ b/tests/parser_v2_rules_override_test.cpp @@ -232,4 +232,93 @@ TEST(TestParserV2RulesOverride, ParseInconsistentRuleOverride) EXPECT_EQ(overrides.by_tags.size(), 0); } +TEST(TestParserV2RulesOverride, ParseRuleOverrideForTags) +{ + auto object = yaml_to_object( + R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block], tags: {category: new_category, threshold: 25}}])"); + + ddwaf::ruleset_info::section_info section; + auto override_array = static_cast(parameter(object)); + auto overrides = parser::v2::parse_overrides(override_array, section); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("index:0"), loaded.end()); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(overrides.by_ids.size(), 0); + EXPECT_EQ(overrides.by_tags.size(), 1); + + auto &ovrd = overrides.by_tags[0]; + EXPECT_FALSE(ovrd.enabled.has_value()); + EXPECT_TRUE(ovrd.actions.has_value()); + EXPECT_EQ(ovrd.actions->size(), 1); + EXPECT_STR((*ovrd.actions)[0], "block"); + EXPECT_EQ(ovrd.targets.size(), 1); + EXPECT_EQ(ovrd.tags.size(), 2); + EXPECT_STR(ovrd.tags["category"], "new_category"); + EXPECT_STR(ovrd.tags["threshold"], "25"); + + auto &target = ovrd.targets[0]; + EXPECT_EQ(target.type, parser::reference_type::tags); + EXPECT_TRUE(target.ref_id.empty()); + EXPECT_EQ(target.tags.size(), 1); + EXPECT_STR(target.tags["confidence"], "1"); +} + +TEST(TestParserV2RulesOverride, ParseInvalidTagsField) +{ + auto object = yaml_to_object( + R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block], tags: [{category: new_category}, {threshold: 25}]}])"); + + ddwaf::ruleset_info::section_info section; + auto override_array = static_cast(parameter(object)); + auto overrides = parser::v2::parse_overrides(override_array, section); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("bad cast, expected 'map', obtained 'array'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(overrides.by_ids.size(), 0); + EXPECT_EQ(overrides.by_tags.size(), 0); +} + } // namespace From 38a4b0efcf04f2c391facdce063952a01d5699d7 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 11 Jul 2024 16:35:42 +0100 Subject: [PATCH 09/17] Add support for dynamic exclusion filter data (#316) --- cmake/objects.cmake | 2 +- src/context.cpp | 6 +- src/exclusion/input_filter.cpp | 7 +- src/exclusion/input_filter.hpp | 5 +- src/exclusion/rule_filter.cpp | 7 +- src/exclusion/rule_filter.hpp | 5 +- src/matcher/exact_match.hpp | 4 +- src/matcher/ip_match.hpp | 4 +- .../{rule_data_parser.cpp => data_parser.cpp} | 32 +- src/parser/exclusion_parser.cpp | 22 +- src/parser/expression_parser.cpp | 4 +- src/parser/parser.hpp | 10 +- src/parser/specification.hpp | 2 +- src/rule.hpp | 2 +- src/ruleset.hpp | 3 +- src/ruleset_builder.cpp | 45 +- src/ruleset_builder.hpp | 8 +- tests/context_test.cpp | 18 +- tests/{ => exclusion}/input_filter_test.cpp | 136 +++-- tests/{ => exclusion}/object_filter_test.cpp | 2 +- tests/{ => exclusion}/rule_filter_test.cpp | 97 +++- tests/integration/exclusion_data/test.cpp | 537 ++++++++++++++++++ .../yaml/exclude_one_input_by_ip.yaml | 39 ++ .../yaml/exclude_one_rule_by_ip.yaml | 36 ++ .../yaml/exclude_one_rule_by_user.yaml | 36 ++ ..._data_test.cpp => parser_v2_data_test.cpp} | 122 ++-- tests/parser_v2_input_filters_test.cpp | 33 +- tests/parser_v2_rule_filters_test.cpp | 48 +- tests/parser_v2_rules_test.cpp | 83 --- tests/test_utils.hpp | 4 +- 30 files changed, 1082 insertions(+), 277 deletions(-) rename src/parser/{rule_data_parser.cpp => data_parser.cpp} (72%) rename tests/{ => exclusion}/input_filter_test.cpp (87%) rename tests/{ => exclusion}/object_filter_test.cpp (99%) rename tests/{ => exclusion}/rule_filter_test.cpp (93%) create mode 100644 tests/integration/exclusion_data/test.cpp create mode 100644 tests/integration/exclusion_data/yaml/exclude_one_input_by_ip.yaml create mode 100644 tests/integration/exclusion_data/yaml/exclude_one_rule_by_ip.yaml create mode 100644 tests/integration/exclusion_data/yaml/exclude_one_rule_by_user.yaml rename tests/{parser_v2_rules_data_test.cpp => parser_v2_data_test.cpp} (70%) diff --git a/cmake/objects.cmake b/cmake/objects.cmake index a10ee021c..c6a3b452e 100644 --- a/cmake/objects.cmake +++ b/cmake/objects.cmake @@ -35,7 +35,7 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/parser/common.cpp ${libddwaf_SOURCE_DIR}/src/parser/parser.cpp ${libddwaf_SOURCE_DIR}/src/parser/parser_v1.cpp - ${libddwaf_SOURCE_DIR}/src/parser/rule_data_parser.cpp + ${libddwaf_SOURCE_DIR}/src/parser/data_parser.cpp ${libddwaf_SOURCE_DIR}/src/parser/processor_parser.cpp ${libddwaf_SOURCE_DIR}/src/parser/expression_parser.cpp ${libddwaf_SOURCE_DIR}/src/parser/matcher_parser.cpp diff --git a/src/context.cpp b/src/context.cpp index a3c97ba59..f99e2b82d 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -156,7 +156,7 @@ exclusion::context_policy &context::eval_filters(ddwaf::timer &deadline) } rule_filter::cache_type &cache = it->second; - auto exclusion = filter->match(store_, cache, deadline); + auto exclusion = filter->match(store_, cache, ruleset_->exclusion_matchers, deadline); if (exclusion.has_value()) { for (const auto &rule : exclusion->rules) { exclusion_policy_.add_rule_exclusion( @@ -181,7 +181,7 @@ exclusion::context_policy &context::eval_filters(ddwaf::timer &deadline) } input_filter::cache_type &cache = it->second; - auto exclusion = filter->match(store_, cache, deadline); + auto exclusion = filter->match(store_, cache, ruleset_->exclusion_matchers, deadline); if (exclusion.has_value()) { for (const auto &rule : exclusion->rules) { exclusion_policy_.add_input_exclusion(rule, exclusion->objects); @@ -203,7 +203,7 @@ std::vector context::eval_rules( auto [new_it, res] = collection_cache_.emplace(type, collection_cache{}); it = new_it; } - collection.match(events, store_, it->second, policy, ruleset_->dynamic_matchers, deadline); + collection.match(events, store_, it->second, policy, ruleset_->rule_matchers, deadline); }; // Evaluate user priority collections first diff --git a/src/exclusion/input_filter.cpp b/src/exclusion/input_filter.cpp index a401e0fe1..d13a9ddac 100644 --- a/src/exclusion/input_filter.cpp +++ b/src/exclusion/input_filter.cpp @@ -27,14 +27,15 @@ input_filter::input_filter(std::string id, std::shared_ptr expr, } } -std::optional input_filter::match( - const object_store &store, cache_type &cache, ddwaf::timer &deadline) const +std::optional input_filter::match(const object_store &store, cache_type &cache, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) const { DDWAF_DEBUG("Evaluating input filter '{}'", id_); // An event was already produced, so we skip the rule // Note that conditions in a filter are optional - auto res = expr_->eval(cache.expr_cache, store, {}, {}, deadline); + auto res = expr_->eval(cache.expr_cache, store, {}, dynamic_matchers, deadline); if (!res.outcome) { return std::nullopt; } diff --git a/src/exclusion/input_filter.hpp b/src/exclusion/input_filter.hpp index 30def345a..b4869fe9e 100644 --- a/src/exclusion/input_filter.hpp +++ b/src/exclusion/input_filter.hpp @@ -37,8 +37,9 @@ class input_filter { input_filter &operator=(input_filter &&) = delete; virtual ~input_filter() = default; - virtual std::optional match( - const object_store &store, cache_type &cache, ddwaf::timer &deadline) const; + virtual std::optional match(const object_store &store, cache_type &cache, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) const; std::string_view get_id() { return id_; } diff --git a/src/exclusion/rule_filter.cpp b/src/exclusion/rule_filter.cpp index 392430da9..9065f5393 100644 --- a/src/exclusion/rule_filter.cpp +++ b/src/exclusion/rule_filter.cpp @@ -26,8 +26,9 @@ rule_filter::rule_filter(std::string id, std::shared_ptr expr, } } -std::optional rule_filter::match( - const object_store &store, cache_type &cache, ddwaf::timer &deadline) const +std::optional rule_filter::match(const object_store &store, cache_type &cache, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) const { DDWAF_DEBUG("Evaluating rule filter '{}'", id_); @@ -36,7 +37,7 @@ std::optional rule_filter::match( return std::nullopt; } - auto res = expr_->eval(cache, store, {}, {}, deadline); + auto res = expr_->eval(cache, store, {}, dynamic_matchers, deadline); if (!res.outcome) { return std::nullopt; } diff --git a/src/exclusion/rule_filter.hpp b/src/exclusion/rule_filter.hpp index 041f0f071..d3c52290b 100644 --- a/src/exclusion/rule_filter.hpp +++ b/src/exclusion/rule_filter.hpp @@ -36,8 +36,9 @@ class rule_filter { rule_filter &operator=(rule_filter &&) = default; virtual ~rule_filter() = default; - virtual std::optional match( - const object_store &store, cache_type &cache, ddwaf::timer &deadline) const; + virtual std::optional match(const object_store &store, cache_type &cache, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) const; std::string_view get_id() const { return id_; } diff --git a/src/matcher/exact_match.hpp b/src/matcher/exact_match.hpp index b372a2fa9..469357ef9 100644 --- a/src/matcher/exact_match.hpp +++ b/src/matcher/exact_match.hpp @@ -16,11 +16,11 @@ namespace ddwaf::matcher { class exact_match : public base_impl { public: - using rule_data_type = std::vector>; + using data_type = std::vector>; exact_match() = default; explicit exact_match(std::vector &&data); - explicit exact_match(const rule_data_type &data); + explicit exact_match(const data_type &data); ~exact_match() override = default; exact_match(const exact_match &) = default; exact_match(exact_match &&) = default; diff --git a/src/matcher/ip_match.hpp b/src/matcher/ip_match.hpp index 5c88d1563..e3fb84b2a 100644 --- a/src/matcher/ip_match.hpp +++ b/src/matcher/ip_match.hpp @@ -17,7 +17,7 @@ namespace ddwaf::matcher { class ip_match : public base_impl { public: - using rule_data_type = std::vector>; + using data_type = std::vector>; ip_match() = default; explicit ip_match(const std::vector &ip_list); @@ -32,7 +32,7 @@ class ip_match : public base_impl { init_tree(ip_list); } - explicit ip_match(const rule_data_type &ip_list); + explicit ip_match(const data_type &ip_list); ~ip_match() override = default; ip_match(const ip_match &) = delete; ip_match(ip_match &&) = default; diff --git a/src/parser/rule_data_parser.cpp b/src/parser/data_parser.cpp similarity index 72% rename from src/parser/rule_data_parser.cpp rename to src/parser/data_parser.cpp index 853ee0229..914c4e32f 100644 --- a/src/parser/rule_data_parser.cpp +++ b/src/parser/data_parser.cpp @@ -41,12 +41,12 @@ data_with_expiration parse_data(std::string_view type, par } // namespace -rule_data_container parse_rule_data(parameter::vector &rule_data, base_section_info &info, - std::unordered_map &rule_data_ids) +matcher_container parse_data(parameter::vector &data_array, + std::unordered_map &data_ids_to_type, base_section_info &info) { - rule_data_container matchers; - for (unsigned i = 0; i < rule_data.size(); ++i) { - const ddwaf::parameter object = rule_data[i]; + matcher_container matchers; + for (unsigned i = 0; i < data_array.size(); ++i) { + const ddwaf::parameter object = data_array[i]; std::string id; try { const auto entry = static_cast(object); @@ -57,15 +57,15 @@ rule_data_container parse_rule_data(parameter::vector &rule_data, base_section_i auto data = at(entry, "data"); std::string_view matcher_name; - auto it = rule_data_ids.find(id); - if (it == rule_data_ids.end()) { + auto it = data_ids_to_type.find(id); + if (it == data_ids_to_type.end()) { // Infer matcher from data type if (type == "ip_with_expiration") { matcher_name = "ip_match"; } else if (type == "data_with_expiration") { matcher_name = "exact_match"; } else { - DDWAF_DEBUG("Failed to process rule data id '{}", id); + DDWAF_DEBUG("Failed to process dynamic data id '{}", id); info.add_failed(id, "failed to infer matcher"); continue; } @@ -75,21 +75,21 @@ rule_data_container parse_rule_data(parameter::vector &rule_data, base_section_i std::shared_ptr matcher; if (matcher_name == "ip_match") { - using rule_data_type = matcher::ip_match::rule_data_type; - auto parsed_data = parse_data(type, data); + using data_type = matcher::ip_match::data_type; + auto parsed_data = parse_data(type, data); matcher = std::make_shared(parsed_data); } else if (matcher_name == "exact_match") { - using rule_data_type = matcher::exact_match::rule_data_type; - auto parsed_data = parse_data(type, data); + using data_type = matcher::exact_match::data_type; + auto parsed_data = parse_data(type, data); matcher = std::make_shared(parsed_data); } else { - DDWAF_WARN("Matcher {} doesn't support dynamic rule data", matcher_name.data()); - info.add_failed(id, - "matcher " + std::string(matcher_name) + " doesn't support dynamic rule data"); + DDWAF_WARN("Matcher {} doesn't support dynamic data", matcher_name.data()); + info.add_failed( + id, "matcher " + std::string(matcher_name) + " doesn't support dynamic data"); continue; } - DDWAF_DEBUG("Parsed rule data {}", id); + DDWAF_DEBUG("Parsed dynamic data {}", id); info.add_loaded(id); matchers.emplace(std::move(id), std::move(matcher)); } catch (const ddwaf::exception &e) { diff --git a/src/parser/exclusion_parser.cpp b/src/parser/exclusion_parser.cpp index f56f1b4a4..8c5234594 100644 --- a/src/parser/exclusion_parser.cpp +++ b/src/parser/exclusion_parser.cpp @@ -15,12 +15,13 @@ namespace ddwaf::parser::v2 { namespace { -input_filter_spec parse_input_filter( - const parameter::map &filter, address_container &addresses, const object_limits &limits) +input_filter_spec parse_input_filter(const parameter::map &filter, address_container &addresses, + std::unordered_map &filter_data_ids, const object_limits &limits) { // Check for conditions first auto conditions_array = at(filter, "conditions", {}); - auto expr = parse_simplified_expression(conditions_array, addresses, limits); + auto expr = parse_expression( + conditions_array, filter_data_ids, data_source::values, {}, addresses, limits); std::vector rules_target; auto rules_target_array = at(filter, "rules_target", {}); @@ -54,12 +55,13 @@ input_filter_spec parse_input_filter( return {std::move(expr), std::move(obj_filter), std::move(rules_target)}; } -rule_filter_spec parse_rule_filter( - const parameter::map &filter, address_container &addresses, const object_limits &limits) +rule_filter_spec parse_rule_filter(const parameter::map &filter, address_container &addresses, + std::unordered_map &filter_data_ids, const object_limits &limits) { // Check for conditions first auto conditions_array = at(filter, "conditions", {}); - auto expr = parse_simplified_expression(conditions_array, addresses, limits); + auto expr = parse_expression( + conditions_array, filter_data_ids, data_source::values, {}, addresses, limits); std::vector rules_target; auto rules_target_array = at(filter, "rules_target", {}); @@ -94,8 +96,8 @@ rule_filter_spec parse_rule_filter( } // namespace -filter_spec_container parse_filters( - parameter::vector &filter_array, base_section_info &info, const object_limits &limits) +filter_spec_container parse_filters(parameter::vector &filter_array, base_section_info &info, + std::unordered_map &filter_data_ids, const object_limits &limits) { filter_spec_container filters; for (unsigned i = 0; i < filter_array.size(); i++) { @@ -112,11 +114,11 @@ filter_spec_container parse_filters( } if (node.find("inputs") != node.end()) { - auto filter = parse_input_filter(node, addresses, limits); + auto filter = parse_input_filter(node, addresses, filter_data_ids, limits); filters.ids.emplace(id); filters.input_filters.emplace(id, std::move(filter)); } else { - auto filter = parse_rule_filter(node, addresses, limits); + auto filter = parse_rule_filter(node, addresses, filter_data_ids, limits); filters.ids.emplace(id); filters.rule_filters.emplace(id, std::move(filter)); } diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index d4a7458e2..5cb22bced 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -86,7 +86,7 @@ std::vector parse_arguments(const parameter::map ¶ms, d } // namespace std::shared_ptr parse_expression(const parameter::vector &conditions_array, - std::unordered_map &data_ids, data_source source, + std::unordered_map &data_ids_to_type, data_source source, const std::vector &transformers, address_container &addresses, const object_limits &limits) { @@ -113,7 +113,7 @@ std::shared_ptr parse_expression(const parameter::vector &conditions auto [data_id, matcher] = parse_matcher(operator_name, params); if (!matcher && !data_id.empty()) { - data_ids.emplace(data_id, operator_name); + data_ids_to_type.emplace(data_id, operator_name); } auto arguments = diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 80b19d9d8..cf3bb64aa 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -34,13 +34,13 @@ rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info std::unordered_map &rule_data_ids, const object_limits &limits, rule::source_type source = rule::source_type::base); -rule_data_container parse_rule_data(parameter::vector &rule_data, base_section_info &info, - std::unordered_map &rule_data_ids); +matcher_container parse_data(parameter::vector &data_array, + std::unordered_map &data_ids_to_type, base_section_info &info); override_spec_container parse_overrides(parameter::vector &override_array, base_section_info &info); -filter_spec_container parse_filters( - parameter::vector &filter_array, base_section_info &info, const object_limits &limits); +filter_spec_container parse_filters(parameter::vector &filter_array, base_section_info &info, + std::unordered_map &filter_data_ids, const object_limits &limits); processor_container parse_processors( parameter::vector &processor_array, base_section_info &info, const object_limits &limits); @@ -51,7 +51,7 @@ std::shared_ptr parse_actions( parameter::vector &actions_array, base_section_info &info); std::shared_ptr parse_expression(const parameter::vector &conditions_array, - std::unordered_map &data_ids, data_source source, + std::unordered_map &data_ids_to_type, data_source source, const std::vector &transformers, address_container &addresses, const object_limits &limits); diff --git a/src/parser/specification.hpp b/src/parser/specification.hpp index 87e6ec941..750ba2c1e 100644 --- a/src/parser/specification.hpp +++ b/src/parser/specification.hpp @@ -58,7 +58,7 @@ struct input_filter_spec { // Containers using rule_spec_container = std::unordered_map; -using rule_data_container = std::unordered_map>; +using matcher_container = std::unordered_map>; using scanner_container = std::unordered_map>; struct override_spec_container { diff --git a/src/rule.hpp b/src/rule.hpp index 976ea84b5..48126109b 100644 --- a/src/rule.hpp +++ b/src/rule.hpp @@ -62,7 +62,7 @@ class rule { return std::nullopt; } - return {ddwaf::event{this, expression::get_matches(cache), res.ephemeral}}; + return {ddwaf::event{this, expression::get_matches(cache), res.ephemeral, {}}}; } [[nodiscard]] bool is_enabled() const { return enabled_; } diff --git a/src/ruleset.hpp b/src/ruleset.hpp index 3bddc62ce..7defda476 100644 --- a/src/ruleset.hpp +++ b/src/ruleset.hpp @@ -133,7 +133,8 @@ struct ruleset { std::unordered_map> input_filters; std::vector> rules; - std::unordered_map> dynamic_matchers; + std::unordered_map> rule_matchers; + std::unordered_map> exclusion_matchers; std::vector> scanners; std::shared_ptr actions; diff --git a/src/ruleset_builder.cpp b/src/ruleset_builder.cpp index ae7e4ec18..3001b1cf0 100644 --- a/src/ruleset_builder.cpp +++ b/src/ruleset_builder.cpp @@ -195,7 +195,8 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules rs->insert_filters(input_filters_); rs->insert_preprocessors(preprocessors_); rs->insert_postprocessors(postprocessors_); - rs->dynamic_matchers = dynamic_matchers_; + rs->rule_matchers = rule_matchers_; + rs->exclusion_matchers = exclusion_matchers_; rs->scanners = scanners_.items(); rs->actions = actions_; rs->free_fn = free_fn_; @@ -304,20 +305,19 @@ ruleset_builder::change_state ruleset_builder::load(parameter::map &root, base_r try { auto rules_data = static_cast(it->second); if (!rules_data.empty()) { - auto new_matchers = - parser::v2::parse_rule_data(rules_data, section, rule_data_ids_); + auto new_matchers = parser::v2::parse_data(rules_data, rule_data_ids_, section); if (new_matchers.empty()) { // The rules_data array might have unrelated IDs, so we need // to consider "no valid IDs" as an empty rules_data - dynamic_matchers_.clear(); + rule_matchers_.clear(); } else { - dynamic_matchers_ = std::move(new_matchers); + rule_matchers_ = std::move(new_matchers); } } else { DDWAF_DEBUG("Clearing all rule data"); - dynamic_matchers_.clear(); + rule_matchers_.clear(); } - state = state | change_state::data; + state = state | change_state::rule_data; } catch (const std::exception &e) { DDWAF_WARN("Failed to parse rule data: {}", e.what()); section.set_error(e.what()); @@ -349,8 +349,10 @@ ruleset_builder::change_state ruleset_builder::load(parameter::map &root, base_r auto §ion = info.add_section("exclusions"); try { auto exclusions = static_cast(it->second); + filter_data_ids_.clear(); if (!exclusions.empty()) { - exclusions_ = parser::v2::parse_filters(exclusions, section, limits_); + exclusions_ = + parser::v2::parse_filters(exclusions, section, filter_data_ids_, limits_); } else { DDWAF_DEBUG("Clearing all exclusions"); exclusions_.clear(); @@ -362,6 +364,33 @@ ruleset_builder::change_state ruleset_builder::load(parameter::map &root, base_r } } + it = root.find("exclusion_data"); + if (it != root.end()) { + DDWAF_DEBUG("Parsing exclusion data"); + auto §ion = info.add_section("exclusions_data"); + try { + auto exclusions_data = static_cast(it->second); + if (!exclusions_data.empty()) { + auto new_matchers = + parser::v2::parse_data(exclusions_data, filter_data_ids_, section); + if (new_matchers.empty()) { + // The exclusions_data array might have unrelated IDs, so we need + // to consider "no valid IDs" as an empty exclusions_data + exclusion_matchers_.clear(); + } else { + exclusion_matchers_ = std::move(new_matchers); + } + } else { + DDWAF_DEBUG("Clearing all exclusion data"); + exclusion_matchers_.clear(); + } + state = state | change_state::exclusion_data; + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse exclusion data: {}", e.what()); + section.set_error(e.what()); + } + } + it = root.find("processors"); if (it != root.end()) { DDWAF_DEBUG("Parsing processors"); diff --git a/src/ruleset_builder.hpp b/src/ruleset_builder.hpp index ac7fed445..453e752f5 100644 --- a/src/ruleset_builder.hpp +++ b/src/ruleset_builder.hpp @@ -49,10 +49,11 @@ class ruleset_builder { custom_rules = 2, overrides = 4, filters = 8, - data = 16, + rule_data = 16, processors = 32, scanners = 64, actions = 128, + exclusion_data = 256, }; friend constexpr change_state operator|(change_state lhs, change_state rhs); @@ -69,6 +70,7 @@ class ruleset_builder { // Map representing rule data IDs to matcher type, this is obtained // from parsing the ruleset ('rules' key). std::unordered_map rule_data_ids_; + std::unordered_map filter_data_ids_; // These contain the specification of each main component obtained directly // from the parser. These are only modified on update, if the relevant key @@ -82,11 +84,13 @@ class ruleset_builder { // Obtained from 'custom_rules' parser::rule_spec_container user_rules_; // Obtained from 'rules_data', depends on base_rules_ - parser::rule_data_container dynamic_matchers_; + parser::matcher_container rule_matchers_; // Obtained from 'rules_override' parser::override_spec_container overrides_; // Obtained from 'exclusions' parser::filter_spec_container exclusions_; + // Obtained from 'exclusion_data', depends on exclusions_ + parser::matcher_container exclusion_matchers_; // Obtained from 'processors' processor_container processors_; // These are the contents of the latest generated ruleset diff --git a/tests/context_test.cpp b/tests/context_test.cpp index b8144528a..a9bd9d7b8 100644 --- a/tests/context_test.cpp +++ b/tests/context_test.cpp @@ -74,7 +74,10 @@ class rule_filter : public ddwaf::exclusion::rule_filter { ~rule_filter() override = default; MOCK_METHOD(std::optional, match, - (const object_store &store, cache_type &cache, ddwaf::timer &deadline), (const override)); + (const object_store &store, cache_type &cache, + (const std::unordered_map> &), + ddwaf::timer &deadline), + (const override)); }; class input_filter : public ddwaf::exclusion::input_filter { @@ -89,7 +92,10 @@ class input_filter : public ddwaf::exclusion::input_filter { ~input_filter() override = default; MOCK_METHOD(std::optional, match, - (const object_store &store, cache_type &cache, ddwaf::timer &deadline), (const override)); + (const object_store &store, cache_type &cache, + (const std::unordered_map> &), + ddwaf::timer &deadline), + (const override)); }; class processor : public ddwaf::base_processor { @@ -1021,7 +1027,7 @@ TEST(TestContext, SkipRuleFilterNoTargets) } EXPECT_CALL(*rule, match(_, _, _, _, _)).Times(0); - EXPECT_CALL(*filter, match(_, _, _)).Times(0); + EXPECT_CALL(*filter, match(_, _, _, _)).Times(0); ddwaf_object root; ddwaf_object tmp; @@ -1070,7 +1076,7 @@ TEST(TestContext, SkipRuleButNotRuleFilterNoTargets) } EXPECT_CALL(*rule, match(_, _, _, _, _)).Times(0); - EXPECT_CALL(*filter, match(_, _, _)).WillOnce(Return(std::nullopt)); + EXPECT_CALL(*filter, match(_, _, _, _)).WillOnce(Return(std::nullopt)); ddwaf_object root; ddwaf_object tmp; @@ -1837,7 +1843,7 @@ TEST(TestContext, SkipInputFilterNoTargets) } EXPECT_CALL(*rule, match(_, _, _, _, _)).Times(0); - EXPECT_CALL(*filter, match(_, _, _)).Times(0); + EXPECT_CALL(*filter, match(_, _, _, _)).Times(0); ddwaf_object root; ddwaf_object tmp; @@ -1883,7 +1889,7 @@ TEST(TestContext, SkipRuleButNotInputFilterNoTargets) } EXPECT_CALL(*rule, match(_, _, _, _, _)).Times(0); - EXPECT_CALL(*filter, match(_, _, _)).WillOnce(Return(std::nullopt)); + EXPECT_CALL(*filter, match(_, _, _, _)).WillOnce(Return(std::nullopt)); ddwaf_object root; ddwaf_object tmp; diff --git a/tests/input_filter_test.cpp b/tests/exclusion/input_filter_test.cpp similarity index 87% rename from tests/input_filter_test.cpp rename to tests/exclusion/input_filter_test.cpp index 250e15ce7..2c4b4d1e1 100644 --- a/tests/input_filter_test.cpp +++ b/tests/exclusion/input_filter_test.cpp @@ -4,8 +4,8 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. -#include "test.hpp" -#include "test_utils.hpp" +#include "../test.hpp" +#include "../test_utils.hpp" #include "exclusion/input_filter.hpp" #include "matcher/exact_match.hpp" @@ -35,7 +35,7 @@ TEST(TestInputFilter, InputExclusionNoConditions) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -64,7 +64,7 @@ TEST(TestInputFilter, EphemeralInputExclusionNoConditions) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -98,7 +98,7 @@ TEST(TestInputFilter, ObjectExclusionNoConditions) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -132,7 +132,7 @@ TEST(TestInputFilter, EphemeralObjectExclusionNoConditions) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -166,7 +166,7 @@ TEST(TestInputFilter, PersistentInputExclusionWithPersistentCondition) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -200,7 +200,7 @@ TEST(TestInputFilter, EphemeralInputExclusionWithEphemeralCondition) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -238,7 +238,7 @@ TEST(TestInputFilter, PersistentInputExclusionWithEphemeralCondition) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -276,7 +276,7 @@ TEST(TestInputFilter, EphemeralInputExclusionWithPersistentCondition) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -310,7 +310,7 @@ TEST(TestInputFilter, InputExclusionWithConditionAndTransformers) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -343,7 +343,7 @@ TEST(TestInputFilter, InputExclusionFailedCondition) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_FALSE(opt_spec.has_value()); } @@ -378,7 +378,7 @@ TEST(TestInputFilter, ObjectExclusionWithCondition) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -417,7 +417,7 @@ TEST(TestInputFilter, ObjectExclusionFailedCondition) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_FALSE(opt_spec.has_value()); } @@ -454,7 +454,7 @@ TEST(TestInputFilter, InputValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); } { @@ -467,7 +467,7 @@ TEST(TestInputFilter, InputValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -509,7 +509,7 @@ TEST(TestInputFilter, InputValidateCachedEphemeralMatch) store.insert(root); ddwaf::timer deadline{2s}; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -530,7 +530,7 @@ TEST(TestInputFilter, InputValidateCachedEphemeralMatch) store.insert(root); ddwaf::timer deadline{2s}; - ASSERT_FALSE(filter.match(store, cache, deadline)); + ASSERT_FALSE(filter.match(store, cache, {}, deadline)); } { @@ -543,7 +543,7 @@ TEST(TestInputFilter, InputValidateCachedEphemeralMatch) store.insert(root, object_store::attribute::ephemeral); ddwaf::timer deadline{2s}; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -584,7 +584,7 @@ TEST(TestInputFilter, InputMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); } { @@ -598,7 +598,7 @@ TEST(TestInputFilter, InputMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); } } @@ -636,7 +636,7 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); } { @@ -651,7 +651,7 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -697,7 +697,7 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -716,7 +716,7 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - ASSERT_FALSE(filter.match(store, cache, deadline).has_value()); + ASSERT_FALSE(filter.match(store, cache, {}, deadline).has_value()); } } @@ -758,7 +758,7 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); } { @@ -776,7 +776,7 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -821,7 +821,7 @@ TEST(TestInputFilter, ObjectMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); } { @@ -840,7 +840,7 @@ TEST(TestInputFilter, ObjectMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); } } @@ -883,7 +883,7 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); } { @@ -896,7 +896,7 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -946,7 +946,7 @@ TEST(TestInputFilter, ObjectCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - auto opt_spec = filter.match(store, cache, deadline); + auto opt_spec = filter.match(store, cache, {}, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -964,6 +964,76 @@ TEST(TestInputFilter, ObjectCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - ASSERT_FALSE(filter.match(store, cache, deadline).has_value()); + ASSERT_FALSE(filter.match(store, cache, {}, deadline).has_value()); + } +} + +TEST(TestInputFilter, MatchWithDynamicMatcher) +{ + test::expression_builder builder(2); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition_with_data("ip_data"); + + builder.start_condition(); + builder.add_argument(); + builder.add_target("usr.id"); + builder.end_condition(std::vector{"admin"}); + + auto obj_filter = std::make_shared(); + obj_filter->insert(get_target_index("query"), "query", {"params"}); + auto rule = + std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); + + { + ddwaf::object_store store; + input_filter::cache_type cache; + + ddwaf_object root; + ddwaf_object object; + ddwaf_object tmp; + ddwaf_object_map(&object); + ddwaf_object_map_add(&object, "params", ddwaf_object_string(&tmp, "value")); + + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + ddwaf_object_map_add(&root, "query", &object); + + store.insert(root); + + ddwaf::timer deadline{2s}; + auto opt_spec = filter.match(store, cache, {}, deadline); + ASSERT_FALSE(opt_spec.has_value()); + } + + { + ddwaf::object_store store; + input_filter::cache_type cache; + + ddwaf_object root; + ddwaf_object object; + ddwaf_object tmp; + ddwaf_object_map(&object); + ddwaf_object_map_add(&object, "params", ddwaf_object_string(&tmp, "value")); + + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + ddwaf_object_map_add(&root, "query", &object); + + store.insert(root); + + std::unordered_map> matchers{{"ip_data", + std::make_shared(std::vector{"192.168.0.1"})}}; + + ddwaf::timer deadline{2s}; + auto opt_spec = filter.match(store, cache, matchers, deadline); + ASSERT_TRUE(opt_spec.has_value()); + EXPECT_EQ(opt_spec->rules.size(), 1); + EXPECT_EQ(opt_spec->objects.size(), 1); + EXPECT_EQ(opt_spec->objects.persistent.size(), 1); } } diff --git a/tests/object_filter_test.cpp b/tests/exclusion/object_filter_test.cpp similarity index 99% rename from tests/object_filter_test.cpp rename to tests/exclusion/object_filter_test.cpp index eaf528159..b2ea7a7ff 100644 --- a/tests/object_filter_test.cpp +++ b/tests/exclusion/object_filter_test.cpp @@ -4,9 +4,9 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. +#include "../test_utils.hpp" #include "exception.hpp" #include "exclusion/object_filter.hpp" -#include "test_utils.hpp" using namespace ddwaf; using namespace ddwaf::exclusion; diff --git a/tests/rule_filter_test.cpp b/tests/exclusion/rule_filter_test.cpp similarity index 93% rename from tests/rule_filter_test.cpp rename to tests/exclusion/rule_filter_test.cpp index 8df318f1f..1b48c4e92 100644 --- a/tests/rule_filter_test.cpp +++ b/tests/exclusion/rule_filter_test.cpp @@ -4,11 +4,11 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. +#include "../test_utils.hpp" #include "exclusion/rule_filter.hpp" #include "expression.hpp" #include "matcher/exact_match.hpp" #include "matcher/ip_match.hpp" -#include "test_utils.hpp" using namespace ddwaf; using namespace std::literals; @@ -42,15 +42,72 @@ TEST(TestRuleFilter, Match) ddwaf::timer deadline{2s}; - exclusion::rule_filter::excluded_set default_set{{}, true, {}}; + exclusion::rule_filter::excluded_set default_set{{}, true, {}, {}}; ddwaf::exclusion::rule_filter::cache_type cache; - auto res = filter.match(store, cache, deadline); + auto res = filter.match(store, cache, {}, deadline); EXPECT_FALSE(res.value_or(default_set).rules.empty()); EXPECT_FALSE(res.value_or(default_set).ephemeral); EXPECT_EQ(res.value_or(default_set).mode, exclusion::filter_mode::bypass); } +TEST(TestRuleFilter, MatchWithDynamicMatcher) +{ + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition_with_data("ip_data"); + + auto rule = + std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + ddwaf::exclusion::rule_filter filter{"filter", builder.build(), {rule.get()}}; + + std::unordered_map addresses; + filter.get_addresses(addresses); + EXPECT_EQ(addresses.size(), 1); + EXPECT_STREQ(addresses.begin()->second.c_str(), "http.client_ip"); + + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + ddwaf::object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + + ddwaf::exclusion::rule_filter::cache_type cache; + auto res = filter.match(store, cache, {}, deadline); + EXPECT_FALSE(res.has_value()); + } + + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + ddwaf::object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + + std::unordered_map> matchers{{"ip_data", + std::make_shared(std::vector{"192.168.0.1"})}}; + + exclusion::rule_filter::excluded_set default_set{{}, true, {}, {}}; + + ddwaf::exclusion::rule_filter::cache_type cache; + auto res = filter.match(store, cache, matchers, deadline); + EXPECT_FALSE(res.value_or(default_set).rules.empty()); + EXPECT_FALSE(res.value_or(default_set).ephemeral); + EXPECT_EQ(res.value_or(default_set).mode, exclusion::filter_mode::bypass); + } +} + TEST(TestRuleFilter, EphemeralMatch) { test::expression_builder builder(1); @@ -78,10 +135,10 @@ TEST(TestRuleFilter, EphemeralMatch) ddwaf::timer deadline{2s}; - exclusion::rule_filter::excluded_set default_set{{}, false, {}}; + exclusion::rule_filter::excluded_set default_set{{}, false, {}, {}}; ddwaf::exclusion::rule_filter::cache_type cache; - auto res = filter.match(store, cache, deadline); + auto res = filter.match(store, cache, {}, deadline); EXPECT_FALSE(res.value_or(default_set).rules.empty()); EXPECT_TRUE(res.value_or(default_set).ephemeral); EXPECT_EQ(res.value_or(default_set).mode, exclusion::filter_mode::bypass); @@ -108,7 +165,7 @@ TEST(TestRuleFilter, NoMatch) ddwaf::timer deadline{2s}; ddwaf::exclusion::rule_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, cache, deadline)); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)); } TEST(TestRuleFilter, ValidateCachedMatch) @@ -144,7 +201,7 @@ TEST(TestRuleFilter, ValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline)); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)); } { @@ -158,9 +215,9 @@ TEST(TestRuleFilter, ValidateCachedMatch) ddwaf::timer deadline{2s}; - exclusion::rule_filter::excluded_set default_set{{}, false, {}}; + exclusion::rule_filter::excluded_set default_set{{}, false, {}, {}}; - auto res = filter.match(store, cache, deadline); + auto res = filter.match(store, cache, {}, deadline); EXPECT_FALSE(res.value_or(default_set).rules.empty()); EXPECT_FALSE(res.value_or(default_set).ephemeral); EXPECT_EQ(res.value_or(default_set).mode, exclusion::filter_mode::bypass); @@ -202,7 +259,7 @@ TEST(TestRuleFilter, CachedMatchAndEphemeralMatch) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline)); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)); } { @@ -216,9 +273,9 @@ TEST(TestRuleFilter, CachedMatchAndEphemeralMatch) store.insert(root, object_store::attribute::ephemeral); ddwaf::timer deadline{2s}; - exclusion::rule_filter::excluded_set default_set{{}, false, {}}; + exclusion::rule_filter::excluded_set default_set{{}, false, {}, {}}; - auto res = filter.match(store, cache, deadline); + auto res = filter.match(store, cache, {}, deadline); EXPECT_FALSE(res.value_or(default_set).rules.empty()); EXPECT_TRUE(res.value_or(default_set).ephemeral); EXPECT_EQ(res.value_or(default_set).mode, exclusion::filter_mode::bypass); @@ -260,7 +317,7 @@ TEST(TestRuleFilter, ValidateEphemeralMatchCache) store.insert(root, object_store::attribute::ephemeral); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline)); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)); } { @@ -274,7 +331,7 @@ TEST(TestRuleFilter, ValidateEphemeralMatchCache) store.insert(root, object_store::attribute::ephemeral); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline)); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)); } } @@ -310,7 +367,7 @@ TEST(TestRuleFilter, MatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline)); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)); } { @@ -323,7 +380,7 @@ TEST(TestRuleFilter, MatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline)->rules.empty()); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)->rules.empty()); } } @@ -358,7 +415,7 @@ TEST(TestRuleFilter, NoMatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline)); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)); } { @@ -372,7 +429,7 @@ TEST(TestRuleFilter, NoMatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline)); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)); } } @@ -409,7 +466,7 @@ TEST(TestRuleFilter, FullCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline)->rules.empty()); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)->rules.empty()); EXPECT_TRUE(cache.result); } @@ -422,7 +479,7 @@ TEST(TestRuleFilter, FullCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, cache, deadline)); + EXPECT_FALSE(filter.match(store, cache, {}, deadline)); } } diff --git a/tests/integration/exclusion_data/test.cpp b/tests/integration/exclusion_data/test.cpp new file mode 100644 index 000000000..a12625c61 --- /dev/null +++ b/tests/integration/exclusion_data/test.cpp @@ -0,0 +1,537 @@ +// 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_utils.hpp" +#include "ddwaf.h" + +using namespace ddwaf; + +namespace { +constexpr std::string_view base_dir = "integration/exclusion_data/"; + +TEST(TestExclusionDataIntegration, ExcludeRuleByUserID) +{ + auto rule = read_file("exclude_one_rule_by_user.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle1 = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + { + ddwaf_context context = ddwaf_context_init(handle1); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, + {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}, + {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_handle handle2; + { + auto root = yaml_to_object( + R"({exclusion_data: [{id: usr_data, type: data_with_expiration, data: [{value: admin, expiration: 0}]}]})"); + + handle2 = ddwaf_update(handle1, &root, nullptr); + ASSERT_NE(handle2, nullptr); + ddwaf_object_free(&root); + } + + { + ddwaf_context context = ddwaf_context_init(handle2); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_handle handle3; + { + auto root = yaml_to_object(R"({exclusion_data: []})"); + + handle3 = ddwaf_update(handle1, &root, nullptr); + ASSERT_NE(handle3, nullptr); + ddwaf_object_free(&root); + } + + { + ddwaf_context context = ddwaf_context_init(handle3); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, + {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}, + {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "ip_match", + .highlight = "192.168.0.1", + .args = {{ + .value = "192.168.0.1", + .address = "http.client_ip", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + +TEST(TestExclusionDataIntegration, ExcludeRuleByClientIP) +{ + auto rule = read_file("exclude_one_rule_by_ip.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle1 = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + { + ddwaf_context context = ddwaf_context_init(handle1); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, + {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}, + {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_handle handle2; + { + auto root = yaml_to_object( + R"({exclusion_data: [{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.0.1, expiration: 0}]}]})"); + + handle2 = ddwaf_update(handle1, &root, nullptr); + ASSERT_NE(handle2, nullptr); + ddwaf_object_free(&root); + } + + { + ddwaf_context context = ddwaf_context_init(handle2); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_handle handle3; + { + auto root = yaml_to_object(R"({exclusion_data: []})"); + + handle3 = ddwaf_update(handle1, &root, nullptr); + ASSERT_NE(handle3, nullptr); + ddwaf_object_free(&root); + } + + { + ddwaf_context context = ddwaf_context_init(handle3); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, + {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}, + {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}); + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + +TEST(TestExclusionDataIntegration, UnknownDataTypeOnExclusionData) +{ + auto rule = read_file("exclude_one_rule_by_ip.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle1 = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + { + ddwaf_context context = ddwaf_context_init(handle1); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, + {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}, + {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_handle handle2; + { + auto root = yaml_to_object( + R"({exclusion_data: [{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.0.1, expiration: 0}]}]})"); + + handle2 = ddwaf_update(handle1, &root, nullptr); + ASSERT_NE(handle2, nullptr); + ddwaf_object_free(&root); + } + + { + ddwaf_context context = ddwaf_context_init(handle2); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_handle handle3; + { + auto root = + yaml_to_object(R"({exclusion_data: [{id: ip_data, type: unknown_data, data: [{}]}]})"); + + handle3 = ddwaf_update(handle1, &root, nullptr); + ASSERT_NE(handle3, nullptr); + ddwaf_object_free(&root); + } + + { + ddwaf_context context = ddwaf_context_init(handle3); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, + {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}, + {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}); + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + +TEST(TestExclusionDataIntegration, ExcludeInputByClientIP) +{ + auto rule = read_file("exclude_one_input_by_ip.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle1 = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + { + ddwaf_context context = ddwaf_context_init(handle1); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, + {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}, + {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_handle handle2; + { + auto root = yaml_to_object( + R"({exclusion_data: [{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.0.1, expiration: 0}]}]})"); + + handle2 = ddwaf_update(handle1, &root, nullptr); + ASSERT_NE(handle2, nullptr); + ddwaf_object_free(&root); + } + + { + ddwaf_context context = ddwaf_context_init(handle2); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_handle handle3; + { + auto root = yaml_to_object(R"({exclusion_data: []})"); + + handle3 = ddwaf_update(handle1, &root, nullptr); + ASSERT_NE(handle3, nullptr); + ddwaf_object_free(&root); + } + + { + ddwaf_context context = ddwaf_context_init(handle3); + ASSERT_NE(context, nullptr); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + EXPECT_EQ(ddwaf_run(context, &root, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(out, + {.id = "1", + .name = "rule1", + .tags = {{"type", "type1"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}, + {.id = "2", + .name = "rule2", + .tags = {{"type", "type2"}, {"category", "category"}}, + .matches = {{.op = "exact_match", + .highlight = "admin", + .args = {{ + .value = "admin", + .address = "usr.id", + }}}}}); + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + } + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + +} // namespace diff --git a/tests/integration/exclusion_data/yaml/exclude_one_input_by_ip.yaml b/tests/integration/exclusion_data/yaml/exclude_one_input_by_ip.yaml new file mode 100644 index 000000000..14e238755 --- /dev/null +++ b/tests/integration/exclusion_data/yaml/exclude_one_input_by_ip.yaml @@ -0,0 +1,39 @@ +version: '2.1' +exclusions: + - id: "1" + rules_target: + - rule_id: 1 + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + data: ip_data + inputs: + - address: usr.id + +rules: + - id: 1 + name: rule1 + tags: + type: type1 + category: category + conditions: + - operator: exact_match + parameters: + inputs: + - address: usr.id + list: + - admin + - id: 2 + name: rule2 + tags: + type: type2 + category: category + conditions: + - operator: exact_match + parameters: + inputs: + - address: usr.id + list: + - admin diff --git a/tests/integration/exclusion_data/yaml/exclude_one_rule_by_ip.yaml b/tests/integration/exclusion_data/yaml/exclude_one_rule_by_ip.yaml new file mode 100644 index 000000000..ca7089ee4 --- /dev/null +++ b/tests/integration/exclusion_data/yaml/exclude_one_rule_by_ip.yaml @@ -0,0 +1,36 @@ +version: '2.1' +exclusions: + - id: 1 + rules_target: + - rule_id: 1 + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + data: ip_data +rules: + - id: 1 + name: rule1 + tags: + type: type1 + category: category + conditions: + - operator: exact_match + parameters: + inputs: + - address: usr.id + list: + - admin + - id: 2 + name: rule2 + tags: + type: type2 + category: category + conditions: + - operator: exact_match + parameters: + inputs: + - address: usr.id + list: + - admin diff --git a/tests/integration/exclusion_data/yaml/exclude_one_rule_by_user.yaml b/tests/integration/exclusion_data/yaml/exclude_one_rule_by_user.yaml new file mode 100644 index 000000000..3708f1406 --- /dev/null +++ b/tests/integration/exclusion_data/yaml/exclude_one_rule_by_user.yaml @@ -0,0 +1,36 @@ +version: '2.1' +exclusions: + - id: 1 + rules_target: + - rule_id: 1 + conditions: + - operator: exact_match + parameters: + inputs: + - address: usr.id + data: usr_data +rules: + - id: 1 + name: rule1 + tags: + type: type1 + category: category + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + list: + - 192.168.0.1 + - id: 2 + name: rule2 + tags: + type: type2 + category: category + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + list: + - 192.168.0.1 diff --git a/tests/parser_v2_rules_data_test.cpp b/tests/parser_v2_data_test.cpp similarity index 70% rename from tests/parser_v2_rules_data_test.cpp rename to tests/parser_v2_data_test.cpp index 8337e14d9..d3d28d83b 100644 --- a/tests/parser_v2_rules_data_test.cpp +++ b/tests/parser_v2_data_test.cpp @@ -12,16 +12,16 @@ using namespace ddwaf; namespace { -TEST(TestParserV2RuleData, ParseIPData) +TEST(TestParserV2Data, ParseIPData) { - std::unordered_map rule_data_ids{{"ip_data", "ip_match"}}; + std::unordered_map data_ids{{"ip_data", "ip_match"}}; auto object = yaml_to_object( R"([{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); auto input = static_cast(parameter(object)); ddwaf::ruleset_info::section_info section; - auto rule_data = parser::v2::parse_rule_data(input, section, rule_data_ids); + auto data_cfg = parser::v2::parse_data(input, data_ids, section); ddwaf_object_free(&object); { @@ -43,20 +43,20 @@ TEST(TestParserV2RuleData, ParseIPData) ddwaf_object_free(&root); } - EXPECT_EQ(rule_data.size(), 1); - EXPECT_STRV(rule_data["ip_data"]->name(), "ip_match"); + EXPECT_EQ(data_cfg.size(), 1); + EXPECT_STRV(data_cfg["ip_data"]->name(), "ip_match"); } -TEST(TestParserV2RuleData, ParseStringData) +TEST(TestParserV2Data, ParseStringData) { - std::unordered_map rule_data_ids{{"usr_data", "exact_match"}}; + std::unordered_map data_ids{{"usr_data", "exact_match"}}; auto object = yaml_to_object( R"([{id: usr_data, type: data_with_expiration, data: [{value: user, expiration: 500}]}])"); auto input = static_cast(parameter(object)); ddwaf::ruleset_info::section_info section; - auto rule_data = parser::v2::parse_rule_data(input, section, rule_data_ids); + auto data_cfg = parser::v2::parse_data(input, data_ids, section); ddwaf_object_free(&object); { @@ -78,13 +78,13 @@ TEST(TestParserV2RuleData, ParseStringData) ddwaf_object_free(&root); } - EXPECT_EQ(rule_data.size(), 1); - EXPECT_STRV(rule_data["usr_data"]->name(), "exact_match"); + EXPECT_EQ(data_cfg.size(), 1); + EXPECT_STRV(data_cfg["usr_data"]->name(), "exact_match"); } -TEST(TestParserV2RuleData, ParseMultipleRuleData) +TEST(TestParserV2Data, ParseMultipleData) { - std::unordered_map rule_data_ids{ + std::unordered_map data_ids{ {"ip_data", "ip_match"}, {"usr_data", "exact_match"}}; auto object = yaml_to_object( @@ -92,7 +92,7 @@ TEST(TestParserV2RuleData, ParseMultipleRuleData) auto input = static_cast(parameter(object)); ddwaf::ruleset_info::section_info section; - auto rule_data = parser::v2::parse_rule_data(input, section, rule_data_ids); + auto data_cfg = parser::v2::parse_data(input, data_ids, section); ddwaf_object_free(&object); { @@ -115,21 +115,21 @@ TEST(TestParserV2RuleData, ParseMultipleRuleData) ddwaf_object_free(&root); } - EXPECT_EQ(rule_data.size(), 2); - EXPECT_STRV(rule_data["usr_data"]->name(), "exact_match"); - EXPECT_STRV(rule_data["ip_data"]->name(), "ip_match"); + EXPECT_EQ(data_cfg.size(), 2); + EXPECT_STRV(data_cfg["usr_data"]->name(), "exact_match"); + EXPECT_STRV(data_cfg["ip_data"]->name(), "ip_match"); } -TEST(TestParserV2RuleData, ParseUnknownRuleData) +TEST(TestParserV2Data, ParseUnknownDataID) { - std::unordered_map rule_data_ids{{"usr_data", "exact_match"}}; + std::unordered_map data_ids{{"usr_data", "exact_match"}}; auto object = yaml_to_object( R"([{id: usr_data, type: data_with_expiration, data: [{value: user, expiration: 500}]},{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); auto input = static_cast(parameter(object)); ddwaf::ruleset_info::section_info section; - auto rule_data = parser::v2::parse_rule_data(input, section, rule_data_ids); + auto data_cfg = parser::v2::parse_data(input, data_ids, section); ddwaf_object_free(&object); { @@ -152,22 +152,22 @@ TEST(TestParserV2RuleData, ParseUnknownRuleData) ddwaf_object_free(&root); } - EXPECT_EQ(rule_data.size(), 2); - EXPECT_STRV(rule_data["ip_data"]->name(), "ip_match"); - EXPECT_STRV(rule_data["usr_data"]->name(), "exact_match"); + EXPECT_EQ(data_cfg.size(), 2); + EXPECT_STRV(data_cfg["ip_data"]->name(), "ip_match"); + EXPECT_STRV(data_cfg["usr_data"]->name(), "exact_match"); } -TEST(TestParserV2RuleData, ParseUnsupportedProcessor) +TEST(TestParserV2Data, ParseUnsupportedTypes) { - std::unordered_map rule_data_ids{ + std::unordered_map data_ids{ {"usr_data", "match_regex"}, {"ip_data", "phrase_match"}}; auto object = yaml_to_object( - R"([{id: usr_data, type: data_with_expiration, data: [{value: user, expiration: 500}]},{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); + R"([{id: usr_data, type: blob_with_expiration, data: [{value: user, expiration: 500}]},{id: ip_data, type: whatever, data: [{value: 192.168.1.1, expiration: 500}]}])"); auto input = static_cast(parameter(object)); ddwaf::ruleset_info::section_info section; - auto rule_data = parser::v2::parse_rule_data(input, section, rule_data_ids); + auto data_cfg = parser::v2::parse_data(input, data_ids, section); ddwaf_object_free(&object); { @@ -187,7 +187,7 @@ TEST(TestParserV2RuleData, ParseUnsupportedProcessor) auto errors = ddwaf::parser::at(root_map, "errors"); EXPECT_EQ(errors.size(), 2); { - auto it = errors.find("matcher match_regex doesn't support dynamic rule data"); + auto it = errors.find("matcher match_regex doesn't support dynamic data"); EXPECT_NE(it, errors.end()); auto error_rules = static_cast(it->second); @@ -196,7 +196,7 @@ TEST(TestParserV2RuleData, ParseUnsupportedProcessor) } { - auto it = errors.find("matcher phrase_match doesn't support dynamic rule data"); + auto it = errors.find("matcher phrase_match doesn't support dynamic data"); EXPECT_NE(it, errors.end()); auto error_rules = static_cast(it->second); @@ -207,19 +207,59 @@ TEST(TestParserV2RuleData, ParseUnsupportedProcessor) ddwaf_object_free(&root); } - EXPECT_EQ(rule_data.size(), 0); + EXPECT_EQ(data_cfg.size(), 0); +} + +TEST(TestParserV2Data, ParseUnknownDataIDWithUnsupportedType) +{ + std::unordered_map data_ids{}; + + auto object = yaml_to_object( + R"([{id: usr_data, type: blob_with_expiration, data: [{value: user, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + ddwaf::ruleset_info::section_info section; + auto data_cfg = parser::v2::parse_data(input, data_ids, section); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("usr_data"), failed.end()); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("failed to infer matcher"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("usr_data"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(data_cfg.size(), 0); } -TEST(TestParserV2RuleData, ParseMissingType) +TEST(TestParserV2Data, ParseMissingType) { - std::unordered_map rule_data_ids{{"ip_data", "ip_match"}}; + std::unordered_map data_ids{{"ip_data", "ip_match"}}; auto object = yaml_to_object(R"([{id: ip_data, data: [{value: 192.168.1.1, expiration: 500}]}])"); auto input = static_cast(parameter(object)); ddwaf::ruleset_info::section_info section; - auto rule_data = parser::v2::parse_rule_data(input, section, rule_data_ids); + auto data_cfg = parser::v2::parse_data(input, data_ids, section); ddwaf_object_free(&object); { @@ -247,19 +287,19 @@ TEST(TestParserV2RuleData, ParseMissingType) ddwaf_object_free(&root); } - EXPECT_EQ(rule_data.size(), 0); + EXPECT_EQ(data_cfg.size(), 0); } -TEST(TestParserV2RuleData, ParseMissingID) +TEST(TestParserV2Data, ParseMissingID) { - std::unordered_map rule_data_ids{{"ip_data", "ip_match"}}; + std::unordered_map data_ids{{"ip_data", "ip_match"}}; auto object = yaml_to_object( R"([{type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); auto input = static_cast(parameter(object)); ddwaf::ruleset_info::section_info section; - auto rule_data = parser::v2::parse_rule_data(input, section, rule_data_ids); + auto data_cfg = parser::v2::parse_data(input, data_ids, section); ddwaf_object_free(&object); { @@ -287,18 +327,18 @@ TEST(TestParserV2RuleData, ParseMissingID) ddwaf_object_free(&root); } - EXPECT_EQ(rule_data.size(), 0); + EXPECT_EQ(data_cfg.size(), 0); } -TEST(TestParserV2RuleData, ParseMissingData) +TEST(TestParserV2Data, ParseMissingData) { - std::unordered_map rule_data_ids{{"ip_data", "ip_match"}}; + std::unordered_map data_ids{{"ip_data", "ip_match"}}; auto object = yaml_to_object(R"([{id: ip_data, type: ip_with_expiration}])"); auto input = static_cast(parameter(object)); ddwaf::ruleset_info::section_info section; - auto rule_data = parser::v2::parse_rule_data(input, section, rule_data_ids); + auto data_cfg = parser::v2::parse_data(input, data_ids, section); ddwaf_object_free(&object); { @@ -326,6 +366,6 @@ TEST(TestParserV2RuleData, ParseMissingData) ddwaf_object_free(&root); } - EXPECT_EQ(rule_data.size(), 0); + EXPECT_EQ(data_cfg.size(), 0); } } // namespace diff --git a/tests/parser_v2_input_filters_test.cpp b/tests/parser_v2_input_filters_test.cpp index b4b214a8a..10224bfe8 100644 --- a/tests/parser_v2_input_filters_test.cpp +++ b/tests/parser_v2_input_filters_test.cpp @@ -17,9 +17,10 @@ TEST(TestParserV2InputFilters, ParseEmpty) ddwaf::object_limits limits; auto object = yaml_to_object(R"([{id: 1, inputs: []}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -57,9 +58,10 @@ TEST(TestParserV2InputFilters, ParseFilterWithoutID) auto object = yaml_to_object(R"([{inputs: [{address: http.client_ip}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -98,9 +100,10 @@ TEST(TestParserV2InputFilters, ParseDuplicateFilters) auto object = yaml_to_object( R"([{id: 1, inputs: [{address: http.client_ip}]}, {id: 1, inputs: [{address: usr.id}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -139,9 +142,10 @@ TEST(TestParserV2InputFilters, ParseUnconditionalNoTargets) auto object = yaml_to_object(R"([{id: 1, inputs: [{address: http.client_ip}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -182,9 +186,10 @@ TEST(TestParserV2InputFilters, ParseUnconditionalTargetID) auto object = yaml_to_object( R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -230,9 +235,10 @@ TEST(TestParserV2InputFilters, ParseUnconditionalTargetTags) auto object = yaml_to_object( R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{tags: {type: rule, category: unknown}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -280,9 +286,10 @@ TEST(TestParserV2InputFilters, ParseUnconditionalTargetPriority) auto object = yaml_to_object( R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939, tags: {type: rule, category: unknown}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -328,9 +335,10 @@ TEST(TestParserV2InputFilters, ParseUnconditionalMultipleTargets) auto object = yaml_to_object( R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}, {tags: {type: rule, category: unknown}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -387,9 +395,10 @@ TEST(TestParserV2InputFilters, ParseMultipleUnconditional) auto object = yaml_to_object( R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}]}, {id: 2, inputs: [{address: usr.id}], rules_target: [{tags: {type: rule, category: unknown}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -455,9 +464,10 @@ TEST(TestParserV2InputFilters, ParseConditionalSingleCondition) auto object = yaml_to_object( R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -502,9 +512,10 @@ TEST(TestParserV2InputFilters, ParseConditionalMultipleConditions) auto object = yaml_to_object( R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { diff --git a/tests/parser_v2_rule_filters_test.cpp b/tests/parser_v2_rule_filters_test.cpp index 563b8b9fc..a94a7e761 100644 --- a/tests/parser_v2_rule_filters_test.cpp +++ b/tests/parser_v2_rule_filters_test.cpp @@ -18,9 +18,10 @@ TEST(TestParserV2RuleFilters, ParseEmptyFilter) auto object = yaml_to_object(R"([{id: 1}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -58,9 +59,10 @@ TEST(TestParserV2RuleFilters, ParseFilterWithoutID) auto object = yaml_to_object(R"([{rules_target: [{rule_id: 2939}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -99,9 +101,10 @@ TEST(TestParserV2RuleFilters, ParseDuplicateUnconditional) auto object = yaml_to_object( R"([{id: 1, rules_target: [{rule_id: 2939}]},{id: 1, rules_target: [{tags: {type: rule, category: unknown}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -140,9 +143,10 @@ TEST(TestParserV2RuleFilters, ParseUnconditionalTargetID) auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -187,9 +191,10 @@ TEST(TestParserV2RuleFilters, ParseUnconditionalTargetTags) auto object = yaml_to_object(R"([{id: 1, rules_target: [{tags: {type: rule, category: unknown}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -236,9 +241,10 @@ TEST(TestParserV2RuleFilters, ParseUnconditionalTargetPriority) auto object = yaml_to_object( R"([{id: 1, rules_target: [{rule_id: 2939, tags: {type: rule, category: unknown}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -283,9 +289,10 @@ TEST(TestParserV2RuleFilters, ParseUnconditionalMultipleTargets) auto object = yaml_to_object( R"([{id: 1, rules_target: [{rule_id: 2939},{tags: {type: rule, category: unknown}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -341,9 +348,10 @@ TEST(TestParserV2RuleFilters, ParseMultipleUnconditional) auto object = yaml_to_object( R"([{id: 1, rules_target: [{rule_id: 2939}]},{id: 2, rules_target: [{tags: {type: rule, category: unknown}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -407,9 +415,10 @@ TEST(TestParserV2RuleFilters, ParseDuplicateConditional) auto object = yaml_to_object( R"([{id: 1, rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]},{id: 1, rules_target: [{tags: {type: rule, category: unknown}}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); EXPECT_EQ(filters.rule_filters.size(), 1); @@ -423,9 +432,10 @@ TEST(TestParserV2RuleFilters, ParseConditionalSingleCondition) auto object = yaml_to_object( R"([{id: 1, rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -470,9 +480,10 @@ TEST(TestParserV2RuleFilters, ParseConditionalGlobal) auto object = yaml_to_object( R"([{id: 1, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -513,9 +524,10 @@ TEST(TestParserV2RuleFilters, ParseConditionalMultipleConditions) auto object = yaml_to_object( R"([{id: 1, rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -561,9 +573,10 @@ TEST(TestParserV2RuleFilters, ParseOnMatchMonitor) auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: monitor}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -601,9 +614,10 @@ TEST(TestParserV2RuleFilters, ParseOnMatchBypass) auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: bypass}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -642,9 +656,10 @@ TEST(TestParserV2RuleFilters, ParseCustomOnMatch) auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: obliterate}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { @@ -683,9 +698,10 @@ TEST(TestParserV2RuleFilters, ParseInvalidOnMatch) auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: ""}])"); + std::unordered_map data_ids; ddwaf::ruleset_info::section_info section; auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, limits); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); ddwaf_object_free(&object); { diff --git a/tests/parser_v2_rules_test.cpp b/tests/parser_v2_rules_test.cpp index 53d5d79b5..07f9ca937 100644 --- a/tests/parser_v2_rules_test.cpp +++ b/tests/parser_v2_rules_test.cpp @@ -368,87 +368,4 @@ TEST(TestParserV2Rules, ParseMultipleRulesOneDuplicate) EXPECT_STR(rule.tags["category"], "category1"); } } - -TEST(TestParserV2Rules, ParseSingleRuleTooManyTransformers) -{ - ddwaf::object_limits limits; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}], transformers: [base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode]}])"); - - auto rule_array = static_cast(parameter(rule_object)); - EXPECT_EQ(rule_array.size(), 1); - - auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("number of transformers beyond allowed limit"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } -} - -TEST(TestParserV2Rules, ParseSingleRuleTooManyInputTransformers) -{ - ddwaf::object_limits limits; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y], transformers: [base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode, base64_encode]}], regex: .*}}]}])"); - - auto rule_array = static_cast(parameter(rule_object)); - EXPECT_EQ(rule_array.size(), 1); - - auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("number of transformers beyond allowed limit"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } -} - } // namespace diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index f95acc6c0..429e516c6 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -75,11 +75,11 @@ class expression_builder { } template - void end_condition(std::string data_id) + void end_condition_with_data(std::string data_id) requires std::is_base_of_v { conditions_.emplace_back(std::make_unique( - std::move(arguments_), std::move(data_id), std::make_unique())); + std::unique_ptr{}, std::move(data_id), std::move(arguments_))); } template From 09de0e04530562dfaa6cf527d370c88bdfaebd09 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:13:30 +0100 Subject: [PATCH 10/17] Shell injection detection operator (#308) --- .github/workflows/fuzz.yml | 5 +- cmake/objects.cmake | 2 + fuzzer/shell_tokenizer/corpus/corpus-0000 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0001 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0002 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0003 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0004 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0005 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0006 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0007 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0008 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0009 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0010 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0011 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0012 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0013 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0014 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0015 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0016 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0017 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0018 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0019 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0020 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0021 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0022 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0023 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0024 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0025 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0026 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0027 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0028 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0029 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0030 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0031 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0032 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0033 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0034 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0035 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0036 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0037 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0038 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0039 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0040 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0041 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0042 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0043 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0044 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0045 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0046 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0047 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0048 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0049 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0050 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0051 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0052 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0053 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0054 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0055 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0056 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0057 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0058 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0059 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0060 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0061 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0062 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0063 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0064 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0065 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0066 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0067 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0068 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0069 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0070 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0071 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0072 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0073 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0074 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0075 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0076 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0077 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0078 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0079 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0080 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0081 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0082 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0083 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0084 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0085 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0086 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0087 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0088 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0089 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0090 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0091 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0092 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0093 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0094 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0095 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0096 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0097 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0098 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0099 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0100 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0101 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0102 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0103 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0104 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0105 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0106 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0107 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0108 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0109 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0110 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0111 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0112 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0113 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0114 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0115 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0116 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0117 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0118 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0119 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0120 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0121 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0122 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0123 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0124 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0125 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0126 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0127 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0128 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0129 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0130 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0131 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0132 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0133 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0134 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0135 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0136 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0137 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0138 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0139 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0140 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0141 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0142 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0143 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0144 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0145 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0146 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0147 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0148 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0149 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0150 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0151 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0152 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0153 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0154 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0155 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0156 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0157 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0158 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0159 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0160 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0161 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0162 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0163 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0164 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0165 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0166 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0167 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0168 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0169 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0170 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0171 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0172 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0173 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0174 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0175 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0176 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0177 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0178 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0179 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0180 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0181 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0182 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0183 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0184 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0185 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0186 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0187 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0188 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0189 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0190 | 1 + fuzzer/shell_tokenizer/corpus/corpus-0191 | 1 + fuzzer/shell_tokenizer/src/main.cpp | 23 + fuzzer/shi_detector/corpus/corpus-0000 | Bin 0 -> 26 bytes fuzzer/shi_detector/corpus/corpus-0001 | Bin 0 -> 27 bytes fuzzer/shi_detector/corpus/corpus-0002 | Bin 0 -> 38 bytes fuzzer/shi_detector/corpus/corpus-0003 | Bin 0 -> 58 bytes fuzzer/shi_detector/corpus/corpus-0004 | Bin 0 -> 34 bytes fuzzer/shi_detector/corpus/corpus-0005 | Bin 0 -> 31 bytes fuzzer/shi_detector/corpus/corpus-0006 | Bin 0 -> 45 bytes fuzzer/shi_detector/corpus/corpus-0007 | Bin 0 -> 57 bytes fuzzer/shi_detector/corpus/corpus-0008 | Bin 0 -> 58 bytes fuzzer/shi_detector/corpus/corpus-0009 | Bin 0 -> 59 bytes fuzzer/shi_detector/corpus/corpus-0010 | Bin 0 -> 63 bytes fuzzer/shi_detector/corpus/corpus-0011 | Bin 0 -> 62 bytes fuzzer/shi_detector/corpus/corpus-0012 | Bin 0 -> 48 bytes fuzzer/shi_detector/corpus/corpus-0013 | Bin 0 -> 58 bytes fuzzer/shi_detector/corpus/corpus-0014 | Bin 0 -> 62 bytes fuzzer/shi_detector/corpus/corpus-0015 | Bin 0 -> 52 bytes fuzzer/shi_detector/corpus/corpus-0016 | Bin 0 -> 55 bytes fuzzer/shi_detector/corpus/corpus-0017 | Bin 0 -> 67 bytes fuzzer/shi_detector/corpus/corpus-0018 | Bin 0 -> 59 bytes fuzzer/shi_detector/corpus/corpus-0019 | Bin 0 -> 32 bytes fuzzer/shi_detector/corpus/corpus-0020 | Bin 0 -> 60 bytes fuzzer/shi_detector/corpus/corpus-0021 | Bin 0 -> 53 bytes fuzzer/shi_detector/corpus/corpus-0022 | Bin 0 -> 41 bytes fuzzer/shi_detector/corpus/corpus-0023 | Bin 0 -> 55 bytes fuzzer/shi_detector/corpus/corpus-0024 | Bin 0 -> 47 bytes fuzzer/shi_detector/corpus/corpus-0025 | Bin 0 -> 45 bytes fuzzer/shi_detector/corpus/corpus-0026 | Bin 0 -> 34 bytes fuzzer/shi_detector/corpus/corpus-0027 | Bin 0 -> 34 bytes fuzzer/shi_detector/corpus/corpus-0028 | Bin 0 -> 42 bytes fuzzer/shi_detector/corpus/corpus-0029 | Bin 0 -> 36 bytes fuzzer/shi_detector/corpus/corpus-0030 | Bin 0 -> 133 bytes fuzzer/shi_detector/corpus/corpus-0031 | Bin 0 -> 38 bytes fuzzer/shi_detector/corpus/corpus-0032 | Bin 0 -> 37 bytes fuzzer/shi_detector/corpus/corpus-0033 | Bin 0 -> 30 bytes fuzzer/shi_detector/corpus/corpus-0034 | Bin 0 -> 115 bytes fuzzer/shi_detector/corpus/corpus-0035 | Bin 0 -> 115 bytes fuzzer/shi_detector/corpus/corpus-0036 | Bin 0 -> 32 bytes fuzzer/shi_detector/corpus/corpus-0037 | Bin 0 -> 201 bytes fuzzer/shi_detector/corpus/corpus-0038 | Bin 0 -> 212 bytes fuzzer/shi_detector/corpus/corpus-0039 | Bin 0 -> 32 bytes fuzzer/shi_detector/corpus/corpus-0040 | Bin 0 -> 44 bytes fuzzer/shi_detector/corpus/corpus-0041 | Bin 0 -> 20 bytes fuzzer/shi_detector/corpus/corpus-0042 | Bin 0 -> 22 bytes fuzzer/shi_detector/corpus/corpus-0043 | Bin 0 -> 25 bytes fuzzer/shi_detector/corpus/corpus-0044 | Bin 0 -> 22 bytes fuzzer/shi_detector/corpus/corpus-0045 | Bin 0 -> 28 bytes fuzzer/shi_detector/corpus/corpus-0046 | Bin 0 -> 35 bytes fuzzer/shi_detector/corpus/corpus-0047 | Bin 0 -> 33 bytes fuzzer/shi_detector/corpus/corpus-0048 | Bin 0 -> 34 bytes fuzzer/shi_detector/corpus/corpus-0049 | Bin 0 -> 42 bytes fuzzer/shi_detector/corpus/corpus-0050 | Bin 0 -> 46 bytes fuzzer/shi_detector/corpus/corpus-0051 | Bin 0 -> 28 bytes fuzzer/shi_detector/corpus/corpus-0052 | Bin 0 -> 28 bytes fuzzer/shi_detector/corpus/corpus-0053 | Bin 0 -> 30 bytes fuzzer/shi_detector/corpus/corpus-0054 | Bin 0 -> 25 bytes fuzzer/shi_detector/corpus/corpus-0055 | Bin 0 -> 24 bytes fuzzer/shi_detector/corpus/corpus-0056 | Bin 0 -> 32 bytes fuzzer/shi_detector/corpus/corpus-0057 | Bin 0 -> 32 bytes fuzzer/shi_detector/corpus/corpus-0058 | Bin 0 -> 32 bytes fuzzer/shi_detector/corpus/corpus-0059 | Bin 0 -> 36 bytes fuzzer/shi_detector/corpus/corpus-0060 | Bin 0 -> 34 bytes fuzzer/shi_detector/corpus/corpus-0061 | Bin 0 -> 29 bytes fuzzer/shi_detector/corpus/corpus-0062 | Bin 0 -> 23 bytes fuzzer/shi_detector/corpus/corpus-0063 | Bin 0 -> 29 bytes fuzzer/shi_detector/corpus/corpus-0064 | Bin 0 -> 42 bytes fuzzer/shi_detector/corpus/corpus-0065 | Bin 0 -> 47 bytes fuzzer/shi_detector/corpus/corpus-0066 | Bin 0 -> 44 bytes fuzzer/shi_detector/corpus/corpus-0067 | Bin 0 -> 45 bytes fuzzer/shi_detector/corpus/corpus-0068 | Bin 0 -> 38 bytes fuzzer/shi_detector/corpus/corpus-0069 | Bin 0 -> 45 bytes fuzzer/shi_detector/corpus/corpus-0070 | Bin 0 -> 33 bytes fuzzer/shi_detector/corpus/corpus-0071 | Bin 0 -> 41 bytes fuzzer/shi_detector/corpus/corpus-0072 | Bin 0 -> 29 bytes fuzzer/shi_detector/corpus/corpus-0073 | Bin 0 -> 52 bytes fuzzer/shi_detector/corpus/corpus-0074 | Bin 0 -> 35 bytes fuzzer/shi_detector/corpus/corpus-0075 | Bin 0 -> 41 bytes fuzzer/shi_detector/corpus/corpus-0076 | Bin 0 -> 29 bytes fuzzer/shi_detector/corpus/corpus-0077 | Bin 0 -> 46 bytes fuzzer/shi_detector/corpus/corpus-0078 | Bin 0 -> 32 bytes fuzzer/shi_detector/corpus/corpus-0079 | Bin 0 -> 39 bytes fuzzer/shi_detector/corpus/corpus-0080 | Bin 0 -> 20 bytes fuzzer/shi_detector/corpus/corpus-0081 | Bin 0 -> 30 bytes fuzzer/shi_detector/corpus/corpus-0082 | Bin 0 -> 27 bytes fuzzer/shi_detector/corpus/corpus-0083 | Bin 0 -> 23 bytes fuzzer/shi_detector/corpus/corpus-0084 | Bin 0 -> 24 bytes fuzzer/shi_detector/corpus/corpus-0085 | Bin 0 -> 30 bytes fuzzer/shi_detector/corpus/corpus-0086 | Bin 0 -> 31 bytes fuzzer/shi_detector/corpus/corpus-0087 | Bin 0 -> 32 bytes fuzzer/shi_detector/corpus/corpus-0088 | Bin 0 -> 28 bytes fuzzer/shi_detector/corpus/corpus-0089 | Bin 0 -> 30 bytes fuzzer/shi_detector/corpus/corpus-0090 | Bin 0 -> 42 bytes fuzzer/shi_detector/corpus/corpus-0091 | Bin 0 -> 24 bytes fuzzer/shi_detector/corpus/corpus-0092 | Bin 0 -> 31 bytes fuzzer/shi_detector/corpus/corpus-0093 | Bin 0 -> 45 bytes fuzzer/shi_detector/corpus/corpus-0094 | Bin 0 -> 19 bytes fuzzer/shi_detector/corpus/corpus-0095 | Bin 0 -> 18 bytes fuzzer/shi_detector/corpus/corpus-0096 | Bin 0 -> 17 bytes fuzzer/shi_detector/corpus/corpus-0097 | Bin 0 -> 19 bytes fuzzer/shi_detector/corpus/corpus-0098 | Bin 0 -> 18 bytes fuzzer/shi_detector/corpus/corpus-0099 | Bin 0 -> 19 bytes fuzzer/shi_detector/corpus/corpus-0100 | Bin 0 -> 19 bytes fuzzer/shi_detector/corpus/corpus-0101 | Bin 0 -> 20 bytes fuzzer/shi_detector/corpus/corpus-0102 | Bin 0 -> 21 bytes fuzzer/shi_detector/corpus/corpus-0103 | Bin 0 -> 21 bytes fuzzer/shi_detector/corpus/corpus-0104 | Bin 0 -> 18 bytes fuzzer/shi_detector/corpus/corpus-0105 | Bin 0 -> 18 bytes fuzzer/shi_detector/corpus/corpus-0106 | Bin 0 -> 19 bytes fuzzer/shi_detector/corpus/corpus-0107 | Bin 0 -> 20 bytes fuzzer/shi_detector/corpus/corpus-0108 | Bin 0 -> 23 bytes fuzzer/shi_detector/corpus/corpus-0109 | Bin 0 -> 19 bytes fuzzer/shi_detector/corpus/corpus-0110 | Bin 0 -> 18 bytes fuzzer/shi_detector/corpus/corpus-0111 | Bin 0 -> 21 bytes fuzzer/shi_detector/corpus/corpus-0112 | Bin 0 -> 22 bytes fuzzer/shi_detector/corpus/corpus-0113 | Bin 0 -> 22 bytes fuzzer/shi_detector/corpus/corpus-0114 | Bin 0 -> 20 bytes fuzzer/shi_detector/corpus/corpus-0115 | Bin 0 -> 19 bytes fuzzer/shi_detector/corpus/corpus-0116 | Bin 0 -> 21 bytes fuzzer/shi_detector/corpus/corpus-0117 | Bin 0 -> 19 bytes fuzzer/shi_detector/corpus/corpus-0118 | Bin 0 -> 37 bytes fuzzer/shi_detector/corpus/corpus-0119 | Bin 0 -> 46 bytes fuzzer/shi_detector/corpus/corpus-0120 | Bin 0 -> 68 bytes fuzzer/shi_detector/corpus/corpus-0121 | Bin 0 -> 36 bytes fuzzer/shi_detector/corpus/corpus-0122 | Bin 0 -> 36 bytes fuzzer/shi_detector/corpus/corpus-0123 | Bin 0 -> 58 bytes fuzzer/shi_detector/corpus/corpus-0124 | Bin 0 -> 33 bytes fuzzer/shi_detector/corpus/corpus-0125 | Bin 0 -> 35 bytes fuzzer/shi_detector/corpus/corpus-0126 | Bin 0 -> 32 bytes fuzzer/shi_detector/corpus/corpus-0127 | Bin 0 -> 35 bytes fuzzer/shi_detector/corpus/corpus-0128 | Bin 0 -> 34 bytes fuzzer/shi_detector/corpus/corpus-0129 | Bin 0 -> 35 bytes fuzzer/shi_detector/corpus/corpus-0130 | Bin 0 -> 33 bytes fuzzer/shi_detector/corpus/corpus-0131 | Bin 0 -> 43 bytes fuzzer/shi_detector/corpus/corpus-0132 | Bin 0 -> 31 bytes fuzzer/shi_detector/corpus/corpus-0133 | Bin 0 -> 39 bytes fuzzer/shi_detector/corpus/corpus-0134 | Bin 0 -> 34 bytes fuzzer/shi_detector/corpus/corpus-0135 | Bin 0 -> 32 bytes fuzzer/shi_detector/corpus/corpus-0136 | Bin 0 -> 48 bytes fuzzer/shi_detector/corpus/corpus-0137 | Bin 0 -> 35 bytes fuzzer/shi_detector/corpus/corpus-0138 | Bin 0 -> 41 bytes fuzzer/shi_detector/corpus/corpus-0139 | Bin 0 -> 42 bytes fuzzer/shi_detector/corpus/corpus-0140 | Bin 0 -> 42 bytes fuzzer/shi_detector/corpus/corpus-0141 | Bin 0 -> 37 bytes fuzzer/shi_detector/corpus/corpus-0142 | Bin 0 -> 35 bytes fuzzer/shi_detector/corpus/corpus-0143 | Bin 0 -> 36 bytes fuzzer/shi_detector/corpus/corpus-0144 | Bin 0 -> 47 bytes fuzzer/shi_detector/corpus/corpus-0145 | Bin 0 -> 33 bytes fuzzer/shi_detector/corpus/corpus-0146 | Bin 0 -> 37 bytes fuzzer/shi_detector/corpus/corpus-0147 | Bin 0 -> 38 bytes fuzzer/shi_detector/corpus/corpus-0148 | Bin 0 -> 23 bytes fuzzer/shi_detector/corpus/corpus-0149 | Bin 0 -> 28 bytes fuzzer/shi_detector/corpus/corpus-0150 | Bin 0 -> 26 bytes fuzzer/shi_detector/corpus/corpus-0151 | Bin 0 -> 23 bytes fuzzer/shi_detector/corpus/corpus-0152 | Bin 0 -> 26 bytes fuzzer/shi_detector/corpus/corpus-0153 | Bin 0 -> 24 bytes fuzzer/shi_detector/corpus/corpus-0154 | Bin 0 -> 24 bytes fuzzer/shi_detector/corpus/corpus-0155 | Bin 0 -> 28 bytes fuzzer/shi_detector/corpus/corpus-0156 | Bin 0 -> 29 bytes fuzzer/shi_detector/corpus/corpus-0157 | Bin 0 -> 41 bytes fuzzer/shi_detector/corpus/corpus-0158 | Bin 0 -> 33 bytes fuzzer/shi_detector/corpus/corpus-0159 | Bin 0 -> 28 bytes fuzzer/shi_detector/corpus/corpus-0160 | Bin 0 -> 27 bytes fuzzer/shi_detector/corpus/corpus-0161 | Bin 0 -> 36 bytes fuzzer/shi_detector/corpus/corpus-0162 | Bin 0 -> 23 bytes fuzzer/shi_detector/corpus/corpus-0163 | Bin 0 -> 25 bytes fuzzer/shi_detector/corpus/corpus-0164 | Bin 0 -> 29 bytes fuzzer/shi_detector/corpus/corpus-0165 | Bin 0 -> 25 bytes fuzzer/shi_detector/corpus/corpus-0166 | Bin 0 -> 27 bytes fuzzer/shi_detector/corpus/corpus-0167 | Bin 0 -> 27 bytes fuzzer/shi_detector/corpus/corpus-0168 | Bin 0 -> 24 bytes fuzzer/shi_detector/corpus/corpus-0169 | Bin 0 -> 24 bytes fuzzer/shi_detector/corpus/corpus-0170 | Bin 0 -> 24 bytes fuzzer/shi_detector/corpus/corpus-0171 | Bin 0 -> 25 bytes fuzzer/shi_detector/corpus/corpus-0172 | Bin 0 -> 23 bytes fuzzer/shi_detector/corpus/corpus-0173 | Bin 0 -> 23 bytes fuzzer/shi_detector/corpus/corpus-0174 | Bin 0 -> 23 bytes fuzzer/shi_detector/corpus/corpus-0175 | Bin 0 -> 23 bytes fuzzer/shi_detector/corpus/corpus-0176 | Bin 0 -> 25 bytes fuzzer/shi_detector/corpus/corpus-0177 | Bin 0 -> 24 bytes fuzzer/shi_detector/corpus/corpus-0178 | Bin 0 -> 24 bytes fuzzer/shi_detector/corpus/corpus-0179 | Bin 0 -> 58 bytes fuzzer/shi_detector/corpus/corpus-0180 | Bin 0 -> 35 bytes fuzzer/shi_detector/corpus/corpus-0181 | Bin 0 -> 20 bytes fuzzer/shi_detector/corpus/corpus-0182 | Bin 0 -> 30 bytes fuzzer/shi_detector/corpus/corpus-0183 | Bin 0 -> 53 bytes fuzzer/shi_detector/corpus/corpus-0184 | Bin 0 -> 55 bytes fuzzer/shi_detector/corpus/corpus-0185 | Bin 0 -> 39 bytes fuzzer/shi_detector/corpus/corpus-0186 | Bin 0 -> 35 bytes fuzzer/shi_detector/corpus/corpus-0187 | Bin 0 -> 30 bytes fuzzer/shi_detector/corpus/corpus-0188 | Bin 0 -> 30 bytes fuzzer/shi_detector/corpus/corpus-0189 | Bin 0 -> 43 bytes fuzzer/shi_detector/corpus/corpus-0190 | Bin 0 -> 41 bytes fuzzer/shi_detector/corpus/corpus-0191 | Bin 0 -> 41 bytes fuzzer/shi_detector/src/main.cpp | 131 ++++ fuzzer/sql_tokenizer/src/main.cpp | 3 + src/condition/shi_detector.cpp | 115 +++ src/condition/shi_detector.hpp | 29 + src/condition/ssrf_detector.cpp | 2 +- src/parser/expression_parser.cpp | 5 + src/tokenizer/base.hpp | 100 +++ src/tokenizer/shell.cpp | 643 +++++++++++++++ src/tokenizer/shell.hpp | 195 +++++ src/tokenizer/sql_base.cpp | 2 +- src/tokenizer/sql_base.hpp | 67 +- tests/shi_detector_test.cpp | 297 +++++++ tests/ssrf_detector_test.cpp | 1 - tests/tokenizer/shell_tokenizer_test.cpp | 733 ++++++++++++++++++ .../structured/013_shi_basic_run_match.yaml | 28 + validator/tests/rules/structured/ruleset.yaml | 18 + 403 files changed, 2523 insertions(+), 68 deletions(-) create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0000 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0001 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0002 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0003 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0004 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0005 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0006 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0007 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0008 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0009 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0010 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0011 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0012 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0013 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0014 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0015 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0016 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0017 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0018 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0019 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0020 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0021 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0022 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0023 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0024 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0025 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0026 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0027 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0028 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0029 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0030 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0031 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0032 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0033 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0034 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0035 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0036 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0037 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0038 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0039 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0040 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0041 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0042 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0043 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0044 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0045 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0046 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0047 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0048 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0049 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0050 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0051 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0052 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0053 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0054 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0055 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0056 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0057 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0058 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0059 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0060 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0061 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0062 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0063 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0064 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0065 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0066 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0067 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0068 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0069 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0070 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0071 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0072 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0073 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0074 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0075 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0076 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0077 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0078 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0079 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0080 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0081 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0082 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0083 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0084 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0085 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0086 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0087 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0088 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0089 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0090 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0091 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0092 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0093 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0094 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0095 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0096 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0097 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0098 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0099 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0100 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0101 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0102 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0103 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0104 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0105 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0106 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0107 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0108 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0109 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0110 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0111 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0112 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0113 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0114 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0115 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0116 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0117 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0118 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0119 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0120 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0121 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0122 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0123 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0124 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0125 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0126 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0127 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0128 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0129 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0130 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0131 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0132 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0133 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0134 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0135 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0136 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0137 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0138 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0139 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0140 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0141 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0142 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0143 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0144 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0145 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0146 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0147 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0148 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0149 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0150 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0151 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0152 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0153 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0154 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0155 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0156 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0157 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0158 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0159 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0160 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0161 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0162 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0163 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0164 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0165 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0166 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0167 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0168 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0169 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0170 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0171 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0172 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0173 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0174 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0175 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0176 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0177 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0178 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0179 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0180 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0181 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0182 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0183 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0184 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0185 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0186 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0187 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0188 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0189 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0190 create mode 100644 fuzzer/shell_tokenizer/corpus/corpus-0191 create mode 100644 fuzzer/shell_tokenizer/src/main.cpp create mode 100644 fuzzer/shi_detector/corpus/corpus-0000 create mode 100644 fuzzer/shi_detector/corpus/corpus-0001 create mode 100644 fuzzer/shi_detector/corpus/corpus-0002 create mode 100644 fuzzer/shi_detector/corpus/corpus-0003 create mode 100644 fuzzer/shi_detector/corpus/corpus-0004 create mode 100644 fuzzer/shi_detector/corpus/corpus-0005 create mode 100644 fuzzer/shi_detector/corpus/corpus-0006 create mode 100644 fuzzer/shi_detector/corpus/corpus-0007 create mode 100644 fuzzer/shi_detector/corpus/corpus-0008 create mode 100644 fuzzer/shi_detector/corpus/corpus-0009 create mode 100644 fuzzer/shi_detector/corpus/corpus-0010 create mode 100644 fuzzer/shi_detector/corpus/corpus-0011 create mode 100644 fuzzer/shi_detector/corpus/corpus-0012 create mode 100644 fuzzer/shi_detector/corpus/corpus-0013 create mode 100644 fuzzer/shi_detector/corpus/corpus-0014 create mode 100644 fuzzer/shi_detector/corpus/corpus-0015 create mode 100644 fuzzer/shi_detector/corpus/corpus-0016 create mode 100644 fuzzer/shi_detector/corpus/corpus-0017 create mode 100644 fuzzer/shi_detector/corpus/corpus-0018 create mode 100644 fuzzer/shi_detector/corpus/corpus-0019 create mode 100644 fuzzer/shi_detector/corpus/corpus-0020 create mode 100644 fuzzer/shi_detector/corpus/corpus-0021 create mode 100644 fuzzer/shi_detector/corpus/corpus-0022 create mode 100644 fuzzer/shi_detector/corpus/corpus-0023 create mode 100644 fuzzer/shi_detector/corpus/corpus-0024 create mode 100644 fuzzer/shi_detector/corpus/corpus-0025 create mode 100644 fuzzer/shi_detector/corpus/corpus-0026 create mode 100644 fuzzer/shi_detector/corpus/corpus-0027 create mode 100644 fuzzer/shi_detector/corpus/corpus-0028 create mode 100644 fuzzer/shi_detector/corpus/corpus-0029 create mode 100644 fuzzer/shi_detector/corpus/corpus-0030 create mode 100644 fuzzer/shi_detector/corpus/corpus-0031 create mode 100644 fuzzer/shi_detector/corpus/corpus-0032 create mode 100644 fuzzer/shi_detector/corpus/corpus-0033 create mode 100644 fuzzer/shi_detector/corpus/corpus-0034 create mode 100644 fuzzer/shi_detector/corpus/corpus-0035 create mode 100644 fuzzer/shi_detector/corpus/corpus-0036 create mode 100644 fuzzer/shi_detector/corpus/corpus-0037 create mode 100644 fuzzer/shi_detector/corpus/corpus-0038 create mode 100644 fuzzer/shi_detector/corpus/corpus-0039 create mode 100644 fuzzer/shi_detector/corpus/corpus-0040 create mode 100644 fuzzer/shi_detector/corpus/corpus-0041 create mode 100644 fuzzer/shi_detector/corpus/corpus-0042 create mode 100644 fuzzer/shi_detector/corpus/corpus-0043 create mode 100644 fuzzer/shi_detector/corpus/corpus-0044 create mode 100644 fuzzer/shi_detector/corpus/corpus-0045 create mode 100644 fuzzer/shi_detector/corpus/corpus-0046 create mode 100644 fuzzer/shi_detector/corpus/corpus-0047 create mode 100644 fuzzer/shi_detector/corpus/corpus-0048 create mode 100644 fuzzer/shi_detector/corpus/corpus-0049 create mode 100644 fuzzer/shi_detector/corpus/corpus-0050 create mode 100644 fuzzer/shi_detector/corpus/corpus-0051 create mode 100644 fuzzer/shi_detector/corpus/corpus-0052 create mode 100644 fuzzer/shi_detector/corpus/corpus-0053 create mode 100644 fuzzer/shi_detector/corpus/corpus-0054 create mode 100644 fuzzer/shi_detector/corpus/corpus-0055 create mode 100644 fuzzer/shi_detector/corpus/corpus-0056 create mode 100644 fuzzer/shi_detector/corpus/corpus-0057 create mode 100644 fuzzer/shi_detector/corpus/corpus-0058 create mode 100644 fuzzer/shi_detector/corpus/corpus-0059 create mode 100644 fuzzer/shi_detector/corpus/corpus-0060 create mode 100644 fuzzer/shi_detector/corpus/corpus-0061 create mode 100644 fuzzer/shi_detector/corpus/corpus-0062 create mode 100644 fuzzer/shi_detector/corpus/corpus-0063 create mode 100644 fuzzer/shi_detector/corpus/corpus-0064 create mode 100644 fuzzer/shi_detector/corpus/corpus-0065 create mode 100644 fuzzer/shi_detector/corpus/corpus-0066 create mode 100644 fuzzer/shi_detector/corpus/corpus-0067 create mode 100644 fuzzer/shi_detector/corpus/corpus-0068 create mode 100644 fuzzer/shi_detector/corpus/corpus-0069 create mode 100644 fuzzer/shi_detector/corpus/corpus-0070 create mode 100644 fuzzer/shi_detector/corpus/corpus-0071 create mode 100644 fuzzer/shi_detector/corpus/corpus-0072 create mode 100644 fuzzer/shi_detector/corpus/corpus-0073 create mode 100644 fuzzer/shi_detector/corpus/corpus-0074 create mode 100644 fuzzer/shi_detector/corpus/corpus-0075 create mode 100644 fuzzer/shi_detector/corpus/corpus-0076 create mode 100644 fuzzer/shi_detector/corpus/corpus-0077 create mode 100644 fuzzer/shi_detector/corpus/corpus-0078 create mode 100644 fuzzer/shi_detector/corpus/corpus-0079 create mode 100644 fuzzer/shi_detector/corpus/corpus-0080 create mode 100644 fuzzer/shi_detector/corpus/corpus-0081 create mode 100644 fuzzer/shi_detector/corpus/corpus-0082 create mode 100644 fuzzer/shi_detector/corpus/corpus-0083 create mode 100644 fuzzer/shi_detector/corpus/corpus-0084 create mode 100644 fuzzer/shi_detector/corpus/corpus-0085 create mode 100644 fuzzer/shi_detector/corpus/corpus-0086 create mode 100644 fuzzer/shi_detector/corpus/corpus-0087 create mode 100644 fuzzer/shi_detector/corpus/corpus-0088 create mode 100644 fuzzer/shi_detector/corpus/corpus-0089 create mode 100644 fuzzer/shi_detector/corpus/corpus-0090 create mode 100644 fuzzer/shi_detector/corpus/corpus-0091 create mode 100644 fuzzer/shi_detector/corpus/corpus-0092 create mode 100644 fuzzer/shi_detector/corpus/corpus-0093 create mode 100644 fuzzer/shi_detector/corpus/corpus-0094 create mode 100644 fuzzer/shi_detector/corpus/corpus-0095 create mode 100644 fuzzer/shi_detector/corpus/corpus-0096 create mode 100644 fuzzer/shi_detector/corpus/corpus-0097 create mode 100644 fuzzer/shi_detector/corpus/corpus-0098 create mode 100644 fuzzer/shi_detector/corpus/corpus-0099 create mode 100644 fuzzer/shi_detector/corpus/corpus-0100 create mode 100644 fuzzer/shi_detector/corpus/corpus-0101 create mode 100644 fuzzer/shi_detector/corpus/corpus-0102 create mode 100644 fuzzer/shi_detector/corpus/corpus-0103 create mode 100644 fuzzer/shi_detector/corpus/corpus-0104 create mode 100644 fuzzer/shi_detector/corpus/corpus-0105 create mode 100644 fuzzer/shi_detector/corpus/corpus-0106 create mode 100644 fuzzer/shi_detector/corpus/corpus-0107 create mode 100644 fuzzer/shi_detector/corpus/corpus-0108 create mode 100644 fuzzer/shi_detector/corpus/corpus-0109 create mode 100644 fuzzer/shi_detector/corpus/corpus-0110 create mode 100644 fuzzer/shi_detector/corpus/corpus-0111 create mode 100644 fuzzer/shi_detector/corpus/corpus-0112 create mode 100644 fuzzer/shi_detector/corpus/corpus-0113 create mode 100644 fuzzer/shi_detector/corpus/corpus-0114 create mode 100644 fuzzer/shi_detector/corpus/corpus-0115 create mode 100644 fuzzer/shi_detector/corpus/corpus-0116 create mode 100644 fuzzer/shi_detector/corpus/corpus-0117 create mode 100644 fuzzer/shi_detector/corpus/corpus-0118 create mode 100644 fuzzer/shi_detector/corpus/corpus-0119 create mode 100644 fuzzer/shi_detector/corpus/corpus-0120 create mode 100644 fuzzer/shi_detector/corpus/corpus-0121 create mode 100644 fuzzer/shi_detector/corpus/corpus-0122 create mode 100644 fuzzer/shi_detector/corpus/corpus-0123 create mode 100644 fuzzer/shi_detector/corpus/corpus-0124 create mode 100644 fuzzer/shi_detector/corpus/corpus-0125 create mode 100644 fuzzer/shi_detector/corpus/corpus-0126 create mode 100644 fuzzer/shi_detector/corpus/corpus-0127 create mode 100644 fuzzer/shi_detector/corpus/corpus-0128 create mode 100644 fuzzer/shi_detector/corpus/corpus-0129 create mode 100644 fuzzer/shi_detector/corpus/corpus-0130 create mode 100644 fuzzer/shi_detector/corpus/corpus-0131 create mode 100644 fuzzer/shi_detector/corpus/corpus-0132 create mode 100644 fuzzer/shi_detector/corpus/corpus-0133 create mode 100644 fuzzer/shi_detector/corpus/corpus-0134 create mode 100644 fuzzer/shi_detector/corpus/corpus-0135 create mode 100644 fuzzer/shi_detector/corpus/corpus-0136 create mode 100644 fuzzer/shi_detector/corpus/corpus-0137 create mode 100644 fuzzer/shi_detector/corpus/corpus-0138 create mode 100644 fuzzer/shi_detector/corpus/corpus-0139 create mode 100644 fuzzer/shi_detector/corpus/corpus-0140 create mode 100644 fuzzer/shi_detector/corpus/corpus-0141 create mode 100644 fuzzer/shi_detector/corpus/corpus-0142 create mode 100644 fuzzer/shi_detector/corpus/corpus-0143 create mode 100644 fuzzer/shi_detector/corpus/corpus-0144 create mode 100644 fuzzer/shi_detector/corpus/corpus-0145 create mode 100644 fuzzer/shi_detector/corpus/corpus-0146 create mode 100644 fuzzer/shi_detector/corpus/corpus-0147 create mode 100644 fuzzer/shi_detector/corpus/corpus-0148 create mode 100644 fuzzer/shi_detector/corpus/corpus-0149 create mode 100644 fuzzer/shi_detector/corpus/corpus-0150 create mode 100644 fuzzer/shi_detector/corpus/corpus-0151 create mode 100644 fuzzer/shi_detector/corpus/corpus-0152 create mode 100644 fuzzer/shi_detector/corpus/corpus-0153 create mode 100644 fuzzer/shi_detector/corpus/corpus-0154 create mode 100644 fuzzer/shi_detector/corpus/corpus-0155 create mode 100644 fuzzer/shi_detector/corpus/corpus-0156 create mode 100644 fuzzer/shi_detector/corpus/corpus-0157 create mode 100644 fuzzer/shi_detector/corpus/corpus-0158 create mode 100644 fuzzer/shi_detector/corpus/corpus-0159 create mode 100644 fuzzer/shi_detector/corpus/corpus-0160 create mode 100644 fuzzer/shi_detector/corpus/corpus-0161 create mode 100644 fuzzer/shi_detector/corpus/corpus-0162 create mode 100644 fuzzer/shi_detector/corpus/corpus-0163 create mode 100644 fuzzer/shi_detector/corpus/corpus-0164 create mode 100644 fuzzer/shi_detector/corpus/corpus-0165 create mode 100644 fuzzer/shi_detector/corpus/corpus-0166 create mode 100644 fuzzer/shi_detector/corpus/corpus-0167 create mode 100644 fuzzer/shi_detector/corpus/corpus-0168 create mode 100644 fuzzer/shi_detector/corpus/corpus-0169 create mode 100644 fuzzer/shi_detector/corpus/corpus-0170 create mode 100644 fuzzer/shi_detector/corpus/corpus-0171 create mode 100644 fuzzer/shi_detector/corpus/corpus-0172 create mode 100644 fuzzer/shi_detector/corpus/corpus-0173 create mode 100644 fuzzer/shi_detector/corpus/corpus-0174 create mode 100644 fuzzer/shi_detector/corpus/corpus-0175 create mode 100644 fuzzer/shi_detector/corpus/corpus-0176 create mode 100644 fuzzer/shi_detector/corpus/corpus-0177 create mode 100644 fuzzer/shi_detector/corpus/corpus-0178 create mode 100644 fuzzer/shi_detector/corpus/corpus-0179 create mode 100644 fuzzer/shi_detector/corpus/corpus-0180 create mode 100644 fuzzer/shi_detector/corpus/corpus-0181 create mode 100644 fuzzer/shi_detector/corpus/corpus-0182 create mode 100644 fuzzer/shi_detector/corpus/corpus-0183 create mode 100644 fuzzer/shi_detector/corpus/corpus-0184 create mode 100644 fuzzer/shi_detector/corpus/corpus-0185 create mode 100644 fuzzer/shi_detector/corpus/corpus-0186 create mode 100644 fuzzer/shi_detector/corpus/corpus-0187 create mode 100644 fuzzer/shi_detector/corpus/corpus-0188 create mode 100644 fuzzer/shi_detector/corpus/corpus-0189 create mode 100644 fuzzer/shi_detector/corpus/corpus-0190 create mode 100644 fuzzer/shi_detector/corpus/corpus-0191 create mode 100644 fuzzer/shi_detector/src/main.cpp create mode 100644 src/condition/shi_detector.cpp create mode 100644 src/condition/shi_detector.hpp create mode 100644 src/tokenizer/base.hpp create mode 100644 src/tokenizer/shell.cpp create mode 100644 src/tokenizer/shell.hpp create mode 100644 tests/shi_detector_test.cpp create mode 100644 tests/tokenizer/shell_tokenizer_test.cpp create mode 100644 validator/tests/rules/structured/013_shi_basic_run_match.yaml diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 2263c7523..112c21ac4 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -86,9 +86,12 @@ jobs: params: "--dialect=sqlite" - fuzzer: sqli_detector params: "--dialect=standard" + - fuzzer: shell_tokenizer + params: "" + - fuzzer: shi_detector + params: "" - fuzzer: sha256 params: "" - steps: - uses: actions/checkout@v4 with: diff --git a/cmake/objects.cmake b/cmake/objects.cmake index c6a3b452e..b793404d7 100644 --- a/cmake/objects.cmake +++ b/cmake/objects.cmake @@ -28,6 +28,7 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/tokenizer/mysql.cpp ${libddwaf_SOURCE_DIR}/src/tokenizer/sqlite.cpp ${libddwaf_SOURCE_DIR}/src/tokenizer/generic_sql.cpp + ${libddwaf_SOURCE_DIR}/src/tokenizer/shell.cpp ${libddwaf_SOURCE_DIR}/src/exclusion/input_filter.cpp ${libddwaf_SOURCE_DIR}/src/exclusion/object_filter.cpp ${libddwaf_SOURCE_DIR}/src/exclusion/rule_filter.cpp @@ -49,6 +50,7 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/condition/sqli_detector.cpp ${libddwaf_SOURCE_DIR}/src/condition/ssrf_detector.cpp ${libddwaf_SOURCE_DIR}/src/condition/scalar_condition.cpp + ${libddwaf_SOURCE_DIR}/src/condition/shi_detector.cpp ${libddwaf_SOURCE_DIR}/src/matcher/phrase_match.cpp ${libddwaf_SOURCE_DIR}/src/matcher/regex_match.cpp ${libddwaf_SOURCE_DIR}/src/matcher/is_sqli.cpp diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0000 b/fuzzer/shell_tokenizer/corpus/corpus-0000 new file mode 100644 index 000000000..41aebb123 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0000 @@ -0,0 +1 @@ +du -hs diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0001 b/fuzzer/shell_tokenizer/corpus/corpus-0001 new file mode 100644 index 000000000..8293df6f4 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0001 @@ -0,0 +1 @@ +wc -l file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0002 b/fuzzer/shell_tokenizer/corpus/corpus-0002 new file mode 100644 index 000000000..82f4e89c8 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0002 @@ -0,0 +1 @@ +head -n 10 /etc/passwd diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0003 b/fuzzer/shell_tokenizer/corpus/corpus-0003 new file mode 100644 index 000000000..bf32ae5db --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0003 @@ -0,0 +1 @@ +hostname -I | awk '{print $1}' diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0004 b/fuzzer/shell_tokenizer/corpus/corpus-0004 new file mode 100644 index 000000000..151173139 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0004 @@ -0,0 +1 @@ +$(echo ls -l) diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0005 b/fuzzer/shell_tokenizer/corpus/corpus-0005 new file mode 100644 index 000000000..d1af45dda --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0005 @@ -0,0 +1 @@ +{ ( ls -l ) } diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0006 b/fuzzer/shell_tokenizer/corpus/corpus-0006 new file mode 100644 index 000000000..eca6c10df --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0006 @@ -0,0 +1 @@ +cat hello; ls /etc/passwd \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0007 b/fuzzer/shell_tokenizer/corpus/corpus-0007 new file mode 100644 index 000000000..c9e21ff15 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0007 @@ -0,0 +1 @@ +cat "hello"; cat /etc/passwd; echo "" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0008 b/fuzzer/shell_tokenizer/corpus/corpus-0008 new file mode 100644 index 000000000..c0299de5a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0008 @@ -0,0 +1 @@ +ping -c 1 google.com; ls \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0009 b/fuzzer/shell_tokenizer/corpus/corpus-0009 new file mode 100644 index 000000000..9fd76d5c9 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0009 @@ -0,0 +1 @@ +cat "hello\"; cat /etc/passwd; echo " \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0010 b/fuzzer/shell_tokenizer/corpus/corpus-0010 new file mode 100644 index 000000000..85e29bbc5 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0010 @@ -0,0 +1 @@ +ls public/1.json 2> /tmp/toto \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0011 b/fuzzer/shell_tokenizer/corpus/corpus-0011 new file mode 100644 index 000000000..2b2e96c13 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0011 @@ -0,0 +1 @@ +ls public/1.json > /tmp/toto \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0012 b/fuzzer/shell_tokenizer/corpus/corpus-0012 new file mode 100644 index 000000000..0f6bf6447 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0012 @@ -0,0 +1 @@ +ping -c 1 google.com; ${a:-ls} \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0013 b/fuzzer/shell_tokenizer/corpus/corpus-0013 new file mode 100644 index 000000000..82af76cc3 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0013 @@ -0,0 +1 @@ +ping -c 1 google.com; TOTO=ls ${a:-$TOTO} \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0014 b/fuzzer/shell_tokenizer/corpus/corpus-0014 new file mode 100644 index 000000000..eaefc13cc --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0014 @@ -0,0 +1 @@ +ping -c 1 google.com; TOTO=ls $TOTO \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0015 b/fuzzer/shell_tokenizer/corpus/corpus-0015 new file mode 100644 index 000000000..0fce4b7b7 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0015 @@ -0,0 +1 @@ +cat hello> cat /etc/passwd; echo "" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0016 b/fuzzer/shell_tokenizer/corpus/corpus-0016 new file mode 100644 index 000000000..0fce4b7b7 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0016 @@ -0,0 +1 @@ +cat hello> cat /etc/passwd; echo "" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0017 b/fuzzer/shell_tokenizer/corpus/corpus-0017 new file mode 100644 index 000000000..0fce4b7b7 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0017 @@ -0,0 +1 @@ +cat hello> cat /etc/passwd; echo "" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0018 b/fuzzer/shell_tokenizer/corpus/corpus-0018 new file mode 100644 index 000000000..12c66fed7 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0018 @@ -0,0 +1 @@ +diff <(file) <(rm -rf /etc/systemd/) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0019 b/fuzzer/shell_tokenizer/corpus/corpus-0019 new file mode 100644 index 000000000..118a9b56b --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0019 @@ -0,0 +1 @@ +echo >(ls -l) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0020 b/fuzzer/shell_tokenizer/corpus/corpus-0020 new file mode 100644 index 000000000..12c66fed7 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0020 @@ -0,0 +1 @@ +diff <(file) <(rm -rf /etc/systemd/) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0021 b/fuzzer/shell_tokenizer/corpus/corpus-0021 new file mode 100644 index 000000000..ec1ec0234 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0021 @@ -0,0 +1 @@ +echo "$(cat /etc/passwd)" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0022 b/fuzzer/shell_tokenizer/corpus/corpus-0022 new file mode 100644 index 000000000..937587064 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0022 @@ -0,0 +1 @@ +$(cat /etc/passwd) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0023 b/fuzzer/shell_tokenizer/corpus/corpus-0023 new file mode 100644 index 000000000..c5c754f60 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0023 @@ -0,0 +1 @@ +$(echo $(echo $(echo ls))) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0024 b/fuzzer/shell_tokenizer/corpus/corpus-0024 new file mode 100644 index 000000000..c5c754f60 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0024 @@ -0,0 +1 @@ +$(echo $(echo $(echo ls))) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0025 b/fuzzer/shell_tokenizer/corpus/corpus-0025 new file mode 100644 index 000000000..6e4f07fa6 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0025 @@ -0,0 +1 @@ +ls -l $(echo /etc/passwd) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0026 b/fuzzer/shell_tokenizer/corpus/corpus-0026 new file mode 100644 index 000000000..f008fe05d --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0026 @@ -0,0 +1 @@ +{ ( $(echo ls) ) } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0027 b/fuzzer/shell_tokenizer/corpus/corpus-0027 new file mode 100644 index 000000000..f008fe05d --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0027 @@ -0,0 +1 @@ +{ ( $(echo ls) ) } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0028 b/fuzzer/shell_tokenizer/corpus/corpus-0028 new file mode 100644 index 000000000..f008fe05d --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0028 @@ -0,0 +1 @@ +{ ( $(echo ls) ) } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0029 b/fuzzer/shell_tokenizer/corpus/corpus-0029 new file mode 100644 index 000000000..f008fe05d --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0029 @@ -0,0 +1 @@ +{ ( $(echo ls) ) } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0030 b/fuzzer/shell_tokenizer/corpus/corpus-0030 new file mode 100644 index 000000000..f8e398123 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0030 @@ -0,0 +1 @@ +ls /sqreensecure/home/zeta/repos/RubyAgentTests/weblog-rails4/public/; echo "testing"; ls robots.txt \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0031 b/fuzzer/shell_tokenizer/corpus/corpus-0031 new file mode 100644 index 000000000..2fc2412d2 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0031 @@ -0,0 +1 @@ +ls; echo hello \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0032 b/fuzzer/shell_tokenizer/corpus/corpus-0032 new file mode 100644 index 000000000..328827a12 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0032 @@ -0,0 +1 @@ +getconf PAGESIZE \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0033 b/fuzzer/shell_tokenizer/corpus/corpus-0033 new file mode 100644 index 000000000..d158cadc3 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0033 @@ -0,0 +1 @@ +cat hello \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0034 b/fuzzer/shell_tokenizer/corpus/corpus-0034 new file mode 100644 index 000000000..03e8b0e71 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0034 @@ -0,0 +1 @@ +file -b --mime '/tmp/ForumEntr-avec kedge20160204-37527-ctbhbi20160204-37527-tuzome.png' \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0035 b/fuzzer/shell_tokenizer/corpus/corpus-0035 new file mode 100644 index 000000000..03e8b0e71 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0035 @@ -0,0 +1 @@ +file -b --mime '/tmp/ForumEntr-avec kedge20160204-37527-ctbhbi20160204-37527-tuzome.png' \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0036 b/fuzzer/shell_tokenizer/corpus/corpus-0036 new file mode 100644 index 000000000..cfd72d1f2 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0036 @@ -0,0 +1 @@ +echo hello \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0037 b/fuzzer/shell_tokenizer/corpus/corpus-0037 new file mode 100644 index 000000000..5dd31d6a3 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0037 @@ -0,0 +1 @@ +phantomjs /vendor/assets/javascripts/highcharts/highcharts-convert.js -infile /app/tmp/highcharts/json/input.json -outfile /app/tmp/highcharts/png/survey_641_chart.png -width 700 2>&1 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0038 b/fuzzer/shell_tokenizer/corpus/corpus-0038 new file mode 100644 index 000000000..6ec283137 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0038 @@ -0,0 +1 @@ +/usr/bin/generate.sh --margin-bottom 20mm --margin-top 27mm --print-media-type --header-html https://url/blabla-bla --footer-html https://url/blabla-bla https://url/blabla-bla - \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0039 b/fuzzer/shell_tokenizer/corpus/corpus-0039 new file mode 100644 index 000000000..e0d9297a3 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0039 @@ -0,0 +1 @@ +ls -l -r -t \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0040 b/fuzzer/shell_tokenizer/corpus/corpus-0040 new file mode 100644 index 000000000..f008fe05d --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0040 @@ -0,0 +1 @@ +{ ( $(echo ls) ) } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0041 b/fuzzer/shell_tokenizer/corpus/corpus-0041 new file mode 100644 index 000000000..dccbe8ead --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0041 @@ -0,0 +1 @@ +echo \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0042 b/fuzzer/shell_tokenizer/corpus/corpus-0042 new file mode 100644 index 000000000..dccbe8ead --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0042 @@ -0,0 +1 @@ +echo \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0043 b/fuzzer/shell_tokenizer/corpus/corpus-0043 new file mode 100644 index 000000000..44e25f2f1 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0043 @@ -0,0 +1 @@ +test echo \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0044 b/fuzzer/shell_tokenizer/corpus/corpus-0044 new file mode 100644 index 000000000..4617dee36 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0044 @@ -0,0 +1 @@ +ls -l \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0045 b/fuzzer/shell_tokenizer/corpus/corpus-0045 new file mode 100644 index 000000000..3959e17dd --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0045 @@ -0,0 +1 @@ +ls dir1 dir2 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0046 b/fuzzer/shell_tokenizer/corpus/corpus-0046 new file mode 100644 index 000000000..a1f7f3c5a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0046 @@ -0,0 +1 @@ +echo "stuff" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0047 b/fuzzer/shell_tokenizer/corpus/corpus-0047 new file mode 100644 index 000000000..c47e16af0 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0047 @@ -0,0 +1 @@ +echo "var=2" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0048 b/fuzzer/shell_tokenizer/corpus/corpus-0048 new file mode 100644 index 000000000..b5d6f94fa --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0048 @@ -0,0 +1 @@ +echo "literal $0" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0049 b/fuzzer/shell_tokenizer/corpus/corpus-0049 new file mode 100644 index 000000000..cacef1066 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0049 @@ -0,0 +1 @@ +echo "$0 literal" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0050 b/fuzzer/shell_tokenizer/corpus/corpus-0050 new file mode 100644 index 000000000..14c8d0215 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0050 @@ -0,0 +1 @@ +echo "literal $0 literal" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0051 b/fuzzer/shell_tokenizer/corpus/corpus-0051 new file mode 100644 index 000000000..2963126f8 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0051 @@ -0,0 +1 @@ +echo "l$0" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0052 b/fuzzer/shell_tokenizer/corpus/corpus-0052 new file mode 100644 index 000000000..3d3e7a18c --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0052 @@ -0,0 +1 @@ +echo "$0l" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0053 b/fuzzer/shell_tokenizer/corpus/corpus-0053 new file mode 100644 index 000000000..856ad78bf --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0053 @@ -0,0 +1 @@ +echo "l$0l" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0054 b/fuzzer/shell_tokenizer/corpus/corpus-0054 new file mode 100644 index 000000000..dd50ccc8e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0054 @@ -0,0 +1 @@ +"stuff" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0055 b/fuzzer/shell_tokenizer/corpus/corpus-0055 new file mode 100644 index 000000000..4c5d06d94 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0055 @@ -0,0 +1 @@ +"var=2" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0056 b/fuzzer/shell_tokenizer/corpus/corpus-0056 new file mode 100644 index 000000000..1a19fd58b --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0056 @@ -0,0 +1 @@ +"$(( 1+1 ))" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0057 b/fuzzer/shell_tokenizer/corpus/corpus-0057 new file mode 100644 index 000000000..f7ea2af87 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0057 @@ -0,0 +1 @@ +"$[ 1+1 ]" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0058 b/fuzzer/shell_tokenizer/corpus/corpus-0058 new file mode 100644 index 000000000..d1de23be9 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0058 @@ -0,0 +1 @@ +echo 'stuff' \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0059 b/fuzzer/shell_tokenizer/corpus/corpus-0059 new file mode 100644 index 000000000..0e9564ef7 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0059 @@ -0,0 +1 @@ +echo 'var=2' \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0060 b/fuzzer/shell_tokenizer/corpus/corpus-0060 new file mode 100644 index 000000000..3bd8a82dc --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0060 @@ -0,0 +1 @@ +echo 'literal $0' \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0061 b/fuzzer/shell_tokenizer/corpus/corpus-0061 new file mode 100644 index 000000000..dc6a3fbce --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0061 @@ -0,0 +1 @@ +'stuff' \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0062 b/fuzzer/shell_tokenizer/corpus/corpus-0062 new file mode 100644 index 000000000..f7c083db6 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0062 @@ -0,0 +1 @@ +'var=2' \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0063 b/fuzzer/shell_tokenizer/corpus/corpus-0063 new file mode 100644 index 000000000..f06c7cd6e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0063 @@ -0,0 +1 @@ +'literal $0' \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0064 b/fuzzer/shell_tokenizer/corpus/corpus-0064 new file mode 100644 index 000000000..575303a7e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0064 @@ -0,0 +1 @@ +ls "$(ls -l)" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0065 b/fuzzer/shell_tokenizer/corpus/corpus-0065 new file mode 100644 index 000000000..3c49e1072 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0065 @@ -0,0 +1 @@ +ls "literal $(ls -l)" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0066 b/fuzzer/shell_tokenizer/corpus/corpus-0066 new file mode 100644 index 000000000..9ae0f0df6 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0066 @@ -0,0 +1 @@ +ls "l$(ls -l)" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0067 b/fuzzer/shell_tokenizer/corpus/corpus-0067 new file mode 100644 index 000000000..4407b817b --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0067 @@ -0,0 +1 @@ +ls "$(ls -l) literal" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0068 b/fuzzer/shell_tokenizer/corpus/corpus-0068 new file mode 100644 index 000000000..41e48d717 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0068 @@ -0,0 +1 @@ +ls "$(ls -l)l" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0069 b/fuzzer/shell_tokenizer/corpus/corpus-0069 new file mode 100644 index 000000000..80f3457e7 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0069 @@ -0,0 +1 @@ +ls "literal $(ls -l) literal" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0070 b/fuzzer/shell_tokenizer/corpus/corpus-0070 new file mode 100644 index 000000000..08d3490c7 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0070 @@ -0,0 +1 @@ +ls "l$(ls -l)l" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0071 b/fuzzer/shell_tokenizer/corpus/corpus-0071 new file mode 100644 index 000000000..685a4e619 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0071 @@ -0,0 +1 @@ +"$(something)" something \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0072 b/fuzzer/shell_tokenizer/corpus/corpus-0072 new file mode 100644 index 000000000..6e1ffac2f --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0072 @@ -0,0 +1 @@ +ls "`ls -l`" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0073 b/fuzzer/shell_tokenizer/corpus/corpus-0073 new file mode 100644 index 000000000..e81c18b75 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0073 @@ -0,0 +1 @@ +ls "literal `ls -l`" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0074 b/fuzzer/shell_tokenizer/corpus/corpus-0074 new file mode 100644 index 000000000..ff02b68c4 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0074 @@ -0,0 +1 @@ +ls "l`ls -l`" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0075 b/fuzzer/shell_tokenizer/corpus/corpus-0075 new file mode 100644 index 000000000..442237711 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0075 @@ -0,0 +1 @@ +ls "`ls -l` literal" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0076 b/fuzzer/shell_tokenizer/corpus/corpus-0076 new file mode 100644 index 000000000..441497a9a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0076 @@ -0,0 +1 @@ +ls "`ls -l`l" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0077 b/fuzzer/shell_tokenizer/corpus/corpus-0077 new file mode 100644 index 000000000..1613e34e8 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0077 @@ -0,0 +1 @@ +ls "literal `ls -l` literal" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0078 b/fuzzer/shell_tokenizer/corpus/corpus-0078 new file mode 100644 index 000000000..2fb727467 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0078 @@ -0,0 +1 @@ +ls "l`ls -l`l" \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0079 b/fuzzer/shell_tokenizer/corpus/corpus-0079 new file mode 100644 index 000000000..94ba0777a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0079 @@ -0,0 +1 @@ +"`something`" something \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0080 b/fuzzer/shell_tokenizer/corpus/corpus-0080 new file mode 100644 index 000000000..dccbe8ead --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0080 @@ -0,0 +1 @@ +echo \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0081 b/fuzzer/shell_tokenizer/corpus/corpus-0081 new file mode 100644 index 000000000..44e25f2f1 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0081 @@ -0,0 +1 @@ +test echo \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0082 b/fuzzer/shell_tokenizer/corpus/corpus-0082 new file mode 100644 index 000000000..c6ddea823 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0082 @@ -0,0 +1 @@ +{ echo; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0083 b/fuzzer/shell_tokenizer/corpus/corpus-0083 new file mode 100644 index 000000000..dc9839211 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0083 @@ -0,0 +1 @@ +(ls -l) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0084 b/fuzzer/shell_tokenizer/corpus/corpus-0084 new file mode 100644 index 000000000..9c8b4bebe --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0084 @@ -0,0 +1 @@ +$(ls -l) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0085 b/fuzzer/shell_tokenizer/corpus/corpus-0085 new file mode 100644 index 000000000..86b3e249e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0085 @@ -0,0 +1 @@ +diff <(ls -l) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0086 b/fuzzer/shell_tokenizer/corpus/corpus-0086 new file mode 100644 index 000000000..73e0ac8c4 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0086 @@ -0,0 +1 @@ +diff >(ls -l) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0087 b/fuzzer/shell_tokenizer/corpus/corpus-0087 new file mode 100644 index 000000000..a89889ce1 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0087 @@ -0,0 +1 @@ +var= echo hello \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0088 b/fuzzer/shell_tokenizer/corpus/corpus-0088 new file mode 100644 index 000000000..67632c69e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0088 @@ -0,0 +1 @@ +ls | cat \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0089 b/fuzzer/shell_tokenizer/corpus/corpus-0089 new file mode 100644 index 000000000..494e26963 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0089 @@ -0,0 +1 @@ +ls -l | cat \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0090 b/fuzzer/shell_tokenizer/corpus/corpus-0090 new file mode 100644 index 000000000..7cf05e6d8 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0090 @@ -0,0 +1 @@ +ls -l | cat | grep passwd \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0091 b/fuzzer/shell_tokenizer/corpus/corpus-0091 new file mode 100644 index 000000000..e7b2c8eed --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0091 @@ -0,0 +1 @@ +ls ; cat \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0092 b/fuzzer/shell_tokenizer/corpus/corpus-0092 new file mode 100644 index 000000000..df57c2586 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0092 @@ -0,0 +1 @@ +ls -l ; cat \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0093 b/fuzzer/shell_tokenizer/corpus/corpus-0093 new file mode 100644 index 000000000..927b1e008 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0093 @@ -0,0 +1 @@ +ls -l ; cat ; grep passwd \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0094 b/fuzzer/shell_tokenizer/corpus/corpus-0094 new file mode 100644 index 000000000..4484bd403 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0094 @@ -0,0 +1 @@ +&>> \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0095 b/fuzzer/shell_tokenizer/corpus/corpus-0095 new file mode 100644 index 000000000..0e806ce6a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0095 @@ -0,0 +1 @@ +1> \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0096 b/fuzzer/shell_tokenizer/corpus/corpus-0096 new file mode 100644 index 000000000..0817502b1 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0096 @@ -0,0 +1 @@ +> \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0097 b/fuzzer/shell_tokenizer/corpus/corpus-0097 new file mode 100644 index 000000000..621625b15 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0097 @@ -0,0 +1 @@ +>> \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0098 b/fuzzer/shell_tokenizer/corpus/corpus-0098 new file mode 100644 index 000000000..0e0ceadae --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0098 @@ -0,0 +1 @@ +>| \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0099 b/fuzzer/shell_tokenizer/corpus/corpus-0099 new file mode 100644 index 000000000..458c8ea82 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0099 @@ -0,0 +1 @@ +>& \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0100 b/fuzzer/shell_tokenizer/corpus/corpus-0100 new file mode 100644 index 000000000..c6c1c6c99 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0100 @@ -0,0 +1 @@ +1>& \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0101 b/fuzzer/shell_tokenizer/corpus/corpus-0101 new file mode 100644 index 000000000..905a20dc0 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0101 @@ -0,0 +1 @@ +1>&2 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0102 b/fuzzer/shell_tokenizer/corpus/corpus-0102 new file mode 100644 index 000000000..90d8bde95 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0102 @@ -0,0 +1 @@ +1>&2- \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0103 b/fuzzer/shell_tokenizer/corpus/corpus-0103 new file mode 100644 index 000000000..c3a702593 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0103 @@ -0,0 +1 @@ +1>&- \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0104 b/fuzzer/shell_tokenizer/corpus/corpus-0104 new file mode 100644 index 000000000..c5fa78456 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0104 @@ -0,0 +1 @@ +< \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0105 b/fuzzer/shell_tokenizer/corpus/corpus-0105 new file mode 100644 index 000000000..6293b3c63 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0105 @@ -0,0 +1 @@ +1< \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0106 b/fuzzer/shell_tokenizer/corpus/corpus-0106 new file mode 100644 index 000000000..652843780 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0106 @@ -0,0 +1 @@ +2<& \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0107 b/fuzzer/shell_tokenizer/corpus/corpus-0107 new file mode 100644 index 000000000..204475535 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0107 @@ -0,0 +1 @@ +2<< \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0108 b/fuzzer/shell_tokenizer/corpus/corpus-0108 new file mode 100644 index 000000000..48399854f --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0108 @@ -0,0 +1 @@ +2<<- \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0109 b/fuzzer/shell_tokenizer/corpus/corpus-0109 new file mode 100644 index 000000000..aa359d83e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0109 @@ -0,0 +1 @@ +<<- \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0110 b/fuzzer/shell_tokenizer/corpus/corpus-0110 new file mode 100644 index 000000000..7bb7340e4 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0110 @@ -0,0 +1 @@ +<< \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0111 b/fuzzer/shell_tokenizer/corpus/corpus-0111 new file mode 100644 index 000000000..9c3113a1d --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0111 @@ -0,0 +1 @@ +<<< \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0112 b/fuzzer/shell_tokenizer/corpus/corpus-0112 new file mode 100644 index 000000000..e0cd03efc --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0112 @@ -0,0 +1 @@ +1<<< \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0113 b/fuzzer/shell_tokenizer/corpus/corpus-0113 new file mode 100644 index 000000000..50071c3fb --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0113 @@ -0,0 +1 @@ +1<& \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0114 b/fuzzer/shell_tokenizer/corpus/corpus-0114 new file mode 100644 index 000000000..6293b3c63 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0114 @@ -0,0 +1 @@ +1< \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0115 b/fuzzer/shell_tokenizer/corpus/corpus-0115 new file mode 100644 index 000000000..bca5a574e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0115 @@ -0,0 +1 @@ +<& \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0116 b/fuzzer/shell_tokenizer/corpus/corpus-0116 new file mode 100644 index 000000000..4853a5168 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0116 @@ -0,0 +1 @@ +1<> \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0117 b/fuzzer/shell_tokenizer/corpus/corpus-0117 new file mode 100644 index 000000000..6787e487a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0117 @@ -0,0 +1 @@ +<> \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0118 b/fuzzer/shell_tokenizer/corpus/corpus-0118 new file mode 100644 index 000000000..89776f435 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0118 @@ -0,0 +1 @@ +ls > /tmp/test args \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0119 b/fuzzer/shell_tokenizer/corpus/corpus-0119 new file mode 100644 index 000000000..b22c8a78a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0119 @@ -0,0 +1 @@ +ls args > /tmp/test \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0120 b/fuzzer/shell_tokenizer/corpus/corpus-0120 new file mode 100644 index 000000000..b9377bb15 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0120 @@ -0,0 +1 @@ +ls > /tmp/test args \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0121 b/fuzzer/shell_tokenizer/corpus/corpus-0121 new file mode 100644 index 000000000..a8ea00cac --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0121 @@ -0,0 +1 @@ +ls args 2> /tmp/test \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0122 b/fuzzer/shell_tokenizer/corpus/corpus-0122 new file mode 100644 index 000000000..03cb4d42e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0122 @@ -0,0 +1 @@ +ls args >> /tmp/test \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0123 b/fuzzer/shell_tokenizer/corpus/corpus-0123 new file mode 100644 index 000000000..90ebe977b --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0123 @@ -0,0 +1 @@ +ls args > /tmp/test 2> /etc/stderr \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0124 b/fuzzer/shell_tokenizer/corpus/corpus-0124 new file mode 100644 index 000000000..cbdb5c405 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0124 @@ -0,0 +1 @@ +ls args>/tmp/test \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0125 b/fuzzer/shell_tokenizer/corpus/corpus-0125 new file mode 100644 index 000000000..df58b1529 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0125 @@ -0,0 +1 @@ +ls args < file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0126 b/fuzzer/shell_tokenizer/corpus/corpus-0126 new file mode 100644 index 000000000..75138ef08 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0126 @@ -0,0 +1 @@ +ls args file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0132 b/fuzzer/shell_tokenizer/corpus/corpus-0132 new file mode 100644 index 000000000..820de948a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0132 @@ -0,0 +1 @@ +ls args <> file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0133 b/fuzzer/shell_tokenizer/corpus/corpus-0133 new file mode 100644 index 000000000..ea3af2631 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0133 @@ -0,0 +1 @@ +ls args >| file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0134 b/fuzzer/shell_tokenizer/corpus/corpus-0134 new file mode 100644 index 000000000..a739ab171 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0134 @@ -0,0 +1 @@ +ls args <&1 file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0135 b/fuzzer/shell_tokenizer/corpus/corpus-0135 new file mode 100644 index 000000000..286f83941 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0135 @@ -0,0 +1 @@ +ls args 0> file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0136 b/fuzzer/shell_tokenizer/corpus/corpus-0136 new file mode 100644 index 000000000..595c03ab3 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0136 @@ -0,0 +1 @@ +ls args 0>> file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0137 b/fuzzer/shell_tokenizer/corpus/corpus-0137 new file mode 100644 index 000000000..0fc07dea9 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0137 @@ -0,0 +1 @@ +ls args 0< file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0138 b/fuzzer/shell_tokenizer/corpus/corpus-0138 new file mode 100644 index 000000000..17f136232 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0138 @@ -0,0 +1 @@ +ls args 0<< file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0139 b/fuzzer/shell_tokenizer/corpus/corpus-0139 new file mode 100644 index 000000000..a6a66ed14 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0139 @@ -0,0 +1 @@ +ls args 0<& file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0140 b/fuzzer/shell_tokenizer/corpus/corpus-0140 new file mode 100644 index 000000000..c0e0536f5 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0140 @@ -0,0 +1 @@ +ls args 0<&- file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0141 b/fuzzer/shell_tokenizer/corpus/corpus-0141 new file mode 100644 index 000000000..4b1c1a3a5 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0141 @@ -0,0 +1 @@ +ls args 0<&1 file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0142 b/fuzzer/shell_tokenizer/corpus/corpus-0142 new file mode 100644 index 000000000..e6fad5b03 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0142 @@ -0,0 +1 @@ +ls args 0>& file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0143 b/fuzzer/shell_tokenizer/corpus/corpus-0143 new file mode 100644 index 000000000..3d918565e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0143 @@ -0,0 +1 @@ +ls args 0>&1 file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0144 b/fuzzer/shell_tokenizer/corpus/corpus-0144 new file mode 100644 index 000000000..c13cdf83e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0144 @@ -0,0 +1 @@ +ls args 0>&- file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0145 b/fuzzer/shell_tokenizer/corpus/corpus-0145 new file mode 100644 index 000000000..86b07f01d --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0145 @@ -0,0 +1 @@ +ls args 0<<- file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0146 b/fuzzer/shell_tokenizer/corpus/corpus-0146 new file mode 100644 index 000000000..01cd1e489 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0146 @@ -0,0 +1 @@ +ls args 0<> file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0147 b/fuzzer/shell_tokenizer/corpus/corpus-0147 new file mode 100644 index 000000000..d37496fde --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0147 @@ -0,0 +1 @@ +ls args 0>| file \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0148 b/fuzzer/shell_tokenizer/corpus/corpus-0148 new file mode 100644 index 000000000..172e8995b --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0148 @@ -0,0 +1 @@ +var=2 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0149 b/fuzzer/shell_tokenizer/corpus/corpus-0149 new file mode 100644 index 000000000..697255df1 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0149 @@ -0,0 +1 @@ +var=2; var=3 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0150 b/fuzzer/shell_tokenizer/corpus/corpus-0150 new file mode 100644 index 000000000..162106c1a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0150 @@ -0,0 +1 @@ +{ var=2; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0151 b/fuzzer/shell_tokenizer/corpus/corpus-0151 new file mode 100644 index 000000000..247f9a5d0 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0151 @@ -0,0 +1 @@ +`var=2` \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0152 b/fuzzer/shell_tokenizer/corpus/corpus-0152 new file mode 100644 index 000000000..0f102866a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0152 @@ -0,0 +1 @@ +(var=2) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0153 b/fuzzer/shell_tokenizer/corpus/corpus-0153 new file mode 100644 index 000000000..11aae89f6 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0153 @@ -0,0 +1 @@ +$(var=2) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0154 b/fuzzer/shell_tokenizer/corpus/corpus-0154 new file mode 100644 index 000000000..49ca697bf --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0154 @@ -0,0 +1 @@ +<(var=2) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0155 b/fuzzer/shell_tokenizer/corpus/corpus-0155 new file mode 100644 index 000000000..7ad1c8f18 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0155 @@ -0,0 +1 @@ +>(var=2) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0156 b/fuzzer/shell_tokenizer/corpus/corpus-0156 new file mode 100644 index 000000000..e7d3024db --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0156 @@ -0,0 +1 @@ +var=(1 2 3) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0157 b/fuzzer/shell_tokenizer/corpus/corpus-0157 new file mode 100644 index 000000000..fede51ac8 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0157 @@ -0,0 +1 @@ +var=$(( 1+1 )) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0158 b/fuzzer/shell_tokenizer/corpus/corpus-0158 new file mode 100644 index 000000000..df60113fa --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0158 @@ -0,0 +1 @@ +var=$[ 1+1 ] \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0159 b/fuzzer/shell_tokenizer/corpus/corpus-0159 new file mode 100644 index 000000000..ad133de8e --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0159 @@ -0,0 +1 @@ +echo ${var} \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0160 b/fuzzer/shell_tokenizer/corpus/corpus-0160 new file mode 100644 index 000000000..ee18fc99c --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0160 @@ -0,0 +1 @@ +echo $var \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0161 b/fuzzer/shell_tokenizer/corpus/corpus-0161 new file mode 100644 index 000000000..67db1b485 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0161 @@ -0,0 +1 @@ +echo ${var[@]} \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0162 b/fuzzer/shell_tokenizer/corpus/corpus-0162 new file mode 100644 index 000000000..4c7ee8f9d --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0162 @@ -0,0 +1 @@ +echo $0 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0163 b/fuzzer/shell_tokenizer/corpus/corpus-0163 new file mode 100644 index 000000000..22559b6db --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0163 @@ -0,0 +1 @@ +echo $1 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0164 b/fuzzer/shell_tokenizer/corpus/corpus-0164 new file mode 100644 index 000000000..66f3a3f8c --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0164 @@ -0,0 +1 @@ +echo $2 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0165 b/fuzzer/shell_tokenizer/corpus/corpus-0165 new file mode 100644 index 000000000..b040bb2a1 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0165 @@ -0,0 +1 @@ +echo $3 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0166 b/fuzzer/shell_tokenizer/corpus/corpus-0166 new file mode 100644 index 000000000..a953a0a22 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0166 @@ -0,0 +1 @@ +echo $4 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0167 b/fuzzer/shell_tokenizer/corpus/corpus-0167 new file mode 100644 index 000000000..24d7c1a87 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0167 @@ -0,0 +1 @@ +echo $5 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0168 b/fuzzer/shell_tokenizer/corpus/corpus-0168 new file mode 100644 index 000000000..35f42fd65 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0168 @@ -0,0 +1 @@ +echo $6 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0169 b/fuzzer/shell_tokenizer/corpus/corpus-0169 new file mode 100644 index 000000000..e6f96f2a6 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0169 @@ -0,0 +1 @@ +echo $7 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0170 b/fuzzer/shell_tokenizer/corpus/corpus-0170 new file mode 100644 index 000000000..2ea87be7a --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0170 @@ -0,0 +1 @@ +echo $8 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0171 b/fuzzer/shell_tokenizer/corpus/corpus-0171 new file mode 100644 index 000000000..31c792064 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0171 @@ -0,0 +1 @@ +echo $9 \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0172 b/fuzzer/shell_tokenizer/corpus/corpus-0172 new file mode 100644 index 000000000..fe8b288cd --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0172 @@ -0,0 +1 @@ +echo $- \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0173 b/fuzzer/shell_tokenizer/corpus/corpus-0173 new file mode 100644 index 000000000..da684a2b9 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0173 @@ -0,0 +1 @@ +echo $# \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0174 b/fuzzer/shell_tokenizer/corpus/corpus-0174 new file mode 100644 index 000000000..ef9907913 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0174 @@ -0,0 +1 @@ +echo $@ \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0175 b/fuzzer/shell_tokenizer/corpus/corpus-0175 new file mode 100644 index 000000000..cc1464a42 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0175 @@ -0,0 +1 @@ +echo $? \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0176 b/fuzzer/shell_tokenizer/corpus/corpus-0176 new file mode 100644 index 000000000..edff3d954 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0176 @@ -0,0 +1 @@ +echo $* \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0177 b/fuzzer/shell_tokenizer/corpus/corpus-0177 new file mode 100644 index 000000000..ec392f6b3 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0177 @@ -0,0 +1 @@ +echo $$ \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0178 b/fuzzer/shell_tokenizer/corpus/corpus-0178 new file mode 100644 index 000000000..19e471c55 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0178 @@ -0,0 +1 @@ +echo $! \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0179 b/fuzzer/shell_tokenizer/corpus/corpus-0179 new file mode 100644 index 000000000..12c8aba9c --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0179 @@ -0,0 +1 @@ +true && echo "hello" | tr h '' | tr e a \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0180 b/fuzzer/shell_tokenizer/corpus/corpus-0180 new file mode 100644 index 000000000..4121ad3ca --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0180 @@ -0,0 +1 @@ +(( var=1+1 )) \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0181 b/fuzzer/shell_tokenizer/corpus/corpus-0181 new file mode 100644 index 000000000..ca0c525fa --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0181 @@ -0,0 +1 @@ +! ls \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0182 b/fuzzer/shell_tokenizer/corpus/corpus-0182 new file mode 100644 index 000000000..c6ddea823 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0182 @@ -0,0 +1 @@ +{ echo; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0183 b/fuzzer/shell_tokenizer/corpus/corpus-0183 new file mode 100644 index 000000000..429cad1c1 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0183 @@ -0,0 +1 @@ +{ ls -l ; echo hello; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0184 b/fuzzer/shell_tokenizer/corpus/corpus-0184 new file mode 100644 index 000000000..93ed30fee --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0184 @@ -0,0 +1 @@ +{ ls -l | grep passwd; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0185 b/fuzzer/shell_tokenizer/corpus/corpus-0185 new file mode 100644 index 000000000..4497eab76 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0185 @@ -0,0 +1 @@ +{ "command"; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0186 b/fuzzer/shell_tokenizer/corpus/corpus-0186 new file mode 100644 index 000000000..085db0604 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0186 @@ -0,0 +1 @@ +{ 'command'; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0187 b/fuzzer/shell_tokenizer/corpus/corpus-0187 new file mode 100644 index 000000000..bc1dd43a1 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0187 @@ -0,0 +1 @@ +{ "ls" -l; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0188 b/fuzzer/shell_tokenizer/corpus/corpus-0188 new file mode 100644 index 000000000..9c2481dd6 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0188 @@ -0,0 +1 @@ +{ 'ls' -l; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0189 b/fuzzer/shell_tokenizer/corpus/corpus-0189 new file mode 100644 index 000000000..33f6bf9b8 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0189 @@ -0,0 +1 @@ +{ var=10 ls -l; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0190 b/fuzzer/shell_tokenizer/corpus/corpus-0190 new file mode 100644 index 000000000..5c4e48d65 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0190 @@ -0,0 +1 @@ +{ var= ls -l; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/corpus/corpus-0191 b/fuzzer/shell_tokenizer/corpus/corpus-0191 new file mode 100644 index 000000000..8004f88d2 --- /dev/null +++ b/fuzzer/shell_tokenizer/corpus/corpus-0191 @@ -0,0 +1 @@ +{ "$(echo ls)" -l; } \ No newline at end of file diff --git a/fuzzer/shell_tokenizer/src/main.cpp b/fuzzer/shell_tokenizer/src/main.cpp new file mode 100644 index 000000000..8a1bf5211 --- /dev/null +++ b/fuzzer/shell_tokenizer/src/main.cpp @@ -0,0 +1,23 @@ +// 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 + +#include "tokenizer/shell.hpp" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) +{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + std::string_view query{reinterpret_cast(bytes), size}; + ddwaf::shell_tokenizer tokenizer(query); + auto tokens = tokenizer.tokenize(); + + // Force the compiler to not optimize away tokens + // NOLINTNEXTLINE(hicpp-no-assembler) + asm volatile("" : "+m"(tokens) : : "memory"); + + return 0; +} diff --git a/fuzzer/shi_detector/corpus/corpus-0000 b/fuzzer/shi_detector/corpus/corpus-0000 new file mode 100644 index 0000000000000000000000000000000000000000..7d6054cafec5c4f5f0a5cbdbe29dfeb4cafedf5a GIT binary patch literal 26 TcmZQ$fPj=z1>KBd79<`3BRK;y literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0001 b/fuzzer/shi_detector/corpus/corpus-0001 new file mode 100644 index 0000000000000000000000000000000000000000..7e5d739e02378027fff2b50365d1d3b5e6527823 GIT binary patch literal 27 Ycmd;LfPnI31>GElw9K4TMyN6~kI}9;hm02>^@-4tW3o literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0004 b/fuzzer/shi_detector/corpus/corpus-0004 new file mode 100644 index 0000000000000000000000000000000000000000..34ba174b10ad60414d8b9787f2d35358f5ae420f GIT binary patch literal 34 ccmd;OfB+SZ)Z~nOg`8pq-5gC;s34dR08=IfZ2$lO literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0005 b/fuzzer/shi_detector/corpus/corpus-0005 new file mode 100644 index 0000000000000000000000000000000000000000..78fdfe338a788e57eaea0b17c8b0868512b64758 GIT binary patch literal 31 ccmd;OfPiWR4TYRy1>GD4O@&$}sGveE06Z!L4gdfE literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0006 b/fuzzer/shi_detector/corpus/corpus-0006 new file mode 100644 index 0000000000000000000000000000000000000000..af793fce7dccc60cd868dcdcb8b97fbd7ccad382 GIT binary patch literal 45 ocmb1SfPm!05`~P^oSb}Xg`8pq{nV0V{er~e;_?(0r~(ih0OlSF(EtDd literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0007 b/fuzzer/shi_detector/corpus/corpus-0007 new file mode 100644 index 0000000000000000000000000000000000000000..2cb9d2dc4f236d3cc4f0b6f26931373ab3392cf1 GIT binary patch literal 57 ycmY#ofPm!05(TA<)SR4rC2Iu`M?bYBS-&8$xVSvUS|K$#BVR#Di3O?{#0CI?_zda* literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0008 b/fuzzer/shi_detector/corpus/corpus-0008 new file mode 100644 index 0000000000000000000000000000000000000000..303683bba38d5d6ca87a4192a4c1f31eaa965834 GIT binary patch literal 58 ncmb1OfPjL`ymSTKWCcTo^!)tvoK(H!{9J2=oMIuUJf8vADQA#abaXIU`>|i4Ce4q6`3}k`8?U literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0010 b/fuzzer/shi_detector/corpus/corpus-0010 new file mode 100644 index 0000000000000000000000000000000000000000..3b4db664be8fb1019145b1b8b99822f0e57894d3 GIT binary patch literal 63 qcmb1TfPkE0g@V$goXli>L%pox{5%CCI|coc+yecQ{E~bjoQeS4)(|rQ literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0011 b/fuzzer/shi_detector/corpus/corpus-0011 new file mode 100644 index 0000000000000000000000000000000000000000..391550c97fc2d6cab60d3a09a75cb17f33ea664a GIT binary patch literal 62 rcmb1PfPkE0g@V$goXli>L%pox{5%Ca1^trT0{xQwl6)bk8cZ1g#~2Uv literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0012 b/fuzzer/shi_detector/corpus/corpus-0012 new file mode 100644 index 0000000000000000000000000000000000000000..1d03b6d40e3c4bd5cb01365e16fe3599ef985625 GIT binary patch literal 48 tcmb1RfPjL`ymSTKWCcTo^!)tvoK(H!{9J1VmFh$*-JIfDCa5ai9031P3XuQ+ literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0013 b/fuzzer/shi_detector/corpus/corpus-0013 new file mode 100644 index 0000000000000000000000000000000000000000..9fb9eacc8614d61f7875c2db5ae51dbc21a6456b GIT binary patch literal 58 zcmdO7fPjL`ymSTKWCcTo^!)tvoK(H!{9J2=5dRQ=+nizrmFh$*T@?_omJzDK9{`Yw B4PF2M literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0014 b/fuzzer/shi_detector/corpus/corpus-0014 new file mode 100644 index 0000000000000000000000000000000000000000..d52c55d1a5076c62d408e132ae05cf08d6915a79 GIT binary patch literal 62 wcmY#pfPjL`ymSTKWCcTo^!)tvoK(H!{9J2=5dRQ=+nizr6%d0PsuW2G0J#1RcmMzZ literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0015 b/fuzzer/shi_detector/corpus/corpus-0015 new file mode 100644 index 0000000000000000000000000000000000000000..93374aa88638cb575323ea09670299d7360bcbb5 GIT binary patch literal 52 vcmY#pfPm!05`~P^oSb|+1rSp|wIo@;AhEc(JjGfeH8~?+K}m@bs#FO8OU(=O literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0016 b/fuzzer/shi_detector/corpus/corpus-0016 new file mode 100644 index 0000000000000000000000000000000000000000..0cc3bfb3296dd3efe697c02ff8d57cbab8409071 GIT binary patch literal 55 wcmY#pfPm!05`~P^oSb|+1rSp|wIo@;AhEc(JjGfeH8~?+K}m@PsuUy+0CIy3M*si- literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0017 b/fuzzer/shi_detector/corpus/corpus-0017 new file mode 100644 index 0000000000000000000000000000000000000000..e1c4d204b34b589fbac477b5f9092b926b9a1768 GIT binary patch literal 67 wcmY#pfPm!05`~P^oSb|+1rSp|wIo@;AhEc(JjGfeH8~?+K}ksfsuW!g04}K!mjD0& literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0018 b/fuzzer/shi_detector/corpus/corpus-0018 new file mode 100644 index 0000000000000000000000000000000000000000..c8f5694adda62e9e028effd494683db07a013f96 GIT binary patch literal 59 ycmY#kfPj?Dv@``9jkL_1R81gVl&hdyl%}AcT9T|^Tv=R_nwz4p$qrQul>`8gVhz~< literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0019 b/fuzzer/shi_detector/corpus/corpus-0019 new file mode 100644 index 0000000000000000000000000000000000000000..6624c6ca6ae1827354ef77a1b655c2373d0444b6 GIT binary patch literal 32 ccmd;OfPmEGjC=(GD46^+#7jC=+C)RJWVg2dwD@)S)Lr~;560Lz03UH||9 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0026 b/fuzzer/shi_detector/corpus/corpus-0026 new file mode 100644 index 0000000000000000000000000000000000000000..3209dbeac3f46e7bbf01a70f670689afac167507 GIT binary patch literal 34 fcmWe)fPiWR4FwgA)Z~nOg`8qd1x literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0027 b/fuzzer/shi_detector/corpus/corpus-0027 new file mode 100644 index 0000000000000000000000000000000000000000..3209dbeac3f46e7bbf01a70f670689afac167507 GIT binary patch literal 34 fcmWe)fPiWR4FwgA)Z~nOg`8qd1x literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0028 b/fuzzer/shi_detector/corpus/corpus-0028 new file mode 100644 index 0000000000000000000000000000000000000000..be73834a9423fb0aeb7cbaeea565b5ad34b9fa8d GIT binary patch literal 42 hcmWe)fPiWR4FwgA)Z~nOg`8qd1xJn2fhFR literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0029 b/fuzzer/shi_detector/corpus/corpus-0029 new file mode 100644 index 0000000000000000000000000000000000000000..243e55d520ecfea0ca40bb03d16ccc830c55919f GIT binary patch literal 36 hcmWe)fPiWR4FwgA)Z~nOg`8qd1x|5CyqHZeYT?RzIfRj2Ylo6bY&&;kg*}@-Y_Z$< Gx_CQn|0@pw literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0031 b/fuzzer/shi_detector/corpus/corpus-0031 new file mode 100644 index 0000000000000000000000000000000000000000..d54b6c6db6de5e200a176a32b6c0fad2c2f6c8cd GIT binary patch literal 38 dcmd;MfPkE0YlYP0jC_TR)SR4r4yY(h1OSC_2pj+a literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0032 b/fuzzer/shi_detector/corpus/corpus-0032 new file mode 100644 index 0000000000000000000000000000000000000000..51b36499f627517cc70af103678b6437ac92f472 GIT binary patch literal 37 fcmWe&fPnPWlH~lnG=%_1ch_LgC|6dfI9L<_fC&eV literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0033 b/fuzzer/shi_detector/corpus/corpus-0033 new file mode 100644 index 0000000000000000000000000000000000000000..195a80d2916bd51b10abafadb37d2bfa193009f5 GIT binary patch literal 30 Ycmd;NfPm!05`~P^oSb}Cr~sG?07J0_NB{r; literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0034 b/fuzzer/shi_detector/corpus/corpus-0034 new file mode 100644 index 0000000000000000000000000000000000000000..42bb9aa59918e698eb9d1ace0587eb70ce375678 GIT binary patch literal 115 zcma!GfPl2joKywfBn4gF+|1lm1$F(B+yZ^K{G!ra*SwM<-NdrgWQFY1l=M_114AQ9YG{KV6s{Gtky@I@Sb#AEnNJawy>B=45 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0035 b/fuzzer/shi_detector/corpus/corpus-0035 new file mode 100644 index 0000000000000000000000000000000000000000..fddf1c6d0cd4902d18e3c8a330bca0d745f3f5b8 GIT binary patch literal 115 zcma!GfPl2joKywfBn4gF+|1lm1$F(B+yZ^K{G!ra*SwM<-NdrgWQFY1l=M_114AQ9YG{KV6s{Gtky@I@Sb#AEnNXh}{fF0-n literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0036 b/fuzzer/shi_detector/corpus/corpus-0036 new file mode 100644 index 0000000000000000000000000000000000000000..8f49e9f7496aa8a0fa2ae170236661cedafa7fc9 GIT binary patch literal 32 Zcmd;LfPmEGjC_TR)SR4rHmC@M3jk9n1{eSU literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0037 b/fuzzer/shi_detector/corpus/corpus-0037 new file mode 100644 index 0000000000000000000000000000000000000000..f05f886b8b73d3a7dbda49a4562065a11b46ef74 GIT binary patch literal 201 zcmZ{eI}XAy5JV9t*^18D1Vy3aDv{-w*b!N;ytX4w#`Q3t0t%)YjoxVHzI%(ErWDpi z0E?w5MARZS2`;*5G0}7N&d<&`9e=Dcfr^c(sa@si;;$A^GhsQ3p3dTYs UFm^~cRsSRH-5x*1zt1+_6U8k>C;$Ke literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0039 b/fuzzer/shi_detector/corpus/corpus-0039 new file mode 100644 index 0000000000000000000000000000000000000000..ca6a71a1cd63c28b6f1003c585008f6e7f75cc57 GIT binary patch literal 32 acmd;PfPkE01>GD4-693u5>}`Xm;(So1q6uz literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0040 b/fuzzer/shi_detector/corpus/corpus-0040 new file mode 100644 index 0000000000000000000000000000000000000000..30e9fdd41d1813f72ddce1a4611c5adc5f9c9566 GIT binary patch literal 44 hcmWe)fPiWR4FwgA)Z~nOg`8qd1x|2J2?XY72*PM literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0043 b/fuzzer/shi_detector/corpus/corpus-0043 new file mode 100644 index 0000000000000000000000000000000000000000..9cce7657e2c2ff88ca4f0aaff29c3addd1857f86 GIT binary patch literal 25 Wcmd;NfPj+J;u3|_GD*7zY3pUjhpN literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0045 b/fuzzer/shi_detector/corpus/corpus-0045 new file mode 100644 index 0000000000000000000000000000000000000000..13b160292db55e3cc29b2322959d405f9770de47 GIT binary patch literal 28 Xcmd;KfPkE0g_O)9Ll9vE6=DDYJU#>s literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0046 b/fuzzer/shi_detector/corpus/corpus-0046 new file mode 100644 index 0000000000000000000000000000000000000000..5e7c2cfed492e7eba363bf58fcc30b915420553a GIT binary patch literal 35 bcmd;KfPmEGjC=*9;*!#|G$nSZ7*qrRWzhz- literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0047 b/fuzzer/shi_detector/corpus/corpus-0047 new file mode 100644 index 0000000000000000000000000000000000000000..7b39dfba0f0f5f6661454f514b3d57b71e1582fc GIT binary patch literal 33 bcmd;KfPmEGjC=*9vcw`=BPCX-7?=kDQqcv4 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0048 b/fuzzer/shi_detector/corpus/corpus-0048 new file mode 100644 index 0000000000000000000000000000000000000000..9a79ad0b77f86742332165e845b80414e61acc16 GIT binary patch literal 34 fcmWe+fPmEGjC=*9oXnEcqQo2p6$2$ks6;XVXoCgX literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0049 b/fuzzer/shi_detector/corpus/corpus-0049 new file mode 100644 index 0000000000000000000000000000000000000000..0e3762984970aacf304d9391f7b0b8ce9bc36574 GIT binary patch literal 42 gcmWe+fPmEGjC=(p6$6Ew%#zfi#2h6~s03UD0G&?=yZ`_I literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0050 b/fuzzer/shi_detector/corpus/corpus-0050 new file mode 100644 index 0000000000000000000000000000000000000000..f6211125d2fe8a47d0dcbb86b2f476b59de4afb5 GIT binary patch literal 46 kcmb1SfPmEGjC=*9oXnEcqQo2p6$1q*Q;8L(04S~m0Oy(tRsaA1 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0051 b/fuzzer/shi_detector/corpus/corpus-0051 new file mode 100644 index 0000000000000000000000000000000000000000..afeaf4a4ec2c119da37ae6284550a5621d1c6f2d GIT binary patch literal 28 Zcmd;LfPmEGjC=*992Em4Ca8#k5&$nI10nzb literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0052 b/fuzzer/shi_detector/corpus/corpus-0052 new file mode 100644 index 0000000000000000000000000000000000000000..4e024ff98d86bb8f6cafa3517a1433c4c5bd34cd GIT binary patch literal 28 Zcmd;LfPmEGjC=(p6@wflCa8!(4gfCf18e{Q literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0053 b/fuzzer/shi_detector/corpus/corpus-0053 new file mode 100644 index 0000000000000000000000000000000000000000..fea8e5759f3a9b0bf4b7d3740df738ccfa414cad GIT binary patch literal 30 acmd;PfPmEGjC=*992J8cC1$7)kPQGj{R930 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0054 b/fuzzer/shi_detector/corpus/corpus-0054 new file mode 100644 index 0000000000000000000000000000000000000000..ea5898aa5823f856abf81e1b42e9d0da50ff4f65 GIT binary patch literal 25 WcmZQ)fB>c9lG3y^B_=30Ee!x99s@!E literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0055 b/fuzzer/shi_detector/corpus/corpus-0055 new file mode 100644 index 0000000000000000000000000000000000000000..a2b0c0a9a78c2c8b9f6d50b4dc29b9afe562fa90 GIT binary patch literal 24 VcmZQ)fB>bk#3EZGB}OPW5da&a0%iaJ literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0056 b/fuzzer/shi_detector/corpus/corpus-0056 new file mode 100644 index 0000000000000000000000000000000000000000..b5d91718ad26ee3e5a5305632e3c1a49d20f822d GIT binary patch literal 32 bcmd;KfB+>G4GjfDZ9@f3O(hnn7>El1C4>T% literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0057 b/fuzzer/shi_detector/corpus/corpus-0057 new file mode 100644 index 0000000000000000000000000000000000000000..bc74c2fa62bc1a9b47a43ed178e0836dc5011423 GIT binary patch literal 32 Zcmd;LfB+?xXaz%ULxor+HmC?h001j611bOj literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0058 b/fuzzer/shi_detector/corpus/corpus-0058 new file mode 100644 index 0000000000000000000000000000000000000000..2adab121e838096b4e9743ee044b3e6f865d55bc GIT binary patch literal 32 bcmd;KfPmEGjC=+4;*!#|G<6oJ7>EM^RG|g? literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0059 b/fuzzer/shi_detector/corpus/corpus-0059 new file mode 100644 index 0000000000000000000000000000000000000000..f634f0a87bd3141dd8ae919b053685fb8ce9f16e GIT binary patch literal 36 bcmd;KfPmEGjC=+4vcw`=BXtg_7)%5JWkd#k literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0060 b/fuzzer/shi_detector/corpus/corpus-0060 new file mode 100644 index 0000000000000000000000000000000000000000..fe051ead6d0953d91710473b7b7a2ba2cb89381c GIT binary patch literal 34 fcmWe+fPmEGjC=+4oXnEcqQo2p6$5ofs6+_>X)gu! literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0061 b/fuzzer/shi_detector/corpus/corpus-0061 new file mode 100644 index 0000000000000000000000000000000000000000..48e82b6077a3fadd4660d8d1eacc3eeae8f06d51 GIT binary patch literal 29 UcmZQ)fB^O4lG3y^bv6_(05v)U(*OVf literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0062 b/fuzzer/shi_detector/corpus/corpus-0062 new file mode 100644 index 0000000000000000000000000000000000000000..69551feeb18f04a7e676da1be2619a79d8cd580d GIT binary patch literal 23 UcmZQ)fB^Nf#3EZGbtsPk02!?AgP<9slIiI4*T)z%6h literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0066 b/fuzzer/shi_detector/corpus/corpus-0066 new file mode 100644 index 0000000000000000000000000000000000000000..cb41764f1dbc6b284890587c6dae735434df7ed0 GIT binary patch literal 44 acmd;MfPkE01*IGn4IrhPqp8G)RTKb{HV2ge literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0067 b/fuzzer/shi_detector/corpus/corpus-0067 new file mode 100644 index 0000000000000000000000000000000000000000..f50934838298c1f89635458c78d651d507b0c975 GIT binary patch literal 45 jcmWe-fPkE01tk>?AgP<9sgRRdl3J9Qqr?G~gb4ruxd91? literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0068 b/fuzzer/shi_detector/corpus/corpus-0068 new file mode 100644 index 0000000000000000000000000000000000000000..fb007164b5812302e3d70b147161b8371ef46e44 GIT binary patch literal 38 ccmd;MfPkE01tk>?AgP<9nWMx36@`fa0Ax`G-~a#s literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0069 b/fuzzer/shi_detector/corpus/corpus-0069 new file mode 100644 index 0000000000000000000000000000000000000000..460e501c4ea36b7552de64562c55f377d64d4fb7 GIT binary patch literal 45 kcmb1TfPkE01*M$KlGLKa90e5(AWt_(QvoWV1XaTT0Nz^(hyVZp literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0070 b/fuzzer/shi_detector/corpus/corpus-0070 new file mode 100644 index 0000000000000000000000000000000000000000..1026c4f984142680d827674d7d6a914b9c5af189 GIT binary patch literal 33 dcmd;QfPkE01*IGn4IrhPqnV?`1Qk}$1prRA1cv|s literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0071 b/fuzzer/shi_detector/corpus/corpus-0071 new file mode 100644 index 0000000000000000000000000000000000000000..d95f42951c1b4a948a6335e695cf9078e015ad4d GIT binary patch literal 41 ecmb1OfB+>GjpF>=)RK(MymU<^1vr}#CJz9z>j<#` literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0072 b/fuzzer/shi_detector/corpus/corpus-0072 new file mode 100644 index 0000000000000000000000000000000000000000..9f689086da3c146f7d0ebe0c3fe3c171e82a5cc6 GIT binary patch literal 29 Zcmd;KfPkE01*HTap_`MS#0V8r0suOy1Iqva literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0073 b/fuzzer/shi_detector/corpus/corpus-0073 new file mode 100644 index 0000000000000000000000000000000000000000..82fb1bac3ca5deb11d916d118a0b4cbf464482ff GIT binary patch literal 52 icmWe(fPkE01*M$KlGLKa9EAiRM>i)yNdPK`E&%`@T?|wJ literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0074 b/fuzzer/shi_detector/corpus/corpus-0074 new file mode 100644 index 0000000000000000000000000000000000000000..c1aafa8e08c7a0c9a5bd3427d2a9bf38587e3de8 GIT binary patch literal 35 bcmd;OfPkE01*Mz>AgP;^pu`3hga`ouU8M#H literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0075 b/fuzzer/shi_detector/corpus/corpus-0075 new file mode 100644 index 0000000000000000000000000000000000000000..d3854aa5875ec9c6c7101939df972c2c3712f8f2 GIT binary patch literal 41 icmWe(fPkE01*HTap_`MSkds-GT9lZh#0r%I3jzS4%m@1b literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0076 b/fuzzer/shi_detector/corpus/corpus-0076 new file mode 100644 index 0000000000000000000000000000000000000000..88d86529a7c0f7cc33616fedaf5e480480c70bed GIT binary patch literal 29 Zcmd;OfPkE01*HTap_`MCqXZRW002I+1Qh@P literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0077 b/fuzzer/shi_detector/corpus/corpus-0077 new file mode 100644 index 0000000000000000000000000000000000000000..e79ea435e7c8d91c6d7f30f739e93a82dc0bade0 GIT binary patch literal 46 kcmb1PfPkE01*M$KlGLKa9EAiRM>i)y0m@fmf~ipg0Q4^kVgLXD literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0078 b/fuzzer/shi_detector/corpus/corpus-0078 new file mode 100644 index 0000000000000000000000000000000000000000..447211aa354e9023fd72209f8d65c1626ba5ae1f GIT binary patch literal 32 acmd;MfPkE01*Mz>AgK!^m6)KSx;X$*bp;&& literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0079 b/fuzzer/shi_detector/corpus/corpus-0079 new file mode 100644 index 0000000000000000000000000000000000000000..0accfe34c54d5c1abc5dd5f5b49fc0b306b99a56 GIT binary patch literal 39 dcmWeb0;{4pyl8nr}^aLdZI2$U<006L12-pAs literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0080 b/fuzzer/shi_detector/corpus/corpus-0080 new file mode 100644 index 0000000000000000000000000000000000000000..3a469b2134ddf0bb81ae3b966f837a16e1618c7c GIT binary patch literal 20 RcmZQ!fPmEGjC?4Q0RRzL0i*x` literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0081 b/fuzzer/shi_detector/corpus/corpus-0081 new file mode 100644 index 0000000000000000000000000000000000000000..478463152b412f8e7377a719e84e187416e7ca9f GIT binary patch literal 30 Ycmd;NfPj+J;u3|_t<8 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0082 b/fuzzer/shi_detector/corpus/corpus-0082 new file mode 100644 index 0000000000000000000000000000000000000000..b6c5bed8e31636f8a13af8212c466a28bdbed173 GIT binary patch literal 27 Ycmd;NfPiX+)Z~nOYlT`SsDQNs05Apvc>n+a literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0083 b/fuzzer/shi_detector/corpus/corpus-0083 new file mode 100644 index 0000000000000000000000000000000000000000..efe3dc9dfad2bcef18cf9c8160b96105b6c8e12a GIT binary patch literal 23 UcmZQ)fB=o0Vg=nCO(>5602k*0@c;k- literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0084 b/fuzzer/shi_detector/corpus/corpus-0084 new file mode 100644 index 0000000000000000000000000000000000000000..71085b19330b4566ed5ad421816f885b180c52d8 GIT binary patch literal 24 Vcmd;JfB+SZoMHvt98D;f0RS3D0u}%O literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0085 b/fuzzer/shi_detector/corpus/corpus-0085 new file mode 100644 index 0000000000000000000000000000000000000000..20f492745ccfd1dde079a7618fef792a83e7496a GIT binary patch literal 30 bcmd;OfPj?Dv@``9jhtcy-5gCus9-SwLNf$l literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0086 b/fuzzer/shi_detector/corpus/corpus-0086 new file mode 100644 index 0000000000000000000000000000000000000000..c1bb7500eeb744e2249e6e2caaf340f428931c83 GIT binary patch literal 31 acmd;OfPj?Dv@``fjhtcy-5gCOxF7&W3GEl8inM<5@x6nPyhfvTm)1A literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0090 b/fuzzer/shi_detector/corpus/corpus-0090 new file mode 100644 index 0000000000000000000000000000000000000000..a778ca64806affd1b9dd644cdd9d157bfc287705 GIT binary patch literal 42 mcmb1SfPkE01>GEl8inM<5+IpglvGD4YlY;*5*DZshz9^ZU<5e; literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0093 b/fuzzer/shi_detector/corpus/corpus-0093 new file mode 100644 index 0000000000000000000000000000000000000000..4be2c49bcae44923b414b4c20b277b135c36489b GIT binary patch literal 45 ncmb1SfPkE01>GD4YlY;*5+IpglvV!bPyj#x literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0097 b/fuzzer/shi_detector/corpus/corpus-0097 new file mode 100644 index 0000000000000000000000000000000000000000..75576ac50c4f355fc8b74a1df5465617c5c36753 GIT binary patch literal 19 PcmZQ#fB-u?Mi>hK1(pE5 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0098 b/fuzzer/shi_detector/corpus/corpus-0098 new file mode 100644 index 0000000000000000000000000000000000000000..5051cb40e1659d2f6554ac1d9e104d8f23cc05c1 GIT binary patch literal 18 PcmZQ#fB?H1D1!k22L1rO literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0099 b/fuzzer/shi_detector/corpus/corpus-0099 new file mode 100644 index 0000000000000000000000000000000000000000..2c39ac92fb5cc9b45123f9728dd2caa67c237553 GIT binary patch literal 19 PcmZQ#fB-u+Mi>hK1f~F{ literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0100 b/fuzzer/shi_detector/corpus/corpus-0100 new file mode 100644 index 0000000000000000000000000000000000000000..e25d5be5159b80b49068e843bceead16db5f0bf0 GIT binary patch literal 19 QcmZQ(fB-`~H7JV#00t=lnE(I) literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0101 b/fuzzer/shi_detector/corpus/corpus-0101 new file mode 100644 index 0000000000000000000000000000000000000000..f9f256ee2755d04eb541938a39cb505f0c043c2c GIT binary patch literal 20 RcmZQ!fB-`~H6tjK0RRY?0L%aY literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0102 b/fuzzer/shi_detector/corpus/corpus-0102 new file mode 100644 index 0000000000000000000000000000000000000000..465b36e2ec0861688059ee75c057bb671a32dc30 GIT binary patch literal 21 ScmZQ&fB-`~H6vXpn*jg|4gmT9 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0103 b/fuzzer/shi_detector/corpus/corpus-0103 new file mode 100644 index 0000000000000000000000000000000000000000..47b6c022b080353e18483be1ff97fa1940f67a21 GIT binary patch literal 21 ScmZQ!fB-`~HC;w1TMYmSVF2y` literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0104 b/fuzzer/shi_detector/corpus/corpus-0104 new file mode 100644 index 0000000000000000000000000000000000000000..a04e56164b19742110c52b26706e98bab50bb776 GIT binary patch literal 18 OcmZQ%fB+jt7y|$T!2o*z literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0105 b/fuzzer/shi_detector/corpus/corpus-0105 new file mode 100644 index 0000000000000000000000000000000000000000..fee6714ec7b8d8a34235e12f3ee5aa848136a97c GIT binary patch literal 18 PcmZQ#fB-`qD1!k21Uvw6 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0106 b/fuzzer/shi_detector/corpus/corpus-0106 new file mode 100644 index 0000000000000000000000000000000000000000..b1fc8d2609bcd6aac3e4bc2e7f7e76aab411e8c5 GIT binary patch literal 19 QcmZQ(fB+*KH7JV#00tlcm;e9( literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0107 b/fuzzer/shi_detector/corpus/corpus-0107 new file mode 100644 index 0000000000000000000000000000000000000000..c56cee40cdcda3ac8f7b10eae4b59b0b665da149 GIT binary patch literal 20 RcmZQ(fB+*K8%8M81^@@y0P6q% literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0108 b/fuzzer/shi_detector/corpus/corpus-0108 new file mode 100644 index 0000000000000000000000000000000000000000..7da31b1e5de5e8c24c6d34e1cb0d5133d551a964 GIT binary patch literal 23 RcmZQ!fB+*K8(n4u8vqSF0f_(r literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0109 b/fuzzer/shi_detector/corpus/corpus-0109 new file mode 100644 index 0000000000000000000000000000000000000000..cc632ea81c8e81234fb19a440325d5eb9c5b8756 GIT binary patch literal 19 QcmZQ(fB+jCT_}qI00zwfsQ>@~ literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0110 b/fuzzer/shi_detector/corpus/corpus-0110 new file mode 100644 index 0000000000000000000000000000000000000000..a80a945ce9115352e01b368c41e12ad4744e1971 GIT binary patch literal 18 PcmZQ#fB+jCD1!k21gZdg literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0111 b/fuzzer/shi_detector/corpus/corpus-0111 new file mode 100644 index 0000000000000000000000000000000000000000..258d57de975b029a2c9aa198c79cb6f7d6f4d24e GIT binary patch literal 21 QcmZQ(fB+jC8zwjt011o%G5`Po literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0112 b/fuzzer/shi_detector/corpus/corpus-0112 new file mode 100644 index 0000000000000000000000000000000000000000..7d93adb6742cbbe27c28342f3137b1d2bcac2c1d GIT binary patch literal 22 RcmZQ!fB-`q8yhA#8vqPG0a*Y5 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0113 b/fuzzer/shi_detector/corpus/corpus-0113 new file mode 100644 index 0000000000000000000000000000000000000000..7bd079ad0c4fb0305c0eb1856a7f4240fbd222c8 GIT binary patch literal 22 QcmZQ(fB-`qHD&}800}GsEdT%j literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0114 b/fuzzer/shi_detector/corpus/corpus-0114 new file mode 100644 index 0000000000000000000000000000000000000000..9d15da8038d44cbfd8f54caa2d3455ddec818072 GIT binary patch literal 20 PcmZQ#fB-`qCO8WK1X8#tr}p6#**% literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0117 b/fuzzer/shi_detector/corpus/corpus-0117 new file mode 100644 index 0000000000000000000000000000000000000000..f967bcb197c4d8743bfe73fdd3fb10724b1abf29 GIT binary patch literal 19 QcmZQ#fB+jiMkvb;00n>myZ`_I literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0118 b/fuzzer/shi_detector/corpus/corpus-0118 new file mode 100644 index 0000000000000000000000000000000000000000..18c3a935bad3524b676e5191e8bd0fb24834c248 GIT binary patch literal 37 icmWe;fPkE01v>@(lH3CQlGNf7g~X!tVkW3mQ91yGLkEZe literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0119 b/fuzzer/shi_detector/corpus/corpus-0119 new file mode 100644 index 0000000000000000000000000000000000000000..614c6982583af6f47b04eab7612f4296525f3184 GIT binary patch literal 46 icmWe;fPkE0g~X!tVg)+|{gT`Q{gTw;5^kszk_-UXJPH{A literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0120 b/fuzzer/shi_detector/corpus/corpus-0120 new file mode 100644 index 0000000000000000000000000000000000000000..62515b958367824b48ade0b9650da2fe6cdeb644 GIT binary patch literal 68 kcmXqHfPkE01v>@&K))onK))olxI`hbD7}~&su3s(09LsRo&W#< literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0121 b/fuzzer/shi_detector/corpus/corpus-0121 new file mode 100644 index 0000000000000000000000000000000000000000..2ab7badbfd2cfbf9046c2675e22a72b26e4ae8ab GIT binary patch literal 36 hcmWe(fPkE0g~X!tVg(~R1^trT0{xQI;u5G70|0(;2H*ey literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0122 b/fuzzer/shi_detector/corpus/corpus-0122 new file mode 100644 index 0000000000000000000000000000000000000000..e48a763e07ec98b11c7ef1e6e4b450f47e90cd98 GIT binary patch literal 36 hcmWe(fPkE0g~X!tVg)-p1^trT0{xQI;u5G70|0+v2J8R; literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0123 b/fuzzer/shi_detector/corpus/corpus-0123 new file mode 100644 index 0000000000000000000000000000000000000000..e84bf256cbc009ab8762d556f0e00204238b6f32 GIT binary patch literal 58 wcmY#lfPkE0g~X!tVg)+|{gT`Q{gTw;5(Oh5C$%J5zqlkNwWx>#suHFK0F8_e6951J literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0124 b/fuzzer/shi_detector/corpus/corpus-0124 new file mode 100644 index 0000000000000000000000000000000000000000..f4ddb652dd3f65db42b1b57c5748d91f768225a2 GIT binary patch literal 33 ecmWe+fPkE0g~X!tVmtkk+yecQ)Z!ATI0FD|#Rh8t literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0125 b/fuzzer/shi_detector/corpus/corpus-0125 new file mode 100644 index 0000000000000000000000000000000000000000..50b8452d85d7675a645a0d8cc436220190fb2afc GIT binary patch literal 35 dcmd;MfPkE0g~X!tVg(z8w9K4TR;Vag003!91~LEu literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0126 b/fuzzer/shi_detector/corpus/corpus-0126 new file mode 100644 index 0000000000000000000000000000000000000000..7a2f4c1370525cb8e90eb49ebd5129e1e8b8478e GIT binary patch literal 32 bcmd;KfPkE0g~X!tVw<$goKzO57>El1So{VR literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0127 b/fuzzer/shi_detector/corpus/corpus-0127 new file mode 100644 index 0000000000000000000000000000000000000000..f0698bf4c0e674db3fd42897926dc07300783ecd GIT binary patch literal 35 fcmWe&fPkE0g~X!tVg(x;8-=vYoK$A0I8YP-YVig~ literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0128 b/fuzzer/shi_detector/corpus/corpus-0128 new file mode 100644 index 0000000000000000000000000000000000000000..f5b635ffa61cb97aeb7d040e78a5bc689a7b5929 GIT binary patch literal 34 ecmd;QfPkE0g~X!tVg(x;g|y6^RA#6!kOKf?e+C8s literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0129 b/fuzzer/shi_detector/corpus/corpus-0129 new file mode 100644 index 0000000000000000000000000000000000000000..f86f11436e0a2db6a191439646b9a6e6322703c1 GIT binary patch literal 35 fcmWe&fPkE0g~X!tVg(x;U4^vFoK$A0IFJVbX^RE; literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0130 b/fuzzer/shi_detector/corpus/corpus-0130 new file mode 100644 index 0000000000000000000000000000000000000000..1809a1bf547640dfb931ac178ccacce04f776225 GIT binary patch literal 33 ecmd;QfPkE0g~X!tVg(yDg|y6^R3@l!S|$Koo(0bU literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0131 b/fuzzer/shi_detector/corpus/corpus-0131 new file mode 100644 index 0000000000000000000000000000000000000000..7e3983cb4cc3c95985dd83979eaffb78fc28d586 GIT binary patch literal 43 ecmd;QfPkE0g~X!tVg)rjg|y6^R34}>vLFDZj|jK` literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0132 b/fuzzer/shi_detector/corpus/corpus-0132 new file mode 100644 index 0000000000000000000000000000000000000000..a65fdc3c34d52456ce24e941e008b5af1d264f8c GIT binary patch literal 31 ccmd;QfPkE0g~X!tVg(yJg|y6^RH!He08*(1761SM literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0133 b/fuzzer/shi_detector/corpus/corpus-0133 new file mode 100644 index 0000000000000000000000000000000000000000..04678c1b37f5d905b3eabfc7ed1226d170707af2 GIT binary patch literal 39 ecmd;QfPkE0g~X!tVg4 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0140 b/fuzzer/shi_detector/corpus/corpus-0140 new file mode 100644 index 0000000000000000000000000000000000000000..e9161408f8825397bcc60be8ada511d0049da3ab GIT binary patch literal 42 gcmWe+fPkE0g~X!tVg&;mHC=_Y%$!tCs03UP0GuQTu>b%7 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0141 b/fuzzer/shi_detector/corpus/corpus-0141 new file mode 100644 index 0000000000000000000000000000000000000000..08029042a48a20e9784361390bfff3265d9d0b78 GIT binary patch literal 37 gcmWe+fPkE0g~X!tVg&;mHA988%$!sfs04@&0C!gh1poj5 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0142 b/fuzzer/shi_detector/corpus/corpus-0142 new file mode 100644 index 0000000000000000000000000000000000000000..0bd1ea15ebeb1224c72d9e6d8e761ed67878e5b6 GIT binary patch literal 35 fcmWe&fPkE0g~X!tVg&;`HHEayoK$A0I8Y1#X}$)4 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0143 b/fuzzer/shi_detector/corpus/corpus-0143 new file mode 100644 index 0000000000000000000000000000000000000000..d2b37ee5b413a5a8a92e6ed5839acaa9eb8cacef GIT binary patch literal 36 gcmWe+fPkE0g~X!tVg&;`HA988%$!tas05G)0B<%19RL6T literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0144 b/fuzzer/shi_detector/corpus/corpus-0144 new file mode 100644 index 0000000000000000000000000000000000000000..2d006308bacf6e05493e69cc5f598305b7d29ada GIT binary patch literal 47 gcmWe+fPkE0g~X!tVg&;`HC=_Y%$!s{s06Aw0LmE&eEaxV5T1Kc)EdW011g-!8 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0160 b/fuzzer/shi_detector/corpus/corpus-0160 new file mode 100644 index 0000000000000000000000000000000000000000..0fa60b634c619684be5f24618b25fdf5171e3b8d GIT binary patch literal 27 Ycmd;NfPmEGjC=)^vcw`Ls6bgF05Svw4*&oF literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0161 b/fuzzer/shi_detector/corpus/corpus-0161 new file mode 100644 index 0000000000000000000000000000000000000000..60611d8db124745ada044a89111386c1123841e2 GIT binary patch literal 36 dcmd;MfPmEGjC=)^>axV5XouKZHmE2>2mp2~2Q~lz literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0162 b/fuzzer/shi_detector/corpus/corpus-0162 new file mode 100644 index 0000000000000000000000000000000000000000..c1437c3f762ed62f5536994faafe998e9c1027e4 GIT binary patch literal 23 UcmZQ)fPmEGjC=(Z11OIH02=`U8vp8UjYCh2Lh%5 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0177 b/fuzzer/shi_detector/corpus/corpus-0177 new file mode 100644 index 0000000000000000000000000000000000000000..307bc274f1e9ea387d23e3d9af08db776ccb334c GIT binary patch literal 24 VcmZQ)fPmEGjC=(Z6-FpG82}v+0&@TW literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0178 b/fuzzer/shi_detector/corpus/corpus-0178 new file mode 100644 index 0000000000000000000000000000000000000000..a37a4e3d96b7f71678c37e0409af320b06c975e6 GIT binary patch literal 24 VcmZQ)fPmEGjC=(ZMMfw$9{?Qw0(<}f literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0179 b/fuzzer/shi_detector/corpus/corpus-0179 new file mode 100644 index 0000000000000000000000000000000000000000..fe7021cd1cd711ae32c667f9a3a8b8f4bb1110f8 GIT binary patch literal 58 ycmY#qfPj*s(o_XCHHFmVjC=*9jMSW*d?ke%g_0tL3^gT07rBL*8l(j literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0181 b/fuzzer/shi_detector/corpus/corpus-0181 new file mode 100644 index 0000000000000000000000000000000000000000..0499783165feda787dae7b70d5b8b50643cad8d8 GIT binary patch literal 20 RcmZQ!fB;2>oMI@G0RRh=0VMzc literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0182 b/fuzzer/shi_detector/corpus/corpus-0182 new file mode 100644 index 0000000000000000000000000000000000000000..91f426940cc272577e391ed5e3e19a5fe8b2b865 GIT binary patch literal 30 Ycmd;NfPiX+)Z~nOYlT`?r~sG)06rrG&;S4c literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0183 b/fuzzer/shi_detector/corpus/corpus-0183 new file mode 100644 index 0000000000000000000000000000000000000000..6f869353dbc765af426ef27593d866317473c48c GIT binary patch literal 53 mcmWeWsw@B*n+tIO literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0184 b/fuzzer/shi_detector/corpus/corpus-0184 new file mode 100644 index 0000000000000000000000000000000000000000..64441f8c1144f7092d4acffff31736edc3b0021c GIT binary patch literal 55 ncmb1OfPiX+oMHvt9EBQ%^rF-Pg@VN5;_?)0g<5{7JeoKFQ4$TN literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0185 b/fuzzer/shi_detector/corpus/corpus-0185 new file mode 100644 index 0000000000000000000000000000000000000000..15234000224c2ddfd396f63e858dcfccbd0a598f GIT binary patch literal 39 bcmd;MfPiWRrR4nF+{C;TC2NIRPIOTKf~*JT literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0186 b/fuzzer/shi_detector/corpus/corpus-0186 new file mode 100644 index 0000000000000000000000000000000000000000..bafed40e31d29b138352eae8c896ae04d581503b GIT binary patch literal 35 dcmd;MfPiWR_2m5A+{C;Tb!&xMR;Vag003v<2EhOT literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0187 b/fuzzer/shi_detector/corpus/corpus-0187 new file mode 100644 index 0000000000000000000000000000000000000000..c42db485dc2a782423e67de9d2fc372d10c05068 GIT binary patch literal 30 bcmd;KfPiWRrJQ0V1>GELg<2-4n34hjIGqEa literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0188 b/fuzzer/shi_detector/corpus/corpus-0188 new file mode 100644 index 0000000000000000000000000000000000000000..92e7fe1975b844c8fd7b3050575d2bf15bf1a3f3 GIT binary patch literal 30 bcmd;KfPiWR^_*gL1>GELg<2-4SdKLSIt2s| literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0189 b/fuzzer/shi_detector/corpus/corpus-0189 new file mode 100644 index 0000000000000000000000000000000000000000..ef9f126269845c9c57f5594cb39ce8c01bdab51e GIT binary patch literal 43 gcmWe+fPiX+vcw`=Lj#4JVg=nCYlT`as02a;0Gz4^?f?J) literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0190 b/fuzzer/shi_detector/corpus/corpus-0190 new file mode 100644 index 0000000000000000000000000000000000000000..c47653c364e1331e42cef158080bc206cbfe6745 GIT binary patch literal 41 ecmd;QfPiX+vcw`=g`8pq-5hI$S}v$CLJ$Cv?+6J1 literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/corpus/corpus-0191 b/fuzzer/shi_detector/corpus/corpus-0191 new file mode 100644 index 0000000000000000000000000000000000000000..9bd585f0891b50e6366c32b9989b6d70011c18f0 GIT binary patch literal 41 jcmWe(fPiWRB^8a-GELg<4jq9GC|Hh#&^B literal 0 HcmV?d00001 diff --git a/fuzzer/shi_detector/src/main.cpp b/fuzzer/shi_detector/src/main.cpp new file mode 100644 index 000000000..cce32c4ac --- /dev/null +++ b/fuzzer/shi_detector/src/main.cpp @@ -0,0 +1,131 @@ +// 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 +#include + +#include "condition/shi_detector.hpp" + +using namespace ddwaf; +using namespace std::literals; + +extern "C" size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); + +extern "C" int LLVMFuzzerInitialize(const int * /*argc*/, char *** /*argv*/) +{ + ddwaf::memory::set_local_memory_resource(std::pmr::new_delete_resource()); + return 0; +} + +template std::vector gen_param_def(Args... addresses) +{ + return {{{{std::string{addresses}, get_target_index(addresses)}}}...}; +} + +// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) +std::pair deserialize(const uint8_t *data, size_t size) +{ + if (size < sizeof(std::size_t)) { + return {}; + } + + const auto resource_size = *reinterpret_cast(data); + data += sizeof(std::size_t); + size -= sizeof(std::size_t); + + if (size < resource_size) { + return {}; + } + + std::string_view resource{reinterpret_cast(data), resource_size}; + data += resource_size; + size -= resource_size; + + if (size < sizeof(std::size_t)) { + return {}; + } + + const auto param_size = *reinterpret_cast(data); + data += sizeof(std::size_t); + size -= sizeof(std::size_t); + + if (size < param_size) { + return {}; + } + + std::string_view param{reinterpret_cast(data), param_size}; + + return {resource, param}; +} + +uint8_t *serialize_string(uint8_t *Data, std::string_view str) +{ + std::size_t size = str.size(); + memcpy(Data, reinterpret_cast(&size), sizeof(std::size_t)); + Data += sizeof(std::size_t); + memcpy(Data, str.data(), size); + Data += size; + return Data; +} + +std::size_t serialize(uint8_t *Data, std::string_view resource, std::string_view param) +{ + Data = serialize_string(Data, resource); + serialize_string(Data, param); + return sizeof(std::size_t) * 2 + resource.size() + param.size(); +} +// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) + +// NOLINTNEXTLINE +extern "C" size_t LLVMFuzzerCustomMutator( + uint8_t *Data, size_t Size, [[maybe_unused]] size_t MaxSize, [[maybe_unused]] unsigned int Seed) +{ + static thread_local std::random_device dev; + static thread_local std::mt19937 rng(dev()); + + auto [resource, param] = deserialize(Data, Size); + MaxSize -= sizeof(std::size_t) * 2; + + std::string resource_buffer{resource.begin(), resource.end()}; + resource_buffer.resize(std::max(resource_buffer.size(), MaxSize / 2)); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto new_size = LLVMFuzzerMutate(reinterpret_cast(resource_buffer.data()), + resource.size(), resource_buffer.size()); + resource_buffer.resize(new_size); + + auto param_idx = rng() % new_size; + auto param_size = 1 + rng() % (new_size - param_idx); + + // std::cout << "max_size: " << MaxSize << ", new_size: " << new_size << ", idx: " << param_idx + // << ", size: " << param_size << '\n'; + auto param_buffer = resource_buffer.substr(param_idx, param_size); + return serialize(Data, resource_buffer, param_buffer); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) +{ + shi_detector cond{{gen_param_def("server.sys.shell.cmd", "server.request.query")}}; + + auto [resource, param] = deserialize(bytes, size); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.sys.shell.cmd", + ddwaf_object_stringl(&tmp, resource.data(), resource.size())); + ddwaf_object_map_add( + &root, "server.request.query", ddwaf_object_stringl(&tmp, param.data(), param.size())); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + (void)cond.eval(cache, store, {}, {}, deadline); + + return 0; +} diff --git a/fuzzer/sql_tokenizer/src/main.cpp b/fuzzer/sql_tokenizer/src/main.cpp index 038addc57..b95597c4c 100644 --- a/fuzzer/sql_tokenizer/src/main.cpp +++ b/fuzzer/sql_tokenizer/src/main.cpp @@ -30,6 +30,9 @@ template [[clang::optnone]] void tokenize(std::string_view query) { T tokenizer(query); auto tokens = tokenizer.tokenize(); + // Force the compiler to not optimize away tokens + // NOLINTNEXTLINE(hicpp-no-assembler) + asm volatile("" : "+m"(tokens) : : "memory"); } extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) diff --git a/src/condition/shi_detector.cpp b/src/condition/shi_detector.cpp new file mode 100644 index 000000000..00f5b83ad --- /dev/null +++ b/src/condition/shi_detector.cpp @@ -0,0 +1,115 @@ +// 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 "condition/shi_detector.hpp" +#include "exception.hpp" +#include "iterator.hpp" +#include "tokenizer/shell.hpp" +#include "utils.hpp" + +using namespace std::literals; + +namespace ddwaf { + +namespace { + +struct shi_result { + std::string value; + std::vector key_path; +}; + +std::optional shi_string_impl(std::string_view resource, + std::vector &resource_tokens, const ddwaf_object ¶ms, + const exclusion::object_set_ref &objects_excluded, const object_limits &limits, + ddwaf::timer &deadline) +{ + object::kv_iterator it(¶ms, {}, objects_excluded, limits); + for (; it; ++it) { + if (deadline.expired()) { + throw ddwaf::timeout_exception(); + } + + const ddwaf_object &object = *(*it); + if (object.type != DDWAF_OBJ_STRING) { + continue; + } + + std::string_view param{object.stringValue, static_cast(object.nbEntries)}; + auto param_index = resource.find(param); + if (param_index == std::string_view::npos) { + // Seemingly no injection + continue; + } + + if (resource_tokens.empty()) { + shell_tokenizer tokenizer(resource); + resource_tokens = tokenizer.tokenize(); + } + + auto end_index = param_index + param.size(); + + // Find first token + std::size_t i = 0; + for (; i < resource_tokens.size(); ++i) { + const auto &token = resource_tokens[i]; + if (end_index >= token.index && param_index < (token.index + token.str.size())) { + break; + } + } + + // Ignore if it's a single token + if ((i + 1) < resource_tokens.size() && resource_tokens[i + 1].index >= end_index) { + continue; + } + + for (; i < resource_tokens.size(); ++i) { + const auto &token = resource_tokens[i]; + if (token.type == shell_token_type::executable || + token.type == shell_token_type::redirection) { + return {{std::string(param), it.get_current_path()}}; + } + } + } + + return std::nullopt; +} + +} // namespace + +shi_detector::shi_detector(std::vector args, const object_limits &limits) + : base_impl(std::move(args), limits) +{} + +eval_result shi_detector::eval_impl(const unary_argument &resource, + const variadic_argument ¶ms, condition_cache &cache, + const exclusion::object_set_ref &objects_excluded, ddwaf::timer &deadline) const +{ + std::vector resource_tokens; + + for (const auto ¶m : params) { + auto res = shi_string_impl( + resource.value, resource_tokens, *param.value, objects_excluded, limits_, deadline); + if (res.has_value()) { + std::vector resource_kp{ + resource.key_path.begin(), resource.key_path.end()}; + bool ephemeral = resource.ephemeral || param.ephemeral; + + auto &[highlight, param_kp] = res.value(); + + DDWAF_TRACE("Target {} matched parameter value {}", param.address, highlight); + + cache.match = condition_match{ + {{"resource"sv, std::string{resource.value}, resource.address, resource_kp}, + {"params"sv, highlight, param.address, param_kp}}, + {std::move(highlight)}, "shi_detector", {}, ephemeral}; + + return {true, ephemeral}; + } + } + + return {}; +} +} // namespace ddwaf diff --git a/src/condition/shi_detector.hpp b/src/condition/shi_detector.hpp new file mode 100644 index 000000000..6ebcafe7a --- /dev/null +++ b/src/condition/shi_detector.hpp @@ -0,0 +1,29 @@ +// 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" +#include "matcher/ip_match.hpp" + +namespace ddwaf { + +class shi_detector : public base_impl { +public: + static constexpr std::array param_names{"resource", "params"}; + + explicit shi_detector(std::vector args, const object_limits &limits = {}); + +protected: + // TODO support array shell_args + [[nodiscard]] eval_result eval_impl(const unary_argument &resource, + const variadic_argument ¶ms, condition_cache &cache, + const exclusion::object_set_ref &objects_excluded, ddwaf::timer &deadline) const; + + friend class base_impl; +}; + +} // namespace ddwaf diff --git a/src/condition/ssrf_detector.cpp b/src/condition/ssrf_detector.cpp index e41ea0f78..26aea9e46 100644 --- a/src/condition/ssrf_detector.cpp +++ b/src/condition/ssrf_detector.cpp @@ -272,7 +272,7 @@ eval_result ssrf_detector::eval_impl(const unary_argument &uri {"params"sv, highlight, param.address, param_kp}}, {std::move(highlight)}, "ssrf_detector", {}, ephemeral}; - return {true, uri.ephemeral || param.ephemeral}; + return {true, ephemeral}; } } diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index 5cb22bced..f6f805061 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -6,6 +6,7 @@ #include "condition/lfi_detector.hpp" #include "condition/scalar_condition.hpp" +#include "condition/shi_detector.hpp" #include "condition/sqli_detector.hpp" #include "condition/ssrf_detector.hpp" #include "expression.hpp" @@ -109,6 +110,10 @@ std::shared_ptr parse_expression(const parameter::vector &conditions auto arguments = parse_arguments(params, source, transformers, addresses, limits); conditions.emplace_back(std::make_unique(std::move(arguments), limits)); + } else if (operator_name == "shi_detector") { + auto arguments = + parse_arguments(params, source, transformers, addresses, limits); + conditions.emplace_back(std::make_unique(std::move(arguments), limits)); } else { auto [data_id, matcher] = parse_matcher(operator_name, params); diff --git a/src/tokenizer/base.hpp b/src/tokenizer/base.hpp new file mode 100644 index 000000000..88d6feaa7 --- /dev/null +++ b/src/tokenizer/base.hpp @@ -0,0 +1,100 @@ +// 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 "utils.hpp" +#include +#include +#include +#include +#include +#include + +namespace ddwaf { + +template struct base_token { + T type{T::unknown}; + std::string_view str; + std::size_t index{}; +}; + +template class base_tokenizer { +public: + explicit base_tokenizer(std::string_view str, std::unordered_set skip_tokens = {}) + : buffer_(str), skip_tokens_(std::move(skip_tokens)) + {} + +protected: + [[nodiscard]] char peek() const + { + if (idx_ >= buffer_.size()) { + [[unlikely]] return '\0'; + } + return buffer_[idx_]; + } + [[nodiscard]] char prev(std::size_t offset = 1) const + { + if (idx_ < offset) { + [[unlikely]] return '\0'; + } + return buffer_[idx_ - 1]; + } + + bool advance(std::size_t offset = 1) { return (idx_ += offset) < buffer_.size(); } + + [[nodiscard]] char next(std::size_t offset = 1) + { + if ((idx_ + offset) >= buffer_.size()) { + [[unlikely]] return '\0'; + } + return buffer_[idx_ + offset]; + } + + bool eof() { return idx_ >= buffer_.size(); } + + [[nodiscard]] std::size_t index() const { return idx_; } + + std::string_view substr(std::size_t start, std::size_t size = std::string_view::npos) + { + return buffer_.substr(start, size); + } + + std::string_view substr() { return buffer_.substr(idx_); } + + void add_token(T type, std::size_t size = 1) + { + base_token token; + token.index = index(); + token.type = type; + token.str = substr(token.index, size); + emplace_token(token); + advance(token.str.size() - 1); + } + + void add_token_from(T type, std::size_t begin) + { + base_token token; + token.index = begin; + token.type = type; + token.str = substr(begin, index() - begin); + emplace_token(token); + } + + void emplace_token(const base_token &token) + { + if (!skip_tokens_.contains(token.type)) { + tokens_.emplace_back(token); + } + } + + std::string_view buffer_; + std::size_t idx_{0}; + std::unordered_set skip_tokens_{}; + std::vector> tokens_{}; +}; + +} // namespace ddwaf diff --git a/src/tokenizer/shell.cpp b/src/tokenizer/shell.cpp new file mode 100644 index 000000000..fe6df696f --- /dev/null +++ b/src/tokenizer/shell.cpp @@ -0,0 +1,643 @@ +// 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 "tokenizer/shell.hpp" +#include "utils.hpp" + +using namespace std::literals; + +namespace ddwaf { + +namespace { +/* + * &>>? # Matches &> and &>> + * | # OR + * (?: + * [0-9]*> # Matches optional digits followed by > + * (?: + * \| # Matches a | + * | # OR + * > # Matches a > + * | # OR + * &[0-9]*-? # Matches & followed by optional digits and an optional - + * )? + * ) + * | # OR + * (?: + * [0-9]*< # Matches optional digit followed by < + * (?: + * <(?:-|<)? # Matches < optionally followed by - or < + * | # OR + * &[0-9]*-? # Matches & followed by optional digits and an optional - + * | # OR + * > # Matches a > + * )? + * ) + */ +re2::RE2 redirection_regex( + R"((&>>?|(?:[0-9]*>(?:\||>|&[0-9]*-?)?)|(?:[0-9]*<(?:<(?:-|<)?|&[0-9]*-?|>)?)))"); + +// Valid characters in a variable name +bool is_var_char(char c) { return ddwaf::isalnum(c) || c == '_'; } + +// This functions returns true for any character which could potentially belong to a field and has +// no secondary meaning, for example, the { character could be part of a field, but it might also +// have a secondary meaning (command grouping), hence it allows us to attempt to match a potentially +// different token if applicable. +bool is_field_char(char c) +{ + return c != '`' && c != '$' && c != '{' && c != '}' && c != '[' && c != ']' && c != '(' && + c != ')' && c != '\'' && c != '"' && c != '#' && c != '=' && c != '|' && c != '<' && + c != '>' && c != ';' && c != '&' && c != '\n' && c != '\t' && c != ' '; +} + +// Characters used to represent a space +bool is_space_char(char c) { return c == ' ' || c == '\t'; } + +void find_executables_and_strip_whitespaces(std::vector &tokens) +{ + // The scope within the command, this helps identify high level constructs + // which end up evaluating as part of a command, e.g. an executable + // generated from a command substitution + enum class command_scope { + variable_definition_or_executable, + variable_definition, + arguments, + none, + }; + + std::size_t read = 0; + std::size_t write = 0; + + std::vector command_scope_stack{ + command_scope::variable_definition_or_executable}; + for (; read < tokens.size(); read++) { + auto &token = tokens[read]; + auto &scope = command_scope_stack.back(); + + switch (token.type) { + case shell_token_type::backtick_substitution_open: + case shell_token_type::command_substitution_open: + case shell_token_type::process_substitution_open: + case shell_token_type::compound_command_open: + case shell_token_type::subshell_open: + if (scope == command_scope::variable_definition_or_executable) { + // The new scope is expected to contain at least one executable + // as these are effectively the beginning of a new command. Since + // the evaluation of the current scope was currently in the stage + // of looking for the executable, we can now skip it under the + // assumption that there will be one in the child scope. + // + // This might not always be accurate and perhaps sone scopes + // should be flattened to executables. + scope = command_scope::arguments; + } + command_scope_stack.emplace_back(command_scope::variable_definition_or_executable); + break; + case shell_token_type::backtick_substitution_close: + case shell_token_type::command_substitution_close: + case shell_token_type::process_substitution_close: + case shell_token_type::compound_command_close: + case shell_token_type::subshell_close: + case shell_token_type::arithmetic_expansion_close: + case shell_token_type::array_close: + case shell_token_type::file_redirection_close: + case shell_token_type::parameter_expansion_close: + command_scope_stack.pop_back(); + break; + case shell_token_type::variable_definition: + if (scope == command_scope::variable_definition_or_executable) { + scope = command_scope::variable_definition; + } + break; + case shell_token_type::field: + case shell_token_type::variable: + case shell_token_type::single_quoted_string: + case shell_token_type::double_quoted_string: + if (scope == command_scope::variable_definition_or_executable) { + token.type = shell_token_type::executable; + scope = command_scope::arguments; + } + break; + case shell_token_type::whitespace: + if (scope == command_scope::variable_definition) { + scope = command_scope::variable_definition_or_executable; + } + // Skip adding the whitespace + continue; + case shell_token_type::control: + // Control commands reset the command scope + scope = command_scope::variable_definition_or_executable; + break; + case shell_token_type::arithmetic_expansion_open: + case shell_token_type::array_open: + case shell_token_type::file_redirection_open: + case shell_token_type::parameter_expansion_open: + command_scope_stack.emplace_back(command_scope::arguments); + break; + default: + break; + } + tokens[write++] = token; + } + + tokens.resize(write); +} + +} // namespace + +std::ostream &operator<<(std::ostream &os, shell_token_type type) +{ + switch (type) { + case shell_token_type::unknown: + os << "unknown"; + break; + case shell_token_type::whitespace: + os << "whitespace"; + break; + case shell_token_type::executable: + os << "executable"; + break; + case shell_token_type::field: + os << "field"; + break; + case shell_token_type::arithmetic_expansion: + os << "arithmetic_expansion"; + break; + case shell_token_type::arithmetic_expansion_open: + os << "arithmetic_expansion_open"; + break; + case shell_token_type::arithmetic_expansion_close: + os << "arithmetic_expansion_close"; + break; + case shell_token_type::double_quoted_string_open: + os << "double_quoted_string_open"; + break; + case shell_token_type::double_quoted_string_close: + os << "double_quoted_string_close"; + break; + case shell_token_type::double_quoted_string: + os << "double_quoted_string"; + break; + case shell_token_type::single_quoted_string: + os << "single_quoted_string"; + break; + case shell_token_type::control: + os << "control"; + break; + case shell_token_type::variable_definition: + os << "variable_definition"; + break; + case shell_token_type::variable: + os << "variable"; + break; + case shell_token_type::equal: + os << "equal"; + break; + case shell_token_type::backtick_substitution_open: + os << "backtick_substitution_open"; + break; + case shell_token_type::backtick_substitution_close: + os << "backtick_substitution_close"; + break; + case shell_token_type::dollar: + os << "dollar"; + break; + case shell_token_type::redirection: + os << "redirection"; + break; + case shell_token_type::command_substitution_open: + os << "command_substitution_open"; + break; + case shell_token_type::command_substitution_close: + os << "command_substitution_close"; + break; + case shell_token_type::parenthesis_open: + os << "parenthesis_open"; + break; + case shell_token_type::parenthesis_close: + os << "parenthesis_close"; + break; + case shell_token_type::curly_brace_open: + os << "curly_brace_open"; + break; + case shell_token_type::curly_brace_close: + os << "curly_brace_close"; + break; + case shell_token_type::process_substitution_open: + os << "process_substitution_open"; + break; + case shell_token_type::process_substitution_close: + os << "process_substitution_close"; + break; + case shell_token_type::subshell_open: + os << "subshell_open"; + break; + case shell_token_type::subshell_close: + os << "subshell_close"; + break; + case shell_token_type::compound_command_open: + os << "compound_command_open"; + break; + case shell_token_type::compound_command_close: + os << "compound_command_close"; + break; + case shell_token_type::array_open: + os << "array_open"; + break; + case shell_token_type::array_close: + os << "array_close"; + break; + case shell_token_type::parameter_expansion_open: + os << "parameter_expansion_open"; + break; + case shell_token_type::parameter_expansion_close: + os << "parameter_expansion_close"; + break; + case shell_token_type::file_redirection_open: + os << "file_redirection_open"; + break; + case shell_token_type::file_redirection_close: + os << "file_redirection_close"; + break; + } + return os; +} + +shell_tokenizer::shell_tokenizer( + std::string_view str, std::unordered_set skip_tokens) + : base_tokenizer(str, std::move(skip_tokens)) +{ + shell_scope_stack_.reserve(8); + + if (!redirection_regex.ok()) { + throw std::runtime_error("redirection regex not valid: " + redirection_regex.error_arg()); + } +} + +void shell_tokenizer::tokenize_delimited_token(std::string_view delimiter, shell_token_type type) +{ + shell_token token; + token.index = index(); + token.type = type; + + std::size_t idx = 0; + while (idx < delimiter.size() && advance()) { idx = (peek() == delimiter[idx] ? idx + 1 : 0); } + + token.str = substr(token.index, index() - token.index + 1); + emplace_token(token); +} + +void shell_tokenizer::tokenize_variable() +{ + shell_token token; + token.index = index(); + token.type = shell_token_type::variable; + + // Skip dollar + advance(); + + // We know the first character is $ + auto c = peek(); + if (c == '-' || c == '?' || c == '@' || c == '#' || c == '*' || c == '$' || c == '!' || + ddwaf::isdigit(c)) { + // Special variable, these are one character long + dollar + token.str = substr(token.index, 2); + } else { // alphanumeric and underscores + while (is_var_char(next()) && advance()) {}; + token.str = substr(token.index, index() - token.index + 1); + } + + emplace_token(token); +} + +void shell_tokenizer::tokenize_expandable_scope(std::string_view delimiter, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + shell_token_type full_token, shell_token_type open_token, shell_token_type close_token) +{ + unsigned slash_count = 0; + std::size_t delim_idx = 0; + + for (; !eof(); advance()) { + auto c = peek(); + if (delim_idx < delimiter.size() && c == delimiter[delim_idx] && slash_count == 0) { + ++delim_idx; + if (delim_idx == delimiter.size()) { + pop_shell_scope(); + + // At this point we know there's at least one token so no need to check + auto &token = current_token(); + if (token.type == open_token) { + token.type = full_token; + token.str = substr(token.index, index() - token.index + 1); + } else { + add_token(close_token); + } + break; + } + } else { + delim_idx = 0; + } + + if (c == '$' && slash_count == 0) { + auto n = next(); + if (n == '(') { + auto n2 = next(2); + if (n2 == '(') { + add_token(shell_token_type::arithmetic_expansion_open, 3); + push_shell_scope(shell_scope::arithmetic_expansion); + } else if (n2 == '<') { + add_token(shell_token_type::file_redirection_open, 3); + push_shell_scope(shell_scope::file_redirection); + } else { + add_token(shell_token_type::command_substitution_open, 2); + push_shell_scope(shell_scope::command_substitution); + } + break; + } + + if (n == '[') { + add_token(shell_token_type::arithmetic_expansion_open, 2); + push_shell_scope(shell_scope::legacy_arithmetic_expansion); + break; + } + + if (n == '{') { + add_token(shell_token_type::parameter_expansion_open, 2); + push_shell_scope(shell_scope::parameter_expansion); + break; + } + } else if (c == '`') { + add_token(shell_token_type::backtick_substitution_open); + push_shell_scope(shell_scope::backtick_substitution); + break; + } else if (c == '\\') { + slash_count ^= 1; + } + } +} + +void shell_tokenizer::tokenize_field(shell_token_type type) +{ + shell_token token; + token.index = index(); + token.type = type; + + // Find the end of this token by searching for a "known" character + while (is_field_char(next()) && advance()) {} + + token.str = substr(token.index, index() - token.index + 1); + emplace_token(token); +} + +void shell_tokenizer::tokenize_redirection() +{ + shell_token token; + token.index = index(); + token.type = shell_token_type::redirection; + + auto remaining_str = substr(index()); + + re2::StringPiece redirection; + const re2::StringPiece ref(remaining_str.data(), remaining_str.size()); + if (re2::RE2::PartialMatch(ref, redirection_regex, &redirection)) { + // At least one of the strings will contain a match + if (!redirection.empty()) { + token.str = substr(token.index, redirection.size()); + advance(token.str.size() - 1); + emplace_token(token); + } + } +} + +void shell_tokenizer::tokenize_redirection_or_field() +{ + shell_token token; + token.index = index(); + + auto n = next(); + // The first character is a digit, check if there are others + while (ddwaf::isdigit(n) && advance()) { n = next(); } + + // We have exited, either because we reached the end of the string or because + // the next character is not a digit + if (n == '>' || n == '<') { + advance(); // Skip the current digit + auto remaining_str = substr(index()); + + re2::StringPiece redirection; + const re2::StringPiece ref(remaining_str.data(), remaining_str.size()); + if (re2::RE2::PartialMatch(ref, redirection_regex, &redirection)) { + // At least one of the strings will contain a match + if (!redirection.empty()) { + token.type = shell_token_type::redirection; + token.str = substr(token.index, index() - token.index + redirection.size()); + advance(redirection.size()); + emplace_token(token); + } + } + } else { + token.type = shell_token_type::field; + + // Find the end of this token by searching for a "known" character + while (is_field_char(n) && advance()) { n = next(); } + + token.str = substr(token.index, index() - token.index + 1); + emplace_token(token); + } +} + +std::vector shell_tokenizer::tokenize() +{ + // The string is evaluated based on the current scope, if we're in the global + // scope, i.e. the top-level command, the string just needs to tokenized, + // however if the scope is a subcommand (command substitution, backtick + // substution) the string must be tokenized whilst searching for the end of + // the current subcommand. If we're inside a double quoted string, the + // tokenization must attempt to find substitutions / expansions. + push_shell_scope(shell_scope::global); + + for (; !eof(); advance()) { + if (current_shell_scope_ == shell_scope::double_quoted_string) { + tokenize_expandable_scope("\"", shell_token_type::double_quoted_string, + shell_token_type::double_quoted_string_open, + shell_token_type::double_quoted_string_close); + continue; + } + + if (current_shell_scope_ == shell_scope::parameter_expansion) { + tokenize_expandable_scope("}", shell_token_type::variable, + shell_token_type::parameter_expansion_open, + shell_token_type::parameter_expansion_close); + continue; + } + + if (current_shell_scope_ == shell_scope::arithmetic_expansion) { + tokenize_expandable_scope("))", shell_token_type::arithmetic_expansion, + shell_token_type::arithmetic_expansion_open, + shell_token_type::arithmetic_expansion_close); + continue; + } + + if (current_shell_scope_ == shell_scope::legacy_arithmetic_expansion) { + tokenize_expandable_scope("]", shell_token_type::arithmetic_expansion, + shell_token_type::arithmetic_expansion_open, + shell_token_type::arithmetic_expansion_close); + continue; + } + + if (current_shell_scope_ == shell_scope::array) { + tokenize_expandable_scope(")", shell_token_type::field, shell_token_type::array_open, + shell_token_type::array_close); + continue; + } + if (current_shell_scope_ == shell_scope::file_redirection) { + tokenize_expandable_scope(")", shell_token_type::redirection, + shell_token_type::file_redirection_open, shell_token_type::file_redirection_close); + continue; + } + + auto c = peek(); + if (is_space_char(c)) { + shell_token token; + token.index = index(); + token.type = shell_token_type::whitespace; + + while (is_space_char(next()) && advance()) {} + + token.str = substr(token.index, index() - token.index + 1); + emplace_token(token); + } else if (c == '"') { + add_token(shell_token_type::double_quoted_string_open); + push_shell_scope(shell_scope::double_quoted_string); + } else if (c == '$') { + auto n = next(); + // Tokenize: + // - Command substitution $() + // - Arithmetic expansion $(()) or $[] + // - Variable $XYZ + // - Parameter Expansion ${XYZ} + // - Special variable + if (n == '(') { + auto n2 = next(2); + if (n2 == '(') { + add_token(shell_token_type::arithmetic_expansion_open, 3); + push_shell_scope(shell_scope::arithmetic_expansion); + } else if (n2 == '<') { + add_token(shell_token_type::file_redirection_open, 3); + push_shell_scope(shell_scope::file_redirection); + } else { + add_token(shell_token_type::command_substitution_open, 2); + push_shell_scope(shell_scope::command_substitution); + } + } else if (ddwaf::isalnum(n) || n == '_' || n == '-' || n == '?' || n == '@' || + n == '#' || n == '*' || n == '$' || n == '!') { + tokenize_variable(); + } else if (n == '{') { + add_token(shell_token_type::parameter_expansion_open, 2); + push_shell_scope(shell_scope::parameter_expansion); + } else if (n == '[') { + add_token(shell_token_type::arithmetic_expansion_open, 2); + push_shell_scope(shell_scope::legacy_arithmetic_expansion); + } + } else if (c == ')') { + if (current_shell_scope_ == shell_scope::command_substitution) { + add_token(shell_token_type::command_substitution_close); + pop_shell_scope(); + } else if (current_shell_scope_ == shell_scope::process_substitution) { + add_token(shell_token_type::process_substitution_close); + pop_shell_scope(); + } else if (current_shell_scope_ == shell_scope::subshell) { + add_token(shell_token_type::subshell_close); + pop_shell_scope(); + } else { + add_token(shell_token_type::parenthesis_close); + } + } else if (c == '`') { + if (current_shell_scope_ == shell_scope::backtick_substitution) { + // End of the backtick command substitution, add a token for the + // final backtick and exit the scope + add_token(shell_token_type::backtick_substitution_close); + + pop_shell_scope(); + } else { + // Backtick substitution, add a token for the first backtick and + // open a new scope + add_token(shell_token_type::backtick_substitution_open); + push_shell_scope(shell_scope::backtick_substitution); + } + } else if (c == '(') { + if (!tokens_.empty() && current_token_type() == shell_token_type::equal) { + // Array + add_token(shell_token_type::array_open); + push_shell_scope(shell_scope::array); + } else if (next() == '(') { + add_token(shell_token_type::arithmetic_expansion_open, 2); + push_shell_scope(shell_scope::arithmetic_expansion); + } else if (is_beginning_of_command()) { + add_token(shell_token_type::subshell_open); + push_shell_scope(shell_scope::subshell); + } else { + add_token(shell_token_type::parenthesis_open); + } + } else if (c == '=') { + add_token(shell_token_type::equal); + } else if (c == '\n' || c == ';' || c == '!') { + add_token(shell_token_type::control); + } else if (c == '|') { + add_token(shell_token_type::control, next() == '|' ? 2 : 1); + } else if (c == '&') { + auto n = next(); + if (n == '>') { + tokenize_redirection(); + } else if (n == '&') { + add_token(shell_token_type::control, 2); + } else { + add_token(shell_token_type::control); + } + } else if (c == '{') { + auto n = next(); + if (n == ' ' && is_beginning_of_command()) { + add_token(shell_token_type::compound_command_open); + // Swallow the whitespace + advance(); + push_shell_scope(shell_scope::compound_command); + } else { + add_token(shell_token_type::curly_brace_open); + } + } else if (c == '}') { + if (current_shell_scope_ == shell_scope::compound_command && + match_last_nonws_token_with_one_of(";"sv, "\n")) { + add_token(shell_token_type::compound_command_close); + pop_shell_scope(); + } else { + add_token(shell_token_type::curly_brace_close); + } + } else if (c == '\'') { + tokenize_delimited_token("'", shell_token_type::single_quoted_string); + } else if (ddwaf::isdigit(c)) { + tokenize_redirection_or_field(); + } else if (c == '<' || c == '>') { + auto n = next(); + if (n == '(') { + add_token(shell_token_type::process_substitution_open, 2); + push_shell_scope(shell_scope::process_substitution); + } else { + tokenize_redirection(); + } + } else { + tokenize_field(); + if (next() == '=') { + current_token().type = shell_token_type::variable_definition; + } + } + } + + find_executables_and_strip_whitespaces(tokens_); + return tokens_; +} + +} // namespace ddwaf diff --git a/src/tokenizer/shell.hpp b/src/tokenizer/shell.hpp new file mode 100644 index 000000000..a408c2494 --- /dev/null +++ b/src/tokenizer/shell.hpp @@ -0,0 +1,195 @@ +// 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 "tokenizer/base.hpp" +#include +#include +#include +#include +#include +#include + +namespace ddwaf { + +enum class shell_token_type { + unknown, + whitespace, + executable, + field, + arithmetic_expansion, + arithmetic_expansion_open, + arithmetic_expansion_close, + double_quoted_string_open, + double_quoted_string_close, + double_quoted_string, + single_quoted_string, + control, + variable_definition, + variable, + equal, + backtick_substitution_open, + backtick_substitution_close, + dollar, + redirection, + command_substitution_open, + command_substitution_close, + parenthesis_open, + parenthesis_close, + curly_brace_open, + curly_brace_close, + process_substitution_open, + process_substitution_close, + subshell_open, + subshell_close, + compound_command_open, + compound_command_close, + array_open, + array_close, + parameter_expansion_open, + parameter_expansion_close, + file_redirection_open, + file_redirection_close, +}; + +using shell_token = base_token; + +std::ostream &operator<<(std::ostream &os, shell_token_type type); + +class shell_tokenizer : protected base_tokenizer { +public: + explicit shell_tokenizer( + std::string_view str, std::unordered_set skip_tokens = {}); + + std::vector tokenize(); + +protected: + enum class shell_scope { + global, + double_quoted_string, // " ... " + backtick_substitution, // ` ... ` + command_substitution, // $( ... ) + compound_command, // { ... } + subshell, // ( ... ) + process_substitution, // <() or >() + legacy_arithmetic_expansion, // $[] + arithmetic_expansion, // (()) or $(( )) + array, // =() + file_redirection, // $(< ) + parameter_expansion, // ${} + }; + + std::vector shell_scope_stack_; + shell_scope current_shell_scope_{shell_scope::global}; + + void push_shell_scope(shell_scope scope) + { + current_shell_scope_ = scope; + + shell_scope_stack_.emplace_back(scope); + } + + void pop_shell_scope() + { + shell_scope_stack_.pop_back(); + current_shell_scope_ = shell_scope_stack_.back(); + } + + shell_token_type current_token_type() const { return tokens_.back().type; } + + shell_token ¤t_token() { return tokens_.back(); } + + template + bool match_nth_nonws_token_descending(std::size_t n, T expected, Rest... args) const + { + const auto &nth_token = tokens_[n]; + if (nth_token.type == shell_token_type::whitespace) { + return n > 0 && match_nth_nonws_token_descending(n - 1, expected, args...); + } + bool res = false; + if constexpr (std::is_same_v) { + res = (nth_token.type == expected); + } + if constexpr (std::is_same_v) { + res = (nth_token.str == expected); + } + if constexpr (sizeof...(args) > 0) { + return n > 0 && res && match_nth_nonws_token_descending(n - 1, args...); + } else { + return res; + } + } + // Match each provided token or string with the relevant token or string + // starting from the end of the token array, ignoring whitespaces: + // - args[0] == tokens_[last] + // - args[1] == tokens_[last - 1] + template bool match_last_n_nonws_tokens(Args... args) const + { + if (tokens_.size() < sizeof...(Args)) { + return false; + } + + return match_nth_nonws_token_descending(tokens_.size() - 1, args...); + } + + template + bool match_last_nonws_token_with_one_of_T( + const shell_token &obtained, T expected, Rest... args) const + { + bool res = false; + if constexpr (std::is_same_v) { + res = (obtained.type == expected); + } + if constexpr (std::is_same_v || + std::is_constructible_v) { + res = (obtained.str == expected); + } + + if constexpr (sizeof...(args) > 0) { + return res || match_last_nonws_token_with_one_of_T(obtained, args...); + } else { + return res; + } + } + + template bool match_last_nonws_token_with_one_of(Args... args) const + { + auto last_it = tokens_.rbegin(); + if (last_it != tokens_.rend() && last_it->type == shell_token_type::whitespace) { + // Whitespaces are grouped together, so only one token can be expected + ++last_it; + } + + if (last_it == tokens_.rend()) { + return false; + } + + return match_last_nonws_token_with_one_of_T(*last_it, args...); + } + + bool is_beginning_of_command() + { + return tokens_.empty() || match_last_nonws_token_with_one_of(shell_token_type::control, + shell_token_type::backtick_substitution_open, + shell_token_type::command_substitution_open, + shell_token_type::process_substitution_open, + shell_token_type::compound_command_open, + shell_token_type::subshell_open, shell_token_type::control); + } + + void tokenize_single_quoted_string(); + void tokenize_expandable_scope(std::string_view delimiter, shell_token_type full_token, + shell_token_type open_token, shell_token_type close_token); + void tokenize_variable(); + void tokenize_parameter_expansion(); + void tokenize_field(shell_token_type type = shell_token_type::field); + void tokenize_redirection(); + void tokenize_redirection_or_field(); + void tokenize_delimited_token(std::string_view delimiter, shell_token_type type); +}; + +} // namespace ddwaf diff --git a/src/tokenizer/sql_base.cpp b/src/tokenizer/sql_base.cpp index 54539b59e..7709850a6 100644 --- a/src/tokenizer/sql_base.cpp +++ b/src/tokenizer/sql_base.cpp @@ -159,7 +159,7 @@ std::ostream &operator<<(std::ostream &os, sql_token_type type) template sql_tokenizer::sql_tokenizer( std::string_view str, std::unordered_set skip_tokens) - : buffer_(str), skip_tokens_(std::move(skip_tokens)) + : base_tokenizer(str, std::move(skip_tokens)) { if (!number_regex.ok()) { throw std::runtime_error("sql number regex not valid: " + number_regex.error_arg()); diff --git a/src/tokenizer/sql_base.hpp b/src/tokenizer/sql_base.hpp index c7bee8a50..8ee5a1dfc 100644 --- a/src/tokenizer/sql_base.hpp +++ b/src/tokenizer/sql_base.hpp @@ -6,6 +6,7 @@ #pragma once +#include "tokenizer/base.hpp" #include "utils.hpp" #include #include @@ -47,11 +48,7 @@ enum class sql_token_type { curly_brace_close, }; -struct sql_token { - sql_token_type type{sql_token_type::unknown}; - std::string_view str; - std::size_t index{}; -}; +using sql_token = base_token; sql_dialect sql_dialect_from_type(std::string_view type); std::string_view sql_dialect_to_string(sql_dialect dialect); @@ -67,7 +64,7 @@ template <> struct fmt::formatter : fmt::formatter class sql_tokenizer { +template class sql_tokenizer : protected base_tokenizer { public: explicit sql_tokenizer( std::string_view str, std::unordered_set skip_tokens = {}); @@ -75,59 +72,6 @@ template class sql_tokenizer { std::vector tokenize() { return static_cast(this)->tokenize_impl(); } protected: - [[nodiscard]] char peek() const - { - if (idx_ >= buffer_.size()) { - [[unlikely]] return '\0'; - } - return buffer_[idx_]; - } - [[nodiscard]] char prev(std::size_t offset = 1) const - { - if (idx_ < offset) { - [[unlikely]] return '\0'; - } - return buffer_[idx_ - 1]; - } - - bool advance(std::size_t offset = 1) { return (idx_ += offset) < buffer_.size(); } - - [[nodiscard]] char next(std::size_t offset = 1) - { - if ((idx_ + offset) >= buffer_.size()) { - [[unlikely]] return '\0'; - } - return buffer_[idx_ + offset]; - } - - bool eof() { return idx_ >= buffer_.size(); } - - [[nodiscard]] std::size_t index() const { return idx_; } - - std::string_view substr(std::size_t start, std::size_t size = std::string_view::npos) - { - return buffer_.substr(start, size); - } - - std::string_view substr() { return buffer_.substr(idx_); } - - void add_token(sql_token_type type, std::size_t size = 1) - { - sql_token token; - token.index = index(); - token.type = type; - token.str = substr(token.index, size); - emplace_token(token); - advance(token.str.size() - 1); - } - - void emplace_token(const sql_token &token) - { - if (!skip_tokens_.contains(token.type)) { - tokens_.emplace_back(token); - } - } - std::string_view extract_unescaped_string(char quote); std::string_view extract_conforming_string(char quote); std::string_view extract_escaped_string(char quote); @@ -137,11 +81,6 @@ template class sql_tokenizer { void tokenize_conforming_string(char quote, sql_token_type type); void tokenize_escaped_string(char quote, sql_token_type type); void tokenize_number(); - - std::string_view buffer_; - std::size_t idx_{0}; - std::unordered_set skip_tokens_{}; - std::vector tokens_{}; }; } // namespace ddwaf diff --git a/tests/shi_detector_test.cpp b/tests/shi_detector_test.cpp new file mode 100644 index 000000000..53996fe62 --- /dev/null +++ b/tests/shi_detector_test.cpp @@ -0,0 +1,297 @@ +// 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 "condition/shi_detector.hpp" +#include "test_utils.hpp" + +using namespace ddwaf; +using namespace std::literals; + +namespace { + +template std::vector gen_param_def(Args... addresses) +{ + return {{{{std::string{addresses}, get_target_index(addresses)}}}...}; +} + +TEST(TestSHIDetector, NoMatchAndFalsePositives) +{ + shi_detector cond{{gen_param_def("server.sys.shell.cmd", "server.request.query")}}; + + std::vector> samples{ + {R"(getconf PAGESIZE)", R"(get)"}, + {R"(cat hello)", R"(hello)"}, + {R"(file -b --mime '/tmp/ForumEntr-avec kedge20160204-37527-ctbhbi20160204-37527-tuzome.png')", + "file"}, + {R"(file -b --mime '/tmp/ForumEntr-avec kedge20160204-37527-ctbhbi20160204-37527-tuzome.png')", + "file -e"}, + {R"(echo hello)", "b"}, + {R"(phantomjs /vendor/assets/javascripts/highcharts/highcharts-convert.js -infile /app/tmp/highcharts/json/input.json -outfile /app/tmp/highcharts/png/survey_641_chart.png -width 700 2>&1)", + "641"}, + {R"(/usr/bin/generate.sh --margin-bottom 20mm --margin-top 27mm --print-media-type --header-html https://url/blabla-bla --footer-html https://url/blabla-bla https://url/blabla-bla -)", + "blabla-bla"}, + {R"(ls -l -r -t)", "-r -t"}, + {R"!({ ( $(echo ls) ) })!", "ls)"}, + }; + + for (const auto &[resource, param] : samples) { + ddwaf_object tmp; + ddwaf_object root; + + ddwaf_object_map(&root); + ddwaf_object_map_add( + &root, "server.sys.shell.cmd", ddwaf_object_string(&tmp, resource.c_str())); + ddwaf_object_map_add( + &root, "server.request.query", ddwaf_object_string(&tmp, param.c_str())); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_FALSE(res.outcome) << resource; + } +} + +TEST(TestSHIDetector, ExecutablesAndRedirections) +{ + shi_detector cond{{gen_param_def("server.sys.shell.cmd", "server.request.query")}}; + + std::vector> samples{ + {R"( ls /sqreensecure/home/zeta/repos/RubyAgentTests/weblog-rails4/public/; echo "testing"; ls robots.txt)", + R"( echo "testing"; ls robots)"}, + {"ls; echo hello", "echo hello"}, + {"ls 2> file; echo hello", "2> file"}, + {"ls &> file; echo hello", "&> file"}, + {"$(args[0].address, "server.sys.shell.cmd"); + EXPECT_STR(cache.match->args[0].resolved, resource.c_str()); + EXPECT_TRUE(cache.match->args[0].key_path.empty()); + + EXPECT_STRV(cache.match->args[1].address, "server.request.query"); + EXPECT_STR(cache.match->args[1].resolved, param.c_str()); + EXPECT_TRUE(cache.match->args[1].key_path.empty()); + + EXPECT_STR(cache.match->highlights[0], param.c_str()); + } +} + +TEST(TestSHIDetector, InjectionsWithinCommandSubstitution) +{ + shi_detector cond{{gen_param_def("server.sys.shell.cmd", "server.request.query")}}; + + std::vector> samples{ + {R"!(echo "$(cat /etc/passwd)")!", "cat /etc/passwd"}, + {R"!($(cat /etc/passwd))!", "cat /etc/passwd"}, + {R"!($(echo $(echo $(echo ls))))!", "$(echo $(echo ls))"}, + {R"!($(echo $(echo $(echo ls))))!", "echo ls"}, + {R"!(ls -l $(echo /etc/passwd))!", "-l $(echo /etc/passwd)"}, + {R"!({ ( $(echo ls) ) })!", "echo ls"}, + {R"!({ ( $(echo ls) ) })!", "$(echo ls)"}, + {R"!({ ( $(echo ls) ) })!", "( $(echo ls) )"}, + {R"!({ ( $(echo ls) ) })!", "{ ( $(echo ls) ) }"}, + }; + + for (const auto &[resource, param] : samples) { + ddwaf_object tmp; + ddwaf_object root; + + ddwaf_object_map(&root); + ddwaf_object_map_add( + &root, "server.sys.shell.cmd", ddwaf_object_string(&tmp, resource.c_str())); + ddwaf_object_map_add( + &root, "server.request.query", ddwaf_object_string(&tmp, param.c_str())); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome) << resource; + EXPECT_FALSE(res.ephemeral); + + EXPECT_TRUE(cache.match); + EXPECT_STRV(cache.match->args[0].address, "server.sys.shell.cmd"); + EXPECT_STR(cache.match->args[0].resolved, resource.c_str()); + EXPECT_TRUE(cache.match->args[0].key_path.empty()); + + EXPECT_STRV(cache.match->args[1].address, "server.request.query"); + EXPECT_STR(cache.match->args[1].resolved, param.c_str()); + EXPECT_TRUE(cache.match->args[1].key_path.empty()); + + EXPECT_STR(cache.match->highlights[0], param.c_str()); + } +} + +TEST(TestSHIDetector, InjectionsWithinProcessSubstitution) +{ + shi_detector cond{{gen_param_def("server.sys.shell.cmd", "server.request.query")}}; + + std::vector> samples{ + {R"!(echo >(ls -l))!", "ls -l"}, + {R"!(diff <(file) <(rm -rf /etc/systemd/))!", "rm -rf /etc/systemd/"}, + }; + + for (const auto &[resource, param] : samples) { + ddwaf_object tmp; + ddwaf_object root; + + ddwaf_object_map(&root); + ddwaf_object_map_add( + &root, "server.sys.shell.cmd", ddwaf_object_string(&tmp, resource.c_str())); + ddwaf_object_map_add( + &root, "server.request.query", ddwaf_object_string(&tmp, param.c_str())); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome) << resource; + EXPECT_FALSE(res.ephemeral); + + EXPECT_TRUE(cache.match); + EXPECT_STRV(cache.match->args[0].address, "server.sys.shell.cmd"); + EXPECT_STR(cache.match->args[0].resolved, resource.c_str()); + EXPECT_TRUE(cache.match->args[0].key_path.empty()); + + EXPECT_STRV(cache.match->args[1].address, "server.request.query"); + EXPECT_STR(cache.match->args[1].resolved, param.c_str()); + EXPECT_TRUE(cache.match->args[1].key_path.empty()); + + EXPECT_STR(cache.match->highlights[0], param.c_str()); + } +} + +TEST(TestSHIDetector, OffByOnePayloadsMatch) +{ + shi_detector cond{{gen_param_def("server.sys.shell.cmd", "server.request.query")}}; + + std::vector> samples{ + {R"(cat hello> cat /etc/passwd; echo "")", R"(hello>)"}, + {R"(cat hello> cat /etc/passwd; echo "")", R"(t hello)"}, + {R"(cat hello> cat /etc/passwd; echo "")", R"(cat hello)"}, + {R"!(diff <(file) <(rm -rf /etc/systemd/))!", "rm -"}, + }; + + for (const auto &[resource, param] : samples) { + ddwaf_object tmp; + ddwaf_object root; + + ddwaf_object_map(&root); + ddwaf_object_map_add( + &root, "server.sys.shell.cmd", ddwaf_object_string(&tmp, resource.c_str())); + ddwaf_object_map_add( + &root, "server.request.query", ddwaf_object_string(&tmp, param.c_str())); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome) << resource; + EXPECT_FALSE(res.ephemeral); + + EXPECT_TRUE(cache.match); + EXPECT_STRV(cache.match->args[0].address, "server.sys.shell.cmd"); + EXPECT_STR(cache.match->args[0].resolved, resource.c_str()); + EXPECT_TRUE(cache.match->args[0].key_path.empty()); + + EXPECT_STRV(cache.match->args[1].address, "server.request.query"); + EXPECT_STR(cache.match->args[1].resolved, param.c_str()); + EXPECT_TRUE(cache.match->args[1].key_path.empty()); + + EXPECT_STR(cache.match->highlights[0], param.c_str()); + } +} + +TEST(TestSHIDetector, MultipleArgumentsMatch) +{ + shi_detector cond{{gen_param_def("server.sys.shell.cmd", "server.request.query")}}; + + std::string params = R"({ + post: { + blank: "", + name: "Create", + other: "hello", + other2: "hello; ls /etc/passwd", + other3: "hello\"; cat /etc/passwd; echo \"", + other4: "\"hello\\\\\"; cat /etc/passwd; echo", + other5: "1.json 2> /tmp/toto", + other6: "1.json > /tmp/toto", + other7: "google.com; ls", + other8: "google.com; ${a:-ls}", + other9: "google.com; TOTO=ls ${a:-$TOTO}", + other10: "google.com; TOTO=ls $TOTO" + } + })"; + + std::vector samples{ + R"(cat hello; ls /etc/passwd)", + R"(cat "hello"; cat /etc/passwd; echo "")", + R"(ping -c 1 google.com; ls)", + R"(cat "hello\\"; cat /etc/passwd; echo ")", + R"(ls public/1.json 2> /tmp/toto)", + R"(ls public/1.json > /tmp/toto)", + R"(ping -c 1 google.com; ${a:-ls})", + R"(ping -c 1 google.com; TOTO=ls ${a:-$TOTO})", + R"(ping -c 1 google.com; TOTO=ls $TOTO)", + + }; + + for (const auto &resource : samples) { + ddwaf_object tmp; + ddwaf_object root; + + ddwaf_object_map(&root); + ddwaf_object_map_add( + &root, "server.sys.shell.cmd", ddwaf_object_string(&tmp, resource.c_str())); + auto params_obj = yaml_to_object(params); + ddwaf_object_map_add(&root, "server.request.query", ¶ms_obj); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome) << resource; + EXPECT_FALSE(res.ephemeral); + + EXPECT_TRUE(cache.match); + EXPECT_STRV(cache.match->args[0].address, "server.sys.shell.cmd"); + EXPECT_STR(cache.match->args[0].resolved, resource.c_str()); + EXPECT_TRUE(cache.match->args[0].key_path.empty()); + } +} + +} // namespace diff --git a/tests/ssrf_detector_test.cpp b/tests/ssrf_detector_test.cpp index 1e0cf21f4..0971a43cf 100644 --- a/tests/ssrf_detector_test.cpp +++ b/tests/ssrf_detector_test.cpp @@ -5,7 +5,6 @@ // Copyright 2021 Datadog, Inc. #include "condition/ssrf_detector.hpp" -#include "platform.hpp" #include "test_utils.hpp" using namespace ddwaf; diff --git a/tests/tokenizer/shell_tokenizer_test.cpp b/tests/tokenizer/shell_tokenizer_test.cpp new file mode 100644 index 000000000..0eadc0b9a --- /dev/null +++ b/tests/tokenizer/shell_tokenizer_test.cpp @@ -0,0 +1,733 @@ +// 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_utils.hpp" +#include "tokenizer/shell.hpp" + +using namespace ddwaf; +using namespace std::literals; + +namespace { +using stt = shell_token_type; + +TEST(TestShellTokenizer, TokenTypeOstream) +{ + auto stream_token = [](auto token) { + std::stringstream ss; + ss << token; + return ss.str(); + }; + + EXPECT_STR(stream_token(shell_token_type::unknown), "unknown"); + EXPECT_STR(stream_token(shell_token_type::whitespace), "whitespace"); + EXPECT_STR(stream_token(shell_token_type::executable), "executable"); + EXPECT_STR(stream_token(shell_token_type::field), "field"); + EXPECT_STR(stream_token(shell_token_type::arithmetic_expansion), "arithmetic_expansion"); + EXPECT_STR( + stream_token(shell_token_type::double_quoted_string_open), "double_quoted_string_open"); + EXPECT_STR( + stream_token(shell_token_type::double_quoted_string_close), "double_quoted_string_close"); + EXPECT_STR(stream_token(shell_token_type::double_quoted_string), "double_quoted_string"); + EXPECT_STR(stream_token(shell_token_type::single_quoted_string), "single_quoted_string"); + EXPECT_STR(stream_token(shell_token_type::control), "control"); + EXPECT_STR(stream_token(shell_token_type::variable_definition), "variable_definition"); + EXPECT_STR(stream_token(shell_token_type::variable), "variable"); + EXPECT_STR(stream_token(shell_token_type::equal), "equal"); + EXPECT_STR( + stream_token(shell_token_type::backtick_substitution_open), "backtick_substitution_open"); + EXPECT_STR( + stream_token(shell_token_type::backtick_substitution_close), "backtick_substitution_close"); + EXPECT_STR(stream_token(shell_token_type::dollar), "dollar"); + EXPECT_STR(stream_token(shell_token_type::redirection), "redirection"); + EXPECT_STR( + stream_token(shell_token_type::command_substitution_open), "command_substitution_open"); + EXPECT_STR( + stream_token(shell_token_type::command_substitution_close), "command_substitution_close"); + EXPECT_STR(stream_token(shell_token_type::parenthesis_open), "parenthesis_open"); + EXPECT_STR(stream_token(shell_token_type::parenthesis_close), "parenthesis_close"); + EXPECT_STR(stream_token(shell_token_type::curly_brace_open), "curly_brace_open"); + EXPECT_STR(stream_token(shell_token_type::curly_brace_close), "curly_brace_close"); + EXPECT_STR( + stream_token(shell_token_type::process_substitution_open), "process_substitution_open"); + EXPECT_STR( + stream_token(shell_token_type::process_substitution_close), "process_substitution_close"); + EXPECT_STR(stream_token(shell_token_type::subshell_open), "subshell_open"); + EXPECT_STR(stream_token(shell_token_type::subshell_close), "subshell_close"); + EXPECT_STR(stream_token(shell_token_type::compound_command_open), "compound_command_open"); + EXPECT_STR(stream_token(shell_token_type::compound_command_close), "compound_command_close"); + EXPECT_STR( + stream_token(shell_token_type::arithmetic_expansion_open), "arithmetic_expansion_open"); + EXPECT_STR( + stream_token(shell_token_type::arithmetic_expansion_close), "arithmetic_expansion_close"); + EXPECT_STR(stream_token(shell_token_type::array_open), "array_open"); + EXPECT_STR(stream_token(shell_token_type::array_close), "array_close"); + EXPECT_STR( + stream_token(shell_token_type::parameter_expansion_open), "parameter_expansion_open"); + EXPECT_STR( + stream_token(shell_token_type::parameter_expansion_close), "parameter_expansion_close"); + EXPECT_STR(stream_token(shell_token_type::file_redirection_open), "file_redirection_open"); + EXPECT_STR(stream_token(shell_token_type::file_redirection_close), "file_redirection_close"); +} + +TEST(TestShellTokenizer, Basic) +{ + std::vector>> samples{ + {"echo", {stt::executable}}, + {"$(>> samples{ + {R"(echo "stuff")", {stt::executable, stt::double_quoted_string}}, + {R"(echo "var=2")", {stt::executable, stt::double_quoted_string}}, + {R"(echo "literal $0")", {stt::executable, stt::double_quoted_string}}, + {R"(echo "$0 literal")", {stt::executable, stt::double_quoted_string}}, + {R"(echo "literal $0 literal")", {stt::executable, stt::double_quoted_string}}, + {R"(echo "l$0")", {stt::executable, stt::double_quoted_string}}, + {R"(echo "$0l")", {stt::executable, stt::double_quoted_string}}, + {R"(echo "l$0l")", {stt::executable, stt::double_quoted_string}}, + {R"("stuff")", {stt::executable}}, + {R"("var=2")", {stt::executable}}, + {R"!("$(( 1+1 ))")!", {stt::double_quoted_string_open, stt::arithmetic_expansion, + stt::double_quoted_string_close}}, + {R"!("$[ 1+1 ]")!", {stt::double_quoted_string_open, stt::arithmetic_expansion, + stt::double_quoted_string_close}}, + {R"!("$(>> samples{ + {R"(echo 'stuff')", {stt::executable, stt::single_quoted_string}}, + {R"(echo 'var=2')", {stt::executable, stt::single_quoted_string}}, + {R"(echo 'literal $0')", {stt::executable, stt::single_quoted_string}}, + // Executable + {R"('stuff')", {stt::executable}}, + {R"('var=2')", {stt::executable}}, + {R"('literal $0')", {stt::executable}}, + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, DoubleQuotedStringWithCommandSubstitution) +{ + std::vector>> samples{ + {R"!(ls "$(ls -l)")!", + {stt::executable, stt::double_quoted_string_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "literal $(ls -l)")!", + {stt::executable, stt::double_quoted_string_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "l$(ls -l)")!", + {stt::executable, stt::double_quoted_string_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "$(ls -l) literal")!", + {stt::executable, stt::double_quoted_string_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "$(ls -l)l")!", + {stt::executable, stt::double_quoted_string_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "literal $(ls -l) literal")!", + {stt::executable, stt::double_quoted_string_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "l$(ls -l)l")!", + {stt::executable, stt::double_quoted_string_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, + stt::double_quoted_string_close}}, + {R"!("$(something)" something)!", + {stt::double_quoted_string_open, stt::command_substitution_open, stt::executable, + stt::command_substitution_close, stt::double_quoted_string_close, stt::field}}, + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, DoubleQuotedStringWithBacktickSubstitution) +{ + std::vector>> samples{ + {R"!(ls "`ls -l`")!", + {stt::executable, stt::double_quoted_string_open, stt::backtick_substitution_open, + stt::executable, stt::field, stt::backtick_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "literal `ls -l`")!", + {stt::executable, stt::double_quoted_string_open, stt::backtick_substitution_open, + stt::executable, stt::field, stt::backtick_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "l`ls -l`")!", + {stt::executable, stt::double_quoted_string_open, stt::backtick_substitution_open, + stt::executable, stt::field, stt::backtick_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "`ls -l` literal")!", + {stt::executable, stt::double_quoted_string_open, stt::backtick_substitution_open, + stt::executable, stt::field, stt::backtick_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "`ls -l`l")!", + {stt::executable, stt::double_quoted_string_open, stt::backtick_substitution_open, + stt::executable, stt::field, stt::backtick_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "literal `ls -l` literal")!", + {stt::executable, stt::double_quoted_string_open, stt::backtick_substitution_open, + stt::executable, stt::field, stt::backtick_substitution_close, + stt::double_quoted_string_close}}, + {R"!(ls "l`ls -l`l")!", + {stt::executable, stt::double_quoted_string_open, stt::backtick_substitution_open, + stt::executable, stt::field, stt::backtick_substitution_close, + stt::double_quoted_string_close}}, + {R"!("`something`" something)!", + {stt::double_quoted_string_open, stt::backtick_substitution_open, stt::executable, + stt::backtick_substitution_close, stt::double_quoted_string_close, stt::field}}, + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, Executable) +{ + std::vector>> samples{ + {"echo", {stt::executable}}, + {"echo 291292;", {stt::executable, stt::field, stt::control}}, + {"echo 291292", {stt::executable, stt::field}}, + {"echo 2111111a9sd1c2d92", {stt::executable, stt::field}}, + {"test echo", {stt::executable, stt::field}}, + {"ls &", {stt::executable, stt::control}}, + {"ls & ls -l", {stt::executable, stt::control, stt::executable, stt::field}}, + {"{ echo; }", {stt::compound_command_open, stt::executable, stt::control, + stt::compound_command_close}}, + {"(ls -l)", {stt::subshell_open, stt::executable, stt::field, stt::subshell_close}}, + {"$(ls -l)", {stt::command_substitution_open, stt::executable, stt::field, + stt::command_substitution_close}}, + {"diff <(ls -l)", {stt::executable, stt::process_substitution_open, stt::executable, + stt::field, stt::process_substitution_close}}, + {"diff >(ls -l)", {stt::executable, stt::process_substitution_open, stt::executable, + stt::field, stt::process_substitution_close}}, + {"var= echo hello", {stt::variable_definition, stt::equal, stt::executable, stt::field}}, + {"var=$[1+$(echo ]2)]", + {stt::variable_definition, stt::equal, stt::arithmetic_expansion_open, + stt::command_substitution_open, stt::executable, stt::field, + stt::command_substitution_close, stt::arithmetic_expansion_close}}, + {"var=$[1+`echo 2`]", + {stt::variable_definition, stt::equal, stt::arithmetic_expansion_open, + stt::backtick_substitution_open, stt::executable, stt::field, + stt::backtick_substitution_close, stt::arithmetic_expansion_close}}, + {"(( 1 + `echo 2` ))", + {stt::arithmetic_expansion_open, stt::backtick_substitution_open, stt::executable, + stt::field, stt::backtick_substitution_close, stt::arithmetic_expansion_close}}, + {"var=($(echo 1))", + {stt::variable_definition, stt::equal, stt::array_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, stt::array_close}}, + {"var=($(echo 1) `echo 2`)", + {stt::variable_definition, stt::equal, stt::array_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, + stt::backtick_substitution_open, stt::executable, stt::field, + stt::backtick_substitution_close, stt::array_close}}, + {"var=($(echo 1) `echo 2`) ls -l", + {stt::variable_definition, stt::equal, stt::array_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, + stt::backtick_substitution_open, stt::executable, stt::field, + stt::backtick_substitution_close, stt::array_close, stt::executable, stt::field}}, + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, Pipe) +{ + std::vector>> samples{ + {"ls | cat", {stt::executable, stt::control, stt::executable}}, + {"ls & ls | cat", + {stt::executable, stt::control, stt::executable, stt::control, stt::executable}}, + {"ls -l | cat", {stt::executable, stt::field, stt::control, stt::executable}}, + {"ls -l | cat | grep passwd", {stt::executable, stt::field, stt::control, stt::executable, + stt::control, stt::executable, stt::field}}, + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, CommandSequence) +{ + std::vector>> samples{ + {"ls ; cat", {stt::executable, stt::control, stt::executable}}, + {"ls -l ; cat", {stt::executable, stt::field, stt::control, stt::executable}}, + {"ls -l ; cat ; grep passwd", {stt::executable, stt::field, stt::control, stt::executable, + stt::control, stt::executable, stt::field}}, + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, RedirectionTokens) +{ + std::vector samples{"&>>", "1>", ">", ">>", ">|", ">&", "1>&", "1>&2", "1>&2-", + "1>&-", "<", "1<", "2<&", "2<<", "2<<-", "<<-", "<<", "<<<", "1<<<", "1<&", "1<", "<&", + "1<>", "<>", "11>", "21>&", "11>&22", "21>&12-", "111>&-", "221<", "332<&", "12<<", "42<<-", + "22<<<", "11<&", "31<", "221<>"}; + + for (const auto &sample : samples) { + shell_tokenizer tokenizer(sample); + auto tokens = tokenizer.tokenize(); + EXPECT_EQ(tokens.size(), 1); + EXPECT_EQ(tokens[0].type, stt::redirection); + EXPECT_STRV(tokens[0].str, sample.c_str()); + } +} + +TEST(TestShellTokenizer, Redirections) +{ + std::vector>> samples{ + {"ls > /tmp/test args", {stt::executable, stt::redirection, stt::field, stt::field}}, + {"ls args > /tmp/test", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls > /tmp/test args", + {stt::executable, stt::redirection, stt::field, stt::field}}, + {"ls args 2> /tmp/test", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args >> /tmp/test", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args > /tmp/test 2> /etc/stderr", {stt::executable, stt::field, stt::redirection, + stt::field, stt::redirection, stt::field}}, + {"ls args>/tmp/test", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args < file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args <> file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args >| file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args <&1 file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0> file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0>> file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0< file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0<< file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0<& file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0<&- file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0<&1 file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0>& file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0>&1 file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0>&- file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0<<- file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0<> file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 0>| file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args <&221 file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args <&221", {stt::executable, stt::field, stt::redirection}}, + {"ls args 01> file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 02>> file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 03< file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 04<< file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 05<& file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 06<&- file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 01<&21 file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 01<&21", {stt::executable, stt::field, stt::redirection}}, + {"ls args 01>& file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 01>&31 file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 01>&31", {stt::executable, stt::field, stt::redirection}}, + {"ls args 01>&- file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 10<<- file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 22<> file", {stt::executable, stt::field, stt::redirection, stt::field}}, + {"ls args 33>| file", {stt::executable, stt::field, stt::redirection, stt::field}}, + // TODO invalid / seemingly redirections + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, VariableDefinition) +{ + std::vector>> samples{ + {"var=2", {stt::variable_definition, stt::equal, stt::field}}, + {"var=2; var=3", {stt::variable_definition, stt::equal, stt::field, stt::control, + stt::variable_definition, stt::equal, stt::field}}, + {"{ var=2; }", {stt::compound_command_open, stt::variable_definition, stt::equal, + stt::field, stt::control, stt::compound_command_close}}, + {"`var=2`", {stt::backtick_substitution_open, stt::variable_definition, stt::equal, + stt::field, stt::backtick_substitution_close}}, + {"(var=2)", {stt::subshell_open, stt::variable_definition, stt::equal, stt::field, + stt::subshell_close}}, + {"$(var=2)", {stt::command_substitution_open, stt::variable_definition, stt::equal, + stt::field, stt::command_substitution_close}}, + {"<(var=2)", {stt::process_substitution_open, stt::variable_definition, stt::equal, + stt::field, stt::process_substitution_close}}, + {">(var=2)", {stt::process_substitution_open, stt::variable_definition, stt::equal, + stt::field, stt::process_substitution_close}}, + {"var=(1 2 3)", {stt::variable_definition, stt::equal, stt::field}}, + {"var=$(( 1+1 ))", {stt::variable_definition, stt::equal, stt::arithmetic_expansion}}, + {"var=$[ 1+1 ]", {stt::variable_definition, stt::equal, stt::arithmetic_expansion}}, + {"var=$[1+1]", {stt::variable_definition, stt::equal, stt::arithmetic_expansion}}, + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, Variable) +{ + std::vector>> samples{ + {"echo ${var}", {stt::executable, stt::variable}}, + {"echo $var", {stt::executable, stt::variable}}, + {"echo ${var[@]}", {stt::executable, stt::variable}}, + {"echo ${var[$(echo @)]}", + {stt::executable, stt::parameter_expansion_open, stt::command_substitution_open, + stt::executable, stt::field, stt::command_substitution_close, + stt::parameter_expansion_close}}, + {"echo $0", {stt::executable, stt::variable}}, + {"echo $1", {stt::executable, stt::variable}}, + {"echo $2", {stt::executable, stt::variable}}, + {"echo $3", {stt::executable, stt::variable}}, + {"echo $4", {stt::executable, stt::variable}}, + {"echo $5", {stt::executable, stt::variable}}, + {"echo $6", {stt::executable, stt::variable}}, + {"echo $7", {stt::executable, stt::variable}}, + {"echo $8", {stt::executable, stt::variable}}, + {"echo $9", {stt::executable, stt::variable}}, + {"echo $-", {stt::executable, stt::variable}}, + {"echo $#", {stt::executable, stt::variable}}, + {"echo $@", {stt::executable, stt::variable}}, + {"echo $?", {stt::executable, stt::variable}}, + {"echo $*", {stt::executable, stt::variable}}, + {"echo $$", {stt::executable, stt::variable}}, + {"echo $!", {stt::executable, stt::variable}}, + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, MultipleCommands) +{ + std::vector>> samples{ + {"true && echo \"hello\" | tr h '' | tr e a", + {stt::executable, stt::control, stt::executable, stt::double_quoted_string, + stt::control, stt::executable, stt::field, stt::single_quoted_string, stt::control, + stt::executable, stt::field, stt::field}}, + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, ArithmeticExpansion) +{ + std::vector>> samples{ + {"(( var=1+1 ))", {stt::arithmetic_expansion}}, + {"(( var=$(echo 1) ))", + {stt::arithmetic_expansion_open, stt::command_substitution_open, stt::executable, + stt::field, stt::command_substitution_close, stt::arithmetic_expansion_close}}, + {"(( var=`echo 1` ))", + {stt::arithmetic_expansion_open, stt::backtick_substitution_open, stt::executable, + stt::field, stt::backtick_substitution_close, stt::arithmetic_expansion_close}}, + {"command (( var=`echo 1` )) -2 -3", + {stt::executable, stt::arithmetic_expansion_open, stt::backtick_substitution_open, + stt::executable, stt::field, stt::backtick_substitution_close, + stt::arithmetic_expansion_close, stt::field, stt::field}}, + {"command (( var=${hello} )) -2 -3", + {stt::executable, stt::arithmetic_expansion_open, stt::variable, + stt::arithmetic_expansion_close, stt::field, stt::field}}, + + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, Negation) +{ + std::vector>> samples{ + {"! ls", {stt::control, stt::executable}}}; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, CompoundCommands) +{ + std::vector>> samples{ + {"{ echo; }", {stt::compound_command_open, stt::executable, stt::control, + stt::compound_command_close}}, + {"{ ls -l ; echo hello; }", + {stt::compound_command_open, stt::executable, stt::field, stt::control, stt::executable, + stt::field, stt::control, stt::compound_command_close}}, + {"{ ls -l | grep passwd; }", + {stt::compound_command_open, stt::executable, stt::field, stt::control, stt::executable, + stt::field, stt::control, stt::compound_command_close}}, + {R"({ "command"; })", {stt::compound_command_open, stt::executable, stt::control, + stt::compound_command_close}}, + {R"({ 'command'; })", {stt::compound_command_open, stt::executable, stt::control, + stt::compound_command_close}}, + {R"({ "ls" -l; })", {stt::compound_command_open, stt::executable, stt::field, stt::control, + stt::compound_command_close}}, + {R"({ 'ls' -l; })", {stt::compound_command_open, stt::executable, stt::field, stt::control, + stt::compound_command_close}}, + {R"({ var=10 ls -l; })", + {stt::compound_command_open, stt::variable_definition, stt::equal, stt::field, + stt::executable, stt::field, stt::control, stt::compound_command_close}}, + {R"({ var= ls -l; })", + {stt::compound_command_open, stt::variable_definition, stt::equal, stt::executable, + stt::field, stt::control, stt::compound_command_close}}, + {R"!({ "$(echo ls)" -l; })!", + {stt::compound_command_open, stt::double_quoted_string_open, + stt::command_substitution_open, stt::executable, stt::field, + stt::command_substitution_close, stt::double_quoted_string_close, stt::field, + stt::control, stt::compound_command_close}}, + {R"!({ echo }; })!", {stt::compound_command_open, stt::executable, stt::curly_brace_close, + stt::control, stt::compound_command_close}}, + {R"!({ echo {}; })!", + {stt::compound_command_open, stt::executable, stt::curly_brace_open, + stt::curly_brace_close, stt::control, stt::compound_command_close}}, + {R"!({ echo {};})!", + {stt::compound_command_open, stt::executable, stt::curly_brace_open, + stt::curly_brace_close, stt::control, stt::compound_command_close}}, + {R"!({ echo { }; })!", + {stt::compound_command_open, stt::executable, stt::curly_brace_open, + stt::curly_brace_close, stt::control, stt::compound_command_close}}, + {R"!(echo { }; { echo; })!", + {stt::executable, stt::curly_brace_open, stt::curly_brace_close, stt::control, + stt::compound_command_open, stt::executable, stt::control, + stt::compound_command_close}}, + {R"!(echo { }; { echo + })!", + {stt::executable, stt::curly_brace_open, stt::curly_brace_close, stt::control, + stt::compound_command_open, stt::executable, stt::control, + stt::compound_command_close}}, + + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, Subshell) +{ + std::vector>> samples{ + {"( echo )", {stt::subshell_open, stt::executable, stt::subshell_close}}, + {"ls | ( echo )", {stt::executable, stt::control, stt::subshell_open, stt::executable, + stt::subshell_close}}, + {"( ls -l ) | grep Docs", + {stt::subshell_open, stt::executable, stt::field, stt::subshell_close, stt::control, + stt::executable, stt::field}}, + {"( ls $( echo -l ) )", + {stt::subshell_open, stt::executable, stt::command_substitution_open, stt::executable, + stt::field, stt::command_substitution_close, stt::subshell_close}}, + }; + + for (const auto &[input, expected_tokens] : samples) { + shell_tokenizer tokenizer(input); + + auto tokens = tokenizer.tokenize(); + ASSERT_EQ(tokens.size(), expected_tokens.size()) << input; + + for (std::size_t i = 0; i < tokens.size(); ++i) { + EXPECT_EQ(tokens[i].type, expected_tokens[i]) << input; + } + } +} + +TEST(TestShellTokenizer, FileRedirection) +{ + std::vector>> samples{ + {"$( Date: Tue, 16 Jul 2024 15:25:39 +0100 Subject: [PATCH 11/17] HTTP Endpoint Fingerprint Processor (#318) --- cmake/objects.cmake | 1 + src/builder/processor_builder.cpp | 11 + src/builder/processor_builder.hpp | 6 +- src/obfuscator.hpp | 2 +- src/parser/processor_parser.cpp | 55 ++- src/processor/fingerprint.cpp | 301 +++++++++++++++ src/processor/fingerprint.hpp | 81 ++++ src/sha256.cpp | 45 ++- src/sha256.hpp | 10 +- .../ruleset/postprocessor.json | 0 .../ruleset/preprocessor.json | 0 .../ruleset/processor.json | 0 .../ruleset/processor_with_scanner_by_id.json | 0 .../processor_with_scanner_by_tags.json | 0 .../processors/{ => extract_schema}/test.cpp | 30 +- .../fingerprint/ruleset/postprocessor.json | 82 ++++ .../fingerprint/ruleset/preprocessor.json | 81 ++++ .../fingerprint/ruleset/processor.json | 81 ++++ .../processors/fingerprint/test.cpp | 202 ++++++++++ tests/processor/fingerprint_test.cpp | 365 ++++++++++++++++++ tools/waf_runner.cpp | 2 +- 21 files changed, 1304 insertions(+), 51 deletions(-) create mode 100644 src/processor/fingerprint.cpp create mode 100644 src/processor/fingerprint.hpp rename tests/integration/processors/{ => extract_schema}/ruleset/postprocessor.json (100%) rename tests/integration/processors/{ => extract_schema}/ruleset/preprocessor.json (100%) rename tests/integration/processors/{ => extract_schema}/ruleset/processor.json (100%) rename tests/integration/processors/{ => extract_schema}/ruleset/processor_with_scanner_by_id.json (100%) rename tests/integration/processors/{ => extract_schema}/ruleset/processor_with_scanner_by_tags.json (100%) rename tests/integration/processors/{ => extract_schema}/test.cpp (97%) create mode 100644 tests/integration/processors/fingerprint/ruleset/postprocessor.json create mode 100644 tests/integration/processors/fingerprint/ruleset/preprocessor.json create mode 100644 tests/integration/processors/fingerprint/ruleset/processor.json create mode 100644 tests/integration/processors/fingerprint/test.cpp create mode 100644 tests/processor/fingerprint_test.cpp diff --git a/cmake/objects.cmake b/cmake/objects.cmake index b793404d7..0c50f21ff 100644 --- a/cmake/objects.cmake +++ b/cmake/objects.cmake @@ -46,6 +46,7 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/parser/scanner_parser.cpp ${libddwaf_SOURCE_DIR}/src/parser/exclusion_parser.cpp ${libddwaf_SOURCE_DIR}/src/processor/extract_schema.cpp + ${libddwaf_SOURCE_DIR}/src/processor/fingerprint.cpp ${libddwaf_SOURCE_DIR}/src/condition/lfi_detector.cpp ${libddwaf_SOURCE_DIR}/src/condition/sqli_detector.cpp ${libddwaf_SOURCE_DIR}/src/condition/ssrf_detector.cpp diff --git a/src/builder/processor_builder.cpp b/src/builder/processor_builder.cpp index b81de6337..7ac0a572c 100644 --- a/src/builder/processor_builder.cpp +++ b/src/builder/processor_builder.cpp @@ -8,6 +8,7 @@ #include "builder/processor_builder.hpp" #include "processor/extract_schema.hpp" +#include "processor/fingerprint.hpp" namespace ddwaf { @@ -42,6 +43,14 @@ template <> struct typed_processor_builder { } }; +template <> struct typed_processor_builder { + std::shared_ptr build(const auto &spec) + { + return std::make_shared( + spec.id, spec.expr, spec.mappings, spec.evaluate, spec.output); + } +}; + template concept has_build_with_scanners = requires(typed_processor_builder b, Spec spec, Scanners scanners) { @@ -70,6 +79,8 @@ template switch (type) { case processor_type::extract_schema: return build_with_type(*this, scanners); + case processor_type::http_endpoint_fingerprint: + return build_with_type(*this, scanners); default: break; } diff --git a/src/builder/processor_builder.hpp b/src/builder/processor_builder.hpp index f2d220e1b..0ab3be0f0 100644 --- a/src/builder/processor_builder.hpp +++ b/src/builder/processor_builder.hpp @@ -19,10 +19,10 @@ namespace ddwaf { enum class processor_type : unsigned { extract_schema, // Reserved - http_fingerprint, + http_endpoint_fingerprint, + http_network_fingerprint, + http_header_fingerprint, session_fingerprint, - network_fingerprint, - header_fingerprint, }; struct processor_builder { diff --git a/src/obfuscator.hpp b/src/obfuscator.hpp index 3dd01ad1d..dfa404a60 100644 --- a/src/obfuscator.hpp +++ b/src/obfuscator.hpp @@ -29,7 +29,7 @@ class obfuscator { static constexpr std::string_view redaction_msg{""}; static constexpr std::string_view default_key_regex_str{ - R"((?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key)|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\.net[_-]sessionid|sid|jwt)"}; + R"((?i)pass|pw(?:or)?d|secret|(?:api|private|public|access)[_-]?key|token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\.net[_-]sessionid|sid|jwt)"}; protected: std::unique_ptr key_regex{nullptr}; diff --git a/src/parser/processor_parser.cpp b/src/parser/processor_parser.cpp index abac7e45d..8b9a9a45e 100644 --- a/src/parser/processor_parser.cpp +++ b/src/parser/processor_parser.cpp @@ -8,13 +8,15 @@ #include "parser/common.hpp" #include "parser/parser.hpp" #include "processor/base.hpp" +#include "processor/extract_schema.hpp" +#include "processor/fingerprint.hpp" #include namespace ddwaf::parser::v2 { namespace { std::vector parse_processor_mappings( - const parameter::vector &root, address_container &addresses) + const parameter::vector &root, address_container &addresses, const auto ¶m_names) { if (root.empty()) { throw ddwaf::parsing_error("empty mappings"); @@ -24,22 +26,26 @@ std::vector parse_processor_mappings( for (const auto &node : root) { auto mapping = static_cast(node); - // TODO support n:1 mappings and key paths - auto inputs = at(mapping, "inputs"); - if (inputs.empty()) { - throw ddwaf::parsing_error("empty processor input mapping"); - } + std::vector parameters; + for (const auto ¶m : param_names) { + // TODO support n:1 mappings and key paths + auto inputs = at(mapping, param); + if (inputs.empty()) { + throw ddwaf::parsing_error("empty processor input mapping"); + } - auto input = static_cast(inputs[0]); - auto input_address = at(input, "address"); - auto output = at(mapping, "output"); + auto input = static_cast(inputs[0]); + auto input_address = at(input, "address"); - addresses.optional.emplace(input_address); + addresses.optional.emplace(input_address); + parameters.emplace_back(processor_parameter{ + {processor_target{get_target_index(input_address), std::move(input_address), {}}}}); + } + + auto output = at(mapping, "output"); mappings.emplace_back(processor_mapping{ - {processor_parameter{ - {processor_target{get_target_index(input_address), std::move(input_address), {}}}}}, - {get_target_index(output), std::move(output), {}}}); + std::move(parameters), {get_target_index(output), std::move(output), {}}}); } return mappings; @@ -71,6 +77,20 @@ processor_container parse_processors( auto generator_id = at(node, "generator"); if (generator_id == "extract_schema") { type = processor_type::extract_schema; + } else if (generator_id == "http_endpoint_fingerprint") { + type = processor_type::http_endpoint_fingerprint; + } else if (generator_id == "http_network_fingerprint") { + type = processor_type::http_network_fingerprint; + // Skip for now + continue; + } else if (generator_id == "http_header_fingerprint") { + type = processor_type::http_header_fingerprint; + // Skip for now + continue; + } else if (generator_id == "session_fingerprint") { + type = processor_type::session_fingerprint; + // Skip for now + continue; } else { DDWAF_WARN("Unknown generator: {}", generator_id); info.add_failed(id, "unknown generator '" + generator_id + "'"); @@ -82,7 +102,14 @@ processor_container parse_processors( auto params = at(node, "parameters"); auto mappings_vec = at(params, "mappings"); - auto mappings = parse_processor_mappings(mappings_vec, addresses); + std::vector mappings; + if (type == processor_type::extract_schema) { + mappings = + parse_processor_mappings(mappings_vec, addresses, extract_schema::param_names); + } else { + mappings = parse_processor_mappings( + mappings_vec, addresses, http_endpoint_fingerprint::param_names); + } std::vector scanners; auto scanners_ref_array = at(params, "scanners", {}); diff --git a/src/processor/fingerprint.cpp b/src/processor/fingerprint.cpp new file mode 100644 index 000000000..774fc728c --- /dev/null +++ b/src/processor/fingerprint.cpp @@ -0,0 +1,301 @@ +// 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 "processor/fingerprint.hpp" +#include "sha256.hpp" +#include "transformer/lowercase.hpp" +#include + +namespace ddwaf { +namespace { + +struct string_buffer { + explicit string_buffer(std::size_t length) + // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc,cppcoreguidelines-pro-type-reinterpret-cast) + : buffer(reinterpret_cast(malloc(sizeof(char) * length))), length(length) + { + if (buffer == nullptr) { + throw std::bad_alloc{}; + } + } + + // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc,cppcoreguidelines-pro-type-reinterpret-cast) + ~string_buffer() { free(buffer); } + + string_buffer(const string_buffer &) = delete; + string_buffer(string_buffer &&) = delete; + + string_buffer &operator=(const string_buffer &) = delete; + string_buffer &operator=(string_buffer &&) = delete; + + template + [[nodiscard]] std::span subspan() + requires(N > 0) + { + if ((index + N - 1) >= length) { + throw std::out_of_range("span[index, N) beyond buffer limit"); + } + + std::span res{&buffer[index], N}; + index += N; + return res; + } + + void append(std::string_view str) + { + if (str.empty()) { + return; + } + + if ((index + str.length() - 1) >= length) { + throw std::out_of_range("appending string beyond buffer limit"); + } + memcpy(&buffer[index], str.data(), str.size()); + index += str.size(); + } + + void append_lowercase(std::string_view str) + { + if (str.empty()) { + return; + } + + if ((index + str.length() - 1) >= length) { + throw std::out_of_range("appending string beyond buffer limit"); + } + + for (auto c : str) { buffer[index++] = ddwaf::tolower(c); } + } + + template + void append(std::array str) + requires(N > 0) + { + append(std::string_view{str.data(), N}); + } + + template + void append_lowercase(std::array str) + requires(N > 0) + { + append_lowercase(std::string_view{str.data(), N}); + } + + void append(char c) { append(std::string_view{&c, 1}); } + + std::pair move() + { + auto *ptr = buffer; + buffer = nullptr; + return {ptr, index}; + } + + char *buffer{nullptr}; + std::size_t index{0}; + std::size_t length; +}; + +struct field_generator { + field_generator() = default; + virtual ~field_generator() = default; + field_generator(const field_generator &) = default; + field_generator(field_generator &&) = default; + field_generator &operator=(const field_generator &) = default; + field_generator &operator=(field_generator &&) = default; + + virtual std::size_t length() = 0; + virtual void operator()(string_buffer &output) = 0; +}; + +struct string_field : field_generator { + explicit string_field(std::string_view input) : value(input) {} + ~string_field() override = default; + string_field(const string_field &) = default; + string_field(string_field &&) = default; + string_field &operator=(const string_field &) = default; + string_field &operator=(string_field &&) = default; + + std::size_t length() override { return value.size(); } + void operator()(string_buffer &output) override { output.append_lowercase(value); } + + std::string_view value; +}; + +struct string_hash_field : field_generator { + explicit string_hash_field(std::string_view input) : value(input) {} + ~string_hash_field() override = default; + string_hash_field(const string_hash_field &) = default; + string_hash_field(string_hash_field &&) = default; + string_hash_field &operator=(const string_hash_field &) = default; + string_hash_field &operator=(string_hash_field &&) = default; + + std::size_t length() override { return 8; } + void operator()(string_buffer &output) override; + + std::string_view value; +}; + +struct key_hash_field : field_generator { + explicit key_hash_field(const ddwaf_object &input) : value(input) {} + ~key_hash_field() override = default; + key_hash_field(const key_hash_field &) = default; + key_hash_field(key_hash_field &&) = default; + key_hash_field &operator=(const key_hash_field &) = default; + key_hash_field &operator=(key_hash_field &&) = default; + + std::size_t length() override { return value.type == DDWAF_OBJ_MAP ? 8 : 0; } + void operator()(string_buffer &output) override; + + ddwaf_object value; +}; + +template std::size_t generate_fragment_length(Generators &...generators) +{ + static_assert(sizeof...(generators) > 0, "At least one generator is required"); + return (generators.length() + ...) + sizeof...(generators) - 1; +} + +template +void generate_fragment_field(string_buffer &buffer, T &generator, Rest... rest) +{ + generator(buffer); + if constexpr (sizeof...(rest) > 0) { + buffer.append('-'); + generate_fragment_field(buffer, rest...); + } +} + +template +ddwaf_object generate_fragment(std::string_view header, Generators... generators) +{ + std::size_t total_length = header.size() + 1 + generate_fragment_length(generators...); + + string_buffer buffer{total_length}; + buffer.append_lowercase(header); + buffer.append('-'); + + generate_fragment_field(buffer, generators...); + + ddwaf_object res; + auto [ptr, size] = buffer.move(); + ddwaf_object_stringl_nc(&res, ptr, size); + + return res; +} + +// Return true if the first argument is less than (i.e. is ordered before) the second +bool str_casei_cmp(std::string_view left, std::string_view right) +{ + auto n = std::min(left.size(), right.size()); + for (std::size_t i = 0; i < n; ++i) { + auto lc = ddwaf::tolower(left[i]); + auto rc = ddwaf::tolower(right[i]); + if (lc != rc) { + return lc < rc; + } + } + return left.size() <= right.size(); +} + +void normalize_string(std::string_view key, std::string &buffer, bool trailing_separator) +{ + buffer.clear(); + + if (buffer.capacity() < key.size()) { + // Add space for the extra comma, just in case + buffer.reserve(key.size() + 1); + } + + for (auto c : key) { + if (c == ',') { + buffer.append(R"(\,)"); + } else { + buffer.append(1, ddwaf::tolower(c)); + } + } + + if (trailing_separator) { + buffer.append(1, ','); + } +} + +void string_hash_field::operator()(string_buffer &output) +{ + if (value.empty()) { + return; + } + + cow_string value_lc{value}; + transformer::lowercase::transform(value_lc); + + sha256_hash hasher; + hasher << static_cast(value_lc); + + hasher.write_digest(output.subspan<8>()); +} + +void key_hash_field::operator()(string_buffer &output) +{ + if (value.type != DDWAF_OBJ_MAP or value.nbEntries == 0) { + return; + } + + std::vector keys; + keys.reserve(value.nbEntries); + + for (unsigned i = 0; i < value.nbEntries; ++i) { + const auto &child = value.array[i]; + + std::string_view key{ + child.parameterName, static_cast(child.parameterNameLength)}; + + keys.emplace_back(key); + } + + std::sort(keys.begin(), keys.end(), str_casei_cmp); + + sha256_hash hasher; + std::string normalized; + for (unsigned i = 0; i < keys.size(); ++i) { + normalize_string(keys[i], normalized, (i + 1) < keys.size()); + hasher << normalized; + } + + hasher.write_digest(output.subspan<8>()); +} + +} // namespace + +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +std::pair http_endpoint_fingerprint::eval_impl( + const unary_argument &method, const unary_argument &uri_raw, + const unary_argument &query, + const unary_argument &body, ddwaf::timer &deadline) const +{ + if (deadline.expired()) { + throw ddwaf::timeout_exception(); + } + + // Strip query parameter from raw URI + auto stripped_uri = uri_raw.value; + auto query_or_frag_idx = stripped_uri.find_first_of("?#"); + if (query_or_frag_idx != std::string_view::npos) { + stripped_uri = stripped_uri.substr(0, query_or_frag_idx); + } + + ddwaf_object res; + ddwaf_object_invalid(&res); + try { + res = generate_fragment("http", string_field{method.value}, string_hash_field{stripped_uri}, + key_hash_field{*query.value}, key_hash_field{*body.value}); + } catch (const std::out_of_range &e) { + DDWAF_WARN("Failed to generate http endpoint fingerprint: {}", e.what()); + } + + return {res, object_store::attribute::none}; +} + +} // namespace ddwaf diff --git a/src/processor/fingerprint.hpp b/src/processor/fingerprint.hpp new file mode 100644 index 000000000..c6fb823bc --- /dev/null +++ b/src/processor/fingerprint.hpp @@ -0,0 +1,81 @@ +// 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 +#include +#include +#include + +#include "processor/base.hpp" +#include "scanner.hpp" +#include "utils.hpp" + +namespace ddwaf { + +class http_endpoint_fingerprint : public structured_processor { +public: + static constexpr std::array param_names{ + "method", "uri_raw", "query", "body"}; + + http_endpoint_fingerprint(std::string id, std::shared_ptr expr, + std::vector mappings, bool evaluate, bool output) + : structured_processor( + std::move(id), std::move(expr), std::move(mappings), evaluate, output) + {} + + std::pair eval_impl( + const unary_argument &method, + const unary_argument &uri_raw, + const unary_argument &query, + const unary_argument &body, ddwaf::timer &deadline) const; +}; + +class http_header_fingerprint : public structured_processor { +public: + static constexpr std::array param_names{"headers"}; + + http_header_fingerprint(std::string id, std::shared_ptr expr, + std::vector mappings, bool evaluate, bool output) + : structured_processor( + std::move(id), std::move(expr), std::move(mappings), evaluate, output) + {} + + std::pair eval_impl( + const unary_argument &headers, ddwaf::timer &deadline) const; +}; + +class http_network_fingerprint : public structured_processor { +public: + static constexpr std::array param_names{"headers"}; + + http_network_fingerprint(std::string id, std::shared_ptr expr, + std::vector mappings, bool evaluate, bool output) + : structured_processor( + std::move(id), std::move(expr), std::move(mappings), evaluate, output) + {} + + std::pair eval_impl( + const unary_argument &headers, ddwaf::timer &deadline) const; +}; + +class session_fingerprint : public structured_processor { +public: + static constexpr std::array param_names{ + "cookies", "session_id", "user_id"}; + + session_fingerprint(std::string id, std::shared_ptr expr, + std::vector mappings, bool evaluate, bool output) + : structured_processor( + std::move(id), std::move(expr), std::move(mappings), evaluate, output) + {} + + std::pair eval_impl( + const unary_argument &cookies, + const unary_argument &session_id, + const unary_argument &user_id, ddwaf::timer &deadline) const; +}; + +} // namespace ddwaf diff --git a/src/sha256.cpp b/src/sha256.cpp index 7ef19a7ee..cabc8bb51 100644 --- a/src/sha256.cpp +++ b/src/sha256.cpp @@ -15,6 +15,9 @@ // #include "sha256.hpp" +#include +#include +#include namespace ddwaf { namespace { @@ -84,9 +87,6 @@ constexpr std::array K256 = {0x428a2f98UL, 0x71374491UL, 0xb5c0fbc 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL}; -// Length of the string representation of the digest -constexpr std::size_t sha_digest_length = 64; - } // namespace sha256_hash &sha256_hash::operator<<(std::string_view str) @@ -145,7 +145,22 @@ sha256_hash &sha256_hash::operator<<(std::string_view str) return *this; } +template std::string sha256_hash::digest() + requires(DigestLength % 8 == 0 && DigestLength <= 64) +{ + std::string final_digest; + final_digest.resize(DigestLength, 0); + write_digest(std::span{final_digest}); + return final_digest; +} + +template std::string sha256_hash::digest<64>(); +template std::string sha256_hash::digest<8>(); + +template +void sha256_hash::write_digest(std::span output) + requires(DigestLength % 8 == 0 && DigestLength <= 64) { auto *p = buffer.data(); size_t n = num; @@ -178,25 +193,25 @@ std::string sha256_hash::digest() num = 0; memset(p, 0, block_size); - std::array final_digest{0}; - for (unsigned int nn = 0; nn < sha_digest_length; nn += 8) { + for (unsigned int nn = 0; nn < DigestLength; nn += 8) { uint32_t ll = hash[nn >> 3]; - final_digest[nn + 0] = UINT8_TO_HEX_CHAR(static_cast((ll >> 28) & 0x0f)); - final_digest[nn + 1] = UINT8_TO_HEX_CHAR(static_cast((ll >> 24) & 0x0f)); - final_digest[nn + 2] = UINT8_TO_HEX_CHAR(static_cast((ll >> 20) & 0x0f)); - final_digest[nn + 3] = UINT8_TO_HEX_CHAR(static_cast((ll >> 16) & 0x0f)); - final_digest[nn + 4] = UINT8_TO_HEX_CHAR(static_cast((ll >> 12) & 0x0f)); - final_digest[nn + 5] = UINT8_TO_HEX_CHAR(static_cast((ll >> 8) & 0x0f)); - final_digest[nn + 6] = UINT8_TO_HEX_CHAR(static_cast((ll >> 4) & 0x0f)); - final_digest[nn + 7] = UINT8_TO_HEX_CHAR(static_cast(ll & 0x0f)); + output[nn + 0] = UINT8_TO_HEX_CHAR(static_cast((ll >> 28) & 0x0f)); + output[nn + 1] = UINT8_TO_HEX_CHAR(static_cast((ll >> 24) & 0x0f)); + output[nn + 2] = UINT8_TO_HEX_CHAR(static_cast((ll >> 20) & 0x0f)); + output[nn + 3] = UINT8_TO_HEX_CHAR(static_cast((ll >> 16) & 0x0f)); + output[nn + 4] = UINT8_TO_HEX_CHAR(static_cast((ll >> 12) & 0x0f)); + output[nn + 5] = UINT8_TO_HEX_CHAR(static_cast((ll >> 8) & 0x0f)); + output[nn + 6] = UINT8_TO_HEX_CHAR(static_cast((ll >> 4) & 0x0f)); + output[nn + 7] = UINT8_TO_HEX_CHAR(static_cast(ll & 0x0f)); } // Reset the hasher and return reset(); - - return std::string{final_digest.data(), 64}; } +template void sha256_hash::write_digest<8>(std::span output); +template void sha256_hash::write_digest<64>(std::span output); + void sha256_hash::sha_block_data_order(const uint8_t *data, size_t len) { unsigned int a; diff --git a/src/sha256.hpp b/src/sha256.hpp index fe03e7c36..59bdcd69a 100644 --- a/src/sha256.hpp +++ b/src/sha256.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -21,7 +22,13 @@ class sha256_hash { sha256_hash &operator=(sha256_hash &&) noexcept = delete; sha256_hash &operator<<(std::string_view str); - [[nodiscard]] std::string digest(); + template + [[nodiscard]] std::string digest() + requires(N % 8 == 0 && N <= 64); + + template + void write_digest(std::span output) + requires(N % 8 == 0 && N <= 64); void reset() { @@ -38,7 +45,6 @@ class sha256_hash { 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; void sha_block_data_order(const uint8_t *data, size_t len); - std::array hash{initial_hash_values}; uint32_t length_low{0}; uint32_t length_high{0}; diff --git a/tests/integration/processors/ruleset/postprocessor.json b/tests/integration/processors/extract_schema/ruleset/postprocessor.json similarity index 100% rename from tests/integration/processors/ruleset/postprocessor.json rename to tests/integration/processors/extract_schema/ruleset/postprocessor.json diff --git a/tests/integration/processors/ruleset/preprocessor.json b/tests/integration/processors/extract_schema/ruleset/preprocessor.json similarity index 100% rename from tests/integration/processors/ruleset/preprocessor.json rename to tests/integration/processors/extract_schema/ruleset/preprocessor.json diff --git a/tests/integration/processors/ruleset/processor.json b/tests/integration/processors/extract_schema/ruleset/processor.json similarity index 100% rename from tests/integration/processors/ruleset/processor.json rename to tests/integration/processors/extract_schema/ruleset/processor.json diff --git a/tests/integration/processors/ruleset/processor_with_scanner_by_id.json b/tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_id.json similarity index 100% rename from tests/integration/processors/ruleset/processor_with_scanner_by_id.json rename to tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_id.json diff --git a/tests/integration/processors/ruleset/processor_with_scanner_by_tags.json b/tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_tags.json similarity index 100% rename from tests/integration/processors/ruleset/processor_with_scanner_by_tags.json rename to tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_tags.json diff --git a/tests/integration/processors/test.cpp b/tests/integration/processors/extract_schema/test.cpp similarity index 97% rename from tests/integration/processors/test.cpp rename to tests/integration/processors/extract_schema/test.cpp index 7d6fefd12..fdaed7461 100644 --- a/tests/integration/processors/test.cpp +++ b/tests/integration/processors/extract_schema/test.cpp @@ -4,14 +4,14 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. -#include "../../test_utils.hpp" +#include "../../../test_utils.hpp" using namespace ddwaf; namespace { -constexpr std::string_view base_dir = "integration/processors/"; +constexpr std::string_view base_dir = "integration/processors/extract_schema"; -TEST(TestProcessors, Postprocessor) +TEST(TestExtractSchemaIntegration, Postprocessor) { auto rule = read_json_file("postprocessor.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -55,7 +55,7 @@ TEST(TestProcessors, Postprocessor) ddwaf_destroy(handle); } -TEST(TestProcessors, Preprocessor) +TEST(TestExtractSchemaIntegration, Preprocessor) { auto rule = read_json_file("preprocessor.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -108,7 +108,7 @@ TEST(TestProcessors, Preprocessor) ddwaf_destroy(handle); } -TEST(TestProcessors, Processor) +TEST(TestExtractSchemaIntegration, Processor) { auto rule = read_json_file("processor.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -166,7 +166,7 @@ TEST(TestProcessors, Processor) ddwaf_destroy(handle); } -TEST(TestProcessors, ProcessorWithScannerByTags) +TEST(TestExtractSchemaIntegration, ProcessorWithScannerByTags) { auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -203,7 +203,7 @@ TEST(TestProcessors, ProcessorWithScannerByTags) ddwaf_destroy(handle); } -TEST(TestProcessors, ProcessorWithScannerByID) +TEST(TestExtractSchemaIntegration, ProcessorWithScannerByID) { auto rule = read_json_file("processor_with_scanner_by_id.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -241,7 +241,7 @@ TEST(TestProcessors, ProcessorWithScannerByID) ddwaf_destroy(handle); } -TEST(TestProcessors, ProcessorUpdate) +TEST(TestExtractSchemaIntegration, ProcessorUpdate) { auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -318,7 +318,7 @@ TEST(TestProcessors, ProcessorUpdate) ddwaf_destroy(handle); } -TEST(TestProcessors, ScannerUpdate) +TEST(TestExtractSchemaIntegration, ScannerUpdate) { auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -397,7 +397,7 @@ TEST(TestProcessors, ScannerUpdate) ddwaf_destroy(handle); } -TEST(TestProcessors, ProcessorAndScannerUpdate) +TEST(TestExtractSchemaIntegration, ProcessorAndScannerUpdate) { auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -475,7 +475,7 @@ TEST(TestProcessors, ProcessorAndScannerUpdate) ddwaf_destroy(handle); } -TEST(TestProcessors, EmptyScannerUpdate) +TEST(TestExtractSchemaIntegration, EmptyScannerUpdate) { auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -552,7 +552,7 @@ TEST(TestProcessors, EmptyScannerUpdate) ddwaf_destroy(handle); } -TEST(TestProcessors, EmptyProcessorUpdate) +TEST(TestExtractSchemaIntegration, EmptyProcessorUpdate) { auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -627,7 +627,7 @@ TEST(TestProcessors, EmptyProcessorUpdate) ddwaf_destroy(handle); } -TEST(TestProcessors, PostprocessorWithEphemeralMapping) +TEST(TestExtractSchemaIntegration, PostprocessorWithEphemeralMapping) { auto rule = read_json_file("postprocessor.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -694,7 +694,7 @@ TEST(TestProcessors, PostprocessorWithEphemeralMapping) ddwaf_destroy(handle); } -TEST(TestProcessors, PreprocessorWithEphemeralMapping) +TEST(TestExtractSchemaIntegration, PreprocessorWithEphemeralMapping) { auto rule = read_json_file("preprocessor.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -777,7 +777,7 @@ TEST(TestProcessors, PreprocessorWithEphemeralMapping) ddwaf_destroy(handle); } -TEST(TestProcessors, ProcessorEphemeralExpression) +TEST(TestExtractSchemaIntegration, ProcessorEphemeralExpression) { auto rule = read_json_file("processor.json", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); diff --git a/tests/integration/processors/fingerprint/ruleset/postprocessor.json b/tests/integration/processors/fingerprint/ruleset/postprocessor.json new file mode 100644 index 000000000..4d210d4e3 --- /dev/null +++ b/tests/integration/processors/fingerprint/ruleset/postprocessor.json @@ -0,0 +1,82 @@ +{ + "version": "2.2", + "metadata": { + "rules_version": "1.8.0" + }, + "rules": [ + { + "id": "rule1", + "name": "rule1", + "tags": { + "type": "flow1", + "category": "category1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.body" + } + ], + "value": 8, + "type": "unsigned" + }, + "operator": "equals" + } + ] + } + ], + "processors": [ + { + "id": "processor-001", + "generator": "http_endpoint_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "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 + } + ] +} diff --git a/tests/integration/processors/fingerprint/ruleset/preprocessor.json b/tests/integration/processors/fingerprint/ruleset/preprocessor.json new file mode 100644 index 000000000..d3d4ce0df --- /dev/null +++ b/tests/integration/processors/fingerprint/ruleset/preprocessor.json @@ -0,0 +1,81 @@ +{ + "version": "2.2", + "metadata": { + "rules_version": "1.8.0" + }, + "rules": [ + { + "id": "rule1", + "name": "rule1", + "tags": { + "type": "flow1", + "category": "category1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "_dd.appsec.fp.http.endpoint" + } + ], + "regex": ".*" + }, + "operator": "match_regex" + } + ] + } + ], + "processors": [ + { + "id": "processor-001", + "generator": "http_endpoint_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "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": true, + "output": false + } + ] +} diff --git a/tests/integration/processors/fingerprint/ruleset/processor.json b/tests/integration/processors/fingerprint/ruleset/processor.json new file mode 100644 index 000000000..91c86875e --- /dev/null +++ b/tests/integration/processors/fingerprint/ruleset/processor.json @@ -0,0 +1,81 @@ +{ + "version": "2.2", + "metadata": { + "rules_version": "1.8.0" + }, + "rules": [ + { + "id": "rule1", + "name": "rule1", + "tags": { + "type": "flow1", + "category": "category1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "_dd.appsec.fp.http.endpoint" + } + ], + "regex": ".*" + }, + "operator": "match_regex" + } + ] + } + ], + "processors": [ + { + "id": "processor-001", + "generator": "http_endpoint_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "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": true, + "output": true + } + ] +} diff --git a/tests/integration/processors/fingerprint/test.cpp b/tests/integration/processors/fingerprint/test.cpp new file mode 100644 index 000000000..5ec090b55 --- /dev/null +++ b/tests/integration/processors/fingerprint/test.cpp @@ -0,0 +1,202 @@ +// 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_utils.hpp" + +using namespace ddwaf; + +namespace { +constexpr std::string_view base_dir = "integration/processors/fingerprint"; + +TEST(TestFingerprintIntegration, Postprocessor) +{ + auto rule = read_json_file("postprocessor.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); + + uint32_t size; + const char *const *addresses = ddwaf_known_addresses(handle, &size); + EXPECT_EQ(size, 5); + std::unordered_set address_set(addresses, addresses + size); + EXPECT_TRUE(address_set.contains("server.request.body")); + EXPECT_TRUE(address_set.contains("server.request.uri.raw")); + EXPECT_TRUE(address_set.contains("server.request.method")); + EXPECT_TRUE(address_set.contains("server.request.query")); + EXPECT_TRUE(address_set.contains("waf.context.processor")); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object settings = 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(&settings, "fingerprint", ddwaf_object_bool(&tmp, true)); + ddwaf_object_map_add(&map, "waf.context.processor", &settings); + + 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), 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); +} + +TEST(TestFingerprintIntegration, Preprocessor) +{ + auto rule = read_json_file("preprocessor.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); + + uint32_t size; + const char *const *addresses = ddwaf_known_addresses(handle, &size); + EXPECT_EQ(size, 6); + std::unordered_set address_set(addresses, addresses + size); + EXPECT_TRUE(address_set.contains("server.request.body")); + EXPECT_TRUE(address_set.contains("server.request.uri.raw")); + EXPECT_TRUE(address_set.contains("server.request.method")); + EXPECT_TRUE(address_set.contains("server.request.query")); + EXPECT_TRUE(address_set.contains("waf.context.processor")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.endpoint")); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object settings = 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(&settings, "fingerprint", ddwaf_object_bool(&tmp, true)); + ddwaf_object_map_add(&map, "waf.context.processor", &settings); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + + EXPECT_EVENTS(out, {.id = "rule1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}}, + .matches = {{.op = "match_regex", + .op_value = ".*", + .highlight = "http-put-729d56c3-2c70e12b-2c70e12b", + .args = {{ + .value = "http-put-729d56c3-2c70e12b-2c70e12b", + .address = "_dd.appsec.fp.http.endpoint", + .path = {}, + }}}}}); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 0); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestFingerprintIntegration, Processor) +{ + auto rule = read_json_file("processor.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); + + uint32_t size; + const char *const *addresses = ddwaf_known_addresses(handle, &size); + EXPECT_EQ(size, 6); + std::unordered_set address_set(addresses, addresses + size); + EXPECT_TRUE(address_set.contains("server.request.body")); + EXPECT_TRUE(address_set.contains("server.request.uri.raw")); + EXPECT_TRUE(address_set.contains("server.request.method")); + EXPECT_TRUE(address_set.contains("server.request.query")); + EXPECT_TRUE(address_set.contains("waf.context.processor")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.endpoint")); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object settings = 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(&settings, "fingerprint", ddwaf_object_bool(&tmp, true)); + ddwaf_object_map_add(&map, "waf.context.processor", &settings); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + + EXPECT_EVENTS(out, {.id = "rule1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}}, + .matches = {{.op = "match_regex", + .op_value = ".*", + .highlight = "http-put-729d56c3-2c70e12b-2c70e12b", + .args = {{ + .value = "http-put-729d56c3-2c70e12b-2c70e12b", + .address = "_dd.appsec.fp.http.endpoint", + .path = {}, + }}}}}); + + 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 diff --git a/tests/processor/fingerprint_test.cpp b/tests/processor/fingerprint_test.cpp new file mode 100644 index 000000000..a22d20c28 --- /dev/null +++ b/tests/processor/fingerprint_test.cpp @@ -0,0 +1,365 @@ +// 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 2023 Datadog, Inc. + +#include "../test_utils.hpp" +#include "ddwaf.h" +#include "matcher/regex_match.hpp" +#include "processor/fingerprint.hpp" + +using namespace ddwaf; +using namespace std::literals; + +namespace { + +TEST(TestHttpEndpointFingerprint, Basic) +{ + ddwaf_object tmp; + + ddwaf_object query; + ddwaf_object_map(&query); + ddwaf_object_map_add(&query, "Key1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "key,3", ddwaf_object_invalid(&tmp)); + + ddwaf_object body; + ddwaf_object_map(&body); + ddwaf_object_map_add(&body, "KEY1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "3", ddwaf_object_invalid(&tmp)); + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-0ac3796a-9798c0e4"); + + ddwaf_object_free(&query); + ddwaf_object_free(&body); + ddwaf_object_free(&output); +} + +TEST(TestHttpEndpointFingerprint, EmptyQuery) +{ + ddwaf_object tmp; + + ddwaf_object query; + ddwaf_object_map(&query); + + ddwaf_object body; + ddwaf_object_map(&body); + ddwaf_object_map_add(&body, "KEY1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "3", ddwaf_object_invalid(&tmp)); + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, static_cast(output.nbEntries)}; + EXPECT_STRV(output_sv, "http-get-0ede9e60--9798c0e4"); + + ddwaf_object_free(&query); + ddwaf_object_free(&body); + ddwaf_object_free(&output); +} + +TEST(TestHttpEndpointFingerprint, EmptyBody) +{ + ddwaf_object tmp; + + ddwaf_object query; + ddwaf_object_map(&query); + ddwaf_object_map_add(&query, "Key1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "key,3", ddwaf_object_invalid(&tmp)); + + ddwaf_object body; + ddwaf_object_map(&body); + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, static_cast(output.nbEntries)}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-0ac3796a-"); + + ddwaf_object_free(&query); + ddwaf_object_free(&body); + ddwaf_object_free(&output); +} + +TEST(TestHttpEndpointFingerprint, EmptyEverything) +{ + ddwaf_object query; + ddwaf_object_map(&query); + + ddwaf_object body; + ddwaf_object_map(&body); + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, ""}, {{}, {}, false, ""}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, static_cast(output.nbEntries)}; + EXPECT_STRV(output_sv, "http----"); + + ddwaf_object_free(&query); + ddwaf_object_free(&body); + ddwaf_object_free(&output); +} + +TEST(TestHttpEndpointFingerprint, KeyConsistency) +{ + ddwaf_object tmp; + + ddwaf_object query; + ddwaf_object_map(&query); + ddwaf_object_map_add(&query, "Key1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "key3,Key4", ddwaf_object_invalid(&tmp)); + + ddwaf_object body; + ddwaf_object_map(&body); + ddwaf_object_map_add(&body, "KeY1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "kEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY3", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KeY4", ddwaf_object_invalid(&tmp)); + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, static_cast(output.nbEntries)}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-ced401fa-ff07216e"); + + ddwaf_object_free(&query); + ddwaf_object_free(&body); + ddwaf_object_free(&output); +} + +TEST(TestHttpEndpointFingerprint, InvalidQueryType) +{ + ddwaf_object tmp; + + ddwaf_object query; + ddwaf_object_array(&query); + ddwaf_object_array_add(&query, ddwaf_object_string(&tmp, "Key1")); + ddwaf_object_array_add(&query, ddwaf_object_string(&tmp, "KEY2")); + ddwaf_object_array_add(&query, ddwaf_object_string(&tmp, "key,3")); + + ddwaf_object body; + ddwaf_object_map(&body); + ddwaf_object_map_add(&body, "KEY1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "3", ddwaf_object_invalid(&tmp)); + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, static_cast(output.nbEntries)}; + EXPECT_STRV(output_sv, "http-get-0ede9e60--9798c0e4"); + + ddwaf_object_free(&query); + ddwaf_object_free(&body); + ddwaf_object_free(&output); +} + +TEST(TestHttpEndpointFingerprint, InvalidBodyType) +{ + ddwaf_object tmp; + + ddwaf_object query; + ddwaf_object_map(&query); + ddwaf_object_map_add(&query, "Key1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "key,3", ddwaf_object_invalid(&tmp)); + + ddwaf_object body; + ddwaf_object_array(&body); + ddwaf_object_array_add(&body, ddwaf_object_string(&tmp, "KEY1")); + ddwaf_object_array_add(&body, ddwaf_object_string(&tmp, "KEY2")); + ddwaf_object_array_add(&body, ddwaf_object_string(&tmp, "KEY")); + ddwaf_object_array_add(&body, ddwaf_object_string(&tmp, "3")); + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, static_cast(output.nbEntries)}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-0ac3796a-"); + + ddwaf_object_free(&query); + ddwaf_object_free(&body); + ddwaf_object_free(&output); +} + +TEST(TestHttpEndpointFingerprint, InvalidQueryAndBodyType) +{ + ddwaf_object tmp; + + ddwaf_object query; + ddwaf_object_array(&query); + ddwaf_object_array_add(&query, ddwaf_object_string(&tmp, "Key1")); + ddwaf_object_array_add(&query, ddwaf_object_string(&tmp, "KEY2")); + ddwaf_object_array_add(&query, ddwaf_object_string(&tmp, "key,3")); + + ddwaf_object body; + ddwaf_object_array(&body); + ddwaf_object_array_add(&body, ddwaf_object_string(&tmp, "KEY1")); + ddwaf_object_array_add(&body, ddwaf_object_string(&tmp, "KEY2")); + ddwaf_object_array_add(&body, ddwaf_object_string(&tmp, "KEY")); + ddwaf_object_array_add(&body, ddwaf_object_string(&tmp, "3")); + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, static_cast(output.nbEntries)}; + EXPECT_STRV(output_sv, "http-get-0ede9e60--"); + + ddwaf_object_free(&query); + ddwaf_object_free(&body); + ddwaf_object_free(&output); +} + +TEST(TestHttpEndpointFingerprint, UriRawConsistency) +{ + ddwaf_object tmp; + + ddwaf_object query; + ddwaf_object_map(&query); + ddwaf_object_map_add(&query, "Key1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "key,3", ddwaf_object_invalid(&tmp)); + + ddwaf_object body; + ddwaf_object_map(&body); + ddwaf_object_map_add(&body, "KEY1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "3", ddwaf_object_invalid(&tmp)); + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + { + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-0ac3796a-9798c0e4"); + ddwaf_object_free(&output); + } + + { + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever#fragment"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-0ac3796a-9798c0e4"); + ddwaf_object_free(&output); + } + + { + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, + {{}, {}, false, "/path/to/whatever?param=hello#fragment"}, {{}, {}, false, &query}, + {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-0ac3796a-9798c0e4"); + ddwaf_object_free(&output); + } + + { + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-0ac3796a-9798c0e4"); + ddwaf_object_free(&output); + } + + { + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/PaTh/To/WhAtEVER"}, + {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-0ac3796a-9798c0e4"); + ddwaf_object_free(&output); + } + + ddwaf_object_free(&query); + ddwaf_object_free(&body); +} + +} // namespace diff --git a/tools/waf_runner.cpp b/tools/waf_runner.cpp index bd073d5ae..07686bdfd 100644 --- a/tools/waf_runner.cpp +++ b/tools/waf_runner.cpp @@ -47,7 +47,7 @@ auto parse_args(int argc, char *argv[]) } return args; } -const char *key_regex = R"((?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key)|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\.net[_-]sessionid|sid|jwt)"; +const char *key_regex = R"((?i)pass|pw(?:or)?d|secret|(?:api|private|public|access)[_-]?key|token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\.net[_-]sessionid|sid|jwt)"; const char *value_regex = R"((?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key(?:[_-]?id)?|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|jsessionid|phpsessid|asp\.net(?:[_-]|-)sessionid|sid|jwt)(?:\s*=[^;]|"\s*:\s*"[^"]+")|bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,})"; From 3c57f4257c1c17f3bf8d9f185a9ec21054f2f268 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:24:57 +0100 Subject: [PATCH 12/17] Exists operator and `waf.context.event` virtual address (#321) --- src/collection.cpp | 2 +- src/collection.hpp | 2 +- src/condition/exists.hpp | 34 ++++++++ src/context.cpp | 26 +++++++ src/parser/expression_parser.cpp | 6 ++ .../ruleset/context_event_address.json | 76 ++++++++++++++++++ tests/integration/context/test.cpp | 77 +++++++++++++++++++ tests/operator/exists_condition_test.cpp | 56 ++++++++++++++ tests/waf_test.cpp | 66 +++++----------- .../exists/001_rule1_exists_match.yaml | 20 +++++ .../exists/002_rule1_no_exists_match.yaml | 11 +++ .../tests/rules/operators/exists/ruleset.yaml | 12 +++ 12 files changed, 338 insertions(+), 50 deletions(-) create mode 100644 src/condition/exists.hpp create mode 100644 tests/integration/context/ruleset/context_event_address.json create mode 100644 tests/operator/exists_condition_test.cpp create mode 100644 validator/tests/rules/operators/exists/001_rule1_exists_match.yaml create mode 100644 validator/tests/rules/operators/exists/002_rule1_no_exists_match.yaml create mode 100644 validator/tests/rules/operators/exists/ruleset.yaml diff --git a/src/collection.cpp b/src/collection.cpp index 69795b4ee..97f0baa57 100644 --- a/src/collection.cpp +++ b/src/collection.cpp @@ -71,7 +71,7 @@ std::optional match_rule(rule *rule, const object_store &store, } template -void base_collection::match(std::vector &events, const object_store &store, +void base_collection::match(std::vector &events, object_store &store, collection_cache &cache, const exclusion::context_policy &exclusion, const std::unordered_map> &dynamic_matchers, ddwaf::timer &deadline) const diff --git a/src/collection.hpp b/src/collection.hpp index 3f5659a1c..6f4e883ce 100644 --- a/src/collection.hpp +++ b/src/collection.hpp @@ -44,7 +44,7 @@ template class base_collection { void insert(const std::shared_ptr &rule) { rules_.emplace_back(rule.get()); } - void match(std::vector &events, const object_store &store, collection_cache &cache, + void match(std::vector &events, object_store &store, collection_cache &cache, const exclusion::context_policy &exclusion, const std::unordered_map> &dynamic_matchers, ddwaf::timer &deadline) const; diff --git a/src/condition/exists.hpp b/src/condition/exists.hpp new file mode 100644 index 000000000..d66d27baa --- /dev/null +++ b/src/condition/exists.hpp @@ -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 { +public: + static constexpr std::array param_names{"inputs"}; + + explicit exists_condition( + std::vector args, const object_limits &limits = {}) + : base_impl(std::move(args), limits) + {} + +protected: + [[nodiscard]] eval_result eval_impl(const unary_argument &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; +}; + +} // namespace ddwaf diff --git a/src/context.cpp b/src/context.cpp index f99e2b82d..7e12e4b0f 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -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 persistent, optional_ref ephemeral, optional_ref res, uint64_t timeout) @@ -81,6 +104,9 @@ DDWAF_RET_CODE context::run(optional_ref persistent, if (should_eval_rules) { events = eval_rules(policy, deadline); + if (!events.empty()) { + set_context_event_address(store_); + } } } diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index f6f805061..b15990e6d 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -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" @@ -114,6 +115,11 @@ std::shared_ptr parse_expression(const parameter::vector &conditions auto arguments = parse_arguments(params, source, transformers, addresses, limits); conditions.emplace_back(std::make_unique(std::move(arguments), limits)); + } else if (operator_name == "exists") { + auto arguments = + parse_arguments(params, source, transformers, addresses, limits); + conditions.emplace_back( + std::make_unique(std::move(arguments), limits)); } else { auto [data_id, matcher] = parse_matcher(operator_name, params); diff --git a/tests/integration/context/ruleset/context_event_address.json b/tests/integration/context/ruleset/context_event_address.json new file mode 100644 index 000000000..e09ebc375 --- /dev/null +++ b/tests/integration/context/ruleset/context_event_address.json @@ -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 + } + ] +} diff --git a/tests/integration/context/test.cpp b/tests/integration/context/test.cpp index c9a6d04b8..86cf4b24d 100644 --- a/tests/integration/context/test.cpp +++ b/tests/integration/context/test.cpp @@ -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 diff --git a/tests/operator/exists_condition_test.cpp b/tests/operator/exists_condition_test.cpp new file mode 100644 index 000000000..43ea12870 --- /dev/null +++ b/tests/operator/exists_condition_test.cpp @@ -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 std::vector 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 diff --git a/tests/waf_test.cpp b/tests/waf_test.cpp index 55699258f..f51a28715 100644 --- a/tests/waf_test.cpp +++ b/tests/waf_test.cpp @@ -62,55 +62,25 @@ TEST(TestWaf, RuleDisabledInRuleset) TEST(TestWaf, AddressUniqueness) { - std::unordered_set indices; + std::array addresses{"grpc.server.method", "grpc.server.request.message", + "grpc.server.request.metadata", "grpc.server.response.message", + "grpc.server.response.metadata.headers", "grpc.server.response.metadata.trailers", + "grpc.server.response.status", "graphql.server.all_resolvers", "graphql.server.resolver", + "http.client_ip", "server.request.body", "server.request.headers.no_cookies", + "server.request.path_params", "server.request.query", "server.request.uri.raw", + "server.request.trailers", "server.request.cookies", "server.response.body", + "server.response.headers.no_cookies", "server.response.status", "usr.id", "usr.session_id", + "waf.context.processor", "waf.context.event", "_dd.appsec.fp.http.endpoint", + "_dd.appsec.fp.http.header", "_dd.appsec.fp.http.network", + "_dd.appsec.fp.session" + "_dd.appsec.s.req.body", + "_dd.appsec.s.req.cookies", "_dd.appsec.s.req.query", "_dd.appsec.s.req.params", + "_dd.appsec.s.res.body", "_dd.appsec.s.graphql.all_resolvers", + "_dd.appsec.s.graphql.resolver", "_dd.appsec.s.req.headers", "_dd.appsec.s.res.headers"}; - { - std::size_t hash = std::hash()("grpc.server.request.message"); - EXPECT_EQ(indices.find(hash), indices.end()); - indices.insert(hash); - } - { - std::size_t hash = std::hash()("grpc.server.request.metadata"); - EXPECT_EQ(indices.find(hash), indices.end()); - indices.insert(hash); - } - { - std::size_t hash = std::hash()("http.client_ip"); - EXPECT_EQ(indices.find(hash), indices.end()); - indices.insert(hash); - } - { - std::size_t hash = std::hash()("server.request.body"); - EXPECT_EQ(indices.find(hash), indices.end()); - indices.insert(hash); - } - { - std::size_t hash = std::hash()("server.request.headers.no_cookies"); - EXPECT_EQ(indices.find(hash), indices.end()); - indices.insert(hash); - } - { - std::size_t hash = std::hash()("server.request.path_params"); - EXPECT_EQ(indices.find(hash), indices.end()); - indices.insert(hash); - } - { - std::size_t hash = std::hash()("server.request.query"); - EXPECT_EQ(indices.find(hash), indices.end()); - indices.insert(hash); - } - { - std::size_t hash = std::hash()("server.request.uri.raw"); - EXPECT_EQ(indices.find(hash), indices.end()); - indices.insert(hash); - } - { - std::size_t hash = std::hash()("server.response.status"); - EXPECT_EQ(indices.find(hash), indices.end()); - indices.insert(hash); - } - { - std::size_t hash = std::hash()("usr.id"); + std::unordered_set indices; + for (auto addr : addresses) { + std::size_t hash = std::hash()(addr); EXPECT_EQ(indices.find(hash), indices.end()); indices.insert(hash); } diff --git a/validator/tests/rules/operators/exists/001_rule1_exists_match.yaml b/validator/tests/rules/operators/exists/001_rule1_exists_match.yaml new file mode 100644 index 000000000..bd56ab484 --- /dev/null +++ b/validator/tests/rules/operators/exists/001_rule1_exists_match.yaml @@ -0,0 +1,20 @@ +{ + name: "Basic run with exists operator", + runs: [ + { + persistent-input: { + rule1-input: "something else" + }, + rules: [ + { + 1: [ + { + address: rule1-input, + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/exists/002_rule1_no_exists_match.yaml b/validator/tests/rules/operators/exists/002_rule1_no_exists_match.yaml new file mode 100644 index 000000000..d6c5e9c1a --- /dev/null +++ b/validator/tests/rules/operators/exists/002_rule1_no_exists_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with exists operator and no match", + runs: [ + { + persistent-input: { + rule2-input: "something" + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/exists/ruleset.yaml b/validator/tests/rules/operators/exists/ruleset.yaml new file mode 100644 index 000000000..fe5982507 --- /dev/null +++ b/validator/tests/rules/operators/exists/ruleset.yaml @@ -0,0 +1,12 @@ +version: '2.1' +rules: + - id: "1" + name: rule1-exists + tags: + type: flow1 + category: category + conditions: + - operator: exists + parameters: + inputs: + - address: rule1-input From a217ab2aded3fcd1124471d3b466102e0dc5b919 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:48:36 +0100 Subject: [PATCH 13/17] Add function to obtain available actions (#324) --- include/ddwaf.h | 26 +++- libddwaf.def | 1 + smoketest/smoke.c | 15 +++ src/exclusion/rule_filter.hpp | 2 + src/interface.cpp | 17 +++ src/ruleset.hpp | 38 ++++++ src/waf.hpp | 5 + tests/interface_test.cpp | 235 ++++++++++++++++++++++++++++++++++ 8 files changed, 335 insertions(+), 4 deletions(-) diff --git a/include/ddwaf.h b/include/ddwaf.h index cfad7f935..b23be0e95 100644 --- a/include/ddwaf.h +++ b/include/ddwaf.h @@ -228,7 +228,7 @@ ddwaf_handle ddwaf_update(ddwaf_handle handle, const ddwaf_object *ruleset, * * Destroy a WAF instance. * - * @param Handle to the WAF instance. + * @param handle Handle to the WAF instance. */ void ddwaf_destroy(ddwaf_handle handle); @@ -242,16 +242,34 @@ void ddwaf_destroy(ddwaf_handle handle); * * The memory is owned by the WAF and should not be freed. * - * @param Handle to the WAF instance. + * @param handle Handle to the WAF instance. * @param size Output parameter in which the size will be returned. The value of * size will be 0 if the return value is NULL. * @return NULL if empty, otherwise a pointer to an array with size elements. * - * @Note The returned array should be considered invalid after calling ddwaf_destroy + * @note This function is not thread-safe + * @note The returned array should be considered invalid after calling ddwaf_destroy * on the handle used to obtain it. **/ const char* const* ddwaf_known_addresses(const ddwaf_handle handle, uint32_t *size); - +/** + * ddwaf_known_actions + * + * Get an array of all the action types which could be triggered as a result of + * the current set of rules and exclusion filters. + * + * The memory is owned by the WAF and should not be freed. + * + * @param handle Handle to the WAF instance. + * @param size Output parameter in which the size will be returned. The value of + * size will be 0 if the return value is NULL. + * @return NULL if empty, otherwise a pointer to an array with size elements. + * + * @note This function is not thread-safe + * @note The returned array should be considered invalid after calling ddwaf_destroy + * on the handle used to obtain it. + **/ +const char *const *ddwaf_known_actions(const ddwaf_handle handle, uint32_t *size); /** * ddwaf_context_init * diff --git a/libddwaf.def b/libddwaf.def index 957e2570c..4e8a4563b 100644 --- a/libddwaf.def +++ b/libddwaf.def @@ -38,3 +38,4 @@ EXPORTS ddwaf_object_free ddwaf_get_version ddwaf_set_log_cb + ddwaf_known_actions diff --git a/smoketest/smoke.c b/smoketest/smoke.c index 2f4446bdb..faf7e09dc 100644 --- a/smoketest/smoke.c +++ b/smoketest/smoke.c @@ -227,6 +227,21 @@ int main() { return 1; } + puts("addresses:"); + uint32_t addrs_size = 0; + const char * const* addrs = ddwaf_known_addresses(handle, &addrs_size); + for (uint32_t i = 0; i < addrs_size; ++i) { + puts(addrs[i]); + } + + puts("actions:"); + uint32_t actions_size = 0; + const char * const* actions = ddwaf_known_actions(handle, &actions_size); + for (uint32_t i = 0; i < actions_size; ++i) { + puts(actions[i]); + } + + ddwaf_context ctx = ddwaf_context_init(handle); if (!ctx) { puts("ctx is null"); diff --git a/src/exclusion/rule_filter.hpp b/src/exclusion/rule_filter.hpp index d3c52290b..db9be61b7 100644 --- a/src/exclusion/rule_filter.hpp +++ b/src/exclusion/rule_filter.hpp @@ -47,6 +47,8 @@ class rule_filter { expr_->get_addresses(addresses); } + std::string_view get_action() const { return action_; } + protected: std::string id_; std::shared_ptr expr_; diff --git a/src/interface.cpp b/src/interface.cpp index d696c0ab6..2dd320930 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -168,6 +168,23 @@ const char *const *ddwaf_known_addresses(ddwaf::waf *handle, uint32_t *size) return addresses.data(); } +const char *const *ddwaf_known_actions(ddwaf::waf *handle, uint32_t *size) +{ + if (handle == nullptr) { + *size = 0; + return nullptr; + } + + const auto &action_types = handle->get_available_action_types(); + if (action_types.empty() || action_types.size() > std::numeric_limits::max()) { + *size = 0; + return nullptr; + } + + *size = (uint32_t)action_types.size(); + return action_types.data(); +} + ddwaf_context ddwaf_context_init(ddwaf::waf *handle) { try { diff --git a/src/ruleset.hpp b/src/ruleset.hpp index 7defda476..045e41e28 100644 --- a/src/ruleset.hpp +++ b/src/ruleset.hpp @@ -123,6 +123,35 @@ struct ruleset { return root_addresses; } + [[nodiscard]] const std::vector &get_available_action_types() + { + if (available_action_types.empty()) { + std::unordered_set all_types; + // We preallocate at least the total available actions in the mapper + all_types.reserve(actions->size()); + + auto maybe_add_action = [&](auto &&action) { + auto it = actions->find(action); + if (it == actions->end()) { + return; + } + auto [new_it, res] = all_types.emplace(it->second.type_str); + if (res) { + available_action_types.emplace_back(it->second.type_str.c_str()); + } + }; + + for (const auto &rule : rules) { + for (const auto &action : rule->get_actions()) { maybe_add_action(action); } + } + + for (const auto &[name, filter] : rule_filters) { + maybe_add_action(filter->get_action()); + } + } + return available_action_types; + } + ddwaf_object_free_fn free_fn{ddwaf_object_free}; std::shared_ptr event_obfuscator; @@ -151,8 +180,17 @@ struct ruleset { std::unordered_map preprocessor_addresses; std::unordered_map postprocessor_addresses; + // The following two members are computed only when required; they are + // provided to the caller of ddwaf_known_* and are only cached for the + // purpose of avoiding the need for a destruction method in the API. + // // Root addresses, lazily computed std::vector root_addresses; + // A list of the possible action types that can be returned as a result of + // the evaluation of the current set of rules and exclusion filters. + // These are lazily computed andthe underlying memory of each string is + // owned by the action mapper. + std::vector available_action_types; }; } // namespace ddwaf diff --git a/src/waf.hpp b/src/waf.hpp index 8dbd73c20..d1f6cd594 100644 --- a/src/waf.hpp +++ b/src/waf.hpp @@ -31,6 +31,11 @@ class waf { return ruleset_->get_root_addresses(); } + [[nodiscard]] const std::vector &get_available_action_types() const + { + return ruleset_->get_available_action_types(); + } + protected: waf(std::shared_ptr builder, std::shared_ptr ruleset) : builder_(std::move(builder)), ruleset_(std::move(ruleset)) diff --git a/tests/interface_test.cpp b/tests/interface_test.cpp index bf79015da..5593dd3a7 100644 --- a/tests/interface_test.cpp +++ b/tests/interface_test.cpp @@ -535,12 +535,25 @@ TEST(TestInterface, UpdateActionsByID) ASSERT_NE(handle1, nullptr); ddwaf_object_free(&rule); + { + uint32_t actions_size; + const char *const *actions = ddwaf_known_actions(handle1, &actions_size); + EXPECT_EQ(actions_size, 0); + EXPECT_EQ(actions, nullptr); + } + ddwaf_handle handle2; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [block]}]})"); handle2 = ddwaf_update(handle1, &overrides, nullptr); ddwaf_object_free(&overrides); + + uint32_t actions_size; + const char *const *actions = ddwaf_known_actions(handle2, &actions_size); + EXPECT_EQ(actions_size, 1); + ASSERT_NE(actions, nullptr); + EXPECT_STREQ(actions[0], "block_request"); } { @@ -610,6 +623,12 @@ TEST(TestInterface, UpdateActionsByID) R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [redirect]}], actions: [{id: redirect, type: redirect_request, parameters: {location: http://google.com, status_code: 303}}]})"); handle3 = ddwaf_update(handle2, &overrides, nullptr); ddwaf_object_free(&overrides); + + uint32_t actions_size; + const char *const *actions = ddwaf_known_actions(handle3, &actions_size); + EXPECT_EQ(actions_size, 1); + ASSERT_NE(actions, nullptr); + EXPECT_STREQ(actions[0], "redirect_request"); } { @@ -659,12 +678,25 @@ TEST(TestInterface, UpdateActionsByTags) ASSERT_NE(handle1, nullptr); ddwaf_object_free(&rule); + { + uint32_t actions_size; + const char *const *actions = ddwaf_known_actions(handle1, &actions_size); + EXPECT_EQ(actions_size, 0); + EXPECT_EQ(actions, nullptr); + } + ddwaf_handle handle2; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{tags: {confidence: 1}}], on_match: [block]}]})"); handle2 = ddwaf_update(handle1, &overrides, nullptr); ddwaf_object_free(&overrides); + + uint32_t actions_size; + const char *const *actions = ddwaf_known_actions(handle2, &actions_size); + EXPECT_EQ(actions_size, 1); + ASSERT_NE(actions, nullptr); + EXPECT_STREQ(actions[0], "block_request"); } { @@ -2295,4 +2327,207 @@ TEST(TestInterface, KnownAddressesDisabledRule) ddwaf_destroy(handle3); } +TEST(TestInterface, KnownActions) +{ + auto rule = read_file("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + { + uint32_t size; + const char *const *actions = ddwaf_known_actions(handle1, &size); + EXPECT_EQ(size, 0); + EXPECT_EQ(actions, nullptr); + } + + // Add an action + ddwaf_handle handle2; + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [block]}]})"); + handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_object_free(&overrides); + + uint32_t size; + const char *const *actions = ddwaf_known_actions(handle2, &size); + EXPECT_EQ(size, 1); + ASSERT_NE(actions, nullptr); + + std::set available_actions{"block_request"}; + while ((size--) != 0U) { + EXPECT_NE(available_actions.find(actions[size]), available_actions.end()); + } + + ddwaf_destroy(handle1); + } + + // Disable the rule containing the only action + ddwaf_handle handle3; + { + auto overrides = + yaml_to_object(R"({rules_override: [{rules_target: [{rule_id: 1}], enabled: false}]})"); + handle3 = ddwaf_update(handle2, &overrides, nullptr); + ddwaf_object_free(&overrides); + + uint32_t size; + const char *const *actions = ddwaf_known_actions(handle3, &size); + EXPECT_EQ(size, 0); + EXPECT_EQ(actions, nullptr); + + ddwaf_destroy(handle2); + } + + // Add a new action type and update another rule to use it + ddwaf_handle handle4; + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{rule_id: 2}], on_match: [redirect]}], actions: [{id: redirect, type: redirect_request, parameters: {location: http://google.com, status_code: 303}}]})"); + handle4 = ddwaf_update(handle3, &overrides, nullptr); + ddwaf_object_free(&overrides); + + uint32_t size; + const char *const *actions = ddwaf_known_actions(handle4, &size); + EXPECT_EQ(size, 1); + ASSERT_NE(actions, nullptr); + + std::set available_actions{"redirect_request"}; + while ((size--) != 0U) { + EXPECT_NE(available_actions.find(actions[size]), available_actions.end()); + } + + ddwaf_destroy(handle3); + } + + // Add another action to a separate rule + ddwaf_handle handle5; + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [block]}, {rules_target: [{rule_id: 2}], on_match: [redirect]}]})"); + handle5 = ddwaf_update(handle4, &overrides, nullptr); + ddwaf_object_free(&overrides); + + uint32_t size; + const char *const *actions = ddwaf_known_actions(handle5, &size); + EXPECT_EQ(size, 2); + ASSERT_NE(actions, nullptr); + + std::set available_actions{"redirect_request", "block_request"}; + while ((size--) != 0U) { + EXPECT_NE(available_actions.find(actions[size]), available_actions.end()); + } + + ddwaf_destroy(handle4); + } + + // Add two actions to an existing rule + ddwaf_handle handle6; + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [block]}, {rules_target: [{rule_id: 2}], on_match: [redirect]}, {rules_target: [{rule_id: 3}], on_match: [block, stack_trace]}]})"); + handle6 = ddwaf_update(handle5, &overrides, nullptr); + ddwaf_object_free(&overrides); + + uint32_t size; + const char *const *actions = ddwaf_known_actions(handle6, &size); + EXPECT_EQ(size, 3); + ASSERT_NE(actions, nullptr); + + std::set available_actions{ + "redirect_request", "block_request", "generate_stack"}; + while ((size--) != 0U) { + EXPECT_NE(available_actions.find(actions[size]), available_actions.end()); + } + + ddwaf_destroy(handle5); + } + + // Remove the block action from rule1 and add an exclusion filter + ddwaf_handle handle7; + { + auto overrides = yaml_to_object( + R"({exclusions: [{id: 1, rules_target: [{rule_id: 1}], on_match: block}], rules_override: [{rules_target: [{rule_id: 2}], on_match: [redirect]}, {rules_target: [{rule_id: 3}], on_match: [block, stack_trace]}]})"); + handle7 = ddwaf_update(handle6, &overrides, nullptr); + ddwaf_object_free(&overrides); + + uint32_t size; + const char *const *actions = ddwaf_known_actions(handle7, &size); + EXPECT_EQ(size, 3); + ASSERT_NE(actions, nullptr); + + std::set available_actions{ + "redirect_request", "block_request", "generate_stack"}; + while ((size--) != 0U) { + EXPECT_NE(available_actions.find(actions[size]), available_actions.end()); + } + + ddwaf_destroy(handle6); + } + + // Remove actions from all other rules + ddwaf_handle handle8; + { + auto overrides = yaml_to_object(R"({rules_override: []})"); + handle8 = ddwaf_update(handle7, &overrides, nullptr); + ddwaf_object_free(&overrides); + + uint32_t size; + const char *const *actions = ddwaf_known_actions(handle8, &size); + EXPECT_EQ(size, 1); + ASSERT_NE(actions, nullptr); + + std::set available_actions{"block_request"}; + while ((size--) != 0U) { + EXPECT_NE(available_actions.find(actions[size]), available_actions.end()); + } + + ddwaf_destroy(handle7); + } + + // Remove exclusions + ddwaf_handle handle9; + { + auto overrides = yaml_to_object(R"({exclusions: []})"); + handle9 = ddwaf_update(handle8, &overrides, nullptr); + ddwaf_object_free(&overrides); + + uint32_t size; + const char *const *actions = ddwaf_known_actions(handle9, &size); + EXPECT_EQ(size, 0); + ASSERT_EQ(actions, nullptr); + + ddwaf_destroy(handle8); + } + + // Disable the rule containing the only action + ddwaf_handle handle10; + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [whatever]}]})"); + handle10 = ddwaf_update(handle9, &overrides, nullptr); + ddwaf_object_free(&overrides); + + uint32_t size; + const char *const *actions = ddwaf_known_actions(handle10, &size); + EXPECT_EQ(size, 0); + EXPECT_EQ(actions, nullptr); + + ddwaf_destroy(handle9); + } + + ddwaf_destroy(handle10); +} + +TEST(TestInterface, KnownActionsNullHandle) +{ + uint32_t size; + const char *const *actions = ddwaf_known_actions(nullptr, &size); + EXPECT_EQ(size, 0); + EXPECT_EQ(actions, nullptr); +} + } // namespace From 4bedd53a4cbd0d3d310b49dc5b21c49e76d1afb3 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:08:23 +0100 Subject: [PATCH 14/17] HTTP Header, HTTP Network and Session Fingerprints (#320) --- .github/workflows/fuzz.yml | 9 + fuzzer/CMakeLists.txt | 2 +- fuzzer/common/common.hpp | 56 ++ .../corpus/.gitignore | 4 + fuzzer/http_endpoint_fingerprint/src/main.cpp | 55 ++ .../http_header_fingerprint/corpus/.gitignore | 4 + fuzzer/http_header_fingerprint/src/main.cpp | 51 ++ .../corpus/.gitignore | 4 + fuzzer/http_network_fingerprint/src/main.cpp | 50 ++ fuzzer/session_fingerprint/corpus/.gitignore | 4 + fuzzer/session_fingerprint/src/main.cpp | 43 ++ src/builder/processor_builder.cpp | 30 + src/condition/exists.hpp | 7 +- src/parser/processor_parser.cpp | 19 +- src/processor/fingerprint.cpp | 333 +++++++++- src/processor/fingerprint.hpp | 3 - .../fingerprint/ruleset/postprocessor.json | 115 ++++ .../fingerprint/ruleset/preprocessor.json | 179 +++++ .../fingerprint/ruleset/processor.json | 178 +++++ .../processors/fingerprint/test.cpp | 279 +++++++- tests/operator/exists_condition_test.cpp | 36 +- tests/processor/fingerprint_test.cpp | 629 ++++++++++++++++++ tests/test_utils.cpp | 12 + tests/test_utils.hpp | 1 + 24 files changed, 2047 insertions(+), 56 deletions(-) create mode 100644 fuzzer/common/common.hpp create mode 100644 fuzzer/http_endpoint_fingerprint/corpus/.gitignore create mode 100644 fuzzer/http_endpoint_fingerprint/src/main.cpp create mode 100644 fuzzer/http_header_fingerprint/corpus/.gitignore create mode 100644 fuzzer/http_header_fingerprint/src/main.cpp create mode 100644 fuzzer/http_network_fingerprint/corpus/.gitignore create mode 100644 fuzzer/http_network_fingerprint/src/main.cpp create mode 100644 fuzzer/session_fingerprint/corpus/.gitignore create mode 100644 fuzzer/session_fingerprint/src/main.cpp diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 112c21ac4..3e598d636 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -92,6 +92,15 @@ jobs: params: "" - fuzzer: sha256 params: "" + - fuzzer: http_endpoint_fingerprint + params: "" + - fuzzer: http_header_fingerprint + params: "" + - fuzzer: http_network_fingerprint + params: "" + - fuzzer: session_fingerprint + params: "" + steps: - uses: actions/checkout@v4 with: diff --git a/fuzzer/CMakeLists.txt b/fuzzer/CMakeLists.txt index bd03981b2..fe0e3e6f9 100644 --- a/fuzzer/CMakeLists.txt +++ b/fuzzer/CMakeLists.txt @@ -27,7 +27,7 @@ foreach(dir ${subdirs}) COMPILE_FLAGS ${LINK_COMPILE_FLAGS} LINK_FLAGS ${LINK_COMPILE_FLAGS}) - target_include_directories(${FUZZER_NAME} PRIVATE ${LIBDDWAF_PUBLIC_INCLUDES} ${LIBDDWAF_PRIVATE_INCLUDES}) + target_include_directories(${FUZZER_NAME} PRIVATE ${LIBDDWAF_PUBLIC_INCLUDES} ${LIBDDWAF_PRIVATE_INCLUDES} ${CMAKE_CURRENT_SOURCE_DIR}/common/) target_link_libraries(${FUZZER_NAME} PRIVATE fuzzer-common lib_yamlcpp) endforeach() diff --git a/fuzzer/common/common.hpp b/fuzzer/common/common.hpp new file mode 100644 index 000000000..9cffc6497 --- /dev/null +++ b/fuzzer/common/common.hpp @@ -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 +#include +#include + +class random_buffer { +public: + random_buffer(const uint8_t *bytes, size_t size) : bytes_(bytes), size_(size) {} + + template T get() + { + if ((index_ + sizeof(T)) > size_) { + return {}; + } + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + const T *value = reinterpret_cast(&bytes_[index_]); + index_ += sizeof(T) + (sizeof(T) % 2); + return *value; + } + + template <> bool get() + { + if (index_ >= size_) { + return false; + } + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + bool value = bytes_[index_] > 0; + index_ += 2; + return value; + } + + template <> std::string_view get() + { + auto size = std::min(static_cast(get()) % 4096, size_ - index_); + if (size == 0) { + return ""; + } + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + const auto *ptr = reinterpret_cast(&bytes_[index_]); + index_ += size + size % 2; + return {ptr, size}; + } + +protected: + const uint8_t *bytes_; + size_t size_; + size_t index_{0}; +}; diff --git a/fuzzer/http_endpoint_fingerprint/corpus/.gitignore b/fuzzer/http_endpoint_fingerprint/corpus/.gitignore new file mode 100644 index 000000000..5e7d2734c --- /dev/null +++ b/fuzzer/http_endpoint_fingerprint/corpus/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/fuzzer/http_endpoint_fingerprint/src/main.cpp b/fuzzer/http_endpoint_fingerprint/src/main.cpp new file mode 100644 index 000000000..29c9402c1 --- /dev/null +++ b/fuzzer/http_endpoint_fingerprint/src/main.cpp @@ -0,0 +1,55 @@ +// 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 + +#include "common.hpp" +#include + +using namespace ddwaf; +using namespace std::literals; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) +{ + random_buffer buffer{bytes, size}; + + ddwaf_object tmp; + + ddwaf_object query; + ddwaf_object_map(&query); + auto query_size = buffer.get(); + for (uint8_t i = 0; i < query_size; ++i) { + auto key = buffer.get(); + auto value = buffer.get(); + + ddwaf_object_map_addl( + &query, key.data(), key.size(), ddwaf_object_stringl(&tmp, value.data(), value.size())); + } + + ddwaf_object body; + ddwaf_object_map(&body); + auto body_size = buffer.get(); + for (uint8_t i = 0; i < body_size; ++i) { + auto key = buffer.get(); + auto value = buffer.get(); + + ddwaf_object_map_addl( + &body, key.data(), key.size(), ddwaf_object_stringl(&tmp, value.data(), value.size())); + } + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, buffer.get()}, + {{}, {}, false, buffer.get()}, {{}, {}, false, &query}, + {{}, {}, false, &body}, deadline); + + ddwaf_object_free(&query); + ddwaf_object_free(&body); + ddwaf_object_free(&output); + + return 0; +} diff --git a/fuzzer/http_header_fingerprint/corpus/.gitignore b/fuzzer/http_header_fingerprint/corpus/.gitignore new file mode 100644 index 000000000..5e7d2734c --- /dev/null +++ b/fuzzer/http_header_fingerprint/corpus/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/fuzzer/http_header_fingerprint/src/main.cpp b/fuzzer/http_header_fingerprint/src/main.cpp new file mode 100644 index 000000000..f1bdcc370 --- /dev/null +++ b/fuzzer/http_header_fingerprint/src/main.cpp @@ -0,0 +1,51 @@ +// 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 + +#include "common.hpp" +#include + +using namespace ddwaf; +using namespace std::literals; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) +{ + static std::array headers{"referer", "connection", "accept-encoding", + "content-encoding", "cache-control", "accept-charset", "content-type", "accept-language", + "x-forwarded-for", "x-real-ip", "x-client-ip", "forwarded-for", "x-cluster-client-ip", + "fastly-client-ip", "cf-connecting-ip", "cf-connecting-ipv6", "user-agent"}; + + random_buffer buffer{bytes, size}; + + ddwaf_object tmp; + + ddwaf_object header; + ddwaf_object_map(&header); + auto header_size = buffer.get(); + for (uint8_t i = 0; i < header_size; ++i) { + auto value = buffer.get(); + + std::string_view key; + if (buffer.get()) { // Known header + key = headers[buffer.get() % headers.size()]; + } else { + key = buffer.get(); + } + ddwaf_object_map_addl(&header, key.data(), key.size(), + ddwaf_object_stringl(&tmp, value.data(), value.size())); + } + + http_header_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &header}, deadline); + + ddwaf_object_free(&header); + ddwaf_object_free(&output); + + return 0; +} diff --git a/fuzzer/http_network_fingerprint/corpus/.gitignore b/fuzzer/http_network_fingerprint/corpus/.gitignore new file mode 100644 index 000000000..5e7d2734c --- /dev/null +++ b/fuzzer/http_network_fingerprint/corpus/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/fuzzer/http_network_fingerprint/src/main.cpp b/fuzzer/http_network_fingerprint/src/main.cpp new file mode 100644 index 000000000..d61f8a065 --- /dev/null +++ b/fuzzer/http_network_fingerprint/src/main.cpp @@ -0,0 +1,50 @@ +// 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 + +#include "common.hpp" +#include + +using namespace ddwaf; +using namespace std::literals; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) +{ + static std::array headers{"x-forwarded-for", "x-real-ip", "x-client-ip", + "forwarded-for", "x-cluster-client-ip", "fastly-client-ip", "cf-connecting-ip", + "cf-connecting-ipv6"}; + + random_buffer buffer{bytes, size}; + + ddwaf_object tmp; + + ddwaf_object header; + ddwaf_object_map(&header); + auto header_size = buffer.get(); + for (uint8_t i = 0; i < header_size; ++i) { + auto value = buffer.get(); + + std::string_view key; + if (buffer.get()) { // Known header + key = headers[buffer.get() % headers.size()]; + } else { + key = buffer.get(); + } + ddwaf_object_map_addl(&header, key.data(), key.size(), + ddwaf_object_stringl(&tmp, value.data(), value.size())); + } + + http_network_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &header}, deadline); + + ddwaf_object_free(&header); + ddwaf_object_free(&output); + + return 0; +} diff --git a/fuzzer/session_fingerprint/corpus/.gitignore b/fuzzer/session_fingerprint/corpus/.gitignore new file mode 100644 index 000000000..5e7d2734c --- /dev/null +++ b/fuzzer/session_fingerprint/corpus/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/fuzzer/session_fingerprint/src/main.cpp b/fuzzer/session_fingerprint/src/main.cpp new file mode 100644 index 000000000..a4441f81b --- /dev/null +++ b/fuzzer/session_fingerprint/src/main.cpp @@ -0,0 +1,43 @@ +// 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 + +#include "common.hpp" +#include + +using namespace ddwaf; +using namespace std::literals; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) +{ + random_buffer buffer{bytes, size}; + + ddwaf_object tmp; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + auto cookies_size = buffer.get(); + for (uint8_t i = 0; i < cookies_size; ++i) { + auto key = buffer.get(); + auto value = buffer.get(); + + ddwaf_object_map_addl(&cookies, key.data(), key.size(), + ddwaf_object_stringl(&tmp, value.data(), value.size())); + } + + session_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, buffer.get()}, + {{}, {}, false, buffer.get()}, deadline); + + ddwaf_object_free(&cookies); + ddwaf_object_free(&output); + + return 0; +} diff --git a/src/builder/processor_builder.cpp b/src/builder/processor_builder.cpp index 7ac0a572c..4ac8c754f 100644 --- a/src/builder/processor_builder.cpp +++ b/src/builder/processor_builder.cpp @@ -51,6 +51,30 @@ template <> struct typed_processor_builder { } }; +template <> struct typed_processor_builder { + std::shared_ptr build(const auto &spec) + { + return std::make_shared( + spec.id, spec.expr, spec.mappings, spec.evaluate, spec.output); + } +}; + +template <> struct typed_processor_builder { + std::shared_ptr build(const auto &spec) + { + return std::make_shared( + spec.id, spec.expr, spec.mappings, spec.evaluate, spec.output); + } +}; + +template <> struct typed_processor_builder { + std::shared_ptr build(const auto &spec) + { + return std::make_shared( + spec.id, spec.expr, spec.mappings, spec.evaluate, spec.output); + } +}; + template concept has_build_with_scanners = requires(typed_processor_builder b, Spec spec, Scanners scanners) { @@ -81,6 +105,12 @@ template return build_with_type(*this, scanners); case processor_type::http_endpoint_fingerprint: return build_with_type(*this, scanners); + case processor_type::http_header_fingerprint: + return build_with_type(*this, scanners); + case processor_type::http_network_fingerprint: + return build_with_type(*this, scanners); + case processor_type::session_fingerprint: + return build_with_type(*this, scanners); default: break; } diff --git a/src/condition/exists.hpp b/src/condition/exists.hpp index d66d27baa..74c5f0d85 100644 --- a/src/condition/exists.hpp +++ b/src/condition/exists.hpp @@ -20,10 +20,15 @@ class exists_condition : public base_impl { {} protected: - [[nodiscard]] eval_result eval_impl(const unary_argument &input, + [[nodiscard]] eval_result eval_impl(const variadic_argument &inputs, condition_cache &cache, const exclusion::object_set_ref & /*objects_excluded*/, ddwaf::timer & /*deadline*/) const { + if (inputs.empty()) { + return {false, false}; + } + // We only care about the first input + auto input = inputs.front(); cache.match = {{{{"input", {}, input.address, {}}}, {}, "exists", {}, input.ephemeral}}; return {true, input.ephemeral}; } diff --git a/src/parser/processor_parser.cpp b/src/parser/processor_parser.cpp index 8b9a9a45e..5ce97bae0 100644 --- a/src/parser/processor_parser.cpp +++ b/src/parser/processor_parser.cpp @@ -38,11 +38,9 @@ std::vector parse_processor_mappings( auto input_address = at(input, "address"); addresses.optional.emplace(input_address); - parameters.emplace_back(processor_parameter{ {processor_target{get_target_index(input_address), std::move(input_address), {}}}}); } - auto output = at(mapping, "output"); mappings.emplace_back(processor_mapping{ std::move(parameters), {get_target_index(output), std::move(output), {}}}); @@ -81,16 +79,10 @@ processor_container parse_processors( type = processor_type::http_endpoint_fingerprint; } else if (generator_id == "http_network_fingerprint") { type = processor_type::http_network_fingerprint; - // Skip for now - continue; } else if (generator_id == "http_header_fingerprint") { type = processor_type::http_header_fingerprint; - // Skip for now - continue; } else if (generator_id == "session_fingerprint") { type = processor_type::session_fingerprint; - // Skip for now - continue; } else { DDWAF_WARN("Unknown generator: {}", generator_id); info.add_failed(id, "unknown generator '" + generator_id + "'"); @@ -106,9 +98,18 @@ processor_container parse_processors( if (type == processor_type::extract_schema) { mappings = parse_processor_mappings(mappings_vec, addresses, extract_schema::param_names); - } else { + } else if (type == processor_type::http_endpoint_fingerprint) { mappings = parse_processor_mappings( mappings_vec, addresses, http_endpoint_fingerprint::param_names); + } else if (type == processor_type::http_header_fingerprint) { + mappings = parse_processor_mappings( + mappings_vec, addresses, http_header_fingerprint::param_names); + } else if (type == processor_type::http_network_fingerprint) { + mappings = parse_processor_mappings( + mappings_vec, addresses, http_network_fingerprint::param_names); + } else { + mappings = parse_processor_mappings( + mappings_vec, addresses, session_fingerprint::param_names); } std::vector scanners; diff --git a/src/processor/fingerprint.cpp b/src/processor/fingerprint.cpp index 774fc728c..7c0ccfd80 100644 --- a/src/processor/fingerprint.cpp +++ b/src/processor/fingerprint.cpp @@ -5,8 +5,11 @@ // Copyright 2021 Datadog, Inc. #include "processor/fingerprint.hpp" +#include "ddwaf.h" #include "sha256.hpp" #include "transformer/lowercase.hpp" +#include "utils.hpp" + #include namespace ddwaf { @@ -124,6 +127,24 @@ struct string_field : field_generator { std::string_view value; }; +struct unsigned_field : field_generator { + template + explicit unsigned_field(T input) + requires std::is_unsigned_v + : value(ddwaf::to_string(input)) + {} + ~unsigned_field() override = default; + unsigned_field(const unsigned_field &) = default; + unsigned_field(unsigned_field &&) = default; + unsigned_field &operator=(const unsigned_field &) = default; + unsigned_field &operator=(unsigned_field &&) = default; + + std::size_t length() override { return value.size(); } + void operator()(string_buffer &output) override { output.append(value); } + + std::string value; +}; + struct string_hash_field : field_generator { explicit string_hash_field(std::string_view input) : value(input) {} ~string_hash_field() override = default; @@ -146,7 +167,46 @@ struct key_hash_field : field_generator { key_hash_field &operator=(const key_hash_field &) = default; key_hash_field &operator=(key_hash_field &&) = default; - std::size_t length() override { return value.type == DDWAF_OBJ_MAP ? 8 : 0; } + std::size_t length() override + { + return value.type == DDWAF_OBJ_MAP && value.nbEntries > 0 ? 8 : 0; + } + void operator()(string_buffer &output) override; + + ddwaf_object value; +}; + +struct vector_hash_field : field_generator { + explicit vector_hash_field(const std::vector &input) : value(input) {} + ~vector_hash_field() override = default; + vector_hash_field(const vector_hash_field &) = default; + vector_hash_field(vector_hash_field &&) = default; + vector_hash_field &operator=(const vector_hash_field &) = delete; + vector_hash_field &operator=(vector_hash_field &&) = delete; + + std::size_t length() override { return value.empty() ? 0 : 8; } + void operator()(string_buffer &output) override; + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) + const std::vector &value; +}; + +// This particular generator generates multiple fields (hence the fields name) +// This is to prevent having to create intermediate structures for key and value +// when both have to be processed together. This generator also includes the +// relevant separator, whether the map is empty or not. +struct kv_hash_fields : field_generator { + explicit kv_hash_fields(const ddwaf_object &input) : value(input) {} + ~kv_hash_fields() override = default; + kv_hash_fields(const kv_hash_fields &) = default; + kv_hash_fields(kv_hash_fields &&) = default; + kv_hash_fields &operator=(const kv_hash_fields &) = default; + kv_hash_fields &operator=(kv_hash_fields &&) = default; + + std::size_t length() override + { + return value.type == DDWAF_OBJ_MAP && value.nbEntries > 0 ? (8 + 1 + 8) : 1; + } void operator()(string_buffer &output) override; ddwaf_object value; @@ -197,10 +257,14 @@ bool str_casei_cmp(std::string_view left, std::string_view right) return lc < rc; } } - return left.size() <= right.size(); + return left.size() < right.size(); } -void normalize_string(std::string_view key, std::string &buffer, bool trailing_separator) +// Default key normalization implies: +// - Lowercasing the string +// - Escaping commas +// - Adding trailing commas +void normalize_key(std::string_view key, std::string &buffer, bool trailing_separator) { buffer.clear(); @@ -222,6 +286,48 @@ void normalize_string(std::string_view key, std::string &buffer, bool trailing_s } } +// Header normalization implies: +// - Lowercasing the header +// - Replacing '_' with '-' +void normalize_header(std::string_view original, std::string &buffer) +{ + buffer.resize(original.size()); + + for (std::size_t i = 0; i < original.size(); ++i) { + const auto c = original[i]; + buffer[i] = c == '_' ? '-' : ddwaf::tolower(c); + } +} + +// Value (as opposed to key) normalisation only requires escaping commas +void normalize_value(std::string_view key, std::string &buffer, bool trailing_separator) +{ + buffer.clear(); + + if (buffer.capacity() < key.size()) { + // Add space for the extra comma, just in case + buffer.reserve(key.size() + 1); + } + + for (std::size_t i = 0; i < key.size(); ++i) { + auto comma_idx = key.find(',', i); + if (comma_idx != std::string_view::npos) { + if (comma_idx != i) { + buffer.append(key.substr(i, comma_idx - i)); + } + buffer.append(R"(\,)"); + i = comma_idx; + } else { + buffer.append(key.substr(i)); + break; + } + } + + if (trailing_separator) { + buffer.append(1, ','); + } +} + void string_hash_field::operator()(string_buffer &output) { if (value.empty()) { @@ -239,18 +345,22 @@ void string_hash_field::operator()(string_buffer &output) void key_hash_field::operator()(string_buffer &output) { - if (value.type != DDWAF_OBJ_MAP or value.nbEntries == 0) { + if (value.type != DDWAF_OBJ_MAP || value.nbEntries == 0) { return; } std::vector keys; keys.reserve(value.nbEntries); + std::size_t max_string_size = 0; for (unsigned i = 0; i < value.nbEntries; ++i) { const auto &child = value.array[i]; std::string_view key{ child.parameterName, static_cast(child.parameterNameLength)}; + if (max_string_size > key.size()) { + max_string_size = key.size(); + } keys.emplace_back(key); } @@ -259,14 +369,127 @@ void key_hash_field::operator()(string_buffer &output) sha256_hash hasher; std::string normalized; + // By reserving the largest possible size, it should reduce reallocations + // We also add +1 to account for the trailing comma + normalized.reserve(max_string_size + 1); for (unsigned i = 0; i < keys.size(); ++i) { - normalize_string(keys[i], normalized, (i + 1) < keys.size()); + bool trailing_comma = ((i + 1) < keys.size()); + normalize_key(keys[i], normalized, trailing_comma); hasher << normalized; } hasher.write_digest(output.subspan<8>()); } +void vector_hash_field::operator()(string_buffer &output) +{ + if (value.empty()) { + return; + } + + sha256_hash hasher; + for (unsigned i = 0; i < value.size(); ++i) { + hasher << value[i]; + if ((i + 1) < value.size()) { + hasher << ","; + } + } + hasher.write_digest(output.subspan<8>()); +} + +void kv_hash_fields::operator()(string_buffer &output) +{ + if (value.type != DDWAF_OBJ_MAP || value.nbEntries == 0) { + output.append('-'); + return; + } + + std::vector> kv_sorted; + kv_sorted.reserve(value.nbEntries); + + std::size_t max_string_size = 0; + for (std::size_t i = 0; i < value.nbEntries; ++i) { + const auto &child = value.array[i]; + + std::string_view key{ + child.parameterName, static_cast(child.parameterNameLength)}; + + std::string_view val; + if (child.type == DDWAF_OBJ_STRING) { + val = std::string_view{child.stringValue, static_cast(child.nbEntries)}; + } + + auto larger_size = std::max(key.size(), val.size()); + if (max_string_size < larger_size) { + max_string_size = larger_size; + } + + kv_sorted.emplace_back(key, val); + } + + std::sort(kv_sorted.begin(), kv_sorted.end(), + [](auto &left, auto &right) { return str_casei_cmp(left.first, right.first); }); + + sha256_hash key_hasher; + sha256_hash val_hasher; + + std::string normalized; + // By reserving the largest possible size, it should reduce reallocations + // We also add +1 to account for the trailing comma + normalized.reserve(max_string_size + 1); + for (unsigned i = 0; i < kv_sorted.size(); ++i) { + auto [key, val] = kv_sorted[i]; + + bool trailing_comma = ((i + 1) < kv_sorted.size()); + + normalize_key(key, normalized, trailing_comma); + key_hasher << normalized; + + normalize_value(val, normalized, trailing_comma); + val_hasher << normalized; + } + + key_hasher.write_digest(output.subspan<8>()); + output.append('-'); + val_hasher.write_digest(output.subspan<8>()); +} + +enum class header_type { unknown, standard, ip_origin, user_agent, datadog }; + +constexpr std::size_t standard_headers_length = 10; +constexpr std::size_t ip_origin_headers_length = 10; + +std::pair get_header_type_and_index(std::string_view header) +{ + static std::unordered_map> headers{ + {"referer", {header_type::standard, 0}}, {"connection", {header_type::standard, 1}}, + {"accept-encoding", {header_type::standard, 2}}, + {"content-encoding", {header_type::standard, 3}}, + {"cache-control", {header_type::standard, 4}}, {"te", {header_type::standard, 5}}, + {"accept-charset", {header_type::standard, 6}}, + {"content-type", {header_type::standard, 7}}, {"accept", {header_type::standard, 8}}, + {"accept-language", {header_type::standard, 9}}, + {"x-forwarded-for", {header_type::ip_origin, 0}}, + {"x-real-ip", {header_type::ip_origin, 1}}, {"true-client-ip", {header_type::ip_origin, 2}}, + {"x-client-ip", {header_type::ip_origin, 3}}, {"x-forwarded", {header_type::ip_origin, 4}}, + {"forwarded-for", {header_type::ip_origin, 5}}, + {"x-cluster-client-ip", {header_type::ip_origin, 6}}, + {"fastly-client-ip", {header_type::ip_origin, 7}}, + {"cf-connecting-ip", {header_type::ip_origin, 8}}, + {"cf-connecting-ipv6", {header_type::ip_origin, 9}}, + {"user-agent", {header_type::user_agent, 0}}}; + + if (header.starts_with("x-datadog")) { + return {header_type::datadog, 0}; + } + + auto it = headers.find(header); + if (it == headers.end()) { + return {header_type::unknown, 0}; + } + return it->second; +} + } // namespace // NOLINTNEXTLINE(readability-convert-member-functions-to-static) @@ -298,4 +521,104 @@ std::pair http_endpoint_fingerprint::eval return {res, object_store::attribute::none}; } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +std::pair http_header_fingerprint::eval_impl( + const unary_argument &headers, ddwaf::timer &deadline) const +{ + std::string known_header_bitset; + known_header_bitset.resize(standard_headers_length, '0'); + + std::string_view user_agent; + std::vector unknown_headers; + std::string normalized_header; + for (std::size_t i = 0; i < headers.value->nbEntries; ++i) { + if (deadline.expired()) { + throw ddwaf::timeout_exception(); + } + + const auto &child = headers.value->array[i]; + std::string_view header{ + child.parameterName, static_cast(child.parameterNameLength)}; + + normalize_header(header, normalized_header); + auto [type, index] = get_header_type_and_index(normalized_header); + if (type == header_type::standard) { + known_header_bitset[index] = '1'; + } else if (type == header_type::unknown) { + unknown_headers.emplace_back(normalized_header); + } else if (type == header_type::user_agent && child.type == DDWAF_OBJ_STRING) { + user_agent = {child.stringValue, static_cast(child.nbEntries)}; + } + } + std::sort(unknown_headers.begin(), unknown_headers.end()); + + auto res = + generate_fragment("hdr", string_field{known_header_bitset}, string_hash_field{user_agent}, + unsigned_field{unknown_headers.size()}, vector_hash_field{unknown_headers}); + + return {res, object_store::attribute::none}; +} + +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +std::pair http_network_fingerprint::eval_impl( + const unary_argument &headers, ddwaf::timer &deadline) const +{ + std::string ip_origin_bitset; + ip_origin_bitset.resize(ip_origin_headers_length, '0'); + + unsigned chosen_header = ip_origin_headers_length; + std::string_view chosen_header_value; + std::string normalized_header; + for (std::size_t i = 0; i < headers.value->nbEntries; ++i) { + if (deadline.expired()) { + throw ddwaf::timeout_exception(); + } + + const auto &child = headers.value->array[i]; + + std::string_view header{ + child.parameterName, static_cast(child.parameterNameLength)}; + + normalize_header(header, normalized_header); + auto [type, index] = get_header_type_and_index(normalized_header); + if (type == header_type::ip_origin) { + ip_origin_bitset[index] = '1'; + // Verify not only precedence but also type, as a header of an unexpected + // type will be unlikely to be used unless the framework has somehow + // broken down the header into constituent IPs + if (chosen_header > index && child.type == DDWAF_OBJ_STRING) { + chosen_header_value = { + child.stringValue, static_cast(child.nbEntries)}; + chosen_header = index; + } + } + } + + unsigned ip_count = 0; + if (!chosen_header_value.empty()) { + // For now, count commas + ++ip_count; + for (auto c : chosen_header_value) { ip_count += static_cast(c == ','); } + } + + auto res = generate_fragment("net", unsigned_field{ip_count}, string_field{ip_origin_bitset}); + + return {res, object_store::attribute::none}; +} + +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +std::pair session_fingerprint::eval_impl( + const unary_argument &cookies, + const unary_argument &session_id, + const unary_argument &user_id, ddwaf::timer &deadline) const +{ + if (deadline.expired()) { + throw ddwaf::timeout_exception(); + } + + auto res = generate_fragment("ssn", string_hash_field{user_id.value}, + kv_hash_fields{*cookies.value}, string_hash_field{session_id.value}); + return {res, object_store::attribute::none}; +} + } // namespace ddwaf diff --git a/src/processor/fingerprint.hpp b/src/processor/fingerprint.hpp index c6fb823bc..1e6d6c178 100644 --- a/src/processor/fingerprint.hpp +++ b/src/processor/fingerprint.hpp @@ -6,12 +6,9 @@ #include #include -#include #include #include "processor/base.hpp" -#include "scanner.hpp" -#include "utils.hpp" namespace ddwaf { diff --git a/tests/integration/processors/fingerprint/ruleset/postprocessor.json b/tests/integration/processors/fingerprint/ruleset/postprocessor.json index 4d210d4e3..2ddd319ed 100644 --- a/tests/integration/processors/fingerprint/ruleset/postprocessor.json +++ b/tests/integration/processors/fingerprint/ruleset/postprocessor.json @@ -77,6 +77,121 @@ }, "evaluate": false, "output": true + }, + { + "id": "processor-002", + "generator": "http_header_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "parameters": { + "mappings": [ + { + "headers": [ + { + "address": "server.request.headers.no_cookies" + } + ], + "output": "_dd.appsec.fp.http.header" + } + ] + }, + "evaluate": false, + "output": true + }, + { + "id": "processor-003", + "generator": "http_network_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "parameters": { + "mappings": [ + { + "headers": [ + { + "address": "server.request.headers.no_cookies" + } + ], + "output": "_dd.appsec.fp.http.network" + } + ] + }, + "evaluate": false, + "output": true + }, + { + "id": "processor-004", + "generator": "session_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "parameters": { + "mappings": [ + { + "cookies": [ + { + "address": "server.request.cookies" + } + ], + "session_id": [ + { + "address": "usr.session_id" + } + ], + "user_id": [ + { + "address": "usr.id" + } + ], + "output": "_dd.appsec.fp.session" + } + ] + }, + "evaluate": false, + "output": true } ] } diff --git a/tests/integration/processors/fingerprint/ruleset/preprocessor.json b/tests/integration/processors/fingerprint/ruleset/preprocessor.json index d3d4ce0df..f818efd14 100644 --- a/tests/integration/processors/fingerprint/ruleset/preprocessor.json +++ b/tests/integration/processors/fingerprint/ruleset/preprocessor.json @@ -24,7 +24,71 @@ "operator": "match_regex" } ] + }, + { + "id": "rule2", + "name": "rule2", + "tags": { + "type": "flow2", + "category": "category2" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "_dd.appsec.fp.http.header" + } + ], + "regex": ".*" + }, + "operator": "match_regex" + } + ] + }, + { + "id": "rule3", + "name": "rule3", + "tags": { + "type": "flow3", + "category": "category3" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "_dd.appsec.fp.http.network" + } + ], + "regex": ".*" + }, + "operator": "match_regex" + } + ] + }, + { + "id": "rule4", + "name": "rule4", + "tags": { + "type": "flow4", + "category": "category4" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "_dd.appsec.fp.session" + } + ], + "regex": ".*" + }, + "operator": "match_regex" + } + ] } + ], "processors": [ { @@ -76,6 +140,121 @@ }, "evaluate": true, "output": false + }, + { + "id": "processor-002", + "generator": "http_header_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "parameters": { + "mappings": [ + { + "headers": [ + { + "address": "server.request.headers.no_cookies" + } + ], + "output": "_dd.appsec.fp.http.header" + } + ] + }, + "evaluate": true, + "output": false + }, + { + "id": "processor-003", + "generator": "http_network_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "parameters": { + "mappings": [ + { + "headers": [ + { + "address": "server.request.headers.no_cookies" + } + ], + "output": "_dd.appsec.fp.http.network" + } + ] + }, + "evaluate": true, + "output": false + }, + { + "id": "processor-004", + "generator": "session_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "parameters": { + "mappings": [ + { + "cookies": [ + { + "address": "server.request.cookies" + } + ], + "session_id": [ + { + "address": "usr.session_id" + } + ], + "user_id": [ + { + "address": "usr.id" + } + ], + "output": "_dd.appsec.fp.session" + } + ] + }, + "evaluate": true, + "output": false } ] } diff --git a/tests/integration/processors/fingerprint/ruleset/processor.json b/tests/integration/processors/fingerprint/ruleset/processor.json index 91c86875e..a473a6d02 100644 --- a/tests/integration/processors/fingerprint/ruleset/processor.json +++ b/tests/integration/processors/fingerprint/ruleset/processor.json @@ -24,6 +24,69 @@ "operator": "match_regex" } ] + }, + { + "id": "rule2", + "name": "rule2", + "tags": { + "type": "flow2", + "category": "category2" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "_dd.appsec.fp.http.header" + } + ], + "regex": ".*" + }, + "operator": "match_regex" + } + ] + }, + { + "id": "rule3", + "name": "rule3", + "tags": { + "type": "flow3", + "category": "category3" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "_dd.appsec.fp.http.network" + } + ], + "regex": ".*" + }, + "operator": "match_regex" + } + ] + }, + { + "id": "rule4", + "name": "rule4", + "tags": { + "type": "flow4", + "category": "category4" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "_dd.appsec.fp.session" + } + ], + "regex": ".*" + }, + "operator": "match_regex" + } + ] } ], "processors": [ @@ -76,6 +139,121 @@ }, "evaluate": true, "output": true + }, + { + "id": "processor-002", + "generator": "http_header_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "parameters": { + "mappings": [ + { + "headers": [ + { + "address": "server.request.headers.no_cookies" + } + ], + "output": "_dd.appsec.fp.http.header" + } + ] + }, + "evaluate": true, + "output": true + }, + { + "id": "processor-003", + "generator": "http_network_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "parameters": { + "mappings": [ + { + "headers": [ + { + "address": "server.request.headers.no_cookies" + } + ], + "output": "_dd.appsec.fp.http.network" + } + ] + }, + "evaluate": true, + "output": true + }, + { + "id": "processor-004", + "generator": "session_fingerprint", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "fingerprint" + ] + } + ], + "value": true, + "type": "boolean" + } + } + ], + "parameters": { + "mappings": [ + { + "cookies": [ + { + "address": "server.request.cookies" + } + ], + "session_id": [ + { + "address": "usr.session_id" + } + ], + "user_id": [ + { + "address": "usr.id" + } + ], + "output": "_dd.appsec.fp.session" + } + ] + }, + "evaluate": true, + "output": true } ] } diff --git a/tests/integration/processors/fingerprint/test.cpp b/tests/integration/processors/fingerprint/test.cpp index 5ec090b55..2c8b5f2a2 100644 --- a/tests/integration/processors/fingerprint/test.cpp +++ b/tests/integration/processors/fingerprint/test.cpp @@ -21,12 +21,16 @@ TEST(TestFingerprintIntegration, Postprocessor) uint32_t size; const char *const *addresses = ddwaf_known_addresses(handle, &size); - EXPECT_EQ(size, 5); + EXPECT_EQ(size, 9); std::unordered_set address_set(addresses, addresses + size); EXPECT_TRUE(address_set.contains("server.request.body")); EXPECT_TRUE(address_set.contains("server.request.uri.raw")); EXPECT_TRUE(address_set.contains("server.request.method")); EXPECT_TRUE(address_set.contains("server.request.query")); + EXPECT_TRUE(address_set.contains("server.request.headers.no_cookies")); + EXPECT_TRUE(address_set.contains("server.request.cookies")); + EXPECT_TRUE(address_set.contains("usr.id")); + EXPECT_TRUE(address_set.contains("usr.session_id")); EXPECT_TRUE(address_set.contains("waf.context.processor")); ddwaf_context context = ddwaf_context_init(handle); @@ -49,6 +53,46 @@ TEST(TestFingerprintIntegration, Postprocessor) &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 headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "connection", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-control", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "te", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-charset", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-type", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-language", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "user-agent", ddwaf_object_string(&tmp, "Random")); + ddwaf_object_map_add(&headers, "x-forwarded-for", ddwaf_object_string(&tmp, "::1")); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "fastly-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map_add(&map, "server.request.headers.no_cookies", &headers); + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_string(&tmp, "albert")); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_string(&tmp, "en-GB")); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_string(&tmp, "yes")); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add(&cookies, "last_visit", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + ddwaf_object_map_add(&map, "server.request.cookies", &cookies); + ddwaf_object_map_add(&map, "usr.id", ddwaf_object_string(&tmp, "admin")); + ddwaf_object_map_add(&map, "usr.session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add(&settings, "fingerprint", ddwaf_object_bool(&tmp, true)); ddwaf_object_map_add(&map, "waf.context.processor", &settings); @@ -56,10 +100,13 @@ TEST(TestFingerprintIntegration, Postprocessor) ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); EXPECT_FALSE(out.timeout); - EXPECT_EQ(ddwaf_object_size(&out.derivatives), 1); + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 4); - auto json = test::object_to_json(out.derivatives); - EXPECT_STR(json, R"({"_dd.appsec.fp.http.endpoint":"http-put-729d56c3-2c70e12b-2c70e12b"})"); + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.endpoint"], "http-put-729d56c3-2c70e12b-2c70e12b"); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.header"], "hdr-1111111111-a441b15f-0-"); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.network"], "net-1-1111111111"); + EXPECT_STRV(derivatives["_dd.appsec.fp.session"], "ssn-8c6976e5-df6143bc-60ba1602-269500d3"); ddwaf_result_free(&out); ddwaf_context_destroy(context); @@ -76,14 +123,21 @@ TEST(TestFingerprintIntegration, Preprocessor) uint32_t size; const char *const *addresses = ddwaf_known_addresses(handle, &size); - EXPECT_EQ(size, 6); + EXPECT_EQ(size, 13); std::unordered_set address_set(addresses, addresses + size); EXPECT_TRUE(address_set.contains("server.request.body")); EXPECT_TRUE(address_set.contains("server.request.uri.raw")); EXPECT_TRUE(address_set.contains("server.request.method")); EXPECT_TRUE(address_set.contains("server.request.query")); + EXPECT_TRUE(address_set.contains("server.request.headers.no_cookies")); + EXPECT_TRUE(address_set.contains("server.request.cookies")); + EXPECT_TRUE(address_set.contains("usr.id")); + EXPECT_TRUE(address_set.contains("usr.session_id")); EXPECT_TRUE(address_set.contains("waf.context.processor")); EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.endpoint")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.header")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.network")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.session")); ddwaf_context context = ddwaf_context_init(handle); ASSERT_NE(context, nullptr); @@ -105,6 +159,46 @@ TEST(TestFingerprintIntegration, Preprocessor) &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 headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "connection", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-control", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "te", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-charset", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-type", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-language", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "user-agent", ddwaf_object_string(&tmp, "Random")); + ddwaf_object_map_add(&headers, "x-forwarded-for", ddwaf_object_string(&tmp, "::1")); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "fastly-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map_add(&map, "server.request.headers.no_cookies", &headers); + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_string(&tmp, "albert")); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_string(&tmp, "en-GB")); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_string(&tmp, "yes")); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add(&cookies, "last_visit", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + ddwaf_object_map_add(&map, "server.request.cookies", &cookies); + ddwaf_object_map_add(&map, "usr.id", ddwaf_object_string(&tmp, "admin")); + ddwaf_object_map_add(&map, "usr.session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add(&settings, "fingerprint", ddwaf_object_bool(&tmp, true)); ddwaf_object_map_add(&map, "waf.context.processor", &settings); @@ -112,17 +206,51 @@ TEST(TestFingerprintIntegration, Preprocessor) ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); EXPECT_FALSE(out.timeout); - EXPECT_EVENTS(out, {.id = "rule1", - .name = "rule1", - .tags = {{"type", "flow1"}, {"category", "category1"}}, - .matches = {{.op = "match_regex", - .op_value = ".*", - .highlight = "http-put-729d56c3-2c70e12b-2c70e12b", - .args = {{ - .value = "http-put-729d56c3-2c70e12b-2c70e12b", - .address = "_dd.appsec.fp.http.endpoint", - .path = {}, - }}}}}); + EXPECT_EVENTS(out, + {.id = "rule1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}}, + .matches = {{.op = "match_regex", + .op_value = ".*", + .highlight = "http-put-729d56c3-2c70e12b-2c70e12b", + .args = {{ + .value = "http-put-729d56c3-2c70e12b-2c70e12b", + .address = "_dd.appsec.fp.http.endpoint", + .path = {}, + }}}}}, + {.id = "rule2", + .name = "rule2", + .tags = {{"type", "flow2"}, {"category", "category2"}}, + .matches = {{.op = "match_regex", + .op_value = ".*", + .highlight = "hdr-1111111111-a441b15f-0-", + .args = {{ + .value = "hdr-1111111111-a441b15f-0-", + .address = "_dd.appsec.fp.http.header", + .path = {}, + }}}}}, + {.id = "rule3", + .name = "rule3", + .tags = {{"type", "flow3"}, {"category", "category3"}}, + .matches = {{.op = "match_regex", + .op_value = ".*", + .highlight = "net-1-1111111111", + .args = {{ + .value = "net-1-1111111111", + .address = "_dd.appsec.fp.http.network", + .path = {}, + }}}}}, + {.id = "rule4", + .name = "rule4", + .tags = {{"type", "flow4"}, {"category", "category4"}}, + .matches = {{.op = "match_regex", + .op_value = ".*", + .highlight = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", + .args = {{ + .value = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", + .address = "_dd.appsec.fp.session", + .path = {}, + }}}}}, ); EXPECT_EQ(ddwaf_object_size(&out.derivatives), 0); @@ -141,14 +269,22 @@ TEST(TestFingerprintIntegration, Processor) uint32_t size; const char *const *addresses = ddwaf_known_addresses(handle, &size); - EXPECT_EQ(size, 6); + + EXPECT_EQ(size, 13); std::unordered_set address_set(addresses, addresses + size); EXPECT_TRUE(address_set.contains("server.request.body")); EXPECT_TRUE(address_set.contains("server.request.uri.raw")); EXPECT_TRUE(address_set.contains("server.request.method")); EXPECT_TRUE(address_set.contains("server.request.query")); + EXPECT_TRUE(address_set.contains("server.request.headers.no_cookies")); + EXPECT_TRUE(address_set.contains("server.request.cookies")); + EXPECT_TRUE(address_set.contains("usr.id")); + EXPECT_TRUE(address_set.contains("usr.session_id")); EXPECT_TRUE(address_set.contains("waf.context.processor")); EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.endpoint")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.header")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.network")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.session")); ddwaf_context context = ddwaf_context_init(handle); ASSERT_NE(context, nullptr); @@ -170,6 +306,46 @@ TEST(TestFingerprintIntegration, Processor) &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 headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "connection", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-control", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "te", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-charset", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-type", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-language", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "user-agent", ddwaf_object_string(&tmp, "Random")); + ddwaf_object_map_add(&headers, "x-forwarded-for", ddwaf_object_string(&tmp, "::1")); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "fastly-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map_add(&map, "server.request.headers.no_cookies", &headers); + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_string(&tmp, "albert")); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_string(&tmp, "en-GB")); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_string(&tmp, "yes")); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add(&cookies, "last_visit", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + ddwaf_object_map_add(&map, "server.request.cookies", &cookies); + ddwaf_object_map_add(&map, "usr.id", ddwaf_object_string(&tmp, "admin")); + ddwaf_object_map_add(&map, "usr.session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add(&settings, "fingerprint", ddwaf_object_bool(&tmp, true)); ddwaf_object_map_add(&map, "waf.context.processor", &settings); @@ -177,22 +353,59 @@ TEST(TestFingerprintIntegration, Processor) ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); EXPECT_FALSE(out.timeout); - EXPECT_EVENTS(out, {.id = "rule1", - .name = "rule1", - .tags = {{"type", "flow1"}, {"category", "category1"}}, - .matches = {{.op = "match_regex", - .op_value = ".*", - .highlight = "http-put-729d56c3-2c70e12b-2c70e12b", - .args = {{ - .value = "http-put-729d56c3-2c70e12b-2c70e12b", - .address = "_dd.appsec.fp.http.endpoint", - .path = {}, - }}}}}); - - 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"})"); + EXPECT_EVENTS(out, + {.id = "rule1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}}, + .matches = {{.op = "match_regex", + .op_value = ".*", + .highlight = "http-put-729d56c3-2c70e12b-2c70e12b", + .args = {{ + .value = "http-put-729d56c3-2c70e12b-2c70e12b", + .address = "_dd.appsec.fp.http.endpoint", + .path = {}, + }}}}}, + {.id = "rule2", + .name = "rule2", + .tags = {{"type", "flow2"}, {"category", "category2"}}, + .matches = {{.op = "match_regex", + .op_value = ".*", + .highlight = "hdr-1111111111-a441b15f-0-", + .args = {{ + .value = "hdr-1111111111-a441b15f-0-", + .address = "_dd.appsec.fp.http.header", + .path = {}, + }}}}}, + {.id = "rule3", + .name = "rule3", + .tags = {{"type", "flow3"}, {"category", "category3"}}, + .matches = {{.op = "match_regex", + .op_value = ".*", + .highlight = "net-1-1111111111", + .args = {{ + .value = "net-1-1111111111", + .address = "_dd.appsec.fp.http.network", + .path = {}, + }}}}}, + {.id = "rule4", + .name = "rule4", + .tags = {{"type", "flow4"}, {"category", "category4"}}, + .matches = {{.op = "match_regex", + .op_value = ".*", + .highlight = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", + .args = {{ + .value = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", + .address = "_dd.appsec.fp.session", + .path = {}, + }}}}}, ); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 4); + + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.endpoint"], "http-put-729d56c3-2c70e12b-2c70e12b"); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.header"], "hdr-1111111111-a441b15f-0-"); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.network"], "net-1-1111111111"); + EXPECT_STRV(derivatives["_dd.appsec.fp.session"], "ssn-8c6976e5-df6143bc-60ba1602-269500d3"); ddwaf_result_free(&out); ddwaf_context_destroy(context); diff --git a/tests/operator/exists_condition_test.cpp b/tests/operator/exists_condition_test.cpp index 43ea12870..c238cc516 100644 --- a/tests/operator/exists_condition_test.cpp +++ b/tests/operator/exists_condition_test.cpp @@ -6,20 +6,21 @@ #include "../test.hpp" #include "condition/exists.hpp" +#include "utils.hpp" using namespace ddwaf; using namespace std::literals; namespace { -template std::vector gen_param_def(Args... addresses) +template std::vector gen_variadic_param(Args... addresses) { - return {{{{std::string{addresses}, get_target_index(addresses)}}}...}; + return {{{{std::string{addresses}, get_target_index(addresses)}...}}}; } TEST(TestExistsCondition, AddressAvailable) { - exists_condition cond{{gen_param_def("server.request.uri_raw")}}; + exists_condition cond{{gen_variadic_param("server.request.uri_raw")}}; ddwaf_object tmp; ddwaf_object root; @@ -37,7 +38,7 @@ TEST(TestExistsCondition, AddressAvailable) TEST(TestExistsCondition, AddressNotAvaialble) { - exists_condition cond{{gen_param_def("server.request.uri_raw")}}; + exists_condition cond{{gen_variadic_param("server.request.uri_raw")}}; ddwaf_object tmp; ddwaf_object root; @@ -53,4 +54,31 @@ TEST(TestExistsCondition, AddressNotAvaialble) ASSERT_FALSE(res.outcome); } +TEST(TestExistsCondition, MultipleAddresses) +{ + exists_condition cond{ + {gen_variadic_param("server.request.uri_raw", "server.request.body", "usr.id")}}; + + auto validate_address = [&](const std::string &address, bool expected = true) { + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, address.c_str(), 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_EQ(res.outcome, expected); + }; + + validate_address("usr.id"); + validate_address("server.request.body"); + validate_address("server.request.uri_raw"); + validate_address("server.request.query", false); + validate_address("usr.session_id", false); +} + } // namespace diff --git a/tests/processor/fingerprint_test.cpp b/tests/processor/fingerprint_test.cpp index a22d20c28..ad191d887 100644 --- a/tests/processor/fingerprint_test.cpp +++ b/tests/processor/fingerprint_test.cpp @@ -362,4 +362,633 @@ TEST(TestHttpEndpointFingerprint, UriRawConsistency) ddwaf_object_free(&body); } +TEST(TestHttpHeaderFingerprint, AllKnownHeaders) +{ + ddwaf_object tmp; + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "CONNECTION", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "Accept_Encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "CONTENT-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-CONTROL", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "tE", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "ACCEPT_CHARSET", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-type", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accepT", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept_language", ddwaf_object_invalid(&tmp)); + + http_header_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "hdr-1111111111--0-"); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); +} + +TEST(TestHttpHeaderFingerprint, NoHeaders) +{ + ddwaf_object headers; + ddwaf_object_map(&headers); + + http_header_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "hdr-0000000000--0-"); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); +} + +TEST(TestHttpHeaderFingerprint, SomeKnownHeaders) +{ + ddwaf_object tmp; + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-control", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-charset", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-language", ddwaf_object_invalid(&tmp)); + + http_header_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "hdr-1010101011--0-"); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); +} + +TEST(TestHttpHeaderFingerprint, UserAgent) +{ + ddwaf_object tmp; + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "connection", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-control", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "te", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-charset", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-type", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-language", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "user-agent", ddwaf_object_string(&tmp, "Random")); + + http_header_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "hdr-1111111111-a441b15f-0-"); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); +} + +TEST(TestHttpHeaderFingerprint, ExcludedUnknownHeaders) +{ + ddwaf_object tmp; + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "connection", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-control", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "te", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-charset", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-type", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-language", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "user-agent", ddwaf_object_string(&tmp, "Random")); + + // Should be excluded + ddwaf_object_map_add(&headers, "x-datadog-trace-id", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "fastly-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + http_header_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "hdr-1111111111-a441b15f-0-"); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); +} + +TEST(TestHttpHeaderFingerprint, UnknownHeaders) +{ + ddwaf_object tmp; + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "connection", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-control", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "te", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-charset", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-type", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-language", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "user-agent", ddwaf_object_string(&tmp, "Random")); + ddwaf_object_map_add(&headers, "unknown_header", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "Authorization", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "WWW-Authenticate", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "Allow", ddwaf_object_invalid(&tmp)); + + // Should be excluded + ddwaf_object_map_add(&headers, "x-datadog-trace-id", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "fastly-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + http_header_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "hdr-1111111111-a441b15f-4-47280082"); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); +} + +TEST(TestHttpNetworkFingerprint, AllXFFHeaders) +{ + ddwaf_object tmp; + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "x-forwarded-for", ddwaf_object_string(&tmp, "192.168.1.1")); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "fastly-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + http_network_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "net-1-1111111111"); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); +} +TEST(TestHttpNetworkFingerprint, NoHeaders) +{ + ddwaf_object headers; + ddwaf_object_map(&headers); + + http_network_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "net-0-0000000000"); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); +} + +TEST(TestHttpNetworkFingerprint, AllXFFHeadersMultipleChosenIPs) +{ + ddwaf_object tmp; + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add( + &headers, "x-forwarded-for", ddwaf_object_string(&tmp, "192.168.1.1,::1,8.7.6.5")); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "fastly-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + http_network_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "net-3-1111111111"); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); +} + +TEST(TestHttpNetworkFingerprint, AllXFFHeadersRandomChosenHeader) +{ + ddwaf_object tmp; + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "x-forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add( + &headers, "fastly-client-ip", ddwaf_object_string(&tmp, "192.168.1.1,::1,8.7.6.5")); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + http_network_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "net-3-1111111111"); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); +} + +TEST(TestHttpNetworkFingerprint, HeaderPrecedence) +{ + http_network_fingerprint gen{"id", {}, {}, false, true}; + + auto get_headers = [](std::size_t begin) { + ddwaf_object tmp; + ddwaf_object headers; + ddwaf_object_map(&headers); + + std::array names{"x-forwarded-for", "x-real-ip", "true-client-ip", + "x-client-ip", "x-forwarded", "forwarded-for", "x-cluster-client-ip", + "fastly-client-ip", "cf-connecting-ip", "cf-connecting-ipv6"}; + + std::string value = "::1"; + for (std::size_t i = 0; i < begin; ++i) { value += ",::1"; } + + for (std::size_t i = begin; i < names.size(); ++i) { + ddwaf_object_map_add( + &headers, names[i].c_str(), ddwaf_object_string(&tmp, value.c_str())); + value += ",::1"; + } + + return headers; + }; + + auto match_frag = [&](ddwaf_object headers, const std::string &expected) { + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, expected.c_str()); + + ddwaf_object_free(&headers); + ddwaf_object_free(&output); + }; + + match_frag(get_headers(0), "net-1-1111111111"); + match_frag(get_headers(1), "net-2-0111111111"); + match_frag(get_headers(2), "net-3-0011111111"); + match_frag(get_headers(3), "net-4-0001111111"); + match_frag(get_headers(4), "net-5-0000111111"); + match_frag(get_headers(5), "net-6-0000011111"); + match_frag(get_headers(6), "net-7-0000001111"); + match_frag(get_headers(7), "net-8-0000000111"); + match_frag(get_headers(8), "net-9-0000000011"); + match_frag(get_headers(9), "net-10-0000000001"); +} + +TEST(TestSessionFingerprint, UserOnly) +{ + ddwaf_object cookies; + ddwaf_object_map(&cookies); + + session_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl( + {{}, {}, false, &cookies}, {{}, {}, false, {}}, {{}, {}, false, "admin"}, deadline); + + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn-8c6976e5---"); + + ddwaf_object_free(&cookies); + ddwaf_object_free(&output); +} + +TEST(TestSessionFingerprint, SessionOnly) +{ + ddwaf_object cookies; + ddwaf_object_map(&cookies); + + session_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl( + {{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, {{}, {}, false, {}}, deadline); + + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn----269500d3"); + + ddwaf_object_free(&cookies); + ddwaf_object_free(&output); +} + +TEST(TestSessionFingerprint, CookiesOnly) +{ + ddwaf_object tmp; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_string(&tmp, "albert")); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_string(&tmp, "en-GB")); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_string(&tmp, "yes")); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add(&cookies, "last_visit", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + session_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl( + {{}, {}, false, &cookies}, {{}, {}, false, {}}, {{}, {}, false, {}}, deadline); + + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn--df6143bc-60ba1602-"); + + ddwaf_object_free(&cookies); + ddwaf_object_free(&output); +} + +TEST(TestSessionFingerprint, UserCookieAndSession) +{ + ddwaf_object tmp; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_string(&tmp, "albert")); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_string(&tmp, "en-GB")); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_string(&tmp, "yes")); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add(&cookies, "last_visit", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + session_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, + {{}, {}, false, "admin"}, deadline); + + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn-8c6976e5-df6143bc-60ba1602-269500d3"); + + ddwaf_object_free(&cookies); + ddwaf_object_free(&output); +} + +TEST(TestSessionFingerprint, CookieKeysNormalization) +{ + ddwaf_object tmp; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "nAmE", ddwaf_object_string(&tmp, "albert")); + ddwaf_object_map_add(&cookies, "THEME", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language,ID", ddwaf_object_string(&tmp, "en-GB")); + ddwaf_object_map_add(&cookies, "tra,cKing,ID", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "Gdpr_consent", ddwaf_object_string(&tmp, "yes")); + ddwaf_object_map_add(&cookies, "SESSION_ID", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add(&cookies, "last_visiT", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + session_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, + {{}, {}, false, "admin"}, deadline); + + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn-8c6976e5-424e7e09-60ba1602-269500d3"); + + ddwaf_object_free(&cookies); + ddwaf_object_free(&output); +} + +TEST(TestSessionFingerprint, CookieValuesNormalization) +{ + ddwaf_object tmp; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_string(&tmp, "albert,martinez")); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_string(&tmp, "en-GB,en-US")); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_string(&tmp, ",yes")); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_string(&tmp, "ansd0182u2n,")); + ddwaf_object_map_add(&cookies, "last_visit", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + session_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, + {{}, {}, false, "admin"}, deadline); + + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn-8c6976e5-df6143bc-64f82cf7-269500d3"); + + ddwaf_object_free(&cookies); + ddwaf_object_free(&output); +} + +TEST(TestSessionFingerprint, CookieEmptyValues) +{ + ddwaf_object tmp; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&cookies, "last_visit", ddwaf_object_invalid(&tmp)); + + session_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, + {{}, {}, false, "admin"}, deadline); + + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn-8c6976e5-df6143bc-d3648ef2-269500d3"); + + ddwaf_object_free(&cookies); + ddwaf_object_free(&output); +} + +TEST(TestSessionFingerprint, CookieEmptyKeys) +{ + ddwaf_object tmp; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "", ddwaf_object_string(&tmp, "albert,martinez")); + ddwaf_object_map_add(&cookies, "", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "", ddwaf_object_string(&tmp, "en-GB,en-US")); + ddwaf_object_map_add(&cookies, "", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "", ddwaf_object_string(&tmp, ",yes")); + ddwaf_object_map_add(&cookies, "", ddwaf_object_string(&tmp, "ansd0182u2n,")); + ddwaf_object_map_add(&cookies, "", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + session_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, + {{}, {}, false, "admin"}, deadline); + + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn-8c6976e5-d3648ef2-f32e5c3e-269500d3"); + + ddwaf_object_free(&cookies); + ddwaf_object_free(&output); +} + +TEST(TestSessionFingerprint, EmptyEverything) +{ + ddwaf_object cookies; + ddwaf_object_map(&cookies); + + session_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + auto [output, attr] = gen.eval_impl( + {{}, {}, false, &cookies}, {{}, {}, false, {}}, {{}, {}, false, {}}, deadline); + + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{ + output.stringValue, static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn----"); + + ddwaf_object_free(&cookies); + ddwaf_object_free(&output); +} + } // namespace diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp index 31ffd701f..109d3563b 100644 --- a/tests/test_utils.cpp +++ b/tests/test_utils.cpp @@ -258,6 +258,18 @@ rapidjson::Document object_to_rapidjson(const ddwaf_object &obj) return document; } +std::unordered_map object_to_map(const ddwaf_object &obj) +{ + std::unordered_map map; + for (unsigned i = 0; i < obj.nbEntries; ++i) { + const ddwaf_object &child = obj.array[i]; + map.emplace(std::string_view{child.parameterName, + static_cast(child.parameterNameLength)}, + std::string_view{child.stringValue, static_cast(child.nbEntries)}); + } + return map; +} + //} // namespace ddwaf::test void PrintTo(const ddwaf_object &actions, ::std::ostream *os) { diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index 429e516c6..895b91852 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -58,6 +58,7 @@ ::std::ostream &operator<<(::std::ostream &os, const event::match &m); std::string object_to_json(const ddwaf_object &obj); rapidjson::Document object_to_rapidjson(const ddwaf_object &obj); +std::unordered_map object_to_map(const ddwaf_object &obj); class expression_builder { public: From 29fb5c2bf2867ab39b3c7bf009ce52e3d93bf7b2 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:32:23 +0100 Subject: [PATCH 15/17] Release 1.19.0 (#325) --- CHANGELOG.md | 219 ++++++++++++++++++++++++++++++++++++--------------- version | 2 +- 2 files changed, 156 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8524b23b..a07b16797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,97 @@ # libddwaf release -### v1.18.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.19.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) + +### New features +This new version of `libddwaf` introduces a multitude of new features in order to support new use cases and expand existing ones. + +#### Exploit prevention: Shell injection detection +A new operator `shi_detector` has been introduced for detecting and blocking shell injections, based on input parameters and the final shell code being evaluated. This new operator is part of the exploit prevention feature, so it is meant to be used in combination with targeted instrumentation. + +The following example rule takes advantage of the new operator to identify injections originating from request parameters: + +```yaml + - id: rsp-930-004 + name: SHi Exploit detection + tags: + type: shi + category: exploit_detection + module: rasp + conditions: + - parameters: + resource: + - address: server.sys.shell.cmd + params: + - address: server.request.query + - address: server.request.body + - address: server.request.path_params + - address: grpc.server.request.message + - address: graphql.server.all_resolvers + - address: graphql.server.resolver + operator: shi_detector +``` + +#### Attacker \& Request Fingerprinting +This release includes a new family of processors which can be used to generate different fingerprints for a request and / or user, depending on available information: +- `http_endpoint_fingerprint`: this processor generates a fingerprint which uniquely identifies the HTTP endpoint accessed by the request as well as how this endpoint was accessed (i.e. which parameters were used). +- `http_headers_fingerprint`: generates a fingerprint which provides information about the headers used when accessing said HTTP endpoint. +- `http_network_fingerprint`: provides a fingerprint containing some information about the network-related HTTP headers used within the request. +- `session_fingerprint`: this processor generates a specific fingeprint with sufficient information to track a unique session and / or attacker. + +#### Suspicious attacker blocking +Suspicious attackers can now be blocked conditionally when they perform a restricted action or an attack. With the combination of custom exclusion filter actions and exclusion data, it is now possible to change the action of a rule dynamically depending on a condition, e.g. all rules could be set to blocking mode if a given IP performs a known attack. + +The following exclusion filter, in combination with the provided exclusion data, changes the action of all rules based on the client IP: + +```yaml +exclusions: + - id: suspicious_attacker + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + data: ip_data +exclusion_data: + - id: ip_data + type: ip_with_expiration + data: + - value: 1.2.3.4 + expiration: 0 +``` + +#### Other new features +- New operator `exists`: this new operator can be used to assert the presence of one or multiple addresses, regardless of their underlying value. +- Rule tagging overrides: rule overrides now allow adding tags to an existing rule, e.g. to provide information about the policy used. +- New function `ddwaf_known_actions`: this new function can be used to obtain a list of the action types which can be triggered given the set of rules and exclusion filters available. + +### Release changelog +#### Changes +- Multivariate processors and remove generators ([#298](https://github.com/DataDog/libddwaf/pull/298)) +- Custom rule filter actions ([#303](https://github.com/DataDog/libddwaf/pull/303)) +- SHA256 hash based on OpenSSL ([#304](https://github.com/DataDog/libddwaf/pull/304)) +- Shell injection detection operator ([#308](https://github.com/DataDog/libddwaf/pull/308)) +- Limit the number of transformers per rule or input ([#309](https://github.com/DataDog/libddwaf/pull/309)) +- Validate redirection location and restrict status codes ([#310](https://github.com/DataDog/libddwaf/pull/310)) +- Rule override for adding tags ([#313](https://github.com/DataDog/libddwaf/pull/313)) +- Add support for dynamic exclusion filter data ([#316](https://github.com/DataDog/libddwaf/pull/316)) +- HTTP Endpoint Fingerprint Processor ([#318](https://github.com/DataDog/libddwaf/pull/318)) +- HTTP Header, HTTP Network and Session Fingerprints ([#320](https://github.com/DataDog/libddwaf/pull/320)) +- Exists operator and waf.context.event virtual address ([#321](https://github.com/DataDog/libddwaf/pull/321)) +- Add function to obtain available actions ([#324](https://github.com/DataDog/libddwaf/pull/324)) + +#### Fixes +- Transformer fixes and improvements ([#299](https://github.com/DataDog/libddwaf/pull/299)) + +#### Miscellaneous +- Fix object generator stray container ([#294](https://github.com/DataDog/libddwaf/pull/294)) +- Regex tools & benchmark rename ([#290](https://github.com/DataDog/libddwaf/pull/290)) +- Order benchmark scenarios ([#300](https://github.com/DataDog/libddwaf/pull/300)) +- Upgrade to macos-12 ([#312](https://github.com/DataDog/libddwaf/pull/312)) +- Skip disabled rules when generating ruleset ([#314](https://github.com/DataDog/libddwaf/pull/314)) +- Update default obfuscator regex ([#317](https://github.com/DataDog/libddwaf/pull/317)) + +## v1.18.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) This version introduces a new operator `sqli_detector` for the detection of SQL injections. In addition, the ruleset parser has been updated to allow non-string parameter values on action definitions. #### Changes @@ -14,7 +105,7 @@ This version introduces a new operator `sqli_detector` for the detection of SQL - Use SSE4.1 ceilf when available and add badges to readme ([#288](https://github.com/DataDog/libddwaf/pull/288)) - SQLi Detector Fuzzer and improvements ([#291](https://github.com/DataDog/libddwaf/pull/291)) -### v1.17.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.17.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) This new version introduces RASP rules and supporting features, including: - Multivariate operators for the development of complex rules. @@ -43,14 +134,14 @@ The [upgrading guide](UPGRADING.md#upgrading-from-116x-to-1170) has also been up - LFI detector fuzzer ([#274](https://github.com/DataDog/libddwaf/pull/274)) - Remove rpath from linux-musl binary ([#282](https://github.com/DataDog/libddwaf/pull/282)) -### v1.17.0-alpha3 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## 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)) +## v1.17.0-alpha2 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Changes - Server-side request forgery (SSRF) detection operator ([#268](https://github.com/DataDog/libddwaf/pull/268)) @@ -58,14 +149,14 @@ The [upgrading guide](UPGRADING.md#upgrading-from-116x-to-1170) has also been up - Attempt to build libddwaf on arm64 runner ([#270](https://github.com/DataDog/libddwaf/pull/270)) - Run tests on arm64 ([#271](https://github.com/DataDog/libddwaf/pull/271)) -### v1.17.0-alpha1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.17.0-alpha1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Fixes - Fix parsing of variadic arguments ([#267](https://github.com/DataDog/libddwaf/pull/267)) #### Miscellaneous - Update node-16 actions to node-20 ones ([#266](https://github.com/DataDog/libddwaf/pull/266)) -### v1.17.0-alpha0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.17.0-alpha0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Fixes - Add support for old glibc (e.g. RHEL 6) ([#262](https://github.com/DataDog/libddwaf/pull/262)) - Add weak ceilf symbol and definition ([#263](https://github.com/DataDog/libddwaf/pull/263)) @@ -77,12 +168,12 @@ The [upgrading guide](UPGRADING.md#upgrading-from-116x-to-1170) has also been up #### Miscellaneous - Reduce benchmark noise ([#257](https://github.com/DataDog/libddwaf/pull/257), [#259](https://github.com/DataDog/libddwaf/pull/259), [#260](https://github.com/DataDog/libddwaf/pull/260)) -### v1.16.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.16.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) ### Fixes - Add support for old glibc (e.g. RHEL 6) ([#262](https://github.com/DataDog/libddwaf/pull/262)) - Add weak ceilf symbol and definition ([#263](https://github.com/DataDog/libddwaf/pull/263)) -### v1.16.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.16.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Fixes - Address a libinjection false positive ([#251](https://github.com/DataDog/libddwaf/pull/251)) - Remove a few fingerprints causing false positives ([#252](https://github.com/DataDog/libddwaf/pull/252)) @@ -99,12 +190,12 @@ The [upgrading guide](UPGRADING.md#upgrading-from-116x-to-1170) has also been up - Refactor cmake scripts and support LTO ([#232](https://github.com/DataDog/libddwaf/pull/232)) - Microbenchmarks ([#242](https://github.com/DataDog/libddwaf/pull/242), [#243](https://github.com/DataDog/libddwaf/pull/243), [#244](https://github.com/DataDog/libddwaf/pull/244), [#245](https://github.com/DataDog/libddwaf/pull/245), [#246](https://github.com/DataDog/libddwaf/pull/246), [#247](https://github.com/DataDog/libddwaf/pull/247), [#248](https://github.com/DataDog/libddwaf/pull/248), [#250](https://github.com/DataDog/libddwaf/pull/250)) -### v1.15.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.15.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Fixes - Fix duplicate processor check ([#234](https://github.com/DataDog/libddwaf/pull/234)) -### v1.15.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.15.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) This new version of the WAF includes the following new features: - Ephemeral addresses for composite requests @@ -131,7 +222,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Use `fmt::format` for logging and vendorize some dependencies within `src/` ([#226](https://github.com/DataDog/libddwaf/pull/226)) - Reduce linux binary size and fix some flaky tests ([#227](https://github.com/DataDog/libddwaf/pull/227)) -### v1.14.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.14.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) This release of the WAF includes the following new features: - Schema data classification through the use of scanners. @@ -156,12 +247,12 @@ This release of the WAF includes the following new features: - Remove ptr typedefs ([#212](https://github.com/DataDog/libddwaf/pull/212)) - Indexer abstraction to encapsulate rule and scanner search and storage ([#213](https://github.com/DataDog/libddwaf/pull/213)) -### v1.13.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.13.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Changes - Allow conversions between signed/unsigned types during parsing ([#205](https://github.com/DataDog/libddwaf/pull/205)) -### v1.13.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.13.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) This new version of the WAF includes the following new features: - Schema extraction preprocessor @@ -195,7 +286,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Linux musl/libc++ builds using alpine-based sysroots and llvm16 ([#198](https://github.com/DataDog/libddwaf/pull/198))([#200](https://github.com/DataDog/libddwaf/pull/200))([#201](https://github.com/DataDog/libddwaf/pull/201)) -### v1.12.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.12.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Changes - Per-input transformers support on exclusion filter conditions ([#177](https://github.com/DataDog/libddwaf/pull/177)) - Read-only transformers ([#178](https://github.com/DataDog/libddwaf/pull/178))([#185](https://github.com/DataDog/libddwaf/pull/185))([#190](https://github.com/DataDog/libddwaf/pull/190)) @@ -208,7 +299,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Reduce build parallelism ([#183](https://github.com/DataDog/libddwaf/pull/183)) - Change standard to C++20 ([#186](https://github.com/DataDog/libddwaf/pull/186)) -### v1.11.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.11.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### API & Breaking Changes - Full ruleset parsing diagnostics ([#161](https://github.com/DataDog/libddwaf/pull/161)) - Event result as `ddwaf_object` ([#162](https://github.com/DataDog/libddwaf/pull/162)) @@ -226,11 +317,11 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Update ruleset to 1.7.1 ([#173](https://github.com/DataDog/libddwaf/pull/173)) - Refactor and simplify tools to reduce code duplication ([#173](https://github.com/DataDog/libddwaf/pull/173)) -### v1.10.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.10.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Changes - Add all rule tags to event ([#160](https://github.com/DataDog/libddwaf/pull/160)) -### v1.9.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.9.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Changes - Remove a libinjection signature ([#145](https://github.com/DataDog/libddwaf/pull/145)) - Priority collection, rule and filter simplification ([#150](https://github.com/DataDog/libddwaf/pull/150)) @@ -246,7 +337,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Remove unused json rule files and vendorise aho-corasick submodule ([#153](https://github.com/DataDog/libddwaf/pull/153)) - Cancel jobs in progress ([#158](https://github.com/DataDog/libddwaf/pull/158)) -### v1.8.2 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.8.2 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Changes - Use raw pointers instead of shared pointers for rule targets ([#141](https://github.com/DataDog/libddwaf/pull/141)) @@ -254,11 +345,11 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Relax rule override restrictions ([#140](https://github.com/DataDog/libddwaf/pull/140)) - Initialise `ruleset_info` on invalid input ([#142](https://github.com/DataDog/libddwaf/pull/142)) -### v1.8.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.8.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### Fixes - Return `NULL` handle when incorrect version or empty rules provided to `ddwaf_init` ([#139](https://github.com/DataDog/libddwaf/pull/139)) -### v1.8.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +## v1.8.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) #### API \& Breaking Changes - Add `ddwaf_update` for all-in-one ruleset updates ([#138](https://github.com/DataDog/libddwaf/pull/138)) - Remove `ddwaf_required_rule_data_ids` ([#138](https://github.com/DataDog/libddwaf/pull/138)) @@ -268,12 +359,12 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang #### Changes - Add WAF Builder ([#138](https://github.com/DataDog/libddwaf/pull/138)) -### v1.7.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2023/02/06 +## v1.7.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2023/02/06 #### Changes - Handle lifetime extension ([#135](https://github.com/DataDog/libddwaf/pull/135)) - Create macos universal binary ([#136](https://github.com/DataDog/libddwaf/pull/136)) -### v1.6.2 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2023/01/26 +## v1.6.2 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2023/01/26 #### Changes - Add boolean getter ([#132](https://github.com/DataDog/libddwaf/pull/132)) - Add support for converting string to bool in parameter bool cast operator ([#133](https://github.com/DataDog/libddwaf/pull/133)) @@ -284,13 +375,13 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Replace `isdigit` with custom version due to windows locale-dependence ([#133](https://github.com/DataDog/libddwaf/pull/133)) - Minor fixes and parsing improvements ([#133](https://github.com/DataDog/libddwaf/pull/133)) -### v1.6.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2023/01/17 +## v1.6.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2023/01/17 #### Miscellaneous - Add SHA256 to packages ([#128](https://github.com/DataDog/libddwaf/pull/128)) - Automatic draft release on tag ([#129](https://github.com/DataDog/libddwaf/pull/129)) -### v1.6.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2023/01/10 +## v1.6.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2023/01/10 #### Changes - Exclusion filters: targets and conditions ([#110](https://github.com/DataDog/libddwaf/pull/110)) @@ -310,7 +401,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Run clang tidy / format on CI ([#116](https://github.com/DataDog/libddwaf/pull/116)) - Exclusion filters on fuzzer ([#118](https://github.com/DataDog/libddwaf/pull/118)) -### v1.5.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/09/22 +## v1.5.1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/09/22 #### API \& Breaking Changes - Add `ddwaf_required_rule_data_ids` to obtain the rule data IDs defined in the ruleset ([#104](https://github.com/DataDog/libddwaf/pull/104)) @@ -320,7 +411,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Replace `std::optional::value()` with `std::optional::operator*()` ([#105](https://github.com/DataDog/libddwaf/pull/105)) - Add new and missing exports ([#106](https://github.com/DataDog/libddwaf/pull/106)) -### v1.5.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/09/08 +## v1.5.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/09/08 #### API \& Breaking Changes - Remove `ddwaf_version`, `ddwaf_get_version` now returns a version string ([#89](https://github.com/DataDog/libddwaf/pull/89)) @@ -375,7 +466,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Update ruleset version for testing to 1.3.2 ([#101](https://github.com/DataDog/libddwaf/pull/101)) - Fix missing build flags from `utf8proc` build ([#100](https://github.com/DataDog/libddwaf/pull/100)) -### v1.5.0-rc0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/09/02 +## v1.5.0-rc0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/09/02 #### API \& Breaking Changes - Add `ddwaf_object_bool` for backwards-compatible support for boolean `ddwaf_object` ([#99](https://github.com/DataDog/libddwaf/pull/99)) @@ -388,7 +479,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Update ruleset version for testing to 1.3.2 ([#101](https://github.com/DataDog/libddwaf/pull/101)) - Fix missing build flags from `utf8proc` build ([#100](https://github.com/DataDog/libddwaf/pull/100)) -### v1.5.0-alpha1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/08/30 +## v1.5.0-alpha1 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/08/30 #### API \& Breaking Changes - Deanonymize nested structs ([#97](https://github.com/DataDog/libddwaf/pull/97)) @@ -397,7 +488,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Disable the `1)c` libinjection fingerprint ([#94](https://github.com/DataDog/libddwaf/pull/94)) - Configurable rule data ([#96](https://github.com/DataDog/libddwaf/pull/96)) -### v1.5.0-alpha0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/08/04 +## v1.5.0-alpha0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/08/04 #### API \& Breaking Changes - Remove `ddwaf_version`, `ddwaf_get_version` now returns a version string ([#89](https://github.com/DataDog/libddwaf/pull/89)) @@ -444,7 +535,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Add CODEOWNERS ([#88](https://github.com/DataDog/libddwaf/pull/88)) - Add `benchmerge` to merge multiple benchmark results ([#85](https://github.com/DataDog/libddwaf/pull/85)) -### v1.4.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/06/29 +## v1.4.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2022/06/29 - Correct nuget url ([#68](https://github.com/DataDog/libddwaf/pull/68)) - Only take params ownership when needed ([#69](https://github.com/DataDog/libddwaf/pull/69)) - WAF Benchmark Utility ([#70](https://github.com/DataDog/libddwaf/pull/70)) @@ -452,7 +543,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Make libinjection look for backticks ([#80](https://github.com/DataDog/libddwaf/pull/80)) - Add version semantic and unstable release information ([#81](https://github.com/DataDog/libddwaf/pull/81)) -### v1.3.0 (unstable) - 2022/04/04 +## v1.3.0 (unstable) - 2022/04/04 - WAF event obfuscator. - Add obfuscator configuration to `ddwaf_config`. - Changes to limits in `ddwaf_config`: @@ -462,17 +553,17 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - All limits are now `uint32`. - Relevant macros renamed accordingly. -### v1.2.1 (unstable) - 2022/03/17 +## v1.2.1 (unstable) - 2022/03/17 - Fix issue on ruleset error map reallocation causing cached pointer invalidation. - Add check for empty input map on parser. - Update github actions windows build VM to windows-2019. -### v1.2.0 (unstable) - 2022/03/16 +## v1.2.0 (unstable) - 2022/03/16 - Remove metrics collector. - Add `total_runtime` to `ddwaf_result`. - Fix issue when reporting timeouts. -### v1.1.0 (unstable) - 2022/03/09 +## v1.1.0 (unstable) - 2022/03/09 - Add `ddwaf_object` getters. - Provide ruleset parsing diagnostics on `ddwaf_init`. - Add support for metrics collection on `ddwaf_run`. @@ -482,20 +573,20 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Refactor input verification. - Remove deprecated features. -### v1.0.18 (unstable) - 2022/02/16 +## v1.0.18 (unstable) - 2022/02/16 - Add arm64 build to nuget package. - Upgrade RE2 to 2022-02-01. -### v1.0.17 (unstable) - 2022/01/24 +## v1.0.17 (unstable) - 2022/01/24 - Add missing libunwind to x86\_64 linux build. - Fix potential integer overflow in `DDWAF_LOG_HELPER`. - Add missing shared mingw64 build. - Add example tool to run the WAF on a single rule with multiple test vectors. -### v1.0.16 (unstable) - 2021/12/15 +## v1.0.16 (unstable) - 2021/12/15 - Fix duplicate matches in output ([#36](https://github.com/DataDog/libddwaf/issues/36)) -### v1.0.15 (unstable) - 2021/12/07 +## v1.0.15 (unstable) - 2021/12/07 - Support `min_length` option on `regex_match` operator. - Remove `DDWAF_ERR_TIMEOUT` and update value of other errors. - Add timeout field to `ddwaf_result`. @@ -503,41 +594,41 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Support MacOS 10.9. - Minor CMake compatibility improvements. -### v1.0.14 (unstable) - 2021/10/26 +## v1.0.14 (unstable) - 2021/10/26 - WAF output now conforms to the appsec event format v1.0.0. - Add schema for output validation. - Remove zip package generation. - Minor improvements. -### v1.0.13 (unstable) - 2021/10/11 +## v1.0.13 (unstable) - 2021/10/11 - Add support for ruleset format v2.1. - Update fuzzer. - Fix addresses with key path missing from ddwaf\_required\_addresses. - Improve ruleset parsing logging. -### v1.0.12 (unstable) - 2021/10/01 +## v1.0.12 (unstable) - 2021/10/01 - Add libinjection SQL and XSS rule processors. - Add support for ruleset format v1.1 (adding is\_sqli and is\_xss operators). - Improved universal x86\_64 and arm64 builds. - Added darwin arm64 build. - Fixed error on corpus generator for fuzzer. -### v1.0.11 (unstable) - 2021/09/16 +## v1.0.11 (unstable) - 2021/09/16 - Improve contributor onboarding and readme. - Cross-compile aarch64 static/shared libraries. - Improve corpus generator for fuzzer. -### v1.0.10 (unstable) - 2021/09/13 +## v1.0.10 (unstable) - 2021/09/13 - Add license to nuget package. -### v1.0.9 (unstable) - 2021/09/13 +## v1.0.9 (unstable) - 2021/09/13 - Renamed static windows library to `ddwaf_static`. - Correctly publish DSO dependencies. - Add license and notice. - Add copyright note to source files. - Add issue and pull-request templates. -### v1.0.8 (unstable) - 2021/09/07 +## v1.0.8 (unstable) - 2021/09/07 - Removed spdlog dependency. - Fixed crash on base64encode transformer. - Fixed crash on compressWhiteSpace transformer. @@ -545,7 +636,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Fixed missing static library on windows packages. - Other minor fixes and improvements. -### v1.0.7 (unstable) - 2021/08/31 +## v1.0.7 (unstable) - 2021/08/31 - Support for new rule format, using `ddwaf::object`. - Interface updated with `ddwaf` namespace. - Removed pass-by-value and return-by-value from interface. @@ -555,37 +646,37 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Removed functionality not supported by the new rule format. - Added exception catch-all on interface functions to prevent std::terminate. -### v1.0.6 - 2020/10/23 +## v1.0.6 - 2020/10/23 - Convert integers to strings at the input of the WAF - Report the manifest key of the parameter that we matched in the trigger report - Fix a bug where we could send reports from a previously reported attack in follow-up executions of the additive API -### v1.0.5 - 2020/10/13 +## v1.0.5 - 2020/10/13 - Fix behavior of @exist on empty list - Improve the cache bypass logic to only bypass it once per run - Fix the cache overwrite logic when the bypass resulted in a match -### v1.0.4 - 2020/10/01 +## v1.0.4 - 2020/10/01 - Fix an issue where we wouldn't run on keys if the associtated value was a container in specific encapsulated containers - Introduce a `numerize` transformer to better handle `Content-Length` -### v1.0.3 - 2020/09/29 +## v1.0.3 - 2020/09/29 - Fix an issue where we wouldn't run on keys if the associtated value was a container -### v1.0.2 - 2020/09/25 +## v1.0.2 - 2020/09/25 - Fix an issue where reports would be generated when no action is triggered - Fix an issue where only the last step of a flow will trigger a report - Fix an issue where reports would be incomplete if some rules triggered in previous run of the additive API -### v1.0.1 - 2020/09/23 +## v1.0.1 - 2020/09/23 - Fix a bug where we wouldn't run on keys if the associated value was shorter than a rule's options.min_length -### v1.0 - 2020/08/28 +## v1.0 - 2020/08/28 - Introduce transformers to extract CRS targets from the raw URI - Introduce `removeComments` transformer - Introduce `@ipMatch` operator -### v0.9.1 (1.0 preview 2) - 2020/08/24 +## v0.9.1 (1.0 preview 2) - 2020/08/24 - Introduce modifiers for a rule execution - Introduce `@exist` operator - Improve performance of the Additive API @@ -594,7 +685,7 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Introduce allocation helpers - Other performance optimisations -### v0.9.0 (1.0 preview) - 2020/08/10 +## v0.9.0 (1.0 preview) - 2020/08/10 - Introduce Additive API - Introduce expanded initialization format - Introduce Handle API @@ -605,39 +696,39 @@ The [upgrading guide](UPGRADING.md) has also been updated to cover the new chang - Rename and shorten the API names - More... -### v0.7.0 - 2020/06/19 +## v0.7.0 - 2020/06/19 - Fix false positives in libinjection SQL heuristics - Fix a false positive in libinjection XSS heuristics -### v0.6.1 - 2020/04/03 +## v0.6.1 - 2020/04/03 - When running a rule with multiple parameters, don't stop processing if a parameter is missing - Add support for the `config` key in the init payload - Add support for prefixes to operators - Add a switch through both means to revert the first fix -### v0.6.0 - 2020/03/19 +## v0.6.0 - 2020/03/19 - Replace the clock we were using with a more efficient one - When processing a multi step rule where a parameter is missing to one step, fail the step instead of ignoring it -### v0.5.1 - 2020/01/10 +## v0.5.1 - 2020/01/10 - Fix a bug where the Compare operators could read one byte after the end of a PWArgs buffer - Fix a bug where lib injection might read one byte past an internal buffer -### v0.5.0 - 2019/11/15 +## v0.5.0 - 2019/11/15 - Give more control over the safety features to the API -### v0.4.0 - 2019/10/02 +## v0.4.0 - 2019/10/02 - Introduce `@pm` operator -### v0.3.0 - 2019/09/24 +## v0.3.0 - 2019/09/24 - Introduce `@beginsWith`, `@contains`, and `@endsWith` operators - Cap the memory each RE2 object can use to 512kB -### v0.2.0 - 2019/09/13 +## v0.2.0 - 2019/09/13 - Introduce `powerwaf_initializePowerWAFWithDiag` - Fix a UTF-8 trucation bug (SQR-8164) - Cleanup headers - Improved locking performance -### v0.1.0 +## v0.1.0 - Initial release diff --git a/version b/version index 744068368..c1af674ec 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.18.0 \ No newline at end of file +1.19.0 \ No newline at end of file From c6ef63903b64c788ae9f28ecd6500593d79abea4 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:59:21 +0100 Subject: [PATCH 16/17] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a07b16797..63879740c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,7 +61,7 @@ exclusion_data: ``` #### Other new features -- New operator `exists`: this new operator can be used to assert the presence of one or multiple addresses, regardless of their underlying value. +- New operator `exists`: this new operator can be used to assert the presence of at least one address from a given set of addresses, regardless of their underlying value. - Rule tagging overrides: rule overrides now allow adding tags to an existing rule, e.g. to provide information about the policy used. - New function `ddwaf_known_actions`: this new function can be used to obtain a list of the action types which can be triggered given the set of rules and exclusion filters available. From 1a67a2b2364d51275d2bdcc78c43884a531dd231 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:20:16 +0100 Subject: [PATCH 17/17] Upgrade arm64 runner (#326) --- .github/workflows/build.yml | 22 ++++++---------------- .github/workflows/test.yml | 6 +++--- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dbfc897e8..441b47bf9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -216,50 +216,40 @@ jobs: qemu_action_arch: amd64 platform: linux/amd64 package: libddwaf-x86_64-linux-musl - runner: ubuntu-latest - name: i386 arch: i386 qemu_action_arch: i386 platform: linux/386 package: libddwaf-i386-linux-musl - runner: ubuntu-latest - name: aarch64 arch: aarch64 qemu_action_arch: arm64 platform: linux/arm64 package: libddwaf-aarch64-linux-musl - runner: arm-4core-linux - name: armv7 arch: armv7 qemu_action_arch: arm platform: linux/arm/v7 package: libddwaf-armv7-linux-musl - runner: arm-4core-linux - runs-on: ${{ matrix.target.runner }} + runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'arm-4core-linux-arm-limited' }} steps: - uses: actions/checkout@v4 with: submodules: recursive - uses: docker/setup-buildx-action@v3 - if: matrix.target.runner == 'ubuntu-latest' id: buildx with: install: true - - name: Install docker - if: matrix.target.runner == 'arm-4core-linux' - run: | - curl -fsSL https://get.docker.com -o get-docker.sh - sudo sh get-docker.sh - - run: sudo docker build --progress=plain --build-arg "ARCH=${{ matrix.target.arch }}" -f docker/libddwaf/build/Dockerfile -o packages . + - run: docker build --progress=plain --build-arg "ARCH=${{ matrix.target.arch }}" -f docker/libddwaf/build/Dockerfile -o packages . - name: Smoketest musl (gcc) - run: sudo docker build --progress=plain --platform ${{ matrix.target.platform }} --build-arg "ARCH=${{ matrix.target.arch }}" -f docker/libddwaf/smoketest/musl/Dockerfile . + run: docker build --progress=plain --platform ${{ matrix.target.platform }} --build-arg "ARCH=${{ matrix.target.arch }}" -f docker/libddwaf/smoketest/musl/Dockerfile . - name: Smoketest musl (clang) - run: sudo docker build --progress=plain --platform ${{ matrix.target.platform }} --build-arg "ARCH=${{ matrix.target.arch }}" -f docker/libddwaf/smoketest/musl_llvm/Dockerfile . + run: docker build --progress=plain --platform ${{ matrix.target.platform }} --build-arg "ARCH=${{ matrix.target.arch }}" -f docker/libddwaf/smoketest/musl_llvm/Dockerfile . - name: Smoketest gnu (gcc) - run: sudo docker build --progress=plain --platform ${{ matrix.target.platform }} --build-arg "ARCH=${{ matrix.target.arch }}" -f docker/libddwaf/smoketest/gnu/Dockerfile . + run: docker build --progress=plain --platform ${{ matrix.target.platform }} --build-arg "ARCH=${{ matrix.target.arch }}" -f docker/libddwaf/smoketest/gnu/Dockerfile . - name: Smoketest gnu rhel 6 (gcc) if: matrix.target.qemu_action_arch == 'amd64' - run: sudo docker build --progress=plain --platform ${{ matrix.target.platform }} --build-arg "ARCH=${{ matrix.target.arch }}" -f docker/libddwaf/smoketest/gnu_rhel6/Dockerfile . + run: docker build --progress=plain --platform ${{ matrix.target.platform }} --build-arg "ARCH=${{ matrix.target.arch }}" -f docker/libddwaf/smoketest/gnu_rhel6/Dockerfile . - name: Generate Package sha256 working-directory: packages run: for file in *.tar.gz; do sha256sum "$file" > "$file.sha256"; done diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 45ddaea41..d7aeaf8d0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: arch: - amd64 - arm64 - runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'arm-4core-linux' }} + runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'arm-4core-linux-arm-limited' }} steps: - uses: actions/checkout@v4 with: @@ -78,7 +78,7 @@ jobs: arch: - amd64 - arm64 - runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'arm-4core-linux' }} + runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'arm-4core-linux-arm-limited' }} steps: - uses: actions/checkout@v4 @@ -123,7 +123,7 @@ jobs: arch: - amd64 - arm64 - runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'arm-4core-linux' }} + runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'arm-4core-linux-arm-limited' }} steps: - uses: actions/checkout@v4