diff --git a/.clang-tidy b/.clang-tidy index 5c27d6177..e9f9a5a5e 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,7 +1,7 @@ --- # readability-function-cognitive-complexity temporarily disabled until clang-tidy is fixed # right now emalloc causes it to misbehave -Checks: '*,misc-const-correctness,-bugprone-reserved-identifier,-hicpp-signed-bitwise,-llvmlibc-restrict-system-libc-headers,-altera-unroll-loops,-hicpp-named-parameter,-cert-dcl37-c,-cert-dcl51-cpp,-read,-cppcoreguidelines-init-variables,-cppcoreguidelines-avoid-non-const-global-variables,-altera-id-dependent-backward-branch,-performance-no-int-to-ptr,-altera-struct-pack-align,-google-readability-casting,-modernize-use-trailing-return-type,-llvmlibc-implementation-in-namespace,-llvmlibc-callee-namespace,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-fuchsia-default-arguments-declarations,-fuchsia-overloaded-operator,-cppcoreguidelines-pro-type-union-access,-fuchsia-default-arguments-calls,-cppcoreguidelines-non-private-member-variables-in-classes,-misc-non-private-member-variables-in-classes,-google-readability-todo,-llvm-header-guard,-readability-function-cognitive-complexity,-readability-identifier-length,-cppcoreguidelines-owning-memory,-cert-err58-cpp,-fuchsia-statically-constructed-objects,-google-build-using-namespace,-hicpp-avoid-goto,-cppcoreguidelines-avoid-goto,-hicpp-no-array-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-abseil-string-find-str-contains' +Checks: '*,misc-const-correctness,-bugprone-reserved-identifier,-hicpp-signed-bitwise,-llvmlibc-restrict-system-libc-headers,-altera-unroll-loops,-hicpp-named-parameter,-cert-dcl37-c,-cert-dcl51-cpp,-read,-cppcoreguidelines-init-variables,-cppcoreguidelines-avoid-non-const-global-variables,-altera-id-dependent-backward-branch,-performance-no-int-to-ptr,-altera-struct-pack-align,-google-readability-casting,-modernize-use-trailing-return-type,-llvmlibc-implementation-in-namespace,-llvmlibc-callee-namespace,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-fuchsia-default-arguments-declarations,-fuchsia-overloaded-operator,-cppcoreguidelines-pro-type-union-access,-fuchsia-default-arguments-calls,-cppcoreguidelines-non-private-member-variables-in-classes,-misc-non-private-member-variables-in-classes,-google-readability-todo,-llvm-header-guard,-readability-function-cognitive-complexity,-readability-identifier-length,-cppcoreguidelines-owning-memory,-cert-err58-cpp,-fuchsia-statically-constructed-objects,-google-build-using-namespace,-hicpp-avoid-goto,-cppcoreguidelines-avoid-goto,-hicpp-no-array-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-abseil-string-find-str-contains,-bugprone-unchecked-optional-access' WarningsAsErrors: '*' HeaderFilterRegex: '' AnalyzeTemporaryDtors: false diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 289cc0709..7a1cc28a4 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -17,7 +17,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: - fuzzing: + # TODO: build all fuzzers first, then run independently + global-fuzzer: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -31,21 +32,21 @@ jobs: DEBIAN_FRONTEND="noninteractive" sudo apt-get -y install libfuzzer-17-dev - name: Build - run: ./fuzzing/build.sh + run: ./fuzzer/global/build.sh - name: Run fuzzer - run: ./fuzzing/run.sh ${{ github.event.inputs.duration }} + run: ./fuzzer/global/run.sh ${{ github.event.inputs.duration }} - name: Log if: ${{ always() }} - run: grep -v -f fuzzing/scripts/report-negative-patterns.txt fuzzing/fuzz-*.log + run: grep -v -f fuzzer/global/scripts/report-negative-patterns.txt fuzzer/global/fuzz-*.log - name: Show coverage - run: ./fuzzing/scripts/show_coverage.sh 40 + run: ./fuzzer/global/scripts/show_coverage.sh 40 - name: Compress artifact if: ${{ always() }} - run: tar -czvf fuzzing.tar.gz fuzzing/ + run: tar -czvf fuzzing.tar.gz fuzzer/global/ - name: Artifact uses: actions/upload-artifact@v4 @@ -53,3 +54,36 @@ jobs: with: name: fuzzing-data path: fuzzing.tar.gz + local-fuzzer: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + variant: + - uri_parse + - ssrf_detector + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install deps + run: | + DEBIAN_FRONTEND="noninteractive" sudo apt-get -y remove python3-lldb-14 + sudo .github/workflows/scripts/llvm.sh 17 + DEBIAN_FRONTEND="noninteractive" sudo apt-get -y install libfuzzer-17-dev + + - name: Build + env: + CC: clang-17 + CXX: clang++-17 + run: | + mkdir build ; cd build + cmake -DCMAKE_VERBOSE_MAKEFILE=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo .. + make -j $(nproc) ${{ matrix.variant }}_fuzzer + cp fuzzer/${{ matrix.variant }}_fuzzer ../fuzzer/${{ matrix.variant }} + + - name: Run fuzzer + run: | + cd fuzzer/${{ matrix.variant }} + ./${{ matrix.variant }}_fuzzer -max_total_time=${{ github.event.inputs.duration || 60 }} corpus/ diff --git a/.gitignore b/.gitignore index d03ba1d59..503f8d5b9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,20 +12,23 @@ tests/default.profraw perf/test_files/parsed_* perf/test_files/breakdown.numbers -/fuzzing/build -/fuzzing/venv -/fuzzing/corpus +/fuzzer/global/build +/fuzzer/global/venv +/fuzzer/global/corpus -/fuzzing/sample_rules.yml -/fuzzing/sample_dict.txt -/fuzzing/fuzzer -/fuzzing/libddwaf.a -/fuzzing/fuzzer.dSYM -/fuzzing/default.profdata -/fuzzing/default.profraw -/fuzzing/coverage.html -/fuzzing/fuzz-*.log -/fuzzing/results/crash-* +/fuzzer/global/sample_rules.yml +/fuzzer/global/sample_dict.txt +/fuzzer/global/fuzzer +/fuzzer/global/libddwaf.a +/fuzzer/global/fuzzer.dSYM +/fuzzer/global/default.profdata +/fuzzer/global/default.profraw +/fuzzer/global/coverage.html +/fuzzer/global/fuzz-*.log +/fuzzer/global/results/crash-* + +/fuzzer/*/corpus/* +!/fuzzer/*/corpus/corpus-* /src/version.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b6f68eb93..680e03c56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,7 @@ if (LIBDDWAF_TESTING) add_subdirectory(tests EXCLUDE_FROM_ALL) add_subdirectory(validator EXCLUDE_FROM_ALL) add_subdirectory(benchmark EXCLUDE_FROM_ALL) - add_subdirectory(fuzzing EXCLUDE_FROM_ALL) + add_subdirectory(fuzzer EXCLUDE_FROM_ALL) add_subdirectory(tools EXCLUDE_FROM_ALL) include(cmake/clang-tidy.cmake) diff --git a/cmake/clang-format.cmake b/cmake/clang-format.cmake index f459d3ca5..71fee976b 100644 --- a/cmake/clang-format.cmake +++ b/cmake/clang-format.cmake @@ -5,7 +5,7 @@ if(CLANG_FORMAT STREQUAL CLANG_FORMAT-NOTFOUND) endif() set(FILE_LIST "") -foreach(DIR IN ITEMS src tests validator benchmark fuzzing) +foreach(DIR IN ITEMS src tests validator benchmark fuzzer) file(GLOB_RECURSE SOURCE_FILES ${DIR}/*.hpp ${DIR}/*.cpp) list(APPEND FILE_LIST ${SOURCE_FILES}) endforeach() diff --git a/cmake/objects.cmake b/cmake/objects.cmake index 13f44b54b..d0891d881 100644 --- a/cmake/objects.cmake +++ b/cmake/objects.cmake @@ -16,6 +16,7 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/iterator.cpp ${libddwaf_SOURCE_DIR}/src/log.cpp ${libddwaf_SOURCE_DIR}/src/obfuscator.cpp + ${libddwaf_SOURCE_DIR}/src/uri_utils.cpp ${libddwaf_SOURCE_DIR}/src/utils.cpp ${libddwaf_SOURCE_DIR}/src/waf.cpp ${libddwaf_SOURCE_DIR}/src/platform.cpp @@ -29,6 +30,7 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/parser/parser_v2.cpp ${libddwaf_SOURCE_DIR}/src/parser/rule_data_parser.cpp ${libddwaf_SOURCE_DIR}/src/condition/lfi_detector.cpp + ${libddwaf_SOURCE_DIR}/src/condition/ssrf_detector.cpp ${libddwaf_SOURCE_DIR}/src/condition/scalar_condition.cpp ${libddwaf_SOURCE_DIR}/src/matcher/phrase_match.cpp ${libddwaf_SOURCE_DIR}/src/matcher/regex_match.cpp diff --git a/fuzzer/CMakeLists.txt b/fuzzer/CMakeLists.txt new file mode 100644 index 000000000..45b78b83e --- /dev/null +++ b/fuzzer/CMakeLists.txt @@ -0,0 +1,39 @@ +MACRO(GET_DIRS subdirs parent) + FILE(GLOB children RELATIVE ${parent} ${parent}/*) + SET(subdirs "") + FOREACH(child ${children}) + IF(IS_DIRECTORY ${parent}/${child}) + LIST(APPEND subdirs ${child}) + ENDIF() + ENDFOREACH() +ENDMACRO() + +GET_DIRS(subdirs ${CMAKE_CURRENT_SOURCE_DIR}) + +set(LINK_COMPILE_FLAGS "-fsanitize=fuzzer,address,undefined,leak -fprofile-instr-generate -fcoverage-mapping") + +add_library(fuzzer-common OBJECT ${LIBDDWAF_SOURCE}) +set_target_properties(fuzzer-common PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + COMPILE_FLAGS ${LINK_COMPILE_FLAGS} + LINK_FLAGS ${LINK_COMPILE_FLAGS}) +target_include_directories(fuzzer-common PRIVATE ${LIBDDWAF_PUBLIC_INCLUDES} ${LIBDDWAF_PRIVATE_INCLUDES}) + +foreach(dir ${subdirs}) + set(FUZZER_NAME "${dir}_fuzzer") + file(GLOB_RECURSE FUZZER_SOURCE ${dir}/src/*.cpp) + add_executable(${FUZZER_NAME} ${FUZZER_SOURCE}) + + set_target_properties(${FUZZER_NAME} PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + COMPILE_FLAGS ${LINK_COMPILE_FLAGS} + LINK_FLAGS ${LINK_COMPILE_FLAGS}) + + target_include_directories(${FUZZER_NAME} PRIVATE ${LIBDDWAF_PUBLIC_INCLUDES} ${LIBDDWAF_PRIVATE_INCLUDES}) + target_link_libraries(${FUZZER_NAME} PRIVATE fuzzer-common lib_yamlcpp) +endforeach() + diff --git a/fuzzing/README.md b/fuzzer/global/README.md similarity index 100% rename from fuzzing/README.md rename to fuzzer/global/README.md diff --git a/fuzzing/build.sh b/fuzzer/global/build.sh similarity index 70% rename from fuzzing/build.sh rename to fuzzer/global/build.sh index 18c8d116d..612b78f13 100755 --- a/fuzzing/build.sh +++ b/fuzzer/global/build.sh @@ -8,6 +8,6 @@ rm -rf build && mkdir build && cd build cmake -DCMAKE_VERBOSE_MAKEFILE=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo .. -make -j $(nproc) fuzzer +make -j $(nproc) global_fuzzer -cp fuzzing/fuzzer ../fuzzing/ +cp fuzzer/global_fuzzer ../fuzzer/global/ diff --git a/fuzzing/data/blns.json b/fuzzer/global/data/blns.json similarity index 100% rename from fuzzing/data/blns.json rename to fuzzer/global/data/blns.json diff --git a/fuzzing/data/regex.json b/fuzzer/global/data/regex.json similarity index 100% rename from fuzzing/data/regex.json rename to fuzzer/global/data/regex.json diff --git a/fuzzing/results/README.md b/fuzzer/global/results/README.md similarity index 100% rename from fuzzing/results/README.md rename to fuzzer/global/results/README.md diff --git a/fuzzer/global/run.sh b/fuzzer/global/run.sh new file mode 100755 index 000000000..6c5dd91b3 --- /dev/null +++ b/fuzzer/global/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -eu + +cp build/fuzzer/global_fuzzer fuzzer/global/global_fuzzer +python3 fuzzer/global/scripts/build_corpus.py + +cd fuzzer/global + +export ASAN_OPTIONS=detect_leaks=1 + +rm -f fuzz-*.log + +echo "Run global fuzzer for ${1:-60} seconds" +./global_fuzzer -timeout=0.1 -report_slow_units=0.01 -max_total_time=${1:-60} -max_len=1000 -rss_limit_mb=4096 -use_value_profile=1 -dict=sample_dict.txt -artifact_prefix=results/ -jobs=4 -workers=4 -reload=0 corpus diff --git a/fuzzing/scripts/build_corpus.py b/fuzzer/global/scripts/build_corpus.py similarity index 97% rename from fuzzing/scripts/build_corpus.py rename to fuzzer/global/scripts/build_corpus.py index feb6a83e3..11684b05f 100755 --- a/fuzzing/scripts/build_corpus.py +++ b/fuzzer/global/scripts/build_corpus.py @@ -12,8 +12,8 @@ printable_chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\x0b\x0c' class data(): - re2_regexs_with_metadata = json.load(open("fuzzing/data/regex.json", "r")) - blns = json.load(open("fuzzing/data/blns.json", "r")) + re2_regexs_with_metadata = json.load(open("fuzzer/global/data/regex.json", "r")) + blns = json.load(open("fuzzer/global/data/blns.json", "r")) class cached_property(object): @@ -484,9 +484,9 @@ def to_fuzz_dict(c): return "".join("\\x{:02x}".format(i) for i in c.encode("utf-8")) payload = generator.get_payload() - yaml.dump(payload["init_payload"], open("fuzzing/sample_rules.yml", "w"), default_flow_style=False) + yaml.dump(payload["init_payload"], open("fuzzer/global/sample_rules.yml", "w"), default_flow_style=False) - with open("fuzzing/sample_dict.txt", "w") as f: + with open("fuzzer/global/sample_dict.txt", "w") as f: libfuzz_magics = [ "\x06\x06\x06", ] @@ -498,7 +498,7 @@ def to_fuzz_dict(c): f.write("\n") try: - os.mkdir('fuzzing/corpus') + os.mkdir('fuzzer/global/corpus') except FileExistsError: pass @@ -548,7 +548,7 @@ def build_payload_corpus(data): def write_corpus_file(filename, data, log_byte=0, reload_rules=False): reload_rules = 66 if reload_rules else 0 - with open(f"fuzzing/corpus/{filename}", "wb") as f: + with open(f"fuzzer/global/corpus/{filename}", "wb") as f: f.write(bytearray([log_byte, 0, reload_rules])) f.write(bytearray(build_payload_corpus(data))) diff --git a/fuzzing/scripts/build_dict.py b/fuzzer/global/scripts/build_dict.py similarity index 91% rename from fuzzing/scripts/build_dict.py rename to fuzzer/global/scripts/build_dict.py index b88cb33ba..64bacb8cc 100644 --- a/fuzzing/scripts/build_dict.py +++ b/fuzzer/global/scripts/build_dict.py @@ -21,7 +21,7 @@ def to_fuzz_dict(c): def load_values(): - data = yaml.safe_load(open("fuzzing/sample_rules.yml", "r")) + data = yaml.safe_load(open("fuzzer/global/sample_rules.yml", "r")) results = set() for rule in data["rules"]: @@ -49,7 +49,7 @@ def load_values(): def write_values(values): values.add("\x06\x06\x06") # fuzzer magics - with open("fuzzing/sample_dict.txt", "w") as f: + with open("fuzzer/global/sample_dict.txt", "w") as f: for value in values: f.write("# " + repr(value) + "\n") diff --git a/fuzzing/scripts/clean.sh b/fuzzer/global/scripts/clean.sh similarity index 69% rename from fuzzing/scripts/clean.sh rename to fuzzer/global/scripts/clean.sh index 5555e6236..a8c9dc07c 100755 --- a/fuzzing/scripts/clean.sh +++ b/fuzzer/global/scripts/clean.sh @@ -1,11 +1,11 @@ #!/bin/bash set -eu -cd fuzzing +cd fuzzer/global rm -rf corpus/ rm -f fuzz-*.log rm -f sample_dict.txt sample_rules.yml rm -f default.profdata default.profraw coverage.html -rm -rf fuzzer.dSYM -rm -rf fuzzer +rm -rf global_fuzzer.dSYM +rm -rf global_fuzzer diff --git a/fuzzing/scripts/merge_corpus.sh b/fuzzer/global/scripts/merge_corpus.sh similarity index 100% rename from fuzzing/scripts/merge_corpus.sh rename to fuzzer/global/scripts/merge_corpus.sh diff --git a/fuzzing/scripts/report-negative-patterns.txt b/fuzzer/global/scripts/report-negative-patterns.txt similarity index 100% rename from fuzzing/scripts/report-negative-patterns.txt rename to fuzzer/global/scripts/report-negative-patterns.txt diff --git a/fuzzer/global/scripts/show_coverage.sh b/fuzzer/global/scripts/show_coverage.sh new file mode 100755 index 000000000..f58354155 --- /dev/null +++ b/fuzzer/global/scripts/show_coverage.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eu + +cd fuzzer/global + +llvm-profdata-17 merge -sparse *.profraw -o default.profdata +llvm-cov-17 show global_fuzzer -instr-profile=default.profdata -ignore-filename-regex="(vendor|fuzzer|third_party)" -format=html > coverage.html +llvm-cov-17 report -instr-profile default.profdata global_fuzzer -ignore-filename-regex="(vendor|fuzzer|third_party)" -show-region-summary=false + +if [ ! -z ${1:-} ]; then + THRESHOLD=$1 + TOTAL=$(llvm-cov-17 report -instr-profile default.profdata global_fuzzer -ignore-filename-regex="(vendor|fuzzer|third_party)" -show-region-summary=false | grep TOTAL) + ARRAY=($TOTAL) + COVERAGE=$(echo ${ARRAY[3]} | sed -e "s/\.[[:digit:]]*%//g") + + if (( $COVERAGE < $THRESHOLD )); then + echo "Sorry, the fuzzer found no bug, but the coverage is below $THRESHOLD%. Can't call it a success." 1>&2 + exit 1 + fi +fi diff --git a/fuzzing/src/helpers.cpp b/fuzzer/global/src/helpers.cpp similarity index 100% rename from fuzzing/src/helpers.cpp rename to fuzzer/global/src/helpers.cpp diff --git a/fuzzing/src/helpers.hpp b/fuzzer/global/src/helpers.hpp similarity index 100% rename from fuzzing/src/helpers.hpp rename to fuzzer/global/src/helpers.hpp diff --git a/fuzzing/src/interface.cpp b/fuzzer/global/src/interface.cpp similarity index 100% rename from fuzzing/src/interface.cpp rename to fuzzer/global/src/interface.cpp diff --git a/fuzzing/src/interface.hpp b/fuzzer/global/src/interface.hpp similarity index 100% rename from fuzzing/src/interface.hpp rename to fuzzer/global/src/interface.hpp diff --git a/fuzzing/src/main.cpp b/fuzzer/global/src/main.cpp similarity index 100% rename from fuzzing/src/main.cpp rename to fuzzer/global/src/main.cpp diff --git a/fuzzing/src/object_builder.cpp b/fuzzer/global/src/object_builder.cpp similarity index 100% rename from fuzzing/src/object_builder.cpp rename to fuzzer/global/src/object_builder.cpp diff --git a/fuzzing/src/object_builder.hpp b/fuzzer/global/src/object_builder.hpp similarity index 100% rename from fuzzing/src/object_builder.hpp rename to fuzzer/global/src/object_builder.hpp diff --git a/fuzzer/ssrf_detector/corpus/corpus-0000 b/fuzzer/ssrf_detector/corpus/corpus-0000 new file mode 100644 index 000000000..961dc9bc8 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0000 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0001 b/fuzzer/ssrf_detector/corpus/corpus-0001 new file mode 100644 index 000000000..cffac0b43 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0001 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0002 b/fuzzer/ssrf_detector/corpus/corpus-0002 new file mode 100644 index 000000000..6cb3a3b1f Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0002 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0003 b/fuzzer/ssrf_detector/corpus/corpus-0003 new file mode 100644 index 000000000..1e6beb3dd Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0003 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0004 b/fuzzer/ssrf_detector/corpus/corpus-0004 new file mode 100644 index 000000000..4ef9328e7 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0004 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0005 b/fuzzer/ssrf_detector/corpus/corpus-0005 new file mode 100644 index 000000000..8992795b4 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0005 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0006 b/fuzzer/ssrf_detector/corpus/corpus-0006 new file mode 100644 index 000000000..6aaddd14f Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0006 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0007 b/fuzzer/ssrf_detector/corpus/corpus-0007 new file mode 100644 index 000000000..db12c1f21 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0007 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0008 b/fuzzer/ssrf_detector/corpus/corpus-0008 new file mode 100644 index 000000000..cd49cece5 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0008 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0009 b/fuzzer/ssrf_detector/corpus/corpus-0009 new file mode 100644 index 000000000..8adf4847f Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0009 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0010 b/fuzzer/ssrf_detector/corpus/corpus-0010 new file mode 100644 index 000000000..683b55a67 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0010 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0011 b/fuzzer/ssrf_detector/corpus/corpus-0011 new file mode 100644 index 000000000..e5d4acde1 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0011 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0012 b/fuzzer/ssrf_detector/corpus/corpus-0012 new file mode 100644 index 000000000..500d1abab Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0012 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0013 b/fuzzer/ssrf_detector/corpus/corpus-0013 new file mode 100644 index 000000000..1a486ae4c Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0013 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0014 b/fuzzer/ssrf_detector/corpus/corpus-0014 new file mode 100644 index 000000000..c77456044 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0014 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0015 b/fuzzer/ssrf_detector/corpus/corpus-0015 new file mode 100644 index 000000000..484c4d4e9 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0015 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0016 b/fuzzer/ssrf_detector/corpus/corpus-0016 new file mode 100644 index 000000000..c093aa281 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0016 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0017 b/fuzzer/ssrf_detector/corpus/corpus-0017 new file mode 100644 index 000000000..c37059db7 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0017 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0018 b/fuzzer/ssrf_detector/corpus/corpus-0018 new file mode 100644 index 000000000..310f38c1e Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0018 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0019 b/fuzzer/ssrf_detector/corpus/corpus-0019 new file mode 100644 index 000000000..7da14311e Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0019 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0020 b/fuzzer/ssrf_detector/corpus/corpus-0020 new file mode 100644 index 000000000..120111498 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0020 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0021 b/fuzzer/ssrf_detector/corpus/corpus-0021 new file mode 100644 index 000000000..a49508fe7 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0021 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0022 b/fuzzer/ssrf_detector/corpus/corpus-0022 new file mode 100644 index 000000000..670e8b2cf Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0022 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0023 b/fuzzer/ssrf_detector/corpus/corpus-0023 new file mode 100644 index 000000000..f5f4e9177 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0023 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0024 b/fuzzer/ssrf_detector/corpus/corpus-0024 new file mode 100644 index 000000000..1148eb8ba Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0024 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0025 b/fuzzer/ssrf_detector/corpus/corpus-0025 new file mode 100644 index 000000000..12d4b0a1a Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0025 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0026 b/fuzzer/ssrf_detector/corpus/corpus-0026 new file mode 100644 index 000000000..65d7f5af6 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0026 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0027 b/fuzzer/ssrf_detector/corpus/corpus-0027 new file mode 100644 index 000000000..cc79a083c Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0027 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0028 b/fuzzer/ssrf_detector/corpus/corpus-0028 new file mode 100644 index 000000000..def6b2089 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0028 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0029 b/fuzzer/ssrf_detector/corpus/corpus-0029 new file mode 100644 index 000000000..949fd48a7 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0029 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0030 b/fuzzer/ssrf_detector/corpus/corpus-0030 new file mode 100644 index 000000000..2d83f93e0 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0030 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0031 b/fuzzer/ssrf_detector/corpus/corpus-0031 new file mode 100644 index 000000000..086de5071 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0031 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0032 b/fuzzer/ssrf_detector/corpus/corpus-0032 new file mode 100644 index 000000000..6a8be495f Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0032 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0033 b/fuzzer/ssrf_detector/corpus/corpus-0033 new file mode 100644 index 000000000..75dd9b82e Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0033 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0034 b/fuzzer/ssrf_detector/corpus/corpus-0034 new file mode 100644 index 000000000..0b92d3a22 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0034 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0035 b/fuzzer/ssrf_detector/corpus/corpus-0035 new file mode 100644 index 000000000..16a67ccc1 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0035 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0036 b/fuzzer/ssrf_detector/corpus/corpus-0036 new file mode 100644 index 000000000..11bfe7df0 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0036 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0037 b/fuzzer/ssrf_detector/corpus/corpus-0037 new file mode 100644 index 000000000..15162346a Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0037 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0038 b/fuzzer/ssrf_detector/corpus/corpus-0038 new file mode 100644 index 000000000..99668ad43 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0038 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0039 b/fuzzer/ssrf_detector/corpus/corpus-0039 new file mode 100644 index 000000000..03866a285 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0039 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0040 b/fuzzer/ssrf_detector/corpus/corpus-0040 new file mode 100644 index 000000000..205895a35 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0040 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0041 b/fuzzer/ssrf_detector/corpus/corpus-0041 new file mode 100644 index 000000000..d44b9cc99 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0041 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0042 b/fuzzer/ssrf_detector/corpus/corpus-0042 new file mode 100644 index 000000000..db36862bb Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0042 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0043 b/fuzzer/ssrf_detector/corpus/corpus-0043 new file mode 100644 index 000000000..205895a35 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0043 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0044 b/fuzzer/ssrf_detector/corpus/corpus-0044 new file mode 100644 index 000000000..d44b9cc99 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0044 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0045 b/fuzzer/ssrf_detector/corpus/corpus-0045 new file mode 100644 index 000000000..e6175da81 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0045 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0046 b/fuzzer/ssrf_detector/corpus/corpus-0046 new file mode 100644 index 000000000..71c403e6c Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0046 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0047 b/fuzzer/ssrf_detector/corpus/corpus-0047 new file mode 100644 index 000000000..4d03b7104 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0047 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0048 b/fuzzer/ssrf_detector/corpus/corpus-0048 new file mode 100644 index 000000000..16f277b78 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0048 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0049 b/fuzzer/ssrf_detector/corpus/corpus-0049 new file mode 100644 index 000000000..159cb7bce Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0049 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0050 b/fuzzer/ssrf_detector/corpus/corpus-0050 new file mode 100644 index 000000000..44409658d Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0050 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0051 b/fuzzer/ssrf_detector/corpus/corpus-0051 new file mode 100644 index 000000000..2a744f9bf Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0051 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0052 b/fuzzer/ssrf_detector/corpus/corpus-0052 new file mode 100644 index 000000000..3ffcf7d64 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0052 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0053 b/fuzzer/ssrf_detector/corpus/corpus-0053 new file mode 100644 index 000000000..0274e0c53 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0053 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0054 b/fuzzer/ssrf_detector/corpus/corpus-0054 new file mode 100644 index 000000000..b50677faa Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0054 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0055 b/fuzzer/ssrf_detector/corpus/corpus-0055 new file mode 100644 index 000000000..813265359 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0055 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0056 b/fuzzer/ssrf_detector/corpus/corpus-0056 new file mode 100644 index 000000000..d9a8a16f8 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0056 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0057 b/fuzzer/ssrf_detector/corpus/corpus-0057 new file mode 100644 index 000000000..8fef463f8 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0057 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0058 b/fuzzer/ssrf_detector/corpus/corpus-0058 new file mode 100644 index 000000000..49b258b4c Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0058 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0059 b/fuzzer/ssrf_detector/corpus/corpus-0059 new file mode 100644 index 000000000..51d323d33 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0059 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0060 b/fuzzer/ssrf_detector/corpus/corpus-0060 new file mode 100644 index 000000000..7d8856662 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0060 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0061 b/fuzzer/ssrf_detector/corpus/corpus-0061 new file mode 100644 index 000000000..f6b22e21b Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0061 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0062 b/fuzzer/ssrf_detector/corpus/corpus-0062 new file mode 100644 index 000000000..1d442a645 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0062 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0063 b/fuzzer/ssrf_detector/corpus/corpus-0063 new file mode 100644 index 000000000..e9d5dfec2 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0063 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0064 b/fuzzer/ssrf_detector/corpus/corpus-0064 new file mode 100644 index 000000000..1838a46d2 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0064 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0065 b/fuzzer/ssrf_detector/corpus/corpus-0065 new file mode 100644 index 000000000..df06b1e8a Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0065 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0066 b/fuzzer/ssrf_detector/corpus/corpus-0066 new file mode 100644 index 000000000..d491aa809 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0066 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0067 b/fuzzer/ssrf_detector/corpus/corpus-0067 new file mode 100644 index 000000000..13f4c9970 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0067 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0068 b/fuzzer/ssrf_detector/corpus/corpus-0068 new file mode 100644 index 000000000..9fec63234 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0068 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0069 b/fuzzer/ssrf_detector/corpus/corpus-0069 new file mode 100644 index 000000000..556ed0eeb Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0069 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0070 b/fuzzer/ssrf_detector/corpus/corpus-0070 new file mode 100644 index 000000000..b177b39a4 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0070 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0071 b/fuzzer/ssrf_detector/corpus/corpus-0071 new file mode 100644 index 000000000..a29b335f3 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0071 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0072 b/fuzzer/ssrf_detector/corpus/corpus-0072 new file mode 100644 index 000000000..7cd1a1768 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0072 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0073 b/fuzzer/ssrf_detector/corpus/corpus-0073 new file mode 100644 index 000000000..7301175f8 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0073 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0074 b/fuzzer/ssrf_detector/corpus/corpus-0074 new file mode 100644 index 000000000..d2b9ae572 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0074 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0075 b/fuzzer/ssrf_detector/corpus/corpus-0075 new file mode 100644 index 000000000..0f32cc9e9 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0075 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0076 b/fuzzer/ssrf_detector/corpus/corpus-0076 new file mode 100644 index 000000000..d7e408ef7 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0076 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0077 b/fuzzer/ssrf_detector/corpus/corpus-0077 new file mode 100644 index 000000000..5bc511f22 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0077 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0078 b/fuzzer/ssrf_detector/corpus/corpus-0078 new file mode 100644 index 000000000..9e6279574 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0078 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0079 b/fuzzer/ssrf_detector/corpus/corpus-0079 new file mode 100644 index 000000000..44c375bcd Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0079 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0080 b/fuzzer/ssrf_detector/corpus/corpus-0080 new file mode 100644 index 000000000..c79babb68 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0080 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0081 b/fuzzer/ssrf_detector/corpus/corpus-0081 new file mode 100644 index 000000000..45629681a Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0081 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0082 b/fuzzer/ssrf_detector/corpus/corpus-0082 new file mode 100644 index 000000000..b04484284 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0082 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0083 b/fuzzer/ssrf_detector/corpus/corpus-0083 new file mode 100644 index 000000000..9546f94cd Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0083 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0084 b/fuzzer/ssrf_detector/corpus/corpus-0084 new file mode 100644 index 000000000..a2e60f690 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0084 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0085 b/fuzzer/ssrf_detector/corpus/corpus-0085 new file mode 100644 index 000000000..ac03148f0 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0085 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0086 b/fuzzer/ssrf_detector/corpus/corpus-0086 new file mode 100644 index 000000000..0cd09bd05 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0086 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0087 b/fuzzer/ssrf_detector/corpus/corpus-0087 new file mode 100644 index 000000000..e9b27ef93 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0087 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0088 b/fuzzer/ssrf_detector/corpus/corpus-0088 new file mode 100644 index 000000000..d57801bff Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0088 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0089 b/fuzzer/ssrf_detector/corpus/corpus-0089 new file mode 100644 index 000000000..67755388c Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0089 differ diff --git a/fuzzer/ssrf_detector/corpus/corpus-0090 b/fuzzer/ssrf_detector/corpus/corpus-0090 new file mode 100644 index 000000000..e312b6f91 Binary files /dev/null and b/fuzzer/ssrf_detector/corpus/corpus-0090 differ diff --git a/fuzzer/ssrf_detector/src/main.cpp b/fuzzer/ssrf_detector/src/main.cpp new file mode 100644 index 000000000..253141512 --- /dev/null +++ b/fuzzer/ssrf_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 "condition/ssrf_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, const std::vector &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; +} + +void serialize(uint8_t *Data, const std::vector &resource, const std::vector ¶m) +{ + Data = serialize_string(Data, resource); + serialize_string(Data, param); +} +// 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) +{ + auto [resource, param] = deserialize(Data, Size); + + MaxSize -= sizeof(std::size_t) * 2; + + std::vector resource_buffer{resource.begin(), resource.end()}; + resource_buffer.resize(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); + + std::vector param_buffer{resource.begin(), resource.end()}; + param_buffer.resize(resource_buffer.size() + MaxSize / 2); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + new_size = LLVMFuzzerMutate( + reinterpret_cast(param_buffer.data()), resource.size(), resource_buffer.size()); + param_buffer.resize(new_size); + + serialize(Data, resource_buffer, param_buffer); + + return Size; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) +{ + ssrf_detector cond{{gen_param_def("server.io.net.url", "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.io.net.url", 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/uri_parse/corpus/corpus-0000 b/fuzzer/uri_parse/corpus/corpus-0000 new file mode 100644 index 000000000..4c5be8d31 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0000 @@ -0,0 +1 @@ +https://internal-website/path/to/stuffs?bla=42 {.yaml = "/path/to/stuffs?"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0001 b/fuzzer/uri_parse/corpus/corpus-0001 new file mode 100644 index 000000000..185294514 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0001 @@ -0,0 +1 @@ +https://ifconfig.pro {.yaml = "ifconfig.pro"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0002 b/fuzzer/uri_parse/corpus/corpus-0002 new file mode 100644 index 000000000..d836cb6c2 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0002 @@ -0,0 +1 @@ +http://authority:1#f diff --git a/fuzzer/uri_parse/corpus/corpus-0003 b/fuzzer/uri_parse/corpus/corpus-0003 new file mode 100644 index 000000000..34bd08d68 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0003 @@ -0,0 +1 @@ +s://u@h:1/p?q#f diff --git a/fuzzer/uri_parse/corpus/corpus-0004 b/fuzzer/uri_parse/corpus/corpus-0004 new file mode 100644 index 000000000..c052a11bf --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0004 @@ -0,0 +1 @@ +h://a diff --git a/fuzzer/uri_parse/corpus/corpus-0005 b/fuzzer/uri_parse/corpus/corpus-0005 new file mode 100644 index 000000000..0ee3f57c4 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0005 @@ -0,0 +1 @@ +https://blabla.com/path?name=param&auth=43 {.yaml = "param&auth"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0006 b/fuzzer/uri_parse/corpus/corpus-0006 new file mode 100644 index 000000000..d090d9ace --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0006 @@ -0,0 +1 @@ +http://authority.with.dots diff --git a/fuzzer/uri_parse/corpus/corpus-0007 b/fuzzer/uri_parse/corpus/corpus-0007 new file mode 100644 index 000000000..dbf4d5128 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0007 @@ -0,0 +1 @@ +https://blabla.com/path?name=param&name2=param2 {.yaml = "name=param&name2=param2"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0008 b/fuzzer/uri_parse/corpus/corpus-0008 new file mode 100644 index 000000000..f347200a4 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0008 @@ -0,0 +1 @@ +file:/usr/lib/libddwaf.so diff --git a/fuzzer/uri_parse/corpus/corpus-0009 b/fuzzer/uri_parse/corpus/corpus-0009 new file mode 100644 index 000000000..bd1356a63 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0009 @@ -0,0 +1 @@ +http://us@er@host:1234 diff --git a/fuzzer/uri_parse/corpus/corpus-0010 b/fuzzer/uri_parse/corpus/corpus-0010 new file mode 100644 index 000000000..61521cbb5 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0010 @@ -0,0 +1 @@ +http://u@authority#f diff --git a/fuzzer/uri_parse/corpus/corpus-0011 b/fuzzer/uri_parse/corpus/corpus-0011 new file mode 100644 index 000000000..a0d4dc385 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0011 @@ -0,0 +1 @@ +http://authority?query diff --git a/fuzzer/uri_parse/corpus/corpus-0012 b/fuzzer/uri_parse/corpus/corpus-0012 new file mode 100644 index 000000000..4db94b004 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0012 @@ -0,0 +1 @@ +file/blabla/metadata {.yaml = R"({query: {param: "blabla"}})"}} diff --git a/fuzzer/uri_parse/corpus/corpus-0013 b/fuzzer/uri_parse/corpus/corpus-0013 new file mode 100644 index 000000000..6e412b220 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0013 @@ -0,0 +1 @@ +http://host:port diff --git a/fuzzer/uri_parse/corpus/corpus-0014 b/fuzzer/uri_parse/corpus/corpus-0014 new file mode 100644 index 000000000..051014a2b --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0014 @@ -0,0 +1 @@ +http://authority:1 diff --git a/fuzzer/uri_parse/corpus/corpus-0015 b/fuzzer/uri_parse/corpus/corpus-0015 new file mode 100644 index 000000000..860a5fc69 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0015 @@ -0,0 +1 @@ +http://[0:0:0:0:0:ffff:a9fe:a9fe]/latest/meta-data/ diff --git a/fuzzer/uri_parse/corpus/corpus-0016 b/fuzzer/uri_parse/corpus/corpus-0016 new file mode 100644 index 000000000..3405fa111 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0016 @@ -0,0 +1 @@ +http://us@authority/path diff --git a/fuzzer/uri_parse/corpus/corpus-0017 b/fuzzer/uri_parse/corpus/corpus-0017 new file mode 100644 index 000000000..087ce755b --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0017 @@ -0,0 +1 @@ +https://internal-website.evil.com:42/path/to/stuffs?bla=42 diff --git a/fuzzer/uri_parse/corpus/corpus-0018 b/fuzzer/uri_parse/corpus/corpus-0018 new file mode 100644 index 000000000..c5742da40 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0018 @@ -0,0 +1 @@ +tel:+1-816-555-1212 diff --git a/fuzzer/uri_parse/corpus/corpus-0019 b/fuzzer/uri_parse/corpus/corpus-0019 new file mode 100644 index 000000000..51fb3b746 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0019 @@ -0,0 +1 @@ +http://authority:12/path diff --git a/fuzzer/uri_parse/corpus/corpus-0020 b/fuzzer/uri_parse/corpus/corpus-0020 new file mode 100644 index 000000000..d05356ff1 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0020 @@ -0,0 +1 @@ +https://169.254.169.254/somewhere/in/the/app {.yaml = "169.254.169.254"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0021 b/fuzzer/uri_parse/corpus/corpus-0021 new file mode 100644 index 000000000..3f8d42140 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0021 @@ -0,0 +1 @@ +hhttp,: diff --git a/fuzzer/uri_parse/corpus/corpus-0022 b/fuzzer/uri_parse/corpus/corpus-0022 new file mode 100644 index 000000000..7d3bc6dc1 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0022 @@ -0,0 +1 @@ +http:/// diff --git a/fuzzer/uri_parse/corpus/corpus-0023 b/fuzzer/uri_parse/corpus/corpus-0023 new file mode 100644 index 000000000..7ed9608cb --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0023 @@ -0,0 +1 @@ +http://172.16.87.100:1234/pouet/bla/_search?stuff=87.1 diff --git a/fuzzer/uri_parse/corpus/corpus-0024 b/fuzzer/uri_parse/corpus/corpus-0024 new file mode 100644 index 000000000..00ab5fc7f --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0024 @@ -0,0 +1 @@ +http://bla.patreon.com/batch {.yaml = R"({query: {param: "patreon.com/"}})"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0025 b/fuzzer/uri_parse/corpus/corpus-0025 new file mode 100644 index 000000000..2c8c75331 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0025 @@ -0,0 +1 @@ +https://internal-website:4242/path/to/stuffs?bla=42 {.yaml = ":4242/path/to/stuffs?"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0026 b/fuzzer/uri_parse/corpus/corpus-0026 new file mode 100644 index 000000000..9b0e95d74 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0026 @@ -0,0 +1 @@ +http://host@@@something diff --git a/fuzzer/uri_parse/corpus/corpus-0027 b/fuzzer/uri_parse/corpus/corpus-0027 new file mode 100644 index 000000000..405f491ac --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0027 @@ -0,0 +1 @@ +https://254.254.169.254/path {.yaml = "{form: {bla: '254.254.169.254'}}"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0028 b/fuzzer/uri_parse/corpus/corpus-0028 new file mode 100644 index 000000000..10caa45ec --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0028 @@ -0,0 +1 @@ +https://blabla.com/random/path?name=name2#bla/name2 diff --git a/fuzzer/uri_parse/corpus/corpus-0029 b/fuzzer/uri_parse/corpus/corpus-0029 new file mode 100644 index 000000000..5b891b962 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0029 @@ -0,0 +1 @@ +https://blabla.burpcollaborator.net/path {.yaml = "burpcollaborator.net"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0030 b/fuzzer/uri_parse/corpus/corpus-0030 new file mode 100644 index 000000000..c1cd250cd --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0030 @@ -0,0 +1 @@ +h@@:path diff --git a/fuzzer/uri_parse/corpus/corpus-0031 b/fuzzer/uri_parse/corpus/corpus-0031 new file mode 100644 index 000000000..fd7348b9d --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0031 @@ -0,0 +1 @@ +https://blabla.com/path {.yaml = ".com/path"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0032 b/fuzzer/uri_parse/corpus/corpus-0032 new file mode 100644 index 000000000..bdc3085e3 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0032 @@ -0,0 +1 @@ +https://blabla.com/random/..falsestart.something/../with?param=value diff --git a/fuzzer/uri_parse/corpus/corpus-0033 b/fuzzer/uri_parse/corpus/corpus-0033 new file mode 100644 index 000000000..b8709608e --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0033 @@ -0,0 +1 @@ +https://localhost/path {.yaml = "localhost"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0034 b/fuzzer/uri_parse/corpus/corpus-0034 new file mode 100644 index 000000000..035121e89 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0034 @@ -0,0 +1 @@ +http://core-goals/v1/projects/42/goals?projectId=42& diff --git a/fuzzer/uri_parse/corpus/corpus-0035 b/fuzzer/uri_parse/corpus/corpus-0035 new file mode 100644 index 000000000..b16407186 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0035 @@ -0,0 +1 @@ +http://auth[ority diff --git a/fuzzer/uri_parse/corpus/corpus-0036 b/fuzzer/uri_parse/corpus/corpus-0036 new file mode 100644 index 000000000..c3ad92750 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0036 @@ -0,0 +1 @@ +http://[1:1::1:1 diff --git a/fuzzer/uri_parse/corpus/corpus-0037 b/fuzzer/uri_parse/corpus/corpus-0037 new file mode 100644 index 000000000..a09165668 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0037 @@ -0,0 +1 @@ +https://blabla.com/random/path/with?param=val diff --git a/fuzzer/uri_parse/corpus/corpus-0038 b/fuzzer/uri_parse/corpus/corpus-0038 new file mode 100644 index 000000000..2a8ce4e48 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0038 @@ -0,0 +1 @@ +https://internal-website.evil.com/path/to/stuffs?bla=42 diff --git a/fuzzer/uri_parse/corpus/corpus-0039 b/fuzzer/uri_parse/corpus/corpus-0039 new file mode 100644 index 000000000..07400840c --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0039 @@ -0,0 +1 @@ +https://[::1]/path {.yaml = R"("[::1]")", .resolved = "[::1]"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0040 b/fuzzer/uri_parse/corpus/corpus-0040 new file mode 100644 index 000000000..4ed2a35d4 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0040 @@ -0,0 +1 @@ +file:///usr/lib/libddwaf.so diff --git a/fuzzer/uri_parse/corpus/corpus-0041 b/fuzzer/uri_parse/corpus/corpus-0041 new file mode 100644 index 000000000..b1e35eb9f --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0041 @@ -0,0 +1 @@ +https://blabla.com/random/path?name=name2&value=/legit diff --git a/fuzzer/uri_parse/corpus/corpus-0042 b/fuzzer/uri_parse/corpus/corpus-0042 new file mode 100644 index 000000000..9b07a687f --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0042 @@ -0,0 +1 @@ +https://[::ffff:fea9:a9fe]/path {.yaml = "{form: {bla: '[::ffff:fea9:a9fe]'}}"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0043 b/fuzzer/uri_parse/corpus/corpus-0043 new file mode 100644 index 000000000..b2bb553d6 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0043 @@ -0,0 +1 @@ +http://blablabla.com/api/v3/ds/data-checks {.yaml = R"({bla: "ds/data-checks"})"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0044 b/fuzzer/uri_parse/corpus/corpus-0044 new file mode 100644 index 000000000..30202dde4 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0044 @@ -0,0 +1 @@ +https://[::ffff:a9fe:a9fe]/path diff --git a/fuzzer/uri_parse/corpus/corpus-0045 b/fuzzer/uri_parse/corpus/corpus-0045 new file mode 100644 index 000000000..7dd02af7d --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0045 @@ -0,0 +1 @@ +http://2852039166/latest/meta-data/ diff --git a/fuzzer/uri_parse/corpus/corpus-0046 b/fuzzer/uri_parse/corpus/corpus-0046 new file mode 100644 index 000000000..73a8789b0 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0046 @@ -0,0 +1 @@ +https://blabla.com/random/../with?param=value {.yaml = "../with"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0047 b/fuzzer/uri_parse/corpus/corpus-0047 new file mode 100644 index 000000000..25b3c2e97 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0047 @@ -0,0 +1 @@ +http://blablabla.comhttps://blablabla {.yaml = R"({bla: https})"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0048 b/fuzzer/uri_parse/corpus/corpus-0048 new file mode 100644 index 000000000..9f34749da --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0048 @@ -0,0 +1 @@ +http://paco@authority diff --git a/fuzzer/uri_parse/corpus/corpus-0049 b/fuzzer/uri_parse/corpus/corpus-0049 new file mode 100644 index 000000000..62ba31284 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0049 @@ -0,0 +1 @@ +https://graph.microsoft.com/v1.0/me/calendars/base64stuff=/events diff --git a/fuzzer/uri_parse/corpus/corpus-0050 b/fuzzer/uri_parse/corpus/corpus-0050 new file mode 100644 index 000000000..92372ccfe --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0050 @@ -0,0 +1 @@ +gopher://blabla.com/path {.yaml = "gopher"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0051 b/fuzzer/uri_parse/corpus/corpus-0051 new file mode 100644 index 000000000..62515b732 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0051 @@ -0,0 +1 @@ +http://something@:123 diff --git a/fuzzer/uri_parse/corpus/corpus-0052 b/fuzzer/uri_parse/corpus/corpus-0052 new file mode 100644 index 000000000..c7ffe540f --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0052 @@ -0,0 +1 @@ +http://pro.orange.fr/css/fonts/opus-meteo/fonts/opus-meteo.ttf?blablabla diff --git a/fuzzer/uri_parse/corpus/corpus-0053 b/fuzzer/uri_parse/corpus/corpus-0053 new file mode 100644 index 000000000..d9b0440db --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0053 @@ -0,0 +1 @@ +http://authority diff --git a/fuzzer/uri_parse/corpus/corpus-0054 b/fuzzer/uri_parse/corpus/corpus-0054 new file mode 100644 index 000000000..29710e513 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0054 @@ -0,0 +1 @@ +http://authority/path diff --git a/fuzzer/uri_parse/corpus/corpus-0055 b/fuzzer/uri_parse/corpus/corpus-0055 new file mode 100644 index 000000000..41b12205b --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0055 @@ -0,0 +1 @@ +https://blabla.com/path?name=param&name2=param2 {.yaml = "param&name2=param2"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0056 b/fuzzer/uri_parse/corpus/corpus-0056 new file mode 100644 index 000000000..25b1124e0 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0056 @@ -0,0 +1 @@ +tax.internal.patreon.com/services/tax/1.0/quote/batch diff --git a/fuzzer/uri_parse/corpus/corpus-0057 b/fuzzer/uri_parse/corpus/corpus-0057 new file mode 100644 index 000000000..91644bef5 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0057 @@ -0,0 +1 @@ +http://user:pa]ssword@host: diff --git a/fuzzer/uri_parse/corpus/corpus-0058 b/fuzzer/uri_parse/corpus/corpus-0058 new file mode 100644 index 000000000..db7456ddd --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0058 @@ -0,0 +1 @@ +http://[200:22:11:33:44:ab:cc:bf] diff --git a/fuzzer/uri_parse/corpus/corpus-0059 b/fuzzer/uri_parse/corpus/corpus-0059 new file mode 100644 index 000000000..e3580cc51 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0059 @@ -0,0 +1 @@ +http://host:::asdnsk diff --git a/fuzzer/uri_parse/corpus/corpus-0060 b/fuzzer/uri_parse/corpus/corpus-0060 new file mode 100644 index 000000000..a28bc11c0 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0060 @@ -0,0 +1 @@ +file:/../lib/libddwaf.so diff --git a/fuzzer/uri_parse/corpus/corpus-0061 b/fuzzer/uri_parse/corpus/corpus-0061 new file mode 100644 index 000000000..e4a96e60d --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0061 @@ -0,0 +1 @@ +http://user@authority?query diff --git a/fuzzer/uri_parse/corpus/corpus-0062 b/fuzzer/uri_parse/corpus/corpus-0062 new file mode 100644 index 000000000..4592f3cb5 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0062 @@ -0,0 +1 @@ +url.com diff --git a/fuzzer/uri_parse/corpus/corpus-0063 b/fuzzer/uri_parse/corpus/corpus-0063 new file mode 100644 index 000000000..bdf76084e --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0063 @@ -0,0 +1 @@ +urn:oasis:names:specification:docbook:dtd:xml:4.1.2 diff --git a/fuzzer/uri_parse/corpus/corpus-0064 b/fuzzer/uri_parse/corpus/corpus-0064 new file mode 100644 index 000000000..027297211 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0064 @@ -0,0 +1 @@ +https://metadata.google/private_keys/ {.yaml = "{trap: metadata}"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0065 b/fuzzer/uri_parse/corpus/corpus-0065 new file mode 100644 index 000000000..366c3c90d --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0065 @@ -0,0 +1 @@ +mailto:John.Doe@example.com diff --git a/fuzzer/uri_parse/corpus/corpus-0066 b/fuzzer/uri_parse/corpus/corpus-0066 new file mode 100644 index 000000000..3ebd961a7 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0066 @@ -0,0 +1 @@ +http://authority:123?query diff --git a/fuzzer/uri_parse/corpus/corpus-0067 b/fuzzer/uri_parse/corpus/corpus-0067 new file mode 100644 index 000000000..5eba89cdc --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0067 @@ -0,0 +1 @@ +http://[::ffff:a9fe:a9fe]/latest/meta-data/ diff --git a/fuzzer/uri_parse/corpus/corpus-0068 b/fuzzer/uri_parse/corpus/corpus-0068 new file mode 100644 index 000000000..d01e6fee8 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0068 @@ -0,0 +1 @@ +https://blabla.com/random/path?name=/name2 {.yaml = "{form: {bla: '/name2'}}"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0069 b/fuzzer/uri_parse/corpus/corpus-0069 new file mode 100644 index 000000000..f1c9b4807 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0069 @@ -0,0 +1 @@ +https://graph.microsoft.com/v1.0/me/calendars/base64stuff=/events/base64stuff2=' diff --git a/fuzzer/uri_parse/corpus/corpus-0070 b/fuzzer/uri_parse/corpus/corpus-0070 new file mode 100644 index 000000000..1e81295c6 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0070 @@ -0,0 +1 @@ +http// diff --git a/fuzzer/uri_parse/corpus/corpus-0071 b/fuzzer/uri_parse/corpus/corpus-0071 new file mode 100644 index 000000000..176fc8035 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0071 @@ -0,0 +1 @@ +https://123.123.123.123/blablabla {.yaml = "{form: '123.123.123.123'}"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0072 b/fuzzer/uri_parse/corpus/corpus-0072 new file mode 100644 index 000000000..44076f6b5 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0072 @@ -0,0 +1 @@ +http://authority#f diff --git a/fuzzer/uri_parse/corpus/corpus-0073 b/fuzzer/uri_parse/corpus/corpus-0073 new file mode 100644 index 000000000..80532b645 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0073 @@ -0,0 +1 @@ +http:// diff --git a/fuzzer/uri_parse/corpus/corpus-0074 b/fuzzer/uri_parse/corpus/corpus-0074 new file mode 100644 index 000000000..21d6a9e0f --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0074 @@ -0,0 +1 @@ +https://blabla.com/random/path#/name2 {.yaml = "{form: {bla: '#/name2'}}"}}, diff --git a/fuzzer/uri_parse/corpus/corpus-0075 b/fuzzer/uri_parse/corpus/corpus-0075 new file mode 100644 index 000000000..ced6544e1 --- /dev/null +++ b/fuzzer/uri_parse/corpus/corpus-0075 @@ -0,0 +1 @@ +http://core-goals.evil.com/v1/projects/42/goals?projectId=42& diff --git a/fuzzer/uri_parse/src/main.cpp b/fuzzer/uri_parse/src/main.cpp new file mode 100644 index 000000000..33eadd0b2 --- /dev/null +++ b/fuzzer/uri_parse/src/main.cpp @@ -0,0 +1,19 @@ +// 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 "uri_utils.hpp" + +extern "C" int LLVMFuzzerInitialize(const int * /*argc*/, char *** /*argv*/) { return 0; } + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) +{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + std::string_view uri_raw{reinterpret_cast(bytes), size}; + ddwaf::uri_parse(uri_raw); + return 0; +} diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt deleted file mode 100644 index f4d0d6b68..000000000 --- a/fuzzing/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -file(GLOB_RECURSE LIBDDWAF_FUZZER_SOURCE src/*.cpp) -add_executable(fuzzer ${LIBDDWAF_SOURCE} ${LIBDDWAF_FUZZER_SOURCE}) - -set_target_properties(fuzzer PROPERTIES - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED YES - CXX_EXTENSIONS NO) - -target_include_directories(fuzzer PRIVATE ${libddwaf_SOURCE_DIR}/fuzzing/tools) -target_include_directories(fuzzer PRIVATE ${LIBDDWAF_PUBLIC_INCLUDES} ${LIBDDWAF_PRIVATE_INCLUDES}) - - -set_target_properties(fuzzer PROPERTIES COMPILE_FLAGS "-fsanitize=fuzzer,address,undefined,leak -fprofile-instr-generate -fcoverage-mapping") -set_target_properties(fuzzer PROPERTIES LINK_FLAGS "-fsanitize=fuzzer,address,undefined,leak -fprofile-instr-generate -fcoverage-mapping") - -target_link_libraries(fuzzer PRIVATE - ${LIBDDWAF_PRIVATE_LIBRARIES} ${LIBDDWAF_INTERFACE_LIBRARIES} lib_yamlcpp) - - diff --git a/fuzzing/Dockerfile b/fuzzing/Dockerfile deleted file mode 100644 index 5ba9183ce..000000000 --- a/fuzzing/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM ubuntu:focal - -RUN apt-get -y update && apt-get -y upgrade - -# Python 3, Make, CMake >= 3.16, git, clang, libFuzzer -RUN DEBIAN_FRONTEND="noninteractive" apt-get -y install python3 apt-transport-https build-essential wget cmake git clang libfuzzer-10-dev - -# Check versions -RUN python3 --version \ - && make --version \ - && cmake --version \ - && clang --version \ - && git --version - -COPY . /libddwaf -WORKDIR /libddwaf - -RUN ./fuzzing/build.sh diff --git a/fuzzing/run.sh b/fuzzing/run.sh deleted file mode 100755 index e08c210d5..000000000 --- a/fuzzing/run.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -set -eu - -cp build/fuzzing/fuzzer fuzzing/fuzzer -python3 fuzzing/scripts/build_corpus.py - -cd fuzzing - -export ASAN_OPTIONS=detect_leaks=1 - -rm -f fuzz-*.log - -echo "Run fuzzer for ${1:-60} seconds" -./fuzzer -timeout=0.1 -report_slow_units=0.01 -max_total_time=${1:-60} -max_len=1000 -rss_limit_mb=4096 -use_value_profile=1 -dict=sample_dict.txt -artifact_prefix=results/ -jobs=4 -workers=4 -reload=0 corpus diff --git a/fuzzing/scripts/show_coverage.sh b/fuzzing/scripts/show_coverage.sh deleted file mode 100755 index 07e532cc3..000000000 --- a/fuzzing/scripts/show_coverage.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -eu - -cd fuzzing - -llvm-profdata-17 merge -sparse *.profraw -o default.profdata -llvm-cov-17 show fuzzer -instr-profile=default.profdata -ignore-filename-regex="(vendor|fuzzing|third_party)" -format=html > coverage.html -llvm-cov-17 report -instr-profile default.profdata fuzzer -ignore-filename-regex="(vendor|fuzzing|third_party)" -show-region-summary=false - -if [ ! -z ${1:-} ]; then - THRESHOLD=$1 - TOTAL=$(llvm-cov-17 report -instr-profile default.profdata fuzzer -ignore-filename-regex="(vendor|fuzzing|third_party)" -show-region-summary=false | grep TOTAL) - ARRAY=($TOTAL) - COVERAGE=$(echo ${ARRAY[3]} | sed -e "s/\.[[:digit:]]*%//g") - - if (( $COVERAGE < $THRESHOLD )); then - echo "Sorry, the fuzzer found no bug, but the coverage is below $THRESHOLD%. Can't call it a success." 1>&2 - exit 1 - fi -fi diff --git a/src/condition/ssrf_detector.cpp b/src/condition/ssrf_detector.cpp new file mode 100644 index 000000000..dfbefe0e0 --- /dev/null +++ b/src/condition/ssrf_detector.cpp @@ -0,0 +1,279 @@ +// 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/ssrf_detector.hpp" +#include "exception.hpp" +#include "iterator.hpp" +#include "uri_utils.hpp" +#include "utils.hpp" + +using namespace std::literals; + +namespace ddwaf { + +namespace { + +constexpr std::array dangerous_ips{"169.254.0.0/16", "127.0.0.1/32", + "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/10", "::1/128", "fc00::/7", + "fe80::/10", "2001:db8:1234:1a00::/56"}; + +constexpr const std::array dangerous_domains{ + "metadata.google", "burpcollaborator.net", ".local", ".internal", "ram.aliyuncs.com", + "ifconfig.pro", "localhost", "fuf.me", "localtest.me", "ulh.us" // Legacy domains +}; + +constexpr const std::array authorised_schemes{"https", "http", "ftps", "ftp"}; + +constexpr const auto &npos = std::string_view::npos; + +using ssrf_result = std::optional>>; + +bool detect_parameter_injection( + const uri_decomposed &uri, std::string_view param, std::size_t param_index) +{ + const auto param_end = param_index + param.size(); + + // Check if the application is giving full control of the path to the user + // either fully or after a forward-slash. + if (!uri.path.empty()) { + const auto path_end = uri.path_index + uri.path.size(); + + // scheme://userinfo@host:port/path?query#fragment + // ───── + if (uri.path.size() == param.size()) { + return false; + } + // scheme://userinfo@host:port/path?query#fragment + // ──── + if (uri.path[0] == '/' && param_index == uri.path_index + 1 && + param.size() == uri.path.size() - 1) { + return false; + } + + // If part of the path has been injected and said injection contains a + // %2f, flag it as suspicious + // + // scheme://userinfo@host:port/path?query#fragment + // <─────> + if (param_index < path_end && param_end > uri.path_index && + (param.find("%2f") != npos || param.find("%2F") != npos || param.find("%5c") != npos || + param.find("%5C") != npos)) { + return true; + } + + // REST parameter injection will involve introducing a / to the URL, however, such + // slashes are allowed after the ? or the #. Check if the slash is within the path + // + // scheme://userinfo@host:port/path?query#fragment + // ───── + if (auto slash_index = param.find('/'); slash_index != npos) { + slash_index += param_index; + if (slash_index < path_end) { + // The path is partially under control of the user, this might be intentional + // so lets check for a possible LFI + auto relative_dir_index = param.find(".."); + while (relative_dir_index != npos) { + + // We found '..', check if it's enclosed by '/' + auto dir_index = relative_dir_index + param_index; + if (dir_index < path_end && (dir_index + 2) < uri.raw.size() && + uri.raw[dir_index - 1] == '/' && uri.raw[dir_index + 2] == '/') { + return true; + } + relative_dir_index = param.find("..", relative_dir_index + 2); + } + } + } + + // If everything after a certain point before the end of the path has been + // injected, assume it's intentional + // + // scheme://userinfo@host:port/path?query#fragment + // <─────────────────── + if (param_index < path_end && param_end == uri.raw.size()) { + return false; + } + } + + if (!uri.query.empty()) { + // Check if the query string was injected + // + // scheme://userinfo@host:port/path?query#fragment + // <───── + const auto param_query_index = param.find('?'); + if (uri.query_index != npos && param_query_index != npos && + (param_index + param_query_index + 1) == uri.query_index) { + // We had some cases where this was expected behavior, to make sure + // we check whether the next character is a '&' + auto after_param = uri.raw.substr(param_index + param.length()); + return after_param.empty() || after_param[0] != '&'; + } + + // Check if the parameter interfered with what comes after the ? + // + // scheme://userinfo@host:port/path?query#fragment + // ───── + // + // The parameter ends before the ? or starts after #, so it can't be + // interfering with the query parameters, we can stop here. + const auto query_end = uri.query_index + uri.query.size(); + if ((param_end <= uri.query_index - 1) || param_index >= query_end) { + return false; + } + + // Check if more than one parameter was injected + return param.find('&') != npos; + } + + // We only need to check for parameter injections in either the path + // or the query string, so if these are empty we can skip the checks + return false; +} + +ssrf_result ssrf_impl(const uri_decomposed &uri, const ddwaf_object ¶ms, + const exclusion::object_set_ref &objects_excluded, const object_limits &limits, + const std::unique_ptr &dangerous_ip_matcher, + const std::unordered_set &authorised_scheme_set, ddwaf::timer &deadline) +{ + static constexpr std::size_t min_str_len = 4; + + std::string_view dangerous_domain = {}; + for (const auto domain : dangerous_domains) { + if (uri.authority.host.ends_with(domain)) { + dangerous_domain = domain; + break; + } + } + + std::optional parameter_injection; + + 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 || object.nbEntries < min_str_len) { + continue; + } + + std::string_view param{object.stringValue, static_cast(object.nbEntries)}; + auto param_index = uri.raw.find(param); + if (param_index == npos) { + // Seemingly no injection + continue; + } + + // Verify if the injected param intereferes with the authority: + // + // scheme://userinfo@host:port/path?query#fragment + // ────────────────── + // + // Note that if there is no authority, this condition will never be true + // as uri.authority.param_index will be npos (size_t::max) + if (param_index >= uri.authority.index && param_index < uri.scheme_and_authority.size()) { + // Verify if the host was fully modified by the injected param + // + // scheme://userinfo@host:port/path?query#fragment + // ───────────────────> + // + // Note that we require the injection to extend beyond the authority + // to avoid false positives caused by the introduction of a single '/' + if ((param_index + param.size() - 1) > uri.scheme_and_authority.size()) { + return {{std::string(param), it.get_current_path()}}; + } + + // If the entire host has been injected, and it's an IP, check if its + // present in the list of known dangeorus IPs + // + // scheme://userinfo@host:port/path?query#fragment + // <────> + bool host_fully_injected = + param_index <= uri.authority.host_index && + param_index + param.size() >= uri.authority.host_index + uri.authority.host.size(); + + if (host_fully_injected && uri.authority.host_ip.has_value() && + dangerous_ip_matcher->match_ip(uri.authority.host_ip.value())) { + return {{std::string(param), it.get_current_path()}}; + } + + // Otherwise, check if the domain is also a known dangerous one injected + // by the parameter itself + if (!dangerous_domain.empty() && param.find(dangerous_domain) != npos) { + return {{std::string(param), it.get_current_path()}}; + } + } + + // If the injection includes the scheme check if it's still a valid one + // + // scheme://userinfo@host:port/path?query#fragment + // ───────────────────> + if (param_index == 0 && !authorised_scheme_set.contains(uri.scheme)) { + return {{std::string(param), it.get_current_path()}}; + } + + // Finally, since we haven't found an injection on the scheme + authority + // section of the URL, we verify if the parameter has been injected in the + // path or query. + // + // scheme://userinfo@host:port/path?query#fragment + // ─────────── + // + // However we don't report an event yet as there could be a legitimate injection. + if (!parameter_injection.has_value() && + detect_parameter_injection(uri, param, param_index)) { + parameter_injection = {{std::string(param), it.get_current_path()}}; + } + } + + // At this stage, no injection has been found on scheme + authority, so we + // report the parameter injection if one has been detected + if (parameter_injection.has_value()) { + return parameter_injection.value(); + } + + return {}; +} + +} // namespace + +ssrf_detector::ssrf_detector(std::vector args, const object_limits &limits) + : base_impl(std::move(args), limits), + dangerous_ip_matcher_(std::make_unique(dangerous_ips)), + authorised_schemes_(authorised_schemes.begin(), authorised_schemes.end()) +{} + +eval_result ssrf_detector::eval_impl(const unary_argument &uri, + const variadic_argument ¶ms, condition_cache &cache, + const exclusion::object_set_ref &objects_excluded, ddwaf::timer &deadline) const +{ + auto decomposed = uri_parse(uri.value); + if (!decomposed.has_value()) { + return {}; + } + + for (const auto ¶m : params) { + auto res = ssrf_impl(*decomposed, *param.value, objects_excluded, limits_, + dangerous_ip_matcher_, authorised_schemes_, deadline); + if (res.has_value()) { + std::vector uri_kp{uri.key_path.begin(), uri.key_path.end()}; + bool ephemeral = uri.ephemeral || param.ephemeral; + + auto &[highlight, param_kp] = res.value(); + cache.match = + condition_match{{{"resource"sv, std::string{uri.value}, uri.address, uri_kp}, + {"params"sv, highlight, param.address, param_kp}}, + {std::move(highlight)}, "ssrf_detector", {}, ephemeral}; + + return {true, uri.ephemeral || param.ephemeral}; + } + } + + return {}; +} + +} // namespace ddwaf diff --git a/src/condition/ssrf_detector.hpp b/src/condition/ssrf_detector.hpp new file mode 100644 index 000000000..12e9cd88f --- /dev/null +++ b/src/condition/ssrf_detector.hpp @@ -0,0 +1,32 @@ +// 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 ssrf_detector : public base_impl { +public: + static constexpr std::array param_names{"resource", "params"}; + + explicit ssrf_detector( + std::vector args, const object_limits &limits = {}); + +protected: + [[nodiscard]] eval_result eval_impl(const unary_argument &uri, + const variadic_argument ¶ms, condition_cache &cache, + const exclusion::object_set_ref &objects_excluded, ddwaf::timer &deadline) const; + + std::unique_ptr dangerous_ip_matcher_; + std::unordered_set authorised_schemes_; + + friend class base_impl; +}; + +} // namespace ddwaf diff --git a/src/ip_utils.cpp b/src/ip_utils.cpp index 639ef0ec4..41fa2ad5d 100644 --- a/src/ip_utils.cpp +++ b/src/ip_utils.cpp @@ -29,6 +29,39 @@ namespace ddwaf { +namespace { +template bool parse_ip_internal(std::array ip, ipaddr &out) +{ + int ret = inet_pton(af, ip.data(), &out.data); + if (ret != 1) { + return false; + } + + if constexpr (af == AF_INET) { + out.type = ipaddr::address_family::ipv4; + out.mask = 32; + } else if constexpr (af == AF_INET6) { + out.type = ipaddr::address_family::ipv6; + out.mask = 128; + } + + return true; +} + +template bool parse_ipv6_internal(std::array ip, ipaddr &out) +{ + static_assert(N >= INET6_ADDRSTRLEN); + return parse_ip_internal(ip, out); +} + +template bool parse_ipv4_internal(std::array ip, ipaddr &out) +{ + static_assert(N >= INET_ADDRSTRLEN); + return parse_ip_internal(ip, out); +} + +} // namespace + bool parse_ip(std::string_view ip, ipaddr &out) { if (ip.size() >= INET6_ADDRSTRLEN) { @@ -38,24 +71,43 @@ bool parse_ip(std::string_view ip, ipaddr &out) // Assume the string has no '\0' // char ip_cstr[INET6_ADDRSTRLEN] = {0}; std::array ip_cstr{0}; - memcpy(ip_cstr.data(), ip.data(), ip.size()); - int ret = inet_pton(AF_INET, ip_cstr.data(), &out.data); - if (ret != 1) { - ret = inet_pton(AF_INET6, ip_cstr.data(), &out.data); - if (ret != 1) { - return false; - } - out.type = ipaddr::address_family::ipv6; - out.mask = 128; - } else { - out.type = ipaddr::address_family::ipv4; - out.mask = 32; + if (ip.size() > INET_ADDRSTRLEN || !parse_ipv4_internal(ip_cstr, out)) { + return parse_ipv6_internal(ip_cstr, out); } + return true; } +bool parse_ipv4(std::string_view ip, ipaddr &out) +{ + if (ip.size() >= INET_ADDRSTRLEN) { + return false; + } + + // Assume the string has no '\0' + // char ip_cstr[INET_ADDRSTRLEN] = {0}; + std::array ip_cstr{0}; + memcpy(ip_cstr.data(), ip.data(), ip.size()); + + return parse_ipv4_internal(ip_cstr, out); +} + +bool parse_ipv6(std::string_view ip, ipaddr &out) +{ + if (ip.size() >= INET6_ADDRSTRLEN) { + return false; + } + + // Assume the string has no '\0' + // char ip_cstr[INET6_ADDRSTRLEN] = {0}; + std::array ip_cstr{0}; + memcpy(ip_cstr.data(), ip.data(), ip.size()); + + return parse_ipv6_internal(ip_cstr, out); +} + void ipv4_to_ipv6(ipaddr &out) { if (out.type != ipaddr::address_family::ipv4) { diff --git a/src/ip_utils.hpp b/src/ip_utils.hpp index 4b866f680..db71a3214 100644 --- a/src/ip_utils.hpp +++ b/src/ip_utils.hpp @@ -26,6 +26,8 @@ struct ipaddr { }; bool parse_ip(std::string_view ip, ipaddr &out); +bool parse_ipv4(std::string_view ip, ipaddr &out); +bool parse_ipv6(std::string_view ip, ipaddr &out); void ipv4_to_ipv6(ipaddr &out); bool parse_cidr(std::string_view str, ipaddr &out); diff --git a/src/matcher/ip_match.cpp b/src/matcher/ip_match.cpp index 859fe7d7c..f8720cf8b 100644 --- a/src/matcher/ip_match.cpp +++ b/src/matcher/ip_match.cpp @@ -9,7 +9,6 @@ #include #include -#include "ip_utils.hpp" #include "matcher/ip_match.hpp" namespace ddwaf::matcher { @@ -21,16 +20,7 @@ ip_match::ip_match(const std::vector &ip_list) throw std::runtime_error("failed to instantiate radix tree"); } - for (auto str : ip_list) { - // Parse and populate each IP/network - ipaddr ip{}; - if (ddwaf::parse_cidr(str, ip)) { - prefix_t prefix; - // NOLINTNEXTLINE(hicpp-no-array-decay,cppcoreguidelines-pro-bounds-array-to-pointer-decay) - radix_prefix_init(FAMILY_IPv6, ip.data, ip.mask, &prefix); - radix_put_if_absent(rtree_.get(), &prefix, 0); - } - } + init_tree(ip_list); } ip_match::ip_match(const std::vector> &ip_list) @@ -52,29 +42,18 @@ ip_match::ip_match(const std::vector> &ip_ } } -std::pair ip_match::match_impl(std::string_view str) const +[[nodiscard]] bool ip_match::match_ip(const ipaddr &ip) const { - if (!rtree_ || str.empty() || str.data() == nullptr) { - return {false, {}}; - } - - ddwaf::ipaddr ip{}; - if (!ddwaf::parse_ip(str, ip)) { - return {false, {}}; - } - - // Convert the IPv4 to IPv6 - ddwaf::ipv4_to_ipv6(ip); - // Initialize the radix structure to check if the IP exist prefix_t radix_ip; - // NOLINTNEXTLINE(hicpp-no-array-decay,cppcoreguidelines-pro-bounds-array-to-pointer-decay) - radix_prefix_init(FAMILY_IPv6, ip.data, radix_tree_bits, &radix_ip); + // NOLINTNEXTLINE(hicpp-no-array-decay,cppcoreguidelines-pro-bounds-array-to-pointer-decay, + // cppcoreguidelines-pro-type-const-cast) + radix_prefix_init(FAMILY_IPv6, const_cast(ip.data), radix_tree_bits, &radix_ip); // Run the check auto *node = radix_matching_do(rtree_.get(), &radix_ip); if (node == nullptr) { - return {false, {}}; + return false; } if (node->expiration > 0) { @@ -82,10 +61,30 @@ std::pair ip_match::match_impl(std::string_view str) const std::chrono::system_clock::now().time_since_epoch()) .count(); if (node->expiration < now) { - return {false, {}}; + return false; } } + return true; +} + +std::pair ip_match::match_impl(std::string_view str) const +{ + if (!rtree_ || str.empty() || str.data() == nullptr) { + return {false, {}}; + } + + ddwaf::ipaddr ip{}; + if (!ddwaf::parse_ip(str, ip)) { + return {false, {}}; + } + // Convert the IPv4 to IPv6 + ddwaf::ipv4_to_ipv6(ip); + + if (!match_ip(ip)) { + return {false, {}}; + } + return {true, std::string{str}}; } diff --git a/src/matcher/ip_match.hpp b/src/matcher/ip_match.hpp index a1465a77a..5c88d1563 100644 --- a/src/matcher/ip_match.hpp +++ b/src/matcher/ip_match.hpp @@ -10,6 +10,7 @@ #include #include +#include "ip_utils.hpp" #include "matcher/base.hpp" namespace ddwaf::matcher { @@ -20,6 +21,17 @@ class ip_match : public base_impl { ip_match() = default; explicit ip_match(const std::vector &ip_list); + template + explicit ip_match(const std::array &ip_list) + : rtree_(radix_new(radix_tree_bits), radix_free) + { + if (!rtree_) { + throw std::runtime_error("failed to instantiate radix tree"); + } + + init_tree(ip_list); + } + explicit ip_match(const rule_data_type &ip_list); ~ip_match() override = default; ip_match(const ip_match &) = delete; @@ -27,6 +39,8 @@ class ip_match : public base_impl { ip_match &operator=(const ip_match &) = delete; ip_match &operator=(ip_match &&) = default; + [[nodiscard]] bool match_ip(const ipaddr &ip) const; + protected: static constexpr std::string_view to_string_impl() { return ""; } static constexpr std::string_view name_impl() { return "ip_match"; } @@ -34,6 +48,20 @@ class ip_match : public base_impl { [[nodiscard]] std::pair match_impl(std::string_view str) const; + template void init_tree(const T &ip_list) + { + for (auto str : ip_list) { + // Parse and populate each IP/network + ipaddr ip{}; + if (ddwaf::parse_cidr(str, ip)) { + prefix_t prefix; + // NOLINTNEXTLINE(hicpp-no-array-decay,cppcoreguidelines-pro-bounds-array-to-pointer-decay) + radix_prefix_init(FAMILY_IPv6, ip.data, ip.mask, &prefix); + radix_put_if_absent(rtree_.get(), &prefix, 0); + } + } + } + static constexpr unsigned radix_tree_bits = 128; // IPv6 std::unique_ptr rtree_{nullptr, nullptr}; diff --git a/src/parser/parser_v2.cpp b/src/parser/parser_v2.cpp index 1d0cdb859..8d1e215bb 100644 --- a/src/parser/parser_v2.cpp +++ b/src/parser/parser_v2.cpp @@ -12,6 +12,7 @@ #include "condition/lfi_detector.hpp" #include "condition/scalar_condition.hpp" +#include "condition/ssrf_detector.hpp" #include "exception.hpp" #include "exclusion/object_filter.hpp" #include "generator/extract_schema.hpp" @@ -225,8 +226,11 @@ std::shared_ptr parse_expression(const parameter::vector &conditions if (operator_name == "lfi_detector") { auto arguments = parse_arguments(params, source, transformers, addresses); - conditions.emplace_back(std::make_unique(std::move(arguments), limits)); + } else if (operator_name == "ssrf_detector") { + auto arguments = + parse_arguments(params, source, transformers, addresses); + conditions.emplace_back(std::make_unique(std::move(arguments), limits)); } else { auto [data_id, matcher] = parse_matcher(operator_name, params); diff --git a/src/uri_utils.cpp b/src/uri_utils.cpp new file mode 100644 index 000000000..bc1780ad6 --- /dev/null +++ b/src/uri_utils.cpp @@ -0,0 +1,456 @@ +// 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 "uri_utils.hpp" +#include "utils.hpp" +#include + +/* + URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + hier-part = "//" authority path-abempty + / path-absolute + / path-rootless + / path-empty + scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + authority = [ userinfo "@" ] host [ ":" port ] + userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + host = IP-literal / IPv4address / reg-name + port = *DIGIT + IP-literal = "[" IPv6address "]" + IPv6address = 6( h16 ":" ) ls32 + / "::" 5( h16 ":" ) ls32 + / [ h16 ] "::" 4( h16 ":" ) ls32 + / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + / [ *4( h16 ":" ) h16 ] "::" ls32 + / [ *5( h16 ":" ) h16 ] "::" h16 + / [ *6( h16 ":" ) h16 ] "::" + h16 = 1*4HEXDIG + ls32 = ( h16 ":" h16 ) / IPv4address + IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet + dec-octet = DIGIT ; 0-9 + / %x31-39 DIGIT ; 10-99 + / "1" 2DIGIT ; 100-199 + / "2" %x30-34 DIGIT ; 200-249 + / "25" %x30-35 ; 250-255 + reg-name = *( unreserved / pct-encoded / sub-delims ) + path-abempty = *( "/" segment ) + path-absolute = "/" [ segment-nz *( "/" segment ) ] + path-rootless = segment-nz *( "/" segment ) + path-empty = 0 + segment = *pchar + segment-nz = 1*pchar + pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + query = *( pchar / "/" / "?" ) + fragment = *( pchar / "/" / "?" ) + pct-encoded = "%" HEXDIG HEXDIG + unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + / "*" / "+" / "," / ";" / "=" +*/ + +namespace ddwaf { + +namespace { +enum class token_type { + none, + scheme, + hierarchical_part, + authority, + userinfo, + host, + port, + ipv6address, + regname_or_ipv4address, + path, + path_no_authority, + query, + fragment, +}; +constexpr const auto &npos = std::string_view::npos; + +inline bool is_unreserved(char c) +{ + return ddwaf::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~'; +} + +inline bool is_subdelim(char c) +{ + return c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || + c == '+' || c == ',' || c == ';' || c == '='; +} + +inline bool is_scheme_char(char c) { return ddwaf::isalnum(c) || c == '.' || c == '-' || c == '+'; } +inline bool is_host_char(char c) { return is_unreserved(c) || is_subdelim(c) || c == '%'; } +inline bool is_path_char(char c) +{ + return is_unreserved(c) || is_subdelim(c) || c == '%' || c == ':' || c == '@'; +} +inline bool is_query_char(char c) { return is_path_char(c) || c == '/' || c == '?'; } +inline bool is_frag_char(char c) { return is_path_char(c) || c == '/' || c == '?'; } + +inline bool is_userinfo_char(char c) +{ + return is_unreserved(c) || is_subdelim(c) || c == ':' || c == '%'; +} + +inline bool is_regname_char(char c) { return is_unreserved(c) || is_subdelim(c) || c == '%'; } + +} // namespace + +std::optional uri_parse(std::string_view uri) +{ + uri_decomposed decomposed; + decomposed.raw = uri; + + auto expected_token = token_type::scheme; + auto lookahead_token = token_type::none; + + // Authority helpers + std::size_t authority_end = npos; + std::string_view authority_substr; + + for (std::size_t i = 0; i < uri.size();) { + // Dead man's switch + auto current_token = expected_token; + expected_token = token_type::none; + + switch (current_token) { + case token_type::scheme: { + auto token_begin = i; + if (!isalpha(uri[i++])) { + // The URI is malformed as the first character must be alphabetic + return std::nullopt; + } + + bool end_found = false; + while (i < uri.size()) { + const auto c = uri[i++]; + if (c == ':') { + // We reached the end of the scheme, move to the next token + end_found = true; + break; + } + + if (!is_scheme_char(c)) { + return std::nullopt; + } + } + + if (!end_found) { + return std::nullopt; + } + + expected_token = token_type::hierarchical_part; + decomposed.scheme = uri.substr(token_begin, i - 1); + + break; + } + case token_type::hierarchical_part: { + if ((i + 1) < uri.size() && uri[i] == '/' && uri[i + 1] == '/') { + // The authority always starts with // + expected_token = token_type::authority; + i += 2; + } else { + // Otherwise we expect a path (path-absolute, path-rootless, path-empty) + expected_token = token_type::path_no_authority; + } + 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); + if (authority_end != npos) { + const auto c = uri[authority_end]; + if (c == '/') { + lookahead_token = token_type::path; + } else if (c == '?') { + lookahead_token = token_type::query; + } else if (c == '#') { + lookahead_token = token_type::fragment; + } + } else { + authority_end = uri.size(); + } + + if (authority_end > i) { + // The substring starts on 0 to ensure that indices are correct + authority_substr = uri.substr(0, authority_end); + if (authority_substr.find('@', i) != npos) { + expected_token = token_type::userinfo; + } else { + expected_token = token_type::host; + } + + decomposed.authority.index = token_begin; + decomposed.authority.raw = uri.substr(token_begin, authority_end - token_begin); + decomposed.scheme_and_authority = uri.substr(0, authority_end); + } else { + expected_token = lookahead_token; + } + + break; + } + case token_type::userinfo: { + auto token_begin = i; + // Find any unexpected characters, technically the ':' is valid and the + // password is deprecated so allow one or more instances of it. + while (i < uri.size()) { + const auto c = uri[i++]; + + if (c == '@') { + // If we find ourselves in this token, the @ is guaranteed + // to be present. + decomposed.authority.userinfo = uri.substr(token_begin, i - token_begin - 1); + + token_begin = i; + if (i == authority_end) { + expected_token = lookahead_token; + } else { + expected_token = token_type::host; + } + break; + } + + if (!is_userinfo_char(c)) { + // We've found an invalid character, we can consider the + // authority malformed + return std::nullopt; + } + } + + break; + } + case token_type::host: { + if (uri[i] == '[') { + expected_token = token_type::ipv6address; + } else if (uri[i] == ':') { // Empty host + ++i; + expected_token = token_type::port; + } else if (is_host_char(uri[i])) { + expected_token = token_type::regname_or_ipv4address; + } else if (authority_end != uri.size()) { + expected_token = lookahead_token; + } else { + // Not a valid character, malformed + return std::nullopt; + } + break; + } + case token_type::regname_or_ipv4address: { + auto token_begin = i; + // Reg name or IPv4 host + for (; i < authority_end; ++i) { + const auto c = uri[i]; + if (c == ':') { /* Port */ + break; + } + if (!is_regname_char(c)) { + // Unexpected character, find the port and exit + return std::nullopt; + } + } + + decomposed.authority.host = uri.substr(token_begin, i - token_begin); + decomposed.authority.host_index = token_begin; + + ipaddr parsed_ip{}; + if (parse_ipv4(decomposed.authority.host, parsed_ip)) { + ipv4_to_ipv6(parsed_ip); + decomposed.authority.host_ip = parsed_ip; + } + + if (i >= uri.size()) { + return decomposed; + } + + if (uri[i] == ':') { + ++i; + expected_token = token_type::port; + } else { + expected_token = lookahead_token; + } + break; + } + case token_type::ipv6address: { + auto token_begin = i; + // Validate if this is an IPv6 host + bool end_found = false; + for (i += 1; i < uri.size(); ++i) { + const auto c = uri[i]; + if (c == ']') { /* IPv6 End */ + end_found = true; + break; + } + if (!ddwaf::isxdigit(c) && c != ':') { + // The host is already malformed so we can stop here; + return std::nullopt; + } + } + + if (!end_found || i == (token_begin + 1)) { + return std::nullopt; + } + + ipaddr parsed_ip{}; + auto host = uri.substr(token_begin + 1, i - (token_begin + 1)); + if (!parse_ipv6(host, parsed_ip)) { + return std::nullopt; + } + + decomposed.authority.host = host; + decomposed.authority.host_ip = parsed_ip; + decomposed.authority.host_index = token_begin + 1; + + token_begin = ++i; + if (token_begin == authority_end) { + // Keep the next token as it can be the beginning of the + // path which has to be kept + expected_token = lookahead_token; + } else if (i < uri.size() && uri[i] == ':') { + token_begin = ++i; // Skip the ':' + expected_token = token_type::port; + } else { + // Unexpected characters after IPv6 terminator + return std::nullopt; + } + + break; + } + case token_type::port: { + auto token_begin = i; + for (; i < authority_end; ++i) { + if (!ddwaf::isdigit(uri[i])) { + return std::nullopt; + } + } + + auto port_substr = uri.substr(token_begin, i - token_begin); + if (!port_substr.empty()) { + if (auto [res, value] = from_string(port_substr); !res) { + return std::nullopt; + } + decomposed.authority.port = port_substr; + } + + if (authority_end == uri.size()) { + return decomposed; + } + + expected_token = lookahead_token; + + break; + } + case token_type::path: { + auto token_begin = i; + for (; i < uri.size(); ++i) { + const auto c = uri[i]; + if (c == '?') { + expected_token = token_type::query; + break; + } + + if (c == '#') { + expected_token = token_type::fragment; + break; + } + + if (!is_path_char(c) && c != '/') { + return std::nullopt; + } + } + + decomposed.path_index = token_begin; + if (i >= uri.size()) { + decomposed.path = decomposed.raw.substr(token_begin); + return decomposed; + } + + decomposed.path = decomposed.raw.substr(token_begin, i - token_begin); + break; + } + case token_type::query: { + // Skip '?' + auto token_begin = ++i; + for (; i < uri.size(); ++i) { + const auto c = uri[i]; + if (c == '#') { + expected_token = token_type::fragment; + break; + } + + if (!is_query_char(c)) { + return std::nullopt; + } + } + + // Ignore empty query + if (i > token_begin) { + decomposed.query_index = token_begin; + if (i >= uri.size()) { + decomposed.query = decomposed.raw.substr(token_begin); + return decomposed; + } + + decomposed.query = decomposed.raw.substr(token_begin, i - token_begin); + } + break; + } + case token_type::fragment: { + // Skip '#' + auto token_begin = ++i; + for (; i < uri.size(); ++i) { + const auto c = uri[i]; + if (!is_frag_char(c)) { + return std::nullopt; + } + } + + // Ignore empty fragment + if (i > token_begin) { + decomposed.fragment_index = token_begin; + decomposed.fragment = uri.substr(token_begin); + } + return decomposed; + } + case token_type::none: + default: + return std::nullopt; + } + } + + return decomposed; +} + +std::ostream &operator<<(std::ostream &o, const uri_decomposed &uri) +{ + o << "Scheme : " << uri.scheme << '\n' + << "Userinfo : " << uri.authority.userinfo << '\n' + << "Host : " << uri.authority.host << '\n' + << "Port : " << uri.authority.port << '\n' + << "Path : " << uri.path << '\n' + << "Query : " << uri.query << '\n' + << "Fragment : " << uri.fragment << '\n'; + return o; +} +} // namespace ddwaf diff --git a/src/uri_utils.hpp b/src/uri_utils.hpp new file mode 100644 index 000000000..8766444bb --- /dev/null +++ b/src/uri_utils.hpp @@ -0,0 +1,41 @@ +// 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 +#include + +#include "ip_utils.hpp" + +namespace ddwaf { + +// https://datatracker.ietf.org/doc/html/rfc3986#section-3 +struct uri_decomposed { + std::string_view scheme; + struct { + std::size_t index{std::string_view::npos}; + std::size_t host_index{std::string_view::npos}; + std::string_view userinfo{}; + std::string_view host{}; + std::optional host_ip{}; + std::string_view port{}; + std::string_view raw; + } authority; + std::string_view scheme_and_authority; + std::size_t path_index{std::string_view::npos}; + std::string_view path; + std::size_t query_index{std::string_view::npos}; + std::string_view query; + std::size_t fragment_index{std::string_view::npos}; + std::string_view fragment; + std::string_view raw; +}; + +std::optional uri_parse(std::string_view uri); + +std::ostream &operator<<(std::ostream &o, const uri_decomposed &uri); +} // namespace ddwaf diff --git a/tests/ip_test.cpp b/tests/ip_test.cpp index 6b27ca569..a6ef3c751 100644 --- a/tests/ip_test.cpp +++ b/tests/ip_test.cpp @@ -11,13 +11,25 @@ namespace { TEST(TestIP, ParsingIPv4) { - ddwaf::ipaddr ip{}; - EXPECT_TRUE(ddwaf::parse_ip("1.2.3.4", ip)); - EXPECT_EQ(ip.type, ddwaf::ipaddr::address_family::ipv4); - EXPECT_EQ(ip.data[0], 1); - EXPECT_EQ(ip.data[1], 2); - EXPECT_EQ(ip.data[2], 3); - EXPECT_EQ(ip.data[3], 4); + { + ddwaf::ipaddr ip{}; + EXPECT_TRUE(ddwaf::parse_ip("1.2.3.4", ip)); + EXPECT_EQ(ip.type, ddwaf::ipaddr::address_family::ipv4); + EXPECT_EQ(ip.data[0], 1); + EXPECT_EQ(ip.data[1], 2); + EXPECT_EQ(ip.data[2], 3); + EXPECT_EQ(ip.data[3], 4); + } + + { + ddwaf::ipaddr ip{}; + EXPECT_TRUE(ddwaf::parse_ipv4("1.2.3.4", ip)); + EXPECT_EQ(ip.type, ddwaf::ipaddr::address_family::ipv4); + EXPECT_EQ(ip.data[0], 1); + EXPECT_EQ(ip.data[1], 2); + EXPECT_EQ(ip.data[2], 3); + EXPECT_EQ(ip.data[3], 4); + } } TEST(TestIP, ParsingIPv4Class) @@ -30,16 +42,31 @@ TEST(TestIP, ParsingIPv4Class) TEST(TestIP, ParsingIPv6) { - ddwaf::ipaddr ip{}; + { + ddwaf::ipaddr ip{}; - EXPECT_TRUE(ddwaf::parse_ip("abcd::ef01", ip)); - EXPECT_EQ(ip.type, ddwaf::ipaddr::address_family::ipv6); - EXPECT_EQ(ip.data[0], 0xab); - EXPECT_EQ(ip.data[1], 0xcd); - for (int i = 2; i < 14; ++i) { EXPECT_EQ(ip.data[i], 0); } + EXPECT_TRUE(ddwaf::parse_ip("abcd::ef01", ip)); + EXPECT_EQ(ip.type, ddwaf::ipaddr::address_family::ipv6); + EXPECT_EQ(ip.data[0], 0xab); + EXPECT_EQ(ip.data[1], 0xcd); + for (int i = 2; i < 14; ++i) { EXPECT_EQ(ip.data[i], 0); } + + EXPECT_EQ(ip.data[14], 0xef); + EXPECT_EQ(ip.data[15], 0x01); + } - EXPECT_EQ(ip.data[14], 0xef); - EXPECT_EQ(ip.data[15], 0x01); + { + ddwaf::ipaddr ip{}; + + EXPECT_TRUE(ddwaf::parse_ipv6("abcd::ef01", ip)); + EXPECT_EQ(ip.type, ddwaf::ipaddr::address_family::ipv6); + EXPECT_EQ(ip.data[0], 0xab); + EXPECT_EQ(ip.data[1], 0xcd); + for (int i = 2; i < 14; ++i) { EXPECT_EQ(ip.data[i], 0); } + + EXPECT_EQ(ip.data[14], 0xef); + EXPECT_EQ(ip.data[15], 0x01); + } } TEST(TestIP, ParsingIPv4MappedIPv6) diff --git a/tests/ssrf_detector_test.cpp b/tests/ssrf_detector_test.cpp new file mode 100644 index 000000000..4aef9f870 --- /dev/null +++ b/tests/ssrf_detector_test.cpp @@ -0,0 +1,218 @@ +// 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/ssrf_detector.hpp" +#include "platform.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)}}}...}; +} + +struct ssrf_sample { + std::string yaml; + std::string resolved{}; + std::vector key_path{}; +}; + +void match_path_and_input( + const std::vector> &samples, bool match = true) +{ + ssrf_detector cond{{gen_param_def("server.io.net.url", "server.request.query")}}; + + for (const auto &[path, sample] : samples) { + ddwaf_object tmp; + ddwaf_object root; + + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.io.net.url", ddwaf_object_string(&tmp, path.c_str())); + + auto input = yaml_to_object(sample.yaml); + ddwaf_object_map_add(&root, "server.request.query", &input); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + if (match) { + ASSERT_TRUE(res.outcome) << path; + EXPECT_FALSE(res.ephemeral); + + EXPECT_TRUE(cache.match); + if (cache.match) { // Silence linter + EXPECT_STRV(cache.match->args[0].address, "server.io.net.url"); + EXPECT_STR(cache.match->args[0].resolved, path.c_str()); + EXPECT_TRUE(cache.match->args[0].key_path.empty()); + + EXPECT_STRV(cache.match->args[1].address, "server.request.query"); + if (sample.resolved.empty()) { + EXPECT_STR(cache.match->args[1].resolved, sample.yaml.c_str()); + EXPECT_STR(cache.match->highlights[0], sample.yaml.c_str()); + } else { + EXPECT_STR(cache.match->args[1].resolved, sample.resolved.c_str()); + EXPECT_STR(cache.match->highlights[0], sample.resolved.c_str()); + } + EXPECT_TRUE(cache.match->args[1].key_path == sample.key_path) << path; + } + } else { + EXPECT_FALSE(res.outcome) << path; + EXPECT_FALSE(cache.match); + } + } +} + +TEST(TestSSRFDetector, MatchScheme) +{ + match_path_and_input({ + {"gopher://blabla.com/path", {.yaml = "gopher"}}, + }); +} + +TEST(TestSSRFDetector, MatchHost) +{ + match_path_and_input({ + {"https://internal-website.evil.com/path/to/stuffs?bla=42", + {.yaml = ".evil.com/path/to/stuffs?"}}, + {"https://internal-website.evil.com:42/path/to/stuffs?bla=42", + {.yaml = ".evil.com:42/path/to/stuffs?"}}, + {"https://internal-website:4242/path/to/stuffs?bla=42", {.yaml = ":4242/path/to/stuffs?"}}, + {"https://blabla.com/path", {.yaml = ".com/path"}}, + {"http://core-goals.evil.com/v1/projects/42/goals?projectId=42&", + {.yaml = R"({"path":".evil.com/v1/projects/42/goals?"})", + .resolved = ".evil.com/v1/projects/42/goals?", + .key_path = {"path"}}}, + {"http://2852039166/latest/meta-data/", + {.yaml = R"({form: { url: "2852039166/latest/meta-data/" }})", + .resolved = "2852039166/latest/meta-data/", + .key_path = {"form", "url"}}}, + }); +} +TEST(TestSSRFDetector, MatchDangerousIP) +{ + match_path_and_input({ + {"https://169.254.169.254/somewhere/in/the/app", {.yaml = "169.254.169.254"}}, + {"https://[::ffff:a9fe:a9fe]/path", + {.yaml = R"("[::ffff:a9fe:a9fe]")", .resolved = "[::ffff:a9fe:a9fe]"}}, + {"https://[::1]/path", {.yaml = R"("[::1]")", .resolved = "[::1]"}}, + {"http://[::ffff:a9fe:a9fe]/latest/meta-data/", + {.yaml = R"({form: {url: "[::ffff:a9fe:a9fe]/latest/meta-data/"}})", + .resolved = "[::ffff:a9fe:a9fe]/latest/meta-data/", + .key_path = {"form", "url"}}}, + {"http://[0:0:0:0:0:ffff:a9fe:a9fe]/latest/meta-data/", + {.yaml = R"({form: {url: "[0:0:0:0:0:ffff:a9fe:a9fe]/latest/meta-data/"}})", + .resolved = "[0:0:0:0:0:ffff:a9fe:a9fe]/latest/meta-data/", + .key_path = {"form", "url"}}}, + //{"https://127.1/path", "127.1"} // TODO: not parsed by inet_pton + }); +} + +TEST(TestSSRFDetector, MatchDangerousDomain) +{ + match_path_and_input({ + {"https://blabla.burpcollaborator.net/path", {.yaml = "burpcollaborator.net"}}, + {"https://localhost/path", {.yaml = "localhost"}}, + {"https://ifconfig.pro", {.yaml = "ifconfig.pro"}}, + }); +} + +TEST(TestSSRFDetector, NoMatch) +{ + match_path_and_input( + { + {"https://metadata.google/private_keys/", {.yaml = "{trap: metadata}"}}, + {"https://254.254.169.254/path", {.yaml = "{form: {bla: '254.254.169.254'}}"}}, + {"https://[::ffff:fea9:a9fe]/path", {.yaml = "{form: {bla: '[::ffff:fea9:a9fe]'}}"}}, + {"https://blabla.com/random/path?name=/name2", {.yaml = "{form: {bla: '/name2'}}"}}, + {"https://blabla.com/random/path?name=name2#bla/name2", + {.yaml = "{form: {bla: '/name2'}}"}}, + {"https://blabla.com/random/path#/name2", {.yaml = "{form: {bla: '#/name2'}}"}}, + {"https://blabla.com/random/path/with?param=val", + {.yaml = "{form: {bla: '/random/path/with?param=val'}}"}}, + {"https://blabla.com/random/path/with?param=val", + {.yaml = "{form: {bla: 'random/path/with?param=val'}}"}}, + {"https://123.123.123.123/blablabla", {.yaml = "{form: '123.123.123.123'}"}}, + }, + false); +} + +TEST(TestSSRFDetector, MatchParameterInjection) +{ + match_path_and_input({ + {"https://blabla.com/random/../with?param=value", {.yaml = "../with"}}, + {"https://blabla.com/random/with%2fdodgy/characters?param=value", {.yaml = "with%2fdodgy"}}, + {"https://blabla.com/random/with%2Fdodgy/characters?param=value", {.yaml = "with%2Fdodgy"}}, + {"https://blabla.com/random/with%5cdodgy/characters?param=value", {.yaml = "with%5cdodgy"}}, + {"https://blabla.com/random/with%5Cdodgy/characters?param=value", {.yaml = "with%5Cdodgy"}}, + {"https://blabla.com/random/..falsestart.something/../with?param=value", + {.yaml = "..falsestart.something/../with"}}, + {"https://blabla.com/path?name=param&name2=param2", {.yaml = "param&name2=param2"}}, + {"https://blabla.com/path?name=param&name2=param2", {.yaml = "name=param&name2=param2"}}, + {"https://blabla.com/path?name=param&auth=43", {.yaml = "param&auth"}}, + {"https://blabla.com/random/path?name=name2&value=/legit", + {.yaml = "path?name=name2&value="}}, + {"a://b/c/d?e=f&g=h", {.yaml = "d?e=f&g="}}, + {"http://core-goals/v1/projects/42/goals?projectId=42&", + {.yaml = R"(/v1/projects/42/goals?)"}}, + {"https://internal-website/path/to/stuffs?bla=42", {.yaml = "/path/to/stuffs?"}}, + //{"http://0:8000/composer/send_email?to=orange@chroot.org&url=http://127.0.0.1:6379/%0D%0ASET", + //{.yaml="http://127.0.0.1:6379/%0D%0ASET"}}, + }); +} + +TEST(TestSSRFDetector, NoMatchPotentialFalsePositives) +{ + match_path_and_input( + { + {"https://graph.microsoft.com/v1.0/me/calendars/base64stuff=/events/base64stuff2='", + {.yaml = R"({form: {calendarId: "base64stuff="}})"}}, + {"https://graph.microsoft.com/v1.0/me/calendars/base64stuff=/events", + {.yaml = R"({form: {calendarId: "base64stuff="}})"}}, + {"https://s3-us-west-2.amazonaws.com/xxx-hosted-content/iframe_pages/424242/path/" + "to_file.mp3", + {.yaml = R"({form: {path: "path/to_file.mp3"}})"}}, + {"http://policies.production.svc.cluster.local/" + "findTravelPolicies?include[]=rules&where=mongoQuery", + {.yaml = R"({product: flight})"}}, + {"http://internal-microservices-production-4242.eu-west-1.elb.amazonaws.com/" + "service-legacy/users/4242/evaluations.json?page=1", + {.yaml = R"({request: {path: "/users/1519111/evaluations.json?page=1"}})"}}, + {"http://pro.orange.fr/css/fonts/opus-meteo/fonts/opus-meteo.ttf?blablabla", + {.yaml = + R"({query: {base: "https://pro.orange.fr/css/fonts/opus-meteo/style.css", uri: "fonts/opus-meteo.ttf?blablabla"}})"}}, + {"http://threatguard-prod-tg-api.iaas.checkpoint.com/api/v1/lookalike/" + "screenshotB64?date=2020-01-17&lookalike=base64=", + {.yaml = R"({query: {lookalike: base64=}})"}}, + {"http://cn-cache.ryzerobotics.com/support/" + "service-policies?cache=update&cache-gateway=1", + {.yaml = R"({path: "/support/service-policies?cache=update"})"}}, + {"http://s3.eu-central-1.amazonaws.com/bla/production/p/bla/blablabla.../" + "BLABLABLA.html", + {.yaml = R"({path: "/p/bla/blablabla.../BLABLABLA"})"}}, + {"http://blablabla.com/api/v3/ds/data-checks", {.yaml = R"({bla: "ds/data-checks"})"}}, + {"http://blablabla.comhttps://blablabla", {.yaml = R"({bla: https})"}}, + {"http://172.16.87.100:1234/pouet/bla/_search?stuff=87.1", + {.yaml = R"({stuff: "87.1"})"}}, + {"tax.internal.patreon.com/services/tax/1.0/quote/batch", + {.yaml = R"({query: {utm_campaign: ["patreon"]}})"}}, + {"http://bla.patreon.com/batch", {.yaml = R"({query: {param: "patreon.com/"}})"}}, + {"http://google.com/batch", {.yaml = R"({query: {param: "batch"}})"}}, + {"http://google.com/batch", {.yaml = R"({query: {param: "/batch"}})"}}, + {"file/blabla/metadata", {.yaml = R"({query: {param: "blabla"}})"}}, + /* {"http://scrapper-proxy.awsregion.bla.iohttps://images.bla.com/whatever",*/ + /*{.yaml = R"({url: "https://images.bla.com/whatever"})"}},*/ + }, + false); +} + +} // namespace diff --git a/tests/test.hpp b/tests/test.hpp index b72b8caba..99a8c5182 100644 --- a/tests/test.hpp +++ b/tests/test.hpp @@ -39,4 +39,4 @@ } #define EXPECT_STR(a, b) EXPECT_STREQ(a.c_str(), b) -#define EXPECT_STRV(a, b) EXPECT_STREQ(a.data(), b) +#define EXPECT_STRV(a, b) EXPECT_STR(std::string{a}, b) diff --git a/tests/uri_utils_test.cpp b/tests/uri_utils_test.cpp new file mode 100644 index 000000000..3ab965e7e --- /dev/null +++ b/tests/uri_utils_test.cpp @@ -0,0 +1,491 @@ +// 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 "uri_utils.hpp" + +using namespace std::literals; + +namespace { + +TEST(TestURI, Scheme) +{ + { + auto uri = ddwaf::uri_parse("http://"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_TRUE(uri->authority.raw.empty()); + EXPECT_TRUE(uri->scheme_and_authority.empty()); + EXPECT_TRUE(uri->path.empty()); + EXPECT_TRUE(uri->query.empty()); + EXPECT_TRUE(uri->fragment.empty()); + } + + { + auto uri = ddwaf::uri_parse("http:"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_TRUE(uri->authority.raw.empty()); + EXPECT_TRUE(uri->scheme_and_authority.empty()); + EXPECT_TRUE(uri->path.empty()); + EXPECT_TRUE(uri->query.empty()); + EXPECT_TRUE(uri->fragment.empty()); + } +} + +TEST(TestURI, MalformedScheme) +{ + EXPECT_FALSE(ddwaf::uri_parse("@http")); + EXPECT_FALSE(ddwaf::uri_parse("h@@:path")); + EXPECT_FALSE(ddwaf::uri_parse("hhttp,:")); + EXPECT_FALSE(ddwaf::uri_parse("http//")); + EXPECT_FALSE(ddwaf::uri_parse("url.com")); +} + +TEST(TestURI, SchemeAndPath) +{ + { + auto uri = ddwaf::uri_parse("file:///usr/lib/libddwaf.so"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "file"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "/usr/lib/libddwaf.so"); + EXPECT_EQ(uri->path_index, 7); + } + + { + auto uri = ddwaf::uri_parse("file:/usr/lib/libddwaf.so"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "file"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "/usr/lib/libddwaf.so"); + EXPECT_EQ(uri->path_index, 5); + } + + { + auto uri = ddwaf::uri_parse("file:/../lib/libddwaf.so"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "file"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "/../lib/libddwaf.so"); + EXPECT_EQ(uri->path_index, 5); + } + { + auto uri = ddwaf::uri_parse("file:../lib/libddwaf.so"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "file"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "../lib/libddwaf.so"); + EXPECT_EQ(uri->path_index, 5); + } + + { + auto uri = ddwaf::uri_parse("http:///"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "/"); + EXPECT_TRUE(uri->fragment.empty()); + } + { + auto uri = ddwaf::uri_parse("urn:oasis:names:specification:docbook:dtd:xml:4.1.2"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "urn"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "oasis:names:specification:docbook:dtd:xml:4.1.2"); + EXPECT_TRUE(uri->fragment.empty()); + } + + { + auto uri = ddwaf::uri_parse("tel:+1-816-555-1212"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "tel"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "+1-816-555-1212"); + EXPECT_TRUE(uri->fragment.empty()); + } + + { + auto uri = ddwaf::uri_parse("mailto:John.Doe@example.com"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "mailto"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->path, "John.Doe@example.com"); + EXPECT_TRUE(uri->fragment.empty()); + } +} + +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, SchemeHost) +{ + { + auto uri = ddwaf::uri_parse("http://authority"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + } + + { + auto uri = ddwaf::uri_parse("http://authority.with.dots"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "authority.with.dots"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + } + + { + auto uri = ddwaf::uri_parse("h://a"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "h"); + EXPECT_EQ(uri->authority.host_index, 4); + EXPECT_STRV(uri->authority.host, "a"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + } +} + +TEST(TestURI, SchemeIPv4Host) +{ + auto uri = ddwaf::uri_parse("http://1.2.3.4"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "1.2.3.4"); + EXPECT_TRUE(uri->authority.host_ip); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); +} + +TEST(TestURI, SchemeIPv6Host) +{ + auto uri = ddwaf::uri_parse("http://[200:22:11:33:44:ab:cc:bf]"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "200:22:11:33:44:ab:cc:bf"); + EXPECT_TRUE(uri->authority.host_ip); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); +} + +TEST(TestURI, SchemeIPv6HostPort) +{ + auto uri = ddwaf::uri_parse("http://[200:22:11:33:44:ab:cc:bf]:1234"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "200:22:11:33:44:ab:cc:bf"); + EXPECT_TRUE(uri->authority.host_ip); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_STRV(uri->authority.port, "1234"); +} + +TEST(TestURI, SchemeMalformedIPv6Host) +{ + ASSERT_FALSE(ddwaf::uri_parse("http://[200:::::::::::::::::1]")); +} + +TEST(TestURI, SchemeUser) +{ + { + auto uri = ddwaf::uri_parse("http://paco@"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_STRV(uri->authority.userinfo, "paco"); + EXPECT_TRUE(uri->authority.port.empty()); + } + + { + auto uri = ddwaf::uri_parse("http://paco@:"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_STRV(uri->authority.userinfo, "paco"); + EXPECT_TRUE(uri->authority.port.empty()); + } +} + +TEST(TestURI, SchemeUserPort) +{ + { + auto uri = ddwaf::uri_parse("http://paco@:1919"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_STRV(uri->authority.userinfo, "paco"); + EXPECT_EQ(uri->authority.port, "1919"); + } +} + +TEST(TestURI, SchemeUserHost) +{ + { + auto uri = ddwaf::uri_parse("http://paco@authority"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_STRV(uri->authority.userinfo, "paco"); + EXPECT_TRUE(uri->authority.port.empty()); + } +} + +TEST(TestURI, SchemeUserHostPort) +{ + { + auto uri = ddwaf::uri_parse("http://paco@authority:1919"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_STRV(uri->authority.userinfo, "paco"); + EXPECT_EQ(uri->authority.port, "1919"); + } +} + +TEST(TestURI, SchemeHostPort) +{ + { + auto uri = ddwaf::uri_parse("http://authority:1"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_STRV(uri->authority.port, "1"); + } + + { + // Empty host + auto uri = ddwaf::uri_parse("h://:19283"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "h"); + EXPECT_TRUE(uri->authority.host.empty()); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_STRV(uri->authority.port, "19283"); + } + + { + // Empty host + ASSERT_FALSE(ddwaf::uri_parse("h://:65536")); + ASSERT_FALSE(ddwaf::uri_parse("h://:-1")); + } +} + +TEST(TestURI, MalformedPort) +{ + ASSERT_FALSE(ddwaf::uri_parse("h://:65536")); + ASSERT_FALSE(ddwaf::uri_parse("h://:123123123")); + ASSERT_FALSE(ddwaf::uri_parse("h://:-1")); +} + +TEST(TestURI, SchemeHostQuery) +{ + { + auto uri = ddwaf::uri_parse("http://authority?query"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->query, "query"); + } + + { + auto uri = ddwaf::uri_parse("http://authority:?query"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->query, "query"); + } + + { + auto uri = ddwaf::uri_parse("http://authority:?q@uery"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->query, "q@uery"); + } +} + +TEST(TestURI, SchemeHostMalformedQuery) +{ + ASSERT_FALSE(ddwaf::uri_parse("http://authority?que[]ry")); +} + +TEST(TestURI, SchemeHostPath) +{ + auto uri = ddwaf::uri_parse("http://authority/path"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_STRV(uri->path, "/path"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); +} + +TEST(TestURI, SchemeHostMalformedPath) +{ + ASSERT_FALSE(ddwaf::uri_parse("http://authority/pa[]th")); +} + +TEST(TestURI, SchemeHostFragment) +{ + auto uri = ddwaf::uri_parse("http://authority#f"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + EXPECT_STRV(uri->authority.host, "authority"); + EXPECT_TRUE(uri->authority.userinfo.empty()); + EXPECT_TRUE(uri->authority.port.empty()); + EXPECT_STRV(uri->fragment, "f"); +} + +TEST(TestURI, SchemeHostMalformedFragment) +{ + ASSERT_FALSE(ddwaf::uri_parse("http://authority#f[]")); +} + +TEST(TestURI, SchemeHostPortQuery) +{ + auto uri = ddwaf::uri_parse("http://authority:123?query"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + 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, SchemeHostPortPath) +{ + auto uri = ddwaf::uri_parse("http://authority:12/path"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + 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, SchemeHostPortFragment) +{ + auto uri = ddwaf::uri_parse("http://authority:1#f"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + 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, SchemeUserHostQuery) +{ + auto uri = ddwaf::uri_parse("http://user@authority?query"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + 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, SchemeUserHostPath) +{ + auto uri = ddwaf::uri_parse("http://us@authority/path"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + 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, SchemeUserHostFragment) +{ + auto uri = ddwaf::uri_parse("http://u@authority#f"); + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http"); + 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, MalformedAuthority) +{ + ASSERT_FALSE(ddwaf::uri_parse("http://host:::asdnsk")); + ASSERT_FALSE(ddwaf::uri_parse("http://host@@@something")); + ASSERT_FALSE(ddwaf::uri_parse("http://host:port")); + ASSERT_FALSE(ddwaf::uri_parse("http://us@er@host:1234")); + ASSERT_FALSE(ddwaf::uri_parse("http://user:pa]ssword@host:")); + ASSERT_FALSE(ddwaf::uri_parse("http://[1:1::1:1")); + ASSERT_FALSE(ddwaf::uri_parse("http://auth[ority")); + // ASSERT_FALSE(ddwaf::uri_parse("http://something@:123")); +} + +TEST(TestURI, Complete) +{ + { + auto uri = ddwaf::uri_parse( + "http+s.i-a://user@hello.com:1929/path/to/nowhere?query=none#fragment"); + + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "http+s.i-a"); + EXPECT_STRV(uri->authority.host, "hello.com"); + EXPECT_STRV(uri->authority.userinfo, "user"); + EXPECT_STRV(uri->authority.port, "1929"); + EXPECT_STRV(uri->authority.raw, "user@hello.com:1929"); + EXPECT_STRV(uri->scheme_and_authority, "http+s.i-a://user@hello.com:1929"); + EXPECT_STRV(uri->path, "/path/to/nowhere"); + EXPECT_STRV( + uri->raw, "http+s.i-a://user@hello.com:1929/path/to/nowhere?query=none#fragment"); + } + + { + auto uri = ddwaf::uri_parse("s://u@h:1/p?q#f"); + + ASSERT_TRUE(uri); + EXPECT_STRV(uri->scheme, "s"); + EXPECT_STRV(uri->authority.host, "h"); + EXPECT_STRV(uri->authority.userinfo, "u"); + EXPECT_STRV(uri->authority.port, "1"); + EXPECT_STRV(uri->scheme_and_authority, "s://u@h:1"); + EXPECT_STRV(uri->path, "/p"); + EXPECT_STRV(uri->authority.raw, "u@h:1"); + } +} + +} // namespace diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 084f61dca..70f519add 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -10,9 +10,9 @@ set(INSTALL_DIR ${CMAKE_BINARY_DIR}/third_party) file(MAKE_DIRECTORY ${INSTALL_DIR}/include) if(NOT MSVC) - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-unused-parameter") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -Wno-unused-parameter") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wno-unused-parameter") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-unused-parameter -Wno-shadow") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -Wno-unused-parameter -Wno-shadow") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wno-unused-parameter -Wno-shadow") endif() set(RAPIDJSON_COMMIT 22a62fcc2c2fa2418f5d358cdf65c1df73b418ae) diff --git a/validator/tests/rules/structured/003_ssrf_basic_run_match.yaml b/validator/tests/rules/structured/003_ssrf_basic_run_match.yaml new file mode 100644 index 000000000..3c238e0a6 --- /dev/null +++ b/validator/tests/rules/structured/003_ssrf_basic_run_match.yaml @@ -0,0 +1,28 @@ +{ + name: "Basic run with match", + runs: [ + { + persistent-input: { + server.io.net.url: "https://169.254.169.254/somewhere/in/the/app", + server.request.query: [ "169.254.169.254" ] + }, + rules: [ + { + "rsp-930-002": [ + { + resource: { + address: "server.io.net.url", + value: "https://169.254.169.254/somewhere/in/the/app" + }, + params: { + address: "server.request.query", + value: "169.254.169.254" + } + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/structured/ruleset.yaml b/validator/tests/rules/structured/ruleset.yaml index 9842bfcd2..4e55e3b65 100644 --- a/validator/tests/rules/structured/ruleset.yaml +++ b/validator/tests/rules/structured/ruleset.yaml @@ -20,3 +20,21 @@ rules: - address: graphql.server.all_resolvers - address: graphql.server.resolver operator: lfi_detector + - id: rsp-930-002 + name: SSRF Exploit detection + tags: + type: ssrf + category: exploit_detection + module: rasp + conditions: + - parameters: + resource: + - address: server.io.net.url + 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: ssrf_detector