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,300 @@
|
|
|
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/components/interface'
|
|
5
|
+
|
|
6
|
+
module Contrast
|
|
7
|
+
module Agent
|
|
8
|
+
module Protect
|
|
9
|
+
module Rule
|
|
10
|
+
# This is a basic rule for Protect. It's the abstract class which all other
|
|
11
|
+
# protect rules extend in order to function.
|
|
12
|
+
#
|
|
13
|
+
# @abstract Subclass and override {#prefilter}, {#infilter}, {#find_attacker}, {#postfilter} and {#build_details} to implement
|
|
14
|
+
class Base
|
|
15
|
+
include Contrast::Components::Interface
|
|
16
|
+
|
|
17
|
+
access_component :logging, :analysis, :settings
|
|
18
|
+
|
|
19
|
+
UNKNOWN_USER_INPUT = Contrast::Api::Dtm::UserInput.new.tap do |user_input|
|
|
20
|
+
user_input.input_type = :UNKNOWN
|
|
21
|
+
end.cs__freeze
|
|
22
|
+
|
|
23
|
+
BLOCKING_MODES = Set.new(%i[BLOCK BLOCK_AT_PERIMETER]).cs__freeze
|
|
24
|
+
PREFILTER_MODES = Set.new(%i[BLOCK_AT_PERIMETER]).cs__freeze
|
|
25
|
+
POSTFILTER_MODES = Set.new(%i[BLOCK PERMIT MONITOR]).cs__freeze
|
|
26
|
+
STACK_COLLECTION_RESULTS = Set.new(%i[BLOCKED MONITORED]).cs__freeze
|
|
27
|
+
|
|
28
|
+
attr_reader :mode
|
|
29
|
+
|
|
30
|
+
def initialize default_mode = :NO_ACTION
|
|
31
|
+
SETTINGS.protect_rules[name] = self
|
|
32
|
+
@mode = mode_from_settings || default_mode
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Should return the name as it is known to Teamserver; defaults to class
|
|
36
|
+
def name
|
|
37
|
+
cs__class.name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
OFF = 'off'
|
|
41
|
+
|
|
42
|
+
def enabled?
|
|
43
|
+
# 1. it is not enabled because protect is not enabled
|
|
44
|
+
return false unless PROTECT.enabled?
|
|
45
|
+
|
|
46
|
+
rule_configs = Contrast::Agent::FeatureState.instance.protect_rule_config
|
|
47
|
+
unless rule_configs.nil?
|
|
48
|
+
# 2. it is not enabled because it is in the list of disabled protect rules
|
|
49
|
+
disabled_rules = rule_configs.disabled_rules
|
|
50
|
+
return false if disabled_rules&.include?(name)
|
|
51
|
+
|
|
52
|
+
# 3. it is not enabled because it has been turned "off" explicitly
|
|
53
|
+
rule_config = rule_configs.send(name)
|
|
54
|
+
|
|
55
|
+
return rule_config.mode != OFF unless rule_config.mode.nil?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# 4. it is not enabled because it's mode is :NO_ACTION
|
|
59
|
+
@mode != :NO_ACTION
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# this rule is excluded if any of the given exclusions have a protection rule that matches this rule name
|
|
63
|
+
def excluded? exclusions
|
|
64
|
+
Array(exclusions).any? do |ex|
|
|
65
|
+
ex.protection_rule?(name)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def infilter? _context
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# return false for rules that modify or inspect the response body
|
|
74
|
+
# during postfilter
|
|
75
|
+
#
|
|
76
|
+
# @return [Boolean] if the rule can safely be evaluated in streaming
|
|
77
|
+
# requests
|
|
78
|
+
def stream_safe?
|
|
79
|
+
true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Actions required for the rules that have to happen before the
|
|
83
|
+
# application has completed its processing of the request.
|
|
84
|
+
#
|
|
85
|
+
# For most rules, these actions are performed within the analysis
|
|
86
|
+
# engine and communicated as an input analysis result. Those that
|
|
87
|
+
# require specific action need to provide that action.
|
|
88
|
+
#
|
|
89
|
+
# @param _context [Contrast::Agent::RequestContext] the context for
|
|
90
|
+
# the current request
|
|
91
|
+
def prefilter _context; end
|
|
92
|
+
|
|
93
|
+
# This should only ever be called directly from patched code and will
|
|
94
|
+
# have a different implementation based on the rule. As such, there
|
|
95
|
+
# is not parent implementation.
|
|
96
|
+
#
|
|
97
|
+
# @param _context [Contrast::Agent::RequestContext] the context for
|
|
98
|
+
# the current request
|
|
99
|
+
# @param _match_string [String] the input that violated the rule and
|
|
100
|
+
# matched the attack detection logic
|
|
101
|
+
# @param _kwargs [Hash] key-value pairs used by the rule to build a
|
|
102
|
+
# report.
|
|
103
|
+
def infilter _context, _match_string, **_kwargs; end
|
|
104
|
+
|
|
105
|
+
# Actions required for the rules that have to happen after the
|
|
106
|
+
# application has completed its processing of the request.
|
|
107
|
+
#
|
|
108
|
+
# Any implementation here needs to account for the fact that
|
|
109
|
+
# responses may be streaming and, as such, transformations of the
|
|
110
|
+
# response itself may not be permissible.
|
|
111
|
+
#
|
|
112
|
+
# @param _context [Contrast::Agent::RequestContext] the context for
|
|
113
|
+
# the current request
|
|
114
|
+
def postfilter _context; end
|
|
115
|
+
|
|
116
|
+
def build_attack_with_match context, ia_result, result, candidate_string, **kwargs
|
|
117
|
+
result ||= build_attack_result(context)
|
|
118
|
+
update_successful_attack_response(context, ia_result, result, candidate_string)
|
|
119
|
+
append_sample(context, ia_result, result, candidate_string, **kwargs)
|
|
120
|
+
|
|
121
|
+
result
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def build_attack_without_match context, ia_result, result, **kwargs
|
|
125
|
+
result ||= build_attack_result(context)
|
|
126
|
+
update_perimeter_attack_response(context, ia_result, result)
|
|
127
|
+
append_sample(context, ia_result, result, nil, **kwargs)
|
|
128
|
+
|
|
129
|
+
result
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def append_to_activity context, result
|
|
133
|
+
context.activity.results << result if result
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
protected
|
|
137
|
+
|
|
138
|
+
def build_details _input_string, _ia_result
|
|
139
|
+
raise Contrast::InternalException, "Rule #{ name } did not implement build_details"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def mode_from_settings
|
|
143
|
+
SETTINGS.protect_rule_mode(name).tap do |mode|
|
|
144
|
+
logger.debug("rule #{ name } mode = #{ mode }")
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def blocked?
|
|
149
|
+
enabled? && BLOCKING_MODES.include?(mode)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Determine if there's an exclusion that matches an item in the call
|
|
153
|
+
# stack
|
|
154
|
+
#
|
|
155
|
+
# @return [Boolean] if an exclusion was applicable to this request
|
|
156
|
+
# for this rule
|
|
157
|
+
def protect_excluded_by_code?
|
|
158
|
+
exclusions = Contrast::Agent::FeatureState.instance.code_exclusions
|
|
159
|
+
return false unless exclusions
|
|
160
|
+
|
|
161
|
+
for_rule = exclusions.select { |ex| ex.protection_rule?(name) }
|
|
162
|
+
return false if for_rule.empty?
|
|
163
|
+
|
|
164
|
+
stack = caller_locations
|
|
165
|
+
for_rule.any? { |ex| ex.match_code?(stack) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# By default, rules do not have to find attackers as they do not have
|
|
169
|
+
# Input Analysis. Any attack for the standard rule will be evaluated
|
|
170
|
+
# at execution time. As such, those rules are expected to implement
|
|
171
|
+
# this custom behavior
|
|
172
|
+
#
|
|
173
|
+
# @param _context [Contrast::Agent::RequestContext] the context for
|
|
174
|
+
# the current request
|
|
175
|
+
# @param _potential_attack_string [String] the input that may violate
|
|
176
|
+
# the rule and matched the attack detection logic
|
|
177
|
+
# @param _kwargs [Hash] key-value pairs used by the rule to build a
|
|
178
|
+
# report.
|
|
179
|
+
def find_attacker _context, _potential_attack_string, **_kwargs
|
|
180
|
+
raise Contrast::InternalException, "Rule #{ name } did not implement find_attack"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def update_successful_attack_response context, ia_result, result, attack_string = nil
|
|
184
|
+
if mode == :MONITOR
|
|
185
|
+
result.response = :MONITORED
|
|
186
|
+
elsif mode == :BLOCK
|
|
187
|
+
result.response = :BLOCKED
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
ia_result.attack_count = ia_result.attack_count + 1 if ia_result
|
|
191
|
+
log_rule_matched(context, ia_result, result.response, attack_string)
|
|
192
|
+
|
|
193
|
+
result
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def update_perimeter_attack_response context, ia_result, result
|
|
197
|
+
if mode == :BLOCK_AT_PERIMETER
|
|
198
|
+
result.response = :BLOCKED_AT_PERIMETER
|
|
199
|
+
log_rule_matched(context, ia_result, result.response)
|
|
200
|
+
elsif ia_result.nil? || ia_result.attack_count.zero?
|
|
201
|
+
result.response = :PROBED
|
|
202
|
+
log_rule_probed(context, ia_result)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
result
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def build_attack_result _context
|
|
209
|
+
result = Contrast::Api::Dtm::AttackResult.new
|
|
210
|
+
result.rule_id = name
|
|
211
|
+
result
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def append_stack sample, result
|
|
215
|
+
return unless sample
|
|
216
|
+
return unless STACK_COLLECTION_RESULTS.include?(result&.response)
|
|
217
|
+
|
|
218
|
+
stack = Contrast::Utils::StackTraceUtils.build(ignore: true, class_lookup: true, depth: 50)
|
|
219
|
+
return unless stack
|
|
220
|
+
|
|
221
|
+
sample.stack_trace_elements += stack
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def append_sample context, ia_result, result, candidate_string, **kwargs
|
|
225
|
+
return nil unless result
|
|
226
|
+
|
|
227
|
+
sample = build_sample(context, ia_result, candidate_string, **kwargs)
|
|
228
|
+
return nil unless sample
|
|
229
|
+
|
|
230
|
+
append_stack(sample, result)
|
|
231
|
+
|
|
232
|
+
result.samples << sample
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Override if rule can make use of the candidate string or kwargs to
|
|
236
|
+
# build rasp rule sample.
|
|
237
|
+
def build_sample context, ia_result, _candidate_string, **_kwargs
|
|
238
|
+
build_base_sample(context, ia_result)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def build_user_input ia_result
|
|
242
|
+
return UNKNOWN_USER_INPUT unless ia_result
|
|
243
|
+
|
|
244
|
+
input = Contrast::Api::Dtm::UserInput.new
|
|
245
|
+
input.input_type = ia_result.input_type.to_sym
|
|
246
|
+
input.matcher_ids = ia_result.ids
|
|
247
|
+
input.path = ia_result.path.to_s
|
|
248
|
+
input.key = ia_result.key.to_s
|
|
249
|
+
input.value = ia_result.value.to_s
|
|
250
|
+
input
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def build_base_sample context, ia_result
|
|
254
|
+
sample = Contrast::Api::Dtm::RaspRuleSample.new
|
|
255
|
+
sample.timestamp_ms = context.timer.start_ms
|
|
256
|
+
sample.user_input = build_user_input(ia_result)
|
|
257
|
+
sample.user_input.document_type = context.request.dtm.document_type unless sample.user_input.cs__frozen?
|
|
258
|
+
sample
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def log_rule_matched _context, ia_result, response, _matched_string = nil
|
|
262
|
+
return unless ia_result
|
|
263
|
+
|
|
264
|
+
# Log to agent logger
|
|
265
|
+
message = if ia_result
|
|
266
|
+
log_msg(ia_result, true)
|
|
267
|
+
else
|
|
268
|
+
"An effective attack was detected against #{ name }."
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
logger.debug([response, message].join)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
private
|
|
275
|
+
|
|
276
|
+
def log_rule_probed _context, ia_result
|
|
277
|
+
message = if ia_result
|
|
278
|
+
log_msg(ia_result, false)
|
|
279
|
+
else
|
|
280
|
+
"An unsuccessful attack was detected against #{ name }"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
logger.debug(message)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def log_msg ia_result, matched
|
|
287
|
+
key = ia_result.key ? ia_result.key.to_s + ' ' : ''
|
|
288
|
+
val = ia_result.value.to_s
|
|
289
|
+
typ = ia_result.input_type.to_s
|
|
290
|
+
if matched
|
|
291
|
+
"The #{ typ } #{ key } had a value that successfully exploited #{ name } - #{ val }."
|
|
292
|
+
else
|
|
293
|
+
"The #{ typ } #{ key } had a value that matched a signature for, but did not successfully exploit, #{ name } - #{ val }."
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
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 Protect
|
|
7
|
+
module Rule
|
|
8
|
+
# Encapsulate common code for protect rules that do their
|
|
9
|
+
# input analysis on Speedracer rather in ruby code
|
|
10
|
+
class BaseService < Contrast::Agent::Protect::Rule::Base
|
|
11
|
+
def name
|
|
12
|
+
'base-service'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def block_message
|
|
16
|
+
'Contrast Security Protect Rule Triggered. Response blocked.'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def infilter? context
|
|
20
|
+
return false unless context&.speedracer_input_analysis&.results
|
|
21
|
+
return false unless enabled?
|
|
22
|
+
return false if protect_excluded_by_code?
|
|
23
|
+
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Override for rules that need the response
|
|
28
|
+
# Currently postfilter can be applied to streamed responses,
|
|
29
|
+
# if any logic within postfilter changes to modify the response
|
|
30
|
+
# streamed responses will break
|
|
31
|
+
def postfilter context
|
|
32
|
+
return unless enabled? && POSTFILTER_MODES.include?(mode)
|
|
33
|
+
return if mode == :NO_ACTION || mode == :PERMIT
|
|
34
|
+
|
|
35
|
+
result = find_postfilter_attacker(context, nil)
|
|
36
|
+
return unless result&.samples&.any?
|
|
37
|
+
|
|
38
|
+
append_to_activity(context, result)
|
|
39
|
+
return unless result.response == :BLOCKED
|
|
40
|
+
|
|
41
|
+
raise Contrast::SecurityException.new(self, "#{ name } triggered in postfilter. Response blocked.")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
protected
|
|
45
|
+
|
|
46
|
+
def gather_ia_results context
|
|
47
|
+
context.speedracer_input_analysis.results.select do |ia_result|
|
|
48
|
+
ia_result.rule_id == name
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def find_attacker context, potential_attack_string, **kwargs
|
|
53
|
+
ia_results = gather_ia_results(context)
|
|
54
|
+
find_attacker_with_results(context, potential_attack_string, ia_results, **kwargs)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Allows for the InputAnalysis from service to be extracted early
|
|
58
|
+
def find_attacker_with_results context, potential_attack_string, ia_results, **kwargs
|
|
59
|
+
logger.debug("checking: #{ name } vectors=#{ ia_results.length } in '#{ potential_attack_string }'")
|
|
60
|
+
|
|
61
|
+
result = nil
|
|
62
|
+
ia_results.each do |ia_result|
|
|
63
|
+
if potential_attack_string
|
|
64
|
+
idx = potential_attack_string.index(ia_result.value)
|
|
65
|
+
next unless idx
|
|
66
|
+
|
|
67
|
+
result = build_attack_with_match(context, ia_result, result, potential_attack_string, **kwargs)
|
|
68
|
+
else
|
|
69
|
+
result = build_attack_without_match(context, ia_result, result, **kwargs)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def find_postfilter_attacker context, potential_attack_string, **kwargs
|
|
78
|
+
ia_results = gather_ia_results(context)
|
|
79
|
+
ia_results.select! do |ia_result|
|
|
80
|
+
ia_result.score_level == :DEFINITEATTACK
|
|
81
|
+
end
|
|
82
|
+
find_attacker_with_results(context, potential_attack_string, ia_results, **kwargs)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
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/utils/stack_trace_utils'
|
|
5
|
+
cs__scoped_require 'contrast/utils/object_share'
|
|
6
|
+
cs__scoped_require 'contrast/components/interface'
|
|
7
|
+
|
|
8
|
+
module Contrast
|
|
9
|
+
module Agent
|
|
10
|
+
module Protect
|
|
11
|
+
module Rule
|
|
12
|
+
# The Ruby implementation of the Protect Command Injection rule.
|
|
13
|
+
class CmdInjection < Contrast::Agent::Protect::Rule::BaseService
|
|
14
|
+
include Contrast::Components::Interface
|
|
15
|
+
access_component :logging, :contrast_service
|
|
16
|
+
|
|
17
|
+
NAME = 'cmd-injection'
|
|
18
|
+
CHAINED_COMMAND_CHARS = /[;&|<>]/.cs__freeze
|
|
19
|
+
|
|
20
|
+
def name
|
|
21
|
+
NAME
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def infilter context, classname, method, command
|
|
25
|
+
return nil unless infilter?(context)
|
|
26
|
+
|
|
27
|
+
ia_results = gather_ia_results(context)
|
|
28
|
+
return nil if ia_results.empty?
|
|
29
|
+
|
|
30
|
+
if Contrast::Agent::FeatureState.instance.in_new_process?
|
|
31
|
+
logger.debug('Running cmd-injection infilter within new process - creating new context')
|
|
32
|
+
context = Contrast::Agent::RequestContext.new(context.request.rack_request)
|
|
33
|
+
Contrast::Agent::REQUEST_TRACKER.update_current_context(context)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
result = find_attacker_with_results(context, command, ia_results, **{ classname: classname, method: method })
|
|
37
|
+
result ||= report_command_execution(context, command, **{ classname: classname, method: method })
|
|
38
|
+
return nil unless result
|
|
39
|
+
|
|
40
|
+
append_to_activity(context, result)
|
|
41
|
+
if %I[exec `].include?(method)
|
|
42
|
+
# TODO: RUBY-737
|
|
43
|
+
# Kernel#exec replaces the current process and does not go through at_exit hooks
|
|
44
|
+
# Kernel#` runs as a subshell - messages appended here do not seem to be present in the original process?
|
|
45
|
+
CONTRAST_SERVICE.send_message(context.activity)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return unless blocked?
|
|
49
|
+
|
|
50
|
+
raise Contrast::SecurityException.new(
|
|
51
|
+
self,
|
|
52
|
+
"Command Injection rule triggered. Call to #{ classname }.#{ method } blocked.")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def build_attack_with_match context, input_analysis_result, result, candidate_string, **kwargs
|
|
56
|
+
return result if mode == :NO_ACTION || mode == :PERMIT
|
|
57
|
+
|
|
58
|
+
result ||= build_attack_result(context)
|
|
59
|
+
update_successful_attack_response(context, input_analysis_result, result, candidate_string)
|
|
60
|
+
append_sample(context, input_analysis_result, result, candidate_string, **kwargs)
|
|
61
|
+
result
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
protected
|
|
65
|
+
|
|
66
|
+
# Because results are not necessarily on the context across
|
|
67
|
+
# processes; extract early and pass into the method
|
|
68
|
+
def find_attacker_with_results context, potential_attack_string, ia_results, **kwargs
|
|
69
|
+
logger.debug("checking: #{ name } in '#{ potential_attack_string }'")
|
|
70
|
+
result = super(context, potential_attack_string, ia_results, **kwargs)
|
|
71
|
+
if result.nil? && potential_attack_string
|
|
72
|
+
result = find_probable_attacker(
|
|
73
|
+
context,
|
|
74
|
+
potential_attack_string,
|
|
75
|
+
ia_results,
|
|
76
|
+
**kwargs)
|
|
77
|
+
end
|
|
78
|
+
result
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Build a subclass of the RaspRuleSample using the query string and the
|
|
82
|
+
# evaluation
|
|
83
|
+
def build_sample context, input_analysis_result, candidate_string, **_kwargs
|
|
84
|
+
sample = build_base_sample(context, input_analysis_result)
|
|
85
|
+
sample.cmdi = Contrast::Api::Dtm::CmdInjectionDetails.new
|
|
86
|
+
|
|
87
|
+
command = candidate_string || input_analysis_result.value
|
|
88
|
+
command = Contrast::Utils::StringUtils.protobuf_safe_string(command)
|
|
89
|
+
sample.cmdi.command = command
|
|
90
|
+
|
|
91
|
+
# This is a special case where the user input is UNKNOWN_USER_INPUT but
|
|
92
|
+
# we want to send the attack value
|
|
93
|
+
if input_analysis_result.nil?
|
|
94
|
+
ui = Contrast::Api::Dtm::UserInput.new
|
|
95
|
+
ui.input_type = :UNKNOWN
|
|
96
|
+
ui.value = command
|
|
97
|
+
sample.user_input = ui
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
sample
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def report_command_execution context, command, **kwargs
|
|
106
|
+
return unless report_any_command_execution?
|
|
107
|
+
return nil if protect_excluded_by_code?
|
|
108
|
+
|
|
109
|
+
build_attack_with_match(context, nil, nil, command, **kwargs)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def find_probable_attacker context, potential_attack_string, ia_results, **kwargs
|
|
113
|
+
result = nil
|
|
114
|
+
if chained_command?(potential_attack_string) # this is probably an attack
|
|
115
|
+
most_likely = nil
|
|
116
|
+
ia_results.each do |input_analysis_result|
|
|
117
|
+
next unless chained_command?(input_analysis_result.value)
|
|
118
|
+
|
|
119
|
+
most_likely = input_analysis_result
|
|
120
|
+
break
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
return result unless most_likely
|
|
124
|
+
|
|
125
|
+
result ||= build_attack_with_match(
|
|
126
|
+
context,
|
|
127
|
+
most_likely,
|
|
128
|
+
result,
|
|
129
|
+
potential_attack_string,
|
|
130
|
+
**kwargs)
|
|
131
|
+
return nil if result.nil?
|
|
132
|
+
|
|
133
|
+
log_rule_matched(context, most_likely, mode, potential_attack_string)
|
|
134
|
+
result
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def chained_command? command
|
|
138
|
+
return true if CHAINED_COMMAND_CHARS.match(command)
|
|
139
|
+
|
|
140
|
+
false
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Part of the Hardening for Command Injection detection is the
|
|
144
|
+
# ability to detect and prevent any command execution from within the
|
|
145
|
+
# application. This check determines if that hardening has been
|
|
146
|
+
# enabled.
|
|
147
|
+
# @return [Boolean] if the agent should report all command
|
|
148
|
+
# executions.
|
|
149
|
+
def report_any_command_execution?
|
|
150
|
+
Contrast::Agent::FeatureState.instance.report_any_command_execution?
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|