contrast-agent 3.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.clang-format +5 -0
- data/.dockerignore +10 -0
- data/.gitignore +58 -0
- data/.gitmodules +6 -0
- data/.rspec +6 -0
- data/.simplecov +4 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +12 -0
- data/Rakefile +15 -0
- data/exe/contrast_service +29 -0
- data/ext/build_funchook.rb +48 -0
- data/ext/cs__assess_active_record_named/cs__active_record_named.c +47 -0
- data/ext/cs__assess_active_record_named/cs__active_record_named.h +10 -0
- data/ext/cs__assess_active_record_named/extconf.rb +2 -0
- data/ext/cs__assess_array/cs__assess_array.c +38 -0
- data/ext/cs__assess_array/cs__assess_array.h +9 -0
- data/ext/cs__assess_array/extconf.rb +2 -0
- data/ext/cs__assess_basic_object/cs__assess_basic_object.c +50 -0
- data/ext/cs__assess_basic_object/cs__assess_basic_object.h +17 -0
- data/ext/cs__assess_basic_object/extconf.rb +2 -0
- data/ext/cs__assess_fiber_track/cs__assess_fiber_track.c +86 -0
- data/ext/cs__assess_fiber_track/cs__assess_fiber_track.h +34 -0
- data/ext/cs__assess_fiber_track/extconf.rb +2 -0
- data/ext/cs__assess_hash/cs__assess_hash.c +64 -0
- data/ext/cs__assess_hash/cs__assess_hash.h +24 -0
- data/ext/cs__assess_hash/extconf.rb +2 -0
- data/ext/cs__assess_kernel/cs__assess_kernel.c +36 -0
- data/ext/cs__assess_kernel/cs__assess_kernel.h +10 -0
- data/ext/cs__assess_kernel/extconf.rb +2 -0
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +47 -0
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +18 -0
- data/ext/cs__assess_marshal_module/extconf.rb +2 -0
- data/ext/cs__assess_module/cs__assess_module.c +78 -0
- data/ext/cs__assess_module/cs__assess_module.h +25 -0
- data/ext/cs__assess_module/extconf.rb +2 -0
- data/ext/cs__assess_regexp/cs__assess_regexp.c +48 -0
- data/ext/cs__assess_regexp/cs__assess_regexp.h +22 -0
- data/ext/cs__assess_regexp/extconf.rb +2 -0
- data/ext/cs__assess_regexp_track/cs__assess_regexp_track.c +63 -0
- data/ext/cs__assess_regexp_track/cs__assess_regexp_track.h +29 -0
- data/ext/cs__assess_regexp_track/extconf.rb +2 -0
- data/ext/cs__assess_string/cs__assess_string.c +38 -0
- data/ext/cs__assess_string/cs__assess_string.h +19 -0
- data/ext/cs__assess_string/extconf.rb +2 -0
- data/ext/cs__assess_string_interpolation26/cs__assess_string_interpolation26.c +31 -0
- data/ext/cs__assess_string_interpolation26/cs__assess_string_interpolation26.h +13 -0
- data/ext/cs__assess_string_interpolation26/extconf.rb +2 -0
- data/ext/cs__common/cs__common.c +60 -0
- data/ext/cs__common/cs__common.h +28 -0
- data/ext/cs__common/extconf.rb +20 -0
- data/ext/cs__contrast_patch/cs__contrast_patch.c +445 -0
- data/ext/cs__contrast_patch/cs__contrast_patch.h +196 -0
- data/ext/cs__contrast_patch/extconf.rb +2 -0
- data/ext/cs__protect_kernel/cs__protect_kernel.c +37 -0
- data/ext/cs__protect_kernel/cs__protect_kernel.h +11 -0
- data/ext/cs__protect_kernel/extconf.rb +2 -0
- data/ext/cs__scope/cs__scope.c +96 -0
- data/ext/cs__scope/cs__scope.h +33 -0
- data/ext/cs__scope/extconf.rb +2 -0
- data/ext/extconf_common.rb +49 -0
- data/funchook/LICENSE +360 -0
- data/funchook/Makefile +29 -0
- data/funchook/Makefile.in +29 -0
- data/funchook/README.md +121 -0
- data/funchook/appveyor.yml +42 -0
- data/funchook/autogen.sh +3 -0
- data/funchook/autom4te.cache/output.0 +4976 -0
- data/funchook/autom4te.cache/requests +78 -0
- data/funchook/autom4te.cache/traces.0 +364 -0
- data/funchook/config.guess +1530 -0
- data/funchook/config.log +490 -0
- data/funchook/config.status +1016 -0
- data/funchook/config.sub +1773 -0
- data/funchook/configure +4976 -0
- data/funchook/configure.ac +59 -0
- data/funchook/distorm/COPYING +26 -0
- data/funchook/distorm/MANIFEST +25 -0
- data/funchook/distorm/MANIFEST.in +4 -0
- data/funchook/distorm/README.md +12 -0
- data/funchook/distorm/disOps/disOps.py +795 -0
- data/funchook/distorm/disOps/x86db.py +404 -0
- data/funchook/distorm/disOps/x86header.py +247 -0
- data/funchook/distorm/disOps/x86sets.py +1664 -0
- data/funchook/distorm/examples/cs/TestdiStorm/Program.cs +79 -0
- data/funchook/distorm/examples/cs/TestdiStorm/Properties/AssemblyInfo.cs +36 -0
- data/funchook/distorm/examples/cs/TestdiStorm/TestdiStorm.csproj +69 -0
- data/funchook/distorm/examples/cs/distorm-net.sln +26 -0
- data/funchook/distorm/examples/cs/distorm-net/CodeInfo.cs +23 -0
- data/funchook/distorm/examples/cs/distorm-net/DecodedInst.cs +15 -0
- data/funchook/distorm/examples/cs/distorm-net/DecodedResult.cs +14 -0
- data/funchook/distorm/examples/cs/distorm-net/DecomposedInst.cs +36 -0
- data/funchook/distorm/examples/cs/distorm-net/DecomposedResult.cs +14 -0
- data/funchook/distorm/examples/cs/distorm-net/Opcodes.cs +1268 -0
- data/funchook/distorm/examples/cs/distorm-net/Opcodes.tt +37 -0
- data/funchook/distorm/examples/cs/distorm-net/Operand.cs +25 -0
- data/funchook/distorm/examples/cs/distorm-net/Properties/AssemblyInfo.cs +36 -0
- data/funchook/distorm/examples/cs/distorm-net/diStorm3.cs +411 -0
- data/funchook/distorm/examples/cs/distorm-net/distorm-net.csproj +80 -0
- data/funchook/distorm/examples/cs/readme +3 -0
- data/funchook/distorm/examples/ddk/README +48 -0
- data/funchook/distorm/examples/ddk/distorm.ini +11 -0
- data/funchook/distorm/examples/ddk/dummy.c +15 -0
- data/funchook/distorm/examples/ddk/main.c +91 -0
- data/funchook/distorm/examples/ddk/makefile +1 -0
- data/funchook/distorm/examples/ddk/sources +10 -0
- data/funchook/distorm/examples/java/Makefile +23 -0
- data/funchook/distorm/examples/java/distorm/src/Main.java +43 -0
- data/funchook/distorm/examples/java/distorm/src/diStorm3/CodeInfo.java +27 -0
- data/funchook/distorm/examples/java/distorm/src/diStorm3/DecodedInst.java +32 -0
- data/funchook/distorm/examples/java/distorm/src/diStorm3/DecodedResult.java +11 -0
- data/funchook/distorm/examples/java/distorm/src/diStorm3/DecomposedInst.java +89 -0
- data/funchook/distorm/examples/java/distorm/src/diStorm3/DecomposedResult.java +11 -0
- data/funchook/distorm/examples/java/distorm/src/diStorm3/OpcodeEnum.java +131 -0
- data/funchook/distorm/examples/java/distorm/src/diStorm3/Opcodes.java +1123 -0
- data/funchook/distorm/examples/java/distorm/src/diStorm3/Operand.java +24 -0
- data/funchook/distorm/examples/java/distorm/src/diStorm3/distorm3.java +41 -0
- data/funchook/distorm/examples/java/jdistorm.c +405 -0
- data/funchook/distorm/examples/java/jdistorm.h +40 -0
- data/funchook/distorm/examples/java/jdistorm.sln +20 -0
- data/funchook/distorm/examples/java/jdistorm.vcproj +208 -0
- data/funchook/distorm/examples/linux/Makefile +15 -0
- data/funchook/distorm/examples/linux/main.c +181 -0
- data/funchook/distorm/examples/tests/Makefile +15 -0
- data/funchook/distorm/examples/tests/main.cpp +42 -0
- data/funchook/distorm/examples/tests/main.py +66 -0
- data/funchook/distorm/examples/tests/test_distorm3.py +1672 -0
- data/funchook/distorm/examples/tests/tests.sln +20 -0
- data/funchook/distorm/examples/tests/tests.vcxproj +82 -0
- data/funchook/distorm/examples/tests/tests.vcxproj.filters +22 -0
- data/funchook/distorm/examples/win32/disasm.sln +25 -0
- data/funchook/distorm/examples/win32/disasm.vcxproj +201 -0
- data/funchook/distorm/examples/win32/disasm.vcxproj.filters +14 -0
- data/funchook/distorm/examples/win32/main.cpp +163 -0
- data/funchook/distorm/include/distorm.h +482 -0
- data/funchook/distorm/include/mnemonics.h +301 -0
- data/funchook/distorm/make/linux/Makefile +28 -0
- data/funchook/distorm/make/mac/Makefile +24 -0
- data/funchook/distorm/make/win32/cdistorm.vcxproj +239 -0
- data/funchook/distorm/make/win32/cdistorm.vcxproj.filters +80 -0
- data/funchook/distorm/make/win32/distorm.sln +25 -0
- data/funchook/distorm/make/win32/resource.h +14 -0
- data/funchook/distorm/make/win32/resource.rc +99 -0
- data/funchook/distorm/python/distorm3/__init__.py +957 -0
- data/funchook/distorm/python/distorm3/sample.py +51 -0
- data/funchook/distorm/setup.cfg +10 -0
- data/funchook/distorm/setup.py +266 -0
- data/funchook/distorm/src/config.h +169 -0
- data/funchook/distorm/src/decoder.c +641 -0
- data/funchook/distorm/src/decoder.h +33 -0
- data/funchook/distorm/src/distorm.c +413 -0
- data/funchook/distorm/src/instructions.c +597 -0
- data/funchook/distorm/src/instructions.h +463 -0
- data/funchook/distorm/src/insts.c +7939 -0
- data/funchook/distorm/src/insts.h +64 -0
- data/funchook/distorm/src/mnemonics.c +284 -0
- data/funchook/distorm/src/operands.c +1290 -0
- data/funchook/distorm/src/operands.h +28 -0
- data/funchook/distorm/src/prefix.c +368 -0
- data/funchook/distorm/src/prefix.h +64 -0
- data/funchook/distorm/src/textdefs.c +172 -0
- data/funchook/distorm/src/textdefs.h +57 -0
- data/funchook/distorm/src/wstring.c +47 -0
- data/funchook/distorm/src/wstring.h +35 -0
- data/funchook/distorm/src/x86defs.h +82 -0
- data/funchook/include/funchook.h +123 -0
- data/funchook/install-sh +527 -0
- data/funchook/src/Makefile +70 -0
- data/funchook/src/Makefile.in +70 -0
- data/funchook/src/__strerror.h +109 -0
- data/funchook/src/config.h +101 -0
- data/funchook/src/config.h.in +100 -0
- data/funchook/src/decoder.o +0 -0
- data/funchook/src/distorm.o +0 -0
- data/funchook/src/funchook.c +440 -0
- data/funchook/src/funchook.o +0 -0
- data/funchook/src/funchook_internal.h +155 -0
- data/funchook/src/funchook_io.c +182 -0
- data/funchook/src/funchook_io.h +64 -0
- data/funchook/src/funchook_io.o +0 -0
- data/funchook/src/funchook_syscall.S +134 -0
- data/funchook/src/funchook_syscall.o +0 -0
- data/funchook/src/funchook_unix.c +480 -0
- data/funchook/src/funchook_unix.o +0 -0
- data/funchook/src/funchook_windows.c +397 -0
- data/funchook/src/funchook_x86.c +622 -0
- data/funchook/src/funchook_x86.o +0 -0
- data/funchook/src/instructions.o +0 -0
- data/funchook/src/insts.o +0 -0
- data/funchook/src/libfunchook.so +0 -0
- data/funchook/src/mnemonics.o +0 -0
- data/funchook/src/operands.o +0 -0
- data/funchook/src/os_func.c +115 -0
- data/funchook/src/os_func.h +75 -0
- data/funchook/src/os_func.o +0 -0
- data/funchook/src/os_func_unix.c +94 -0
- data/funchook/src/os_func_unix.o +0 -0
- data/funchook/src/os_func_windows.c +32 -0
- data/funchook/src/prefix.o +0 -0
- data/funchook/src/printf_base.c +1688 -0
- data/funchook/src/printf_base.h +46 -0
- data/funchook/src/printf_base.o +0 -0
- data/funchook/src/textdefs.o +0 -0
- data/funchook/src/wstring.o +0 -0
- data/funchook/test/Makefile +43 -0
- data/funchook/test/Makefile.in +43 -0
- data/funchook/test/funchook_test +0 -0
- data/funchook/test/libfunchook_test.c +25 -0
- data/funchook/test/libfunchook_test.so +0 -0
- data/funchook/test/libfunchook_test2.c +18 -0
- data/funchook/test/suffix.list +600 -0
- data/funchook/test/test_main.c +430 -0
- data/funchook/test/test_main.o +0 -0
- data/funchook/test/x86_64_test.S +10 -0
- data/funchook/test/x86_64_test.o +0 -0
- data/funchook/test/x86_test.S +339 -0
- data/funchook/win32/config.h +1 -0
- data/funchook/win32/funchook.sln +52 -0
- data/funchook/win32/funchook.vcxproj +188 -0
- data/funchook/win32/funchook.vcxproj.filters +84 -0
- data/funchook/win32/funchook_test.vcxproj +170 -0
- data/funchook/win32/funchook_test.vcxproj.filters +22 -0
- data/funchook/win32/funchook_test_dll.vcxproj +184 -0
- data/funchook/win32/funchook_test_dll.vcxproj.filters +30 -0
- data/funchook/win32/funchook_test_exe.def +3 -0
- data/lib/contrast-agent.rb +8 -0
- data/lib/contrast.rb +57 -0
- data/lib/contrast/agent.rb +80 -0
- data/lib/contrast/agent/assess.rb +45 -0
- data/lib/contrast/agent/assess/adjusted_span.rb +25 -0
- data/lib/contrast/agent/assess/class_reverter.rb +82 -0
- data/lib/contrast/agent/assess/contrast_event.rb +398 -0
- data/lib/contrast/agent/assess/frozen_properties.rb +41 -0
- data/lib/contrast/agent/assess/insulator.rb +53 -0
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +78 -0
- data/lib/contrast/agent/assess/policy/patcher.rb +85 -0
- data/lib/contrast/agent/assess/policy/policy.rb +116 -0
- data/lib/contrast/agent/assess/policy/policy_node.rb +289 -0
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +44 -0
- data/lib/contrast/agent/assess/policy/preshift.rb +94 -0
- data/lib/contrast/agent/assess/policy/propagation_method.rb +260 -0
- data/lib/contrast/agent/assess/policy/propagation_node.rb +127 -0
- data/lib/contrast/agent/assess/policy/propagator.rb +35 -0
- data/lib/contrast/agent/assess/policy/propagator/append.rb +54 -0
- data/lib/contrast/agent/assess/policy/propagator/base.rb +37 -0
- data/lib/contrast/agent/assess/policy/propagator/center.rb +73 -0
- data/lib/contrast/agent/assess/policy/propagator/custom.rb +36 -0
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +62 -0
- data/lib/contrast/agent/assess/policy/propagator/insert.rb +55 -0
- data/lib/contrast/agent/assess/policy/propagator/keep.rb +26 -0
- data/lib/contrast/agent/assess/policy/propagator/next.rb +42 -0
- data/lib/contrast/agent/assess/policy/propagator/prepend.rb +50 -0
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +76 -0
- data/lib/contrast/agent/assess/policy/propagator/replace.rb +27 -0
- data/lib/contrast/agent/assess/policy/propagator/reverse.rb +38 -0
- data/lib/contrast/agent/assess/policy/propagator/select.rb +86 -0
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +60 -0
- data/lib/contrast/agent/assess/policy/propagator/split.rb +49 -0
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +169 -0
- data/lib/contrast/agent/assess/policy/propagator/trim.rb +81 -0
- data/lib/contrast/agent/assess/policy/rewriter_patch.rb +79 -0
- data/lib/contrast/agent/assess/policy/source_method.rb +209 -0
- data/lib/contrast/agent/assess/policy/source_node.rb +62 -0
- data/lib/contrast/agent/assess/policy/trigger_method.rb +209 -0
- data/lib/contrast/agent/assess/policy/trigger_node.rb +198 -0
- data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +77 -0
- data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +31 -0
- data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +40 -0
- data/lib/contrast/agent/assess/properties.rb +392 -0
- data/lib/contrast/agent/assess/rule.rb +18 -0
- data/lib/contrast/agent/assess/rule/base.rb +72 -0
- data/lib/contrast/agent/assess/rule/csrf.rb +66 -0
- data/lib/contrast/agent/assess/rule/csrf/csrf_action.rb +28 -0
- data/lib/contrast/agent/assess/rule/csrf/csrf_applicator.rb +69 -0
- data/lib/contrast/agent/assess/rule/csrf/csrf_watcher.rb +132 -0
- data/lib/contrast/agent/assess/rule/provider.rb +21 -0
- data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +62 -0
- data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +73 -0
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +121 -0
- data/lib/contrast/agent/assess/rule/redos.rb +68 -0
- data/lib/contrast/agent/assess/rule/response_scanning_rule.rb +47 -0
- data/lib/contrast/agent/assess/rule/response_watcher.rb +36 -0
- data/lib/contrast/agent/assess/rule/watcher.rb +36 -0
- data/lib/contrast/agent/assess/tag.rb +151 -0
- data/lib/contrast/agent/at_exit_hook.rb +33 -0
- data/lib/contrast/agent/class_reopener.rb +195 -0
- data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +26 -0
- data/lib/contrast/agent/deadzone/policy/policy.rb +57 -0
- data/lib/contrast/agent/disable_reaction.rb +24 -0
- data/lib/contrast/agent/exclusion_matcher.rb +190 -0
- data/lib/contrast/agent/feature_state.rb +379 -0
- data/lib/contrast/agent/inventory/policy/policy.rb +32 -0
- data/lib/contrast/agent/inventory/policy/trigger_node.rb +22 -0
- data/lib/contrast/agent/logger_manager.rb +116 -0
- data/lib/contrast/agent/middleware.rb +352 -0
- data/lib/contrast/agent/module_data.rb +16 -0
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +37 -0
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +58 -0
- data/lib/contrast/agent/patching/policy/method_policy.rb +94 -0
- data/lib/contrast/agent/patching/policy/module_policy.rb +116 -0
- data/lib/contrast/agent/patching/policy/patch.rb +312 -0
- data/lib/contrast/agent/patching/policy/patch_status.rb +192 -0
- data/lib/contrast/agent/patching/policy/patcher.rb +310 -0
- data/lib/contrast/agent/patching/policy/policy.rb +138 -0
- data/lib/contrast/agent/patching/policy/policy_node.rb +80 -0
- data/lib/contrast/agent/patching/policy/policy_unpatcher.rb +28 -0
- data/lib/contrast/agent/patching/policy/trigger_node.rb +81 -0
- data/lib/contrast/agent/protect/policy/policy.rb +37 -0
- data/lib/contrast/agent/protect/policy/trigger_node.rb +23 -0
- data/lib/contrast/agent/protect/rule.rb +58 -0
- data/lib/contrast/agent/protect/rule/base.rb +300 -0
- data/lib/contrast/agent/protect/rule/base_service.rb +88 -0
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +156 -0
- data/lib/contrast/agent/protect/rule/csrf.rb +118 -0
- data/lib/contrast/agent/protect/rule/csrf/csrf_evaluator.rb +103 -0
- data/lib/contrast/agent/protect/rule/csrf/csrf_token_injector.rb +85 -0
- data/lib/contrast/agent/protect/rule/default_scanner.rb +300 -0
- data/lib/contrast/agent/protect/rule/deserialization.rb +193 -0
- data/lib/contrast/agent/protect/rule/http_method_tampering.rb +80 -0
- data/lib/contrast/agent/protect/rule/no_sqli.rb +101 -0
- data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +40 -0
- data/lib/contrast/agent/protect/rule/path_traversal.rb +143 -0
- data/lib/contrast/agent/protect/rule/sqli.rb +101 -0
- data/lib/contrast/agent/protect/rule/sqli/default_sql_scanner.rb +16 -0
- data/lib/contrast/agent/protect/rule/sqli/mysql_sql_scanner.rb +38 -0
- data/lib/contrast/agent/protect/rule/sqli/postgres_sql_scanner.rb +22 -0
- data/lib/contrast/agent/protect/rule/sqli/sqlite_sql_scanner.rb +19 -0
- data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +20 -0
- data/lib/contrast/agent/protect/rule/xss.rb +24 -0
- data/lib/contrast/agent/protect/rule/xxe.rb +120 -0
- data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +82 -0
- data/lib/contrast/agent/railtie.rb +30 -0
- data/lib/contrast/agent/reaction_processor.rb +47 -0
- data/lib/contrast/agent/request.rb +493 -0
- data/lib/contrast/agent/request_context.rb +225 -0
- data/lib/contrast/agent/require_state.rb +61 -0
- data/lib/contrast/agent/response.rb +215 -0
- data/lib/contrast/agent/rewriter.rb +244 -0
- data/lib/contrast/agent/scope.rb +28 -0
- data/lib/contrast/agent/service_heartbeat.rb +37 -0
- data/lib/contrast/agent/settings_state.rb +148 -0
- data/lib/contrast/agent/socket_client.rb +125 -0
- data/lib/contrast/agent/thread.rb +26 -0
- data/lib/contrast/agent/tracepoint_hook.rb +51 -0
- data/lib/contrast/agent/version.rb +8 -0
- data/lib/contrast/api.rb +17 -0
- data/lib/contrast/api/.gitkeep +0 -0
- data/lib/contrast/api/connection_status.rb +49 -0
- data/lib/contrast/api/socket.rb +43 -0
- data/lib/contrast/api/speedracer.rb +206 -0
- data/lib/contrast/api/tcp_socket.rb +31 -0
- data/lib/contrast/api/unix_socket.rb +25 -0
- data/lib/contrast/common_agent_configuration.rb +86 -0
- data/lib/contrast/components/agent.rb +85 -0
- data/lib/contrast/components/app_context.rb +188 -0
- data/lib/contrast/components/assess.rb +67 -0
- data/lib/contrast/components/config.rb +135 -0
- data/lib/contrast/components/contrast_service.rb +113 -0
- data/lib/contrast/components/heap_dump.rb +34 -0
- data/lib/contrast/components/interface.rb +178 -0
- data/lib/contrast/components/inventory.rb +23 -0
- data/lib/contrast/components/logger.rb +92 -0
- data/lib/contrast/components/protect.rb +38 -0
- data/lib/contrast/components/sampling.rb +41 -0
- data/lib/contrast/components/scope.rb +106 -0
- data/lib/contrast/components/settings.rb +140 -0
- data/lib/contrast/config.rb +33 -0
- data/lib/contrast/config/agent_configuration.rb +24 -0
- data/lib/contrast/config/application_configuration.rb +27 -0
- data/lib/contrast/config/assess_configuration.rb +22 -0
- data/lib/contrast/config/assess_rules_configuration.rb +18 -0
- data/lib/contrast/config/base_configuration.rb +105 -0
- data/lib/contrast/config/default_value.rb +16 -0
- data/lib/contrast/config/exception_configuration.rb +21 -0
- data/lib/contrast/config/heap_dump_configuration.rb +23 -0
- data/lib/contrast/config/inventory_configuration.rb +20 -0
- data/lib/contrast/config/logger_configuration.rb +20 -0
- data/lib/contrast/config/protect_configuration.rb +20 -0
- data/lib/contrast/config/protect_rule_configuration.rb +37 -0
- data/lib/contrast/config/protect_rules_configuration.rb +30 -0
- data/lib/contrast/config/root_configuration.rb +26 -0
- data/lib/contrast/config/ruby_configuration.rb +39 -0
- data/lib/contrast/config/sampling_configuration.rb +22 -0
- data/lib/contrast/config/server_configuration.rb +23 -0
- data/lib/contrast/config/service_configuration.rb +22 -0
- data/lib/contrast/configuration.rb +214 -0
- data/lib/contrast/core_extensions/assess.rb +51 -0
- data/lib/contrast/core_extensions/assess/array.rb +58 -0
- data/lib/contrast/core_extensions/assess/assess_extension.rb +145 -0
- data/lib/contrast/core_extensions/assess/basic_object.rb +15 -0
- data/lib/contrast/core_extensions/assess/erb.rb +42 -0
- data/lib/contrast/core_extensions/assess/exec_trigger.rb +48 -0
- data/lib/contrast/core_extensions/assess/fiber.rb +125 -0
- data/lib/contrast/core_extensions/assess/hash.rb +22 -0
- data/lib/contrast/core_extensions/assess/kernel.rb +95 -0
- data/lib/contrast/core_extensions/assess/module.rb +14 -0
- data/lib/contrast/core_extensions/assess/regexp.rb +206 -0
- data/lib/contrast/core_extensions/assess/string.rb +75 -0
- data/lib/contrast/core_extensions/assess/tilt_template_trigger.rb +73 -0
- data/lib/contrast/core_extensions/delegator.rb +14 -0
- data/lib/contrast/core_extensions/eval_trigger.rb +52 -0
- data/lib/contrast/core_extensions/inventory.rb +22 -0
- data/lib/contrast/core_extensions/inventory/datastores.rb +37 -0
- data/lib/contrast/core_extensions/module.rb +42 -0
- data/lib/contrast/core_extensions/object.rb +27 -0
- data/lib/contrast/core_extensions/protect.rb +20 -0
- data/lib/contrast/core_extensions/protect/applies_command_injection_rule.rb +70 -0
- data/lib/contrast/core_extensions/protect/applies_deserialization_rule.rb +58 -0
- data/lib/contrast/core_extensions/protect/applies_no_sqli_rule.rb +81 -0
- data/lib/contrast/core_extensions/protect/applies_path_traversal_rule.rb +119 -0
- data/lib/contrast/core_extensions/protect/applies_sqli_rule.rb +63 -0
- data/lib/contrast/core_extensions/protect/applies_xxe_rule.rb +141 -0
- data/lib/contrast/core_extensions/protect/kernel.rb +30 -0
- data/lib/contrast/core_extensions/protect/psych.rb +7 -0
- data/lib/contrast/core_extensions/thread.rb +31 -0
- data/lib/contrast/internal_exception.rb +8 -0
- data/lib/contrast/rails_extensions/assess/action_controller_inheritance.rb +48 -0
- data/lib/contrast/rails_extensions/assess/active_record.rb +32 -0
- data/lib/contrast/rails_extensions/assess/active_record_named.rb +61 -0
- data/lib/contrast/rails_extensions/assess/configuration.rb +26 -0
- data/lib/contrast/rails_extensions/buffer.rb +30 -0
- data/lib/contrast/rails_extensions/rack.rb +45 -0
- data/lib/contrast/security_exception.rb +14 -0
- data/lib/contrast/sinatra_extensions/assess/cookie.rb +26 -0
- data/lib/contrast/sinatra_extensions/inventory/sinatra_base.rb +59 -0
- data/lib/contrast/tasks/service.rb +95 -0
- data/lib/contrast/utils/assess/sampling_util.rb +96 -0
- data/lib/contrast/utils/assess/tracking_util.rb +39 -0
- data/lib/contrast/utils/boolean_util.rb +33 -0
- data/lib/contrast/utils/cache.rb +69 -0
- data/lib/contrast/utils/class_util.rb +58 -0
- data/lib/contrast/utils/comment_range.rb +19 -0
- data/lib/contrast/utils/data_store_util.rb +23 -0
- data/lib/contrast/utils/duck_utils.rb +58 -0
- data/lib/contrast/utils/env_configuration_item.rb +52 -0
- data/lib/contrast/utils/environment_util.rb +152 -0
- data/lib/contrast/utils/freeze_util.rb +36 -0
- data/lib/contrast/utils/gemfile_reader.rb +191 -0
- data/lib/contrast/utils/hash_digest.rb +148 -0
- data/lib/contrast/utils/heap_dump_util.rb +113 -0
- data/lib/contrast/utils/invalid_configuration_util.rb +88 -0
- data/lib/contrast/utils/inventory_util.rb +126 -0
- data/lib/contrast/utils/io_util.rb +61 -0
- data/lib/contrast/utils/object_share.rb +117 -0
- data/lib/contrast/utils/operating_environment.rb +38 -0
- data/lib/contrast/utils/os.rb +49 -0
- data/lib/contrast/utils/path_util.rb +151 -0
- data/lib/contrast/utils/performs_logging.rb +152 -0
- data/lib/contrast/utils/preflight_util.rb +13 -0
- data/lib/contrast/utils/prevent_serialization.rb +52 -0
- data/lib/contrast/utils/rack_assess_session_cookie.rb +104 -0
- data/lib/contrast/utils/rails_assess_configuration.rb +95 -0
- data/lib/contrast/utils/random_util.rb +22 -0
- data/lib/contrast/utils/resource_loader.rb +23 -0
- data/lib/contrast/utils/ruby_ast_rewriter.rb +74 -0
- data/lib/contrast/utils/scope_util.rb +99 -0
- data/lib/contrast/utils/service_response_util.rb +116 -0
- data/lib/contrast/utils/service_sender_util.rb +98 -0
- data/lib/contrast/utils/sha256_builder.rb +69 -0
- data/lib/contrast/utils/sinatra_helper.rb +49 -0
- data/lib/contrast/utils/stack_trace_utils.rb +209 -0
- data/lib/contrast/utils/string_utils.rb +72 -0
- data/lib/contrast/utils/tag_util.rb +139 -0
- data/lib/contrast/utils/thread_tracker.rb +54 -0
- data/lib/contrast/utils/timer.rb +78 -0
- data/resources/assess/policy.json +1673 -0
- data/resources/csrf/inject.js +44 -0
- data/resources/deadzone/policy.json +55 -0
- data/resources/factory-bot-spec/spec_helper.rb +30 -0
- data/resources/inventory/policy.json +110 -0
- data/resources/protect/policy.json +417 -0
- data/resources/rubocops/kernel/catch_cop.rb +37 -0
- data/resources/rubocops/kernel/require_cop.rb +37 -0
- data/resources/rubocops/kernel/require_relative_cop.rb +33 -0
- data/resources/rubocops/module/autoload_cop.rb +37 -0
- data/resources/rubocops/module/const_defined_cop.rb +37 -0
- data/resources/rubocops/module/const_get_cop.rb +37 -0
- data/resources/rubocops/module/const_set_cop.rb +37 -0
- data/resources/rubocops/module/constants_cop.rb +37 -0
- data/resources/rubocops/module/name_cop.rb +37 -0
- data/resources/rubocops/object/class_cop.rb +37 -0
- data/resources/rubocops/object/freeze_cop.rb +37 -0
- data/resources/rubocops/object/frozen_cop.rb +37 -0
- data/resources/rubocops/object/is_a_cop.rb +37 -0
- data/resources/rubocops/object/method_cop.rb +37 -0
- data/resources/rubocops/object/respond_to_cop.rb +37 -0
- data/resources/rubocops/object/singleton_class_cop.rb +37 -0
- data/resources/rubocops/regexp/spelling_cop.rb +44 -0
- data/resources/rubocops/thread/new_cop.rb +39 -0
- data/resources/ruby-spec/ancestors_spec.rb +70 -0
- data/resources/ruby-spec/modulo_spec.rb +831 -0
- data/resources/ruby-spec/parameters_spec.rb +261 -0
- data/resources/ruby-spec/ruby_spec_spec_helper.rb +35 -0
- data/resources/test_marker.txt +1 -0
- data/ruby-agent.gemspec +129 -0
- data/service_executables/.gitkeep +0 -0
- data/service_executables/VERSION +1 -0
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +945 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Contrast
|
|
5
|
+
module Agent
|
|
6
|
+
module Assess
|
|
7
|
+
module Policy
|
|
8
|
+
# This class functions to translate our policy.json into an actionable
|
|
9
|
+
# Ruby object, allowing for dynamic patching over hardcoded patching,
|
|
10
|
+
# specifically for those methods which result in the trigger of a
|
|
11
|
+
# vulnerability (indicate points in the application where uncontrolled
|
|
12
|
+
# user input can do damage).
|
|
13
|
+
class TriggerNode < PolicyNode
|
|
14
|
+
JSON_BAD_VALUE = 'bad_value'
|
|
15
|
+
JSON_GOOD_VALUE = 'good_value'
|
|
16
|
+
JSON_DISALLOWED_TAGS = 'disallowed_tags'
|
|
17
|
+
JSON_REQUIRED_TAGS = 'required_tags'
|
|
18
|
+
JSON_NODES = 'nodes'
|
|
19
|
+
JSON_RULE_NAME = 'name'
|
|
20
|
+
JSON_CUSTOM_PATCH = 'custom_patch'
|
|
21
|
+
|
|
22
|
+
attr_reader :rule_id
|
|
23
|
+
attr_accessor :required_tags, :disallowed_tags, :good_value, :bad_value
|
|
24
|
+
|
|
25
|
+
def initialize trigger_hash = {}, rule_hash = {}
|
|
26
|
+
super(trigger_hash)
|
|
27
|
+
good_value = trigger_hash[JSON_GOOD_VALUE]
|
|
28
|
+
bad_value = trigger_hash[JSON_BAD_VALUE]
|
|
29
|
+
@good_value = Regexp.new(good_value, true) if good_value
|
|
30
|
+
@bad_value = Regexp.new(bad_value, true) if bad_value
|
|
31
|
+
@regexp = !@dataflow && (@good_value || @bad_value)
|
|
32
|
+
@custom_patch = trigger_hash.fetch(JSON_CUSTOM_PATCH, false)
|
|
33
|
+
@rule_id = rule_hash.fetch(JSON_RULE_NAME) # raises KeyError exception if not found
|
|
34
|
+
@dataflow = rule_hash.fetch(JSON_DATAFLOW, true)
|
|
35
|
+
@required_tags = populate_tags(rule_hash[JSON_REQUIRED_TAGS])
|
|
36
|
+
@disallowed_tags = populate_disallowed(rule_hash[JSON_DISALLOWED_TAGS])
|
|
37
|
+
@trigger_class = trigger_hash['trigger_class']
|
|
38
|
+
@trigger_method = trigger_hash['trigger_method']
|
|
39
|
+
@trigger_method = @trigger_method.to_sym if @trigger_method
|
|
40
|
+
validate
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
TRIGGER = 'Trigger'
|
|
44
|
+
def node_class
|
|
45
|
+
TRIGGER
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def apply_custom_trigger context, trigger_node, source, object, ret, invoked, *args
|
|
49
|
+
custom_trigger_class.send(@trigger_method, context, trigger_node, source, object, ret, invoked, *args)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def custom_trigger_class
|
|
53
|
+
@_custom_trigger_class ||= Object.cs__const_get(@trigger_class)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def custom_trigger?
|
|
57
|
+
@trigger_class && @trigger_method
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def custom_patch?
|
|
61
|
+
@custom_patch
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def node_type
|
|
65
|
+
:TYPE_METHOD
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def rule_disabled?
|
|
69
|
+
Contrast::Agent::FeatureState.instance.assess_disabled_rules.include?(rule_id)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Indicate if this is a dataflow based trigger, meaning it has a proper
|
|
73
|
+
# synch that requires a tainted source to reach it. If this returns
|
|
74
|
+
# false, this rule is for method validation, ensuring that an insecure
|
|
75
|
+
# method, such as a non-cryptographically secure random, is not invoked
|
|
76
|
+
def dataflow?
|
|
77
|
+
@dataflow
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Indicate if this is a regexp based trigger, meaning it has a proper
|
|
81
|
+
# synch that requires a source to reach it. While this type of rule
|
|
82
|
+
# does not require the source to be tainted, it does validate it with
|
|
83
|
+
# a regular expression to determine if the method is being invoked
|
|
84
|
+
# safely.
|
|
85
|
+
def regexp_rule?
|
|
86
|
+
@regexp
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# the name of the rule, in capital & underscore format
|
|
90
|
+
# used to make it match enum things in TeamServer
|
|
91
|
+
def loud_name
|
|
92
|
+
@_loud_name ||= rule_id.upcase.gsub(Contrast::Utils::ObjectShare::DASH,
|
|
93
|
+
Contrast::Utils::ObjectShare::UNDERSCORE)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Determine if a dataflow rule violation has occurred
|
|
97
|
+
def violated? source
|
|
98
|
+
# if the source isn't tracked, there can't be a violation
|
|
99
|
+
# this condition may not hold true forever, but for now it's
|
|
100
|
+
# a nice optimization
|
|
101
|
+
return false unless source.cs__tracked?
|
|
102
|
+
|
|
103
|
+
# find the ranges that violate the rule (untrusted, etc)
|
|
104
|
+
vulnerable_ranges = find_ranges_by_tag(source.cs__properties, required_tags)
|
|
105
|
+
# if there aren't any vulnerable ranges, nope out
|
|
106
|
+
return false if vulnerable_ranges.empty?
|
|
107
|
+
|
|
108
|
+
# find the ranges that are exempt from the rule
|
|
109
|
+
# (validated, sanitized, etc)
|
|
110
|
+
secure_ranges = find_ranges_by_tag(source.cs__properties, disallowed_tags)
|
|
111
|
+
# if there are vulnerable ranges and no secure, report
|
|
112
|
+
return true if secure_ranges.empty?
|
|
113
|
+
|
|
114
|
+
# figure out if there are any vulnerable ranges that aren't
|
|
115
|
+
# covered by a secure one. if there are, the rule was violated
|
|
116
|
+
!Contrast::Utils::TagUtil.covered?(vulnerable_ranges, secure_ranges)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Standard validation + TS trace version two rules:
|
|
120
|
+
# Must have source
|
|
121
|
+
def validate
|
|
122
|
+
super
|
|
123
|
+
# If this isn't a dataflow rule, it can't have a source
|
|
124
|
+
return unless dataflow?
|
|
125
|
+
raise(ArgumentError, "Trigger #{ id } did not have a proper source. Unable to create.") unless sources&.any?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
# By default, any rule will be triggered if the source
|
|
131
|
+
# of the rule event has an untrusted tag range that is
|
|
132
|
+
# not covered by one of its disallowed tags.
|
|
133
|
+
UNTRUSTED = 'UNTRUSTED'
|
|
134
|
+
def populate_tags required_tags
|
|
135
|
+
return unless dataflow?
|
|
136
|
+
|
|
137
|
+
validate_rule_tags(required_tags)
|
|
138
|
+
@required_tags = required_tags || []
|
|
139
|
+
@required_tags << UNTRUSTED
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
ENCODER_START = 'CUSTOM_ENCODED_'
|
|
143
|
+
VALIDATOR_START = 'CUSTOM_VALIDATED_'
|
|
144
|
+
# If a level 1 rule comes from TeamServer, it will have the
|
|
145
|
+
# tag 'custom-encoder-#{ name }' or 'custom-validator-#{ name }'.
|
|
146
|
+
# All rules should take this into account.
|
|
147
|
+
# Additionally, if something is marked 'limited-chars' it means
|
|
148
|
+
# it has been properly vetted to not contain dangerous input.
|
|
149
|
+
LIMITED_CHARS = 'LIMITED_CHARS'
|
|
150
|
+
CUSTOM_ENCODED = 'CUSTOM_ENCODED'
|
|
151
|
+
CUSTOM_VALIDATED = 'CUSTOM_VALIDATED'
|
|
152
|
+
def populate_disallowed disallowed_tags
|
|
153
|
+
return unless dataflow?
|
|
154
|
+
|
|
155
|
+
validate_rule_tags(disallowed_tags)
|
|
156
|
+
@disallowed_tags = disallowed_tags || []
|
|
157
|
+
@disallowed_tags << LIMITED_CHARS
|
|
158
|
+
@disallowed_tags << CUSTOM_ENCODED
|
|
159
|
+
@disallowed_tags << CUSTOM_VALIDATED
|
|
160
|
+
@disallowed_tags << ENCODER_START + loud_name
|
|
161
|
+
@disallowed_tags << VALIDATOR_START + loud_name
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
ENCODED_MARKER = '_ENCODED'
|
|
165
|
+
def validate_rule_tags tags
|
|
166
|
+
return unless tags
|
|
167
|
+
|
|
168
|
+
tags.each do |tag|
|
|
169
|
+
raise(ArgumentError, "Rule #{ rule_id } had an invalid tag. #{ tag } is not a known value.") unless
|
|
170
|
+
Contrast::Agent::Assess::Policy::PolicyNode::VALID_TAGS.include?(tag) ||
|
|
171
|
+
Contrast::Agent::Assess::Policy::PolicyNode::VALID_SOURCE_TAGS.include?(tag)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def find_ranges_by_tag cs__properties, tags
|
|
176
|
+
ranges = []
|
|
177
|
+
|
|
178
|
+
# if there aren't any all_tags or tags, break early
|
|
179
|
+
return ranges unless cs__properties.tracked?
|
|
180
|
+
return ranges unless tags&.any?
|
|
181
|
+
|
|
182
|
+
# find all tags that match the desired ones
|
|
183
|
+
tags.each do |desired|
|
|
184
|
+
found = cs__properties.fetch_tag(desired)
|
|
185
|
+
next unless found
|
|
186
|
+
|
|
187
|
+
# we need to dup here so that we don't change the tags if target is
|
|
188
|
+
# used in another trace
|
|
189
|
+
ranges = Contrast::Utils::TagUtil.ordered_merge(ranges, found.dup)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
ranges
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Contrast
|
|
5
|
+
module Agent
|
|
6
|
+
module Assess
|
|
7
|
+
module Policy
|
|
8
|
+
module TriggerValidation
|
|
9
|
+
# Validator used to assert a SSRF finding is actually vulnerable
|
|
10
|
+
# before serializing that finding as a DTM to report to the service.
|
|
11
|
+
module SSRFValidator
|
|
12
|
+
SSRF_RULE = 'ssrf'
|
|
13
|
+
URL_PATTERN =
|
|
14
|
+
%r{(?<protocol>http|https|ftp|sftp|telnet|gopher|rtsp|rtsps|ssh|svn)://(?<host>[^/?]+)(?<path>/?[^?]*)(?<query_string>\?.*)?}i.cs__freeze
|
|
15
|
+
# The Net::HTTP class validates host format on instantiation. Since
|
|
16
|
+
# our triggers for that class are on the instance, they already
|
|
17
|
+
# have this validation done for them. We do not need to apply the
|
|
18
|
+
# validation in this case.
|
|
19
|
+
PATH_ONLY_PATCH_MARKER = 'Assess:Trigger:Net::HTTP#'
|
|
20
|
+
|
|
21
|
+
# A finding is valid for SSRF if the source of the trigger event is
|
|
22
|
+
# a valid URL in which the User controls a section prior to the
|
|
23
|
+
# querystring
|
|
24
|
+
# https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/server_side_request_forgery.md
|
|
25
|
+
def self.valid? patcher, _object, _ret, args
|
|
26
|
+
return true unless SSRF_RULE == patcher&.rule_id
|
|
27
|
+
return true if patcher.id.to_s.start_with?(PATH_ONLY_PATCH_MARKER)
|
|
28
|
+
|
|
29
|
+
url = args[0].to_s
|
|
30
|
+
match = url.match(URL_PATTERN)
|
|
31
|
+
return false unless match
|
|
32
|
+
|
|
33
|
+
# It is dangerous for the user to control a section of the URL
|
|
34
|
+
# between the start of the protocol and the beginning of the
|
|
35
|
+
# querystring. If there is no querystring, then the entire URL is
|
|
36
|
+
# dangerous for the User to control.
|
|
37
|
+
start = match.begin(:protocol)
|
|
38
|
+
finish = match.begin(:query_string)
|
|
39
|
+
finish ||= url.length
|
|
40
|
+
|
|
41
|
+
args[0].cs__properties.any_tags_between?(start, finish)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Some SSRF triggers take multiple parameters to build the URL.
|
|
45
|
+
# For the net_http_# sources, if the first parameter is a String,
|
|
46
|
+
# the URL is built by appending the first and second parameters.
|
|
47
|
+
def self.composite? patcher, args
|
|
48
|
+
id = patcher.id.to_s
|
|
49
|
+
return false unless id.start_with?(PATH_ONLY_PATCH_MARKER)
|
|
50
|
+
|
|
51
|
+
args[0].is_a?(String)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.build_single_source args
|
|
55
|
+
return nil unless args[0].cs__respond_to?(:cs__properties)
|
|
56
|
+
|
|
57
|
+
args[0].to_s
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.build_composite_source args
|
|
61
|
+
if args.length > 1
|
|
62
|
+
return nil unless args[0].cs__respond_to?(:cs__properties) ||
|
|
63
|
+
args[1].cs__respond_to?(:cs__properties)
|
|
64
|
+
|
|
65
|
+
args[0] + args[1]
|
|
66
|
+
else
|
|
67
|
+
return nil unless args[0].cs__respond_to?(:cs__properties)
|
|
68
|
+
|
|
69
|
+
args[0].to_s
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
cs__scoped_require 'contrast/agent/assess/policy/trigger_validation/ssrf_validator'
|
|
5
|
+
cs__scoped_require 'contrast/agent/assess/policy/trigger_validation/xss_validator'
|
|
6
|
+
|
|
7
|
+
module Contrast
|
|
8
|
+
module Agent
|
|
9
|
+
module Assess
|
|
10
|
+
module Policy
|
|
11
|
+
# Some of our triggers require transformation or validation prior to
|
|
12
|
+
# reporting in order to account for false positives or other aberrant
|
|
13
|
+
# conditions. This provides a single place from which those validations
|
|
14
|
+
# can be called.
|
|
15
|
+
module TriggerValidation
|
|
16
|
+
VALIDATORS = [
|
|
17
|
+
Contrast::Agent::Assess::Policy::TriggerValidation::SSRFValidator,
|
|
18
|
+
Contrast::Agent::Assess::Policy::TriggerValidation::XSSValidator
|
|
19
|
+
].cs__freeze
|
|
20
|
+
|
|
21
|
+
def self.valid? patcher, object, ret, args
|
|
22
|
+
VALIDATORS.each do |validator|
|
|
23
|
+
return false unless validator.valid?(patcher, object, ret, args)
|
|
24
|
+
end
|
|
25
|
+
true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Contrast
|
|
5
|
+
module Agent
|
|
6
|
+
module Assess
|
|
7
|
+
module Policy
|
|
8
|
+
module TriggerValidation
|
|
9
|
+
# Validator used to assert a Reflected XSS finding is actually
|
|
10
|
+
# vulnerable before serializing that finding as a DTM to report to
|
|
11
|
+
# the service.
|
|
12
|
+
module XSSValidator
|
|
13
|
+
XSS_RULE = 'reflected-xss'
|
|
14
|
+
SAFE_CONTENT_TYPES = %w[
|
|
15
|
+
/csv
|
|
16
|
+
/javascript
|
|
17
|
+
/json
|
|
18
|
+
/pdf
|
|
19
|
+
/x-javascript
|
|
20
|
+
/x-json
|
|
21
|
+
].cs__freeze
|
|
22
|
+
|
|
23
|
+
# A finding is valid for XSS if the response type is not one of
|
|
24
|
+
# those assumed to be safe
|
|
25
|
+
# https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/reflected_xss.md
|
|
26
|
+
def self.valid? patcher, _object, _ret, _args
|
|
27
|
+
return true unless XSS_RULE == patcher&.rule_id
|
|
28
|
+
|
|
29
|
+
content_type = Contrast::Agent::REQUEST_TRACKER.current&.response&.content_type
|
|
30
|
+
return true unless content_type
|
|
31
|
+
|
|
32
|
+
content_type = content_type.downcase
|
|
33
|
+
SAFE_CONTENT_TYPES.none? { |safe_type| content_type.index(safe_type) }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
cs__scoped_require 'set'
|
|
5
|
+
cs__scoped_require 'base64'
|
|
6
|
+
cs__scoped_require 'contrast/utils/prevent_serialization'
|
|
7
|
+
cs__scoped_require 'contrast/utils/tag_util'
|
|
8
|
+
cs__scoped_require 'contrast/agent/assess/contrast_event'
|
|
9
|
+
|
|
10
|
+
module Contrast
|
|
11
|
+
module Agent
|
|
12
|
+
module Assess
|
|
13
|
+
# Properties associated with a tracked String. If String is monkey
|
|
14
|
+
# patched this object is lazily generated on affected Strings.
|
|
15
|
+
#
|
|
16
|
+
# This class acts as a holder for the Assess information we need in order
|
|
17
|
+
# to properly convey the events that lead up to the state of the tracked
|
|
18
|
+
# user input.
|
|
19
|
+
class Properties
|
|
20
|
+
include Contrast::Utils::PreventSerialization
|
|
21
|
+
|
|
22
|
+
# CONTRAST-36937
|
|
23
|
+
# Creating these on Properties is expensive. We want to delay this for
|
|
24
|
+
# as long as possible.
|
|
25
|
+
def properties
|
|
26
|
+
@_properties ||= {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def events
|
|
30
|
+
@_events ||= []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def tracked?
|
|
34
|
+
tags? && tags.any?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def tagged? label
|
|
38
|
+
tags? && tags.key?(label)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def add_properties hash
|
|
42
|
+
return unless hash
|
|
43
|
+
|
|
44
|
+
properties.merge!(hash)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def add_property name, value
|
|
48
|
+
return unless name && value
|
|
49
|
+
|
|
50
|
+
properties[name] = value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def any_tags_between? start, finish
|
|
54
|
+
return false unless tags?
|
|
55
|
+
|
|
56
|
+
range = Contrast::Agent::Assess::Tag.new(start + finish, start)
|
|
57
|
+
tags.each_value do |tag_array|
|
|
58
|
+
return true if tag_array.any? { |tag| tag.overlaps?(range) }
|
|
59
|
+
end
|
|
60
|
+
false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Find all of the ranges that span a given index. This is used
|
|
64
|
+
# in propagation when we need to shift tags about. For instance, in
|
|
65
|
+
# the append method when we need to see if any tag at the end needs
|
|
66
|
+
# to be expanded out to the size of the new String.
|
|
67
|
+
#
|
|
68
|
+
# Note: Tags do not know their key, so this is only the range covered
|
|
69
|
+
def tags_at idx
|
|
70
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags?
|
|
71
|
+
|
|
72
|
+
at = []
|
|
73
|
+
tags.each_value do |tag_array|
|
|
74
|
+
tag_array.each do |tag|
|
|
75
|
+
if tag.covers?(idx)
|
|
76
|
+
at << tag
|
|
77
|
+
elsif tag.above?(idx)
|
|
78
|
+
break
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
at
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# given a range, select all tags in that range the selected tags are
|
|
86
|
+
# shifted such that the start index of the new tag (0) aligns with the
|
|
87
|
+
# given start index in the range
|
|
88
|
+
#
|
|
89
|
+
# current tags: 5-15
|
|
90
|
+
# range : 5-10
|
|
91
|
+
# result : 0-05
|
|
92
|
+
def tags_at_range range
|
|
93
|
+
return Contrast::Utils::ObjectShare::EMPTY_HASH unless tags?
|
|
94
|
+
|
|
95
|
+
at = Hash.new { |h, k| h[k] = [] }
|
|
96
|
+
length = range.stop - range.start
|
|
97
|
+
tags.each_pair do |key, value|
|
|
98
|
+
add = []
|
|
99
|
+
value.each do |tag|
|
|
100
|
+
comparison = tag.compare_range(range.start, range.stop)
|
|
101
|
+
# BELOW and ABOVE are applicable to this check and are removed.
|
|
102
|
+
case comparison
|
|
103
|
+
# part of the tag is being selected
|
|
104
|
+
when Contrast::Agent::Assess::Tag::LOW_SPAN
|
|
105
|
+
add << Contrast::Agent::Assess::Tag.new(length)
|
|
106
|
+
# the tag exists in the requested range, figure out the boundaries
|
|
107
|
+
when Contrast::Agent::Assess::Tag::WITHIN
|
|
108
|
+
start = tag.start_idx - range.start
|
|
109
|
+
finish = length - start
|
|
110
|
+
add << Contrast::Agent::Assess::Tag.new(finish, start)
|
|
111
|
+
# the tag spans the requested range.
|
|
112
|
+
when Contrast::Agent::Assess::Tag::WITHOUT
|
|
113
|
+
add << Contrast::Agent::Assess::Tag.new(length)
|
|
114
|
+
# part of the tag is being selected
|
|
115
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN
|
|
116
|
+
add << Contrast::Agent::Assess::Tag.new(length)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
next if add.empty?
|
|
120
|
+
|
|
121
|
+
at[key] = add
|
|
122
|
+
end
|
|
123
|
+
at
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Given a tag name and range object, add a new tag to this
|
|
127
|
+
# collection. If the given range touches an existing tag,
|
|
128
|
+
# we'll combine the two, adjusting the existing one and
|
|
129
|
+
# dropping this new one.
|
|
130
|
+
def add_tag label, range
|
|
131
|
+
length = range.stop - range.start
|
|
132
|
+
tag = Contrast::Agent::Assess::Tag.new(length, range.start)
|
|
133
|
+
existing = fetch_tag(label)
|
|
134
|
+
tags[label] = Contrast::Utils::TagUtil.ordered_merge(existing, tag)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def set_tags label, tag_ranges
|
|
138
|
+
tags[label] = tag_ranges
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Remove all tags with a given label
|
|
142
|
+
def delete_tags label
|
|
143
|
+
return unless tags?
|
|
144
|
+
|
|
145
|
+
tags.delete(label)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Reset the tag hash
|
|
149
|
+
def clear_tags
|
|
150
|
+
return unless tags?
|
|
151
|
+
|
|
152
|
+
tags.clear
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Returns a list of all current tag labels, most likely to be used for
|
|
156
|
+
# a splat operation
|
|
157
|
+
def tag_keys
|
|
158
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags?
|
|
159
|
+
|
|
160
|
+
tags.keys
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Calls merge to combine touching or overlapping tags
|
|
164
|
+
# Deletes empty tags
|
|
165
|
+
def cleanup_tags
|
|
166
|
+
return unless tags?
|
|
167
|
+
|
|
168
|
+
Contrast::Utils::TagUtil.merge_tags(tags)
|
|
169
|
+
tags.delete_if { |_, value| value.empty? }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# We'll use this as a helper method to retrieve tags from the hash.
|
|
173
|
+
# Because the hash auto-populates an empty array when we try to access
|
|
174
|
+
# a tag in it, we cannot use the [] method without side effect. To get
|
|
175
|
+
# around this, we'll use a fetch work around.
|
|
176
|
+
def fetch_tag label
|
|
177
|
+
return unless tags?
|
|
178
|
+
|
|
179
|
+
tags.fetch(label, nil)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Convert the tags of this object into the TraceTaintRange requried to
|
|
183
|
+
# be sent to the service
|
|
184
|
+
def tags_to_dtm
|
|
185
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags?
|
|
186
|
+
|
|
187
|
+
ranges = []
|
|
188
|
+
tags.each_pair do |key, value|
|
|
189
|
+
next if value.empty?
|
|
190
|
+
|
|
191
|
+
value.each do |tag|
|
|
192
|
+
range = Contrast::Api::Dtm::TraceTaintRange.new
|
|
193
|
+
range.tag = Contrast::Utils::StringUtils.protobuf_safe_string(key)
|
|
194
|
+
range.range = tag.start_idx.to_s + Contrast::Utils::ObjectShare::COLON + tag.end_idx.to_s
|
|
195
|
+
ranges << range
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
ranges
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Remove all tags within the given ranges.
|
|
202
|
+
# This does not delete an entire tag if part of that tag is
|
|
203
|
+
# outside this range, meaning we may reduce sizes of tags
|
|
204
|
+
# or split them.
|
|
205
|
+
#
|
|
206
|
+
# If shift is true, it is assumed the characters at those ranges were
|
|
207
|
+
# removed. If shift is false, it is assumed those ranges were replaced
|
|
208
|
+
# by the same number of characters and no shift is needed.
|
|
209
|
+
#
|
|
210
|
+
# current tags: 0-15
|
|
211
|
+
# range: 5-10
|
|
212
|
+
# result: 0-5, 10-15
|
|
213
|
+
def delete_tags_at_ranges ranges, shift = true
|
|
214
|
+
return unless tags?
|
|
215
|
+
|
|
216
|
+
# Stage one - delete the tags w/o changing their
|
|
217
|
+
# location.
|
|
218
|
+
ranges.each do |range|
|
|
219
|
+
remove_tags(range)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
return unless shift
|
|
223
|
+
|
|
224
|
+
# the amount we've already removed from the string
|
|
225
|
+
shift = 0
|
|
226
|
+
# Stage two - shift the tags to the left to account
|
|
227
|
+
# for the sections that were deleted.
|
|
228
|
+
ranges.each do |range|
|
|
229
|
+
shift_tags_for_deletion(range, shift)
|
|
230
|
+
shift += (range.stop - range.start)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Clean up and merge any touching tags
|
|
234
|
+
Contrast::Utils::TagUtil.merge_tags(tags)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Shift all the tags in this object by the given ranges.
|
|
238
|
+
# This method assumes the ranges are sorted, meaning
|
|
239
|
+
# the leftmost (lowest) range is first
|
|
240
|
+
#
|
|
241
|
+
# current tags: 0-15
|
|
242
|
+
# range: 5-10
|
|
243
|
+
# result: 0-5, 10-20
|
|
244
|
+
def shift_tags ranges
|
|
245
|
+
return unless tags?
|
|
246
|
+
|
|
247
|
+
ranges.each do |range|
|
|
248
|
+
shift_tags_for_insertion(range)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Add an event to these properties. It will be used to build
|
|
253
|
+
# a trace if this object ends up in a trigger.
|
|
254
|
+
def add_event event
|
|
255
|
+
events << event
|
|
256
|
+
self
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def build_event policy_node, tagged, object, ret, args, invoked = 0, source_type = nil, source_name = nil
|
|
260
|
+
event = Contrast::Agent::Assess::ContrastEvent.new(policy_node, tagged, object, ret, args, invoked, source_type, source_name)
|
|
261
|
+
add_event(event)
|
|
262
|
+
report_sources(tagged, event)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
private
|
|
266
|
+
|
|
267
|
+
def report_sources tagged, event
|
|
268
|
+
return unless tagged && !tagged.to_s.empty?
|
|
269
|
+
return unless event&.source_type
|
|
270
|
+
|
|
271
|
+
current_request = Contrast::Agent::REQUEST_TRACKER.current
|
|
272
|
+
return unless current_request
|
|
273
|
+
return if current_request.observed_route.sources.any? { |source| source.type == event.forced_source_type && source.name == event.forced_source_name }
|
|
274
|
+
|
|
275
|
+
event_source_dtm = event.build_event_source_dtm
|
|
276
|
+
return unless event_source_dtm
|
|
277
|
+
|
|
278
|
+
current_request.observed_route.sources << event_source_dtm
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Because of the auto-fill thing, we should not allow direct access to
|
|
282
|
+
# the tags hash. Instead, the methods above should be used to do
|
|
283
|
+
# operations like add, delete, and fetch.
|
|
284
|
+
#
|
|
285
|
+
# CONTRAST-22914
|
|
286
|
+
# please do NOT expose this w/ an attr_reader / accessor. there are
|
|
287
|
+
# helper methods in this class that safely access the hash. the tags
|
|
288
|
+
# method is private to avoid the side effect of a direct lookup with
|
|
289
|
+
# `[]` adding an empty array to the hash.
|
|
290
|
+
def tags
|
|
291
|
+
@_tags ||= Hash.new { |h, k| h[k] = [] }
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Creating Tags is expensive and we check for Tags all the time on
|
|
295
|
+
# untracked things. ALWAYS!!! call this method before checking if
|
|
296
|
+
# an object has tags
|
|
297
|
+
def tags?
|
|
298
|
+
instance_variable_defined?(:@_tags)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Remove the tag ranges covering the given range
|
|
302
|
+
def remove_tags range
|
|
303
|
+
return unless tags?
|
|
304
|
+
|
|
305
|
+
full_delete = []
|
|
306
|
+
tags.each_pair do |key, value|
|
|
307
|
+
remove = []
|
|
308
|
+
add = []
|
|
309
|
+
value.each do |tag|
|
|
310
|
+
comparison = tag.compare_range(range.start, range.stop)
|
|
311
|
+
# ABOVE and BELOW are not affected by this check
|
|
312
|
+
case comparison
|
|
313
|
+
when Contrast::Agent::Assess::Tag::LOW_SPAN
|
|
314
|
+
tag.update_end(range.start)
|
|
315
|
+
when Contrast::Agent::Assess::Tag::WITHIN
|
|
316
|
+
remove << tag
|
|
317
|
+
when Contrast::Agent::Assess::Tag::WITHOUT
|
|
318
|
+
new_tag = tag.clone
|
|
319
|
+
new_tag.update_start(range.stop)
|
|
320
|
+
add << new_tag
|
|
321
|
+
tag.update_end(range.start)
|
|
322
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN
|
|
323
|
+
tag.update_start(range.stop)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
value.delete_if { |tag| remove.include?(tag) }
|
|
327
|
+
Contrast::Utils::TagUtil.ordered_merge(value, add)
|
|
328
|
+
full_delete << key if value.empty?
|
|
329
|
+
end
|
|
330
|
+
full_delete.each { |key| tags.delete(key) }
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Shift the tag ranges covering the given range
|
|
334
|
+
# We assume this is for a deletion, meaning we
|
|
335
|
+
# have to move tags to the left
|
|
336
|
+
def shift_tags_for_deletion range, shift
|
|
337
|
+
return unless tags?
|
|
338
|
+
|
|
339
|
+
tags.each_value do |value|
|
|
340
|
+
value.each do |tag|
|
|
341
|
+
comparison = tag.compare_range(range.start - shift, range.stop - shift)
|
|
342
|
+
length = range.stop - range.start
|
|
343
|
+
case comparison
|
|
344
|
+
# this is really the only thing we need to shift
|
|
345
|
+
when Contrast::Agent::Assess::Tag::ABOVE
|
|
346
|
+
tag.shift(0 - length)
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Shift the tag ranges covering the given range
|
|
353
|
+
# We assume this is for a insertion, meaning we
|
|
354
|
+
# have to move tags to the right
|
|
355
|
+
def shift_tags_for_insertion range
|
|
356
|
+
return unless tags?
|
|
357
|
+
|
|
358
|
+
tags.each_value do |value|
|
|
359
|
+
add = []
|
|
360
|
+
value.each do |tag|
|
|
361
|
+
comparison = tag.compare_range(range.start, range.stop)
|
|
362
|
+
length = range.stop - range.start
|
|
363
|
+
# BELOW is not affected by this check
|
|
364
|
+
case comparison
|
|
365
|
+
# part of the tag is being inserted on
|
|
366
|
+
when Contrast::Agent::Assess::Tag::LOW_SPAN
|
|
367
|
+
new_tag = tag.clone
|
|
368
|
+
new_tag.update_start(range.start)
|
|
369
|
+
new_tag.shift(length)
|
|
370
|
+
add << new_tag
|
|
371
|
+
tag.update_end(range.start)
|
|
372
|
+
# the tag exists in the inserted range. it is partially shifted
|
|
373
|
+
when Contrast::Agent::Assess::Tag::WITHIN
|
|
374
|
+
tag.shift(length)
|
|
375
|
+
# the tag spans the range. leave the part below alone
|
|
376
|
+
when Contrast::Agent::Assess::Tag::WITHOUT
|
|
377
|
+
new_tag = tag.clone
|
|
378
|
+
new_tag.update_start(range.start)
|
|
379
|
+
new_tag.shift(length)
|
|
380
|
+
add << new_tag
|
|
381
|
+
tag.update_end(range.start)
|
|
382
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE
|
|
383
|
+
tag.shift(length)
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
Contrast::Utils::TagUtil.ordered_merge(value, add)
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|