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.
Files changed (500) hide show
  1. checksums.yaml +7 -0
  2. data/.clang-format +5 -0
  3. data/.dockerignore +10 -0
  4. data/.gitignore +58 -0
  5. data/.gitmodules +6 -0
  6. data/.rspec +6 -0
  7. data/.simplecov +4 -0
  8. data/Gemfile +7 -0
  9. data/LICENSE.txt +12 -0
  10. data/Rakefile +15 -0
  11. data/exe/contrast_service +29 -0
  12. data/ext/build_funchook.rb +48 -0
  13. data/ext/cs__assess_active_record_named/cs__active_record_named.c +47 -0
  14. data/ext/cs__assess_active_record_named/cs__active_record_named.h +10 -0
  15. data/ext/cs__assess_active_record_named/extconf.rb +2 -0
  16. data/ext/cs__assess_array/cs__assess_array.c +38 -0
  17. data/ext/cs__assess_array/cs__assess_array.h +9 -0
  18. data/ext/cs__assess_array/extconf.rb +2 -0
  19. data/ext/cs__assess_basic_object/cs__assess_basic_object.c +50 -0
  20. data/ext/cs__assess_basic_object/cs__assess_basic_object.h +17 -0
  21. data/ext/cs__assess_basic_object/extconf.rb +2 -0
  22. data/ext/cs__assess_fiber_track/cs__assess_fiber_track.c +86 -0
  23. data/ext/cs__assess_fiber_track/cs__assess_fiber_track.h +34 -0
  24. data/ext/cs__assess_fiber_track/extconf.rb +2 -0
  25. data/ext/cs__assess_hash/cs__assess_hash.c +64 -0
  26. data/ext/cs__assess_hash/cs__assess_hash.h +24 -0
  27. data/ext/cs__assess_hash/extconf.rb +2 -0
  28. data/ext/cs__assess_kernel/cs__assess_kernel.c +36 -0
  29. data/ext/cs__assess_kernel/cs__assess_kernel.h +10 -0
  30. data/ext/cs__assess_kernel/extconf.rb +2 -0
  31. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +47 -0
  32. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +18 -0
  33. data/ext/cs__assess_marshal_module/extconf.rb +2 -0
  34. data/ext/cs__assess_module/cs__assess_module.c +78 -0
  35. data/ext/cs__assess_module/cs__assess_module.h +25 -0
  36. data/ext/cs__assess_module/extconf.rb +2 -0
  37. data/ext/cs__assess_regexp/cs__assess_regexp.c +48 -0
  38. data/ext/cs__assess_regexp/cs__assess_regexp.h +22 -0
  39. data/ext/cs__assess_regexp/extconf.rb +2 -0
  40. data/ext/cs__assess_regexp_track/cs__assess_regexp_track.c +63 -0
  41. data/ext/cs__assess_regexp_track/cs__assess_regexp_track.h +29 -0
  42. data/ext/cs__assess_regexp_track/extconf.rb +2 -0
  43. data/ext/cs__assess_string/cs__assess_string.c +38 -0
  44. data/ext/cs__assess_string/cs__assess_string.h +19 -0
  45. data/ext/cs__assess_string/extconf.rb +2 -0
  46. data/ext/cs__assess_string_interpolation26/cs__assess_string_interpolation26.c +31 -0
  47. data/ext/cs__assess_string_interpolation26/cs__assess_string_interpolation26.h +13 -0
  48. data/ext/cs__assess_string_interpolation26/extconf.rb +2 -0
  49. data/ext/cs__common/cs__common.c +60 -0
  50. data/ext/cs__common/cs__common.h +28 -0
  51. data/ext/cs__common/extconf.rb +20 -0
  52. data/ext/cs__contrast_patch/cs__contrast_patch.c +445 -0
  53. data/ext/cs__contrast_patch/cs__contrast_patch.h +196 -0
  54. data/ext/cs__contrast_patch/extconf.rb +2 -0
  55. data/ext/cs__protect_kernel/cs__protect_kernel.c +37 -0
  56. data/ext/cs__protect_kernel/cs__protect_kernel.h +11 -0
  57. data/ext/cs__protect_kernel/extconf.rb +2 -0
  58. data/ext/cs__scope/cs__scope.c +96 -0
  59. data/ext/cs__scope/cs__scope.h +33 -0
  60. data/ext/cs__scope/extconf.rb +2 -0
  61. data/ext/extconf_common.rb +49 -0
  62. data/funchook/LICENSE +360 -0
  63. data/funchook/Makefile +29 -0
  64. data/funchook/Makefile.in +29 -0
  65. data/funchook/README.md +121 -0
  66. data/funchook/appveyor.yml +42 -0
  67. data/funchook/autogen.sh +3 -0
  68. data/funchook/autom4te.cache/output.0 +4976 -0
  69. data/funchook/autom4te.cache/requests +78 -0
  70. data/funchook/autom4te.cache/traces.0 +364 -0
  71. data/funchook/config.guess +1530 -0
  72. data/funchook/config.log +490 -0
  73. data/funchook/config.status +1016 -0
  74. data/funchook/config.sub +1773 -0
  75. data/funchook/configure +4976 -0
  76. data/funchook/configure.ac +59 -0
  77. data/funchook/distorm/COPYING +26 -0
  78. data/funchook/distorm/MANIFEST +25 -0
  79. data/funchook/distorm/MANIFEST.in +4 -0
  80. data/funchook/distorm/README.md +12 -0
  81. data/funchook/distorm/disOps/disOps.py +795 -0
  82. data/funchook/distorm/disOps/x86db.py +404 -0
  83. data/funchook/distorm/disOps/x86header.py +247 -0
  84. data/funchook/distorm/disOps/x86sets.py +1664 -0
  85. data/funchook/distorm/examples/cs/TestdiStorm/Program.cs +79 -0
  86. data/funchook/distorm/examples/cs/TestdiStorm/Properties/AssemblyInfo.cs +36 -0
  87. data/funchook/distorm/examples/cs/TestdiStorm/TestdiStorm.csproj +69 -0
  88. data/funchook/distorm/examples/cs/distorm-net.sln +26 -0
  89. data/funchook/distorm/examples/cs/distorm-net/CodeInfo.cs +23 -0
  90. data/funchook/distorm/examples/cs/distorm-net/DecodedInst.cs +15 -0
  91. data/funchook/distorm/examples/cs/distorm-net/DecodedResult.cs +14 -0
  92. data/funchook/distorm/examples/cs/distorm-net/DecomposedInst.cs +36 -0
  93. data/funchook/distorm/examples/cs/distorm-net/DecomposedResult.cs +14 -0
  94. data/funchook/distorm/examples/cs/distorm-net/Opcodes.cs +1268 -0
  95. data/funchook/distorm/examples/cs/distorm-net/Opcodes.tt +37 -0
  96. data/funchook/distorm/examples/cs/distorm-net/Operand.cs +25 -0
  97. data/funchook/distorm/examples/cs/distorm-net/Properties/AssemblyInfo.cs +36 -0
  98. data/funchook/distorm/examples/cs/distorm-net/diStorm3.cs +411 -0
  99. data/funchook/distorm/examples/cs/distorm-net/distorm-net.csproj +80 -0
  100. data/funchook/distorm/examples/cs/readme +3 -0
  101. data/funchook/distorm/examples/ddk/README +48 -0
  102. data/funchook/distorm/examples/ddk/distorm.ini +11 -0
  103. data/funchook/distorm/examples/ddk/dummy.c +15 -0
  104. data/funchook/distorm/examples/ddk/main.c +91 -0
  105. data/funchook/distorm/examples/ddk/makefile +1 -0
  106. data/funchook/distorm/examples/ddk/sources +10 -0
  107. data/funchook/distorm/examples/java/Makefile +23 -0
  108. data/funchook/distorm/examples/java/distorm/src/Main.java +43 -0
  109. data/funchook/distorm/examples/java/distorm/src/diStorm3/CodeInfo.java +27 -0
  110. data/funchook/distorm/examples/java/distorm/src/diStorm3/DecodedInst.java +32 -0
  111. data/funchook/distorm/examples/java/distorm/src/diStorm3/DecodedResult.java +11 -0
  112. data/funchook/distorm/examples/java/distorm/src/diStorm3/DecomposedInst.java +89 -0
  113. data/funchook/distorm/examples/java/distorm/src/diStorm3/DecomposedResult.java +11 -0
  114. data/funchook/distorm/examples/java/distorm/src/diStorm3/OpcodeEnum.java +131 -0
  115. data/funchook/distorm/examples/java/distorm/src/diStorm3/Opcodes.java +1123 -0
  116. data/funchook/distorm/examples/java/distorm/src/diStorm3/Operand.java +24 -0
  117. data/funchook/distorm/examples/java/distorm/src/diStorm3/distorm3.java +41 -0
  118. data/funchook/distorm/examples/java/jdistorm.c +405 -0
  119. data/funchook/distorm/examples/java/jdistorm.h +40 -0
  120. data/funchook/distorm/examples/java/jdistorm.sln +20 -0
  121. data/funchook/distorm/examples/java/jdistorm.vcproj +208 -0
  122. data/funchook/distorm/examples/linux/Makefile +15 -0
  123. data/funchook/distorm/examples/linux/main.c +181 -0
  124. data/funchook/distorm/examples/tests/Makefile +15 -0
  125. data/funchook/distorm/examples/tests/main.cpp +42 -0
  126. data/funchook/distorm/examples/tests/main.py +66 -0
  127. data/funchook/distorm/examples/tests/test_distorm3.py +1672 -0
  128. data/funchook/distorm/examples/tests/tests.sln +20 -0
  129. data/funchook/distorm/examples/tests/tests.vcxproj +82 -0
  130. data/funchook/distorm/examples/tests/tests.vcxproj.filters +22 -0
  131. data/funchook/distorm/examples/win32/disasm.sln +25 -0
  132. data/funchook/distorm/examples/win32/disasm.vcxproj +201 -0
  133. data/funchook/distorm/examples/win32/disasm.vcxproj.filters +14 -0
  134. data/funchook/distorm/examples/win32/main.cpp +163 -0
  135. data/funchook/distorm/include/distorm.h +482 -0
  136. data/funchook/distorm/include/mnemonics.h +301 -0
  137. data/funchook/distorm/make/linux/Makefile +28 -0
  138. data/funchook/distorm/make/mac/Makefile +24 -0
  139. data/funchook/distorm/make/win32/cdistorm.vcxproj +239 -0
  140. data/funchook/distorm/make/win32/cdistorm.vcxproj.filters +80 -0
  141. data/funchook/distorm/make/win32/distorm.sln +25 -0
  142. data/funchook/distorm/make/win32/resource.h +14 -0
  143. data/funchook/distorm/make/win32/resource.rc +99 -0
  144. data/funchook/distorm/python/distorm3/__init__.py +957 -0
  145. data/funchook/distorm/python/distorm3/sample.py +51 -0
  146. data/funchook/distorm/setup.cfg +10 -0
  147. data/funchook/distorm/setup.py +266 -0
  148. data/funchook/distorm/src/config.h +169 -0
  149. data/funchook/distorm/src/decoder.c +641 -0
  150. data/funchook/distorm/src/decoder.h +33 -0
  151. data/funchook/distorm/src/distorm.c +413 -0
  152. data/funchook/distorm/src/instructions.c +597 -0
  153. data/funchook/distorm/src/instructions.h +463 -0
  154. data/funchook/distorm/src/insts.c +7939 -0
  155. data/funchook/distorm/src/insts.h +64 -0
  156. data/funchook/distorm/src/mnemonics.c +284 -0
  157. data/funchook/distorm/src/operands.c +1290 -0
  158. data/funchook/distorm/src/operands.h +28 -0
  159. data/funchook/distorm/src/prefix.c +368 -0
  160. data/funchook/distorm/src/prefix.h +64 -0
  161. data/funchook/distorm/src/textdefs.c +172 -0
  162. data/funchook/distorm/src/textdefs.h +57 -0
  163. data/funchook/distorm/src/wstring.c +47 -0
  164. data/funchook/distorm/src/wstring.h +35 -0
  165. data/funchook/distorm/src/x86defs.h +82 -0
  166. data/funchook/include/funchook.h +123 -0
  167. data/funchook/install-sh +527 -0
  168. data/funchook/src/Makefile +70 -0
  169. data/funchook/src/Makefile.in +70 -0
  170. data/funchook/src/__strerror.h +109 -0
  171. data/funchook/src/config.h +101 -0
  172. data/funchook/src/config.h.in +100 -0
  173. data/funchook/src/decoder.o +0 -0
  174. data/funchook/src/distorm.o +0 -0
  175. data/funchook/src/funchook.c +440 -0
  176. data/funchook/src/funchook.o +0 -0
  177. data/funchook/src/funchook_internal.h +155 -0
  178. data/funchook/src/funchook_io.c +182 -0
  179. data/funchook/src/funchook_io.h +64 -0
  180. data/funchook/src/funchook_io.o +0 -0
  181. data/funchook/src/funchook_syscall.S +134 -0
  182. data/funchook/src/funchook_syscall.o +0 -0
  183. data/funchook/src/funchook_unix.c +480 -0
  184. data/funchook/src/funchook_unix.o +0 -0
  185. data/funchook/src/funchook_windows.c +397 -0
  186. data/funchook/src/funchook_x86.c +622 -0
  187. data/funchook/src/funchook_x86.o +0 -0
  188. data/funchook/src/instructions.o +0 -0
  189. data/funchook/src/insts.o +0 -0
  190. data/funchook/src/libfunchook.so +0 -0
  191. data/funchook/src/mnemonics.o +0 -0
  192. data/funchook/src/operands.o +0 -0
  193. data/funchook/src/os_func.c +115 -0
  194. data/funchook/src/os_func.h +75 -0
  195. data/funchook/src/os_func.o +0 -0
  196. data/funchook/src/os_func_unix.c +94 -0
  197. data/funchook/src/os_func_unix.o +0 -0
  198. data/funchook/src/os_func_windows.c +32 -0
  199. data/funchook/src/prefix.o +0 -0
  200. data/funchook/src/printf_base.c +1688 -0
  201. data/funchook/src/printf_base.h +46 -0
  202. data/funchook/src/printf_base.o +0 -0
  203. data/funchook/src/textdefs.o +0 -0
  204. data/funchook/src/wstring.o +0 -0
  205. data/funchook/test/Makefile +43 -0
  206. data/funchook/test/Makefile.in +43 -0
  207. data/funchook/test/funchook_test +0 -0
  208. data/funchook/test/libfunchook_test.c +25 -0
  209. data/funchook/test/libfunchook_test.so +0 -0
  210. data/funchook/test/libfunchook_test2.c +18 -0
  211. data/funchook/test/suffix.list +600 -0
  212. data/funchook/test/test_main.c +430 -0
  213. data/funchook/test/test_main.o +0 -0
  214. data/funchook/test/x86_64_test.S +10 -0
  215. data/funchook/test/x86_64_test.o +0 -0
  216. data/funchook/test/x86_test.S +339 -0
  217. data/funchook/win32/config.h +1 -0
  218. data/funchook/win32/funchook.sln +52 -0
  219. data/funchook/win32/funchook.vcxproj +188 -0
  220. data/funchook/win32/funchook.vcxproj.filters +84 -0
  221. data/funchook/win32/funchook_test.vcxproj +170 -0
  222. data/funchook/win32/funchook_test.vcxproj.filters +22 -0
  223. data/funchook/win32/funchook_test_dll.vcxproj +184 -0
  224. data/funchook/win32/funchook_test_dll.vcxproj.filters +30 -0
  225. data/funchook/win32/funchook_test_exe.def +3 -0
  226. data/lib/contrast-agent.rb +8 -0
  227. data/lib/contrast.rb +57 -0
  228. data/lib/contrast/agent.rb +80 -0
  229. data/lib/contrast/agent/assess.rb +45 -0
  230. data/lib/contrast/agent/assess/adjusted_span.rb +25 -0
  231. data/lib/contrast/agent/assess/class_reverter.rb +82 -0
  232. data/lib/contrast/agent/assess/contrast_event.rb +398 -0
  233. data/lib/contrast/agent/assess/frozen_properties.rb +41 -0
  234. data/lib/contrast/agent/assess/insulator.rb +53 -0
  235. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +78 -0
  236. data/lib/contrast/agent/assess/policy/patcher.rb +85 -0
  237. data/lib/contrast/agent/assess/policy/policy.rb +116 -0
  238. data/lib/contrast/agent/assess/policy/policy_node.rb +289 -0
  239. data/lib/contrast/agent/assess/policy/policy_scanner.rb +44 -0
  240. data/lib/contrast/agent/assess/policy/preshift.rb +94 -0
  241. data/lib/contrast/agent/assess/policy/propagation_method.rb +260 -0
  242. data/lib/contrast/agent/assess/policy/propagation_node.rb +127 -0
  243. data/lib/contrast/agent/assess/policy/propagator.rb +35 -0
  244. data/lib/contrast/agent/assess/policy/propagator/append.rb +54 -0
  245. data/lib/contrast/agent/assess/policy/propagator/base.rb +37 -0
  246. data/lib/contrast/agent/assess/policy/propagator/center.rb +73 -0
  247. data/lib/contrast/agent/assess/policy/propagator/custom.rb +36 -0
  248. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +62 -0
  249. data/lib/contrast/agent/assess/policy/propagator/insert.rb +55 -0
  250. data/lib/contrast/agent/assess/policy/propagator/keep.rb +26 -0
  251. data/lib/contrast/agent/assess/policy/propagator/next.rb +42 -0
  252. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +50 -0
  253. data/lib/contrast/agent/assess/policy/propagator/remove.rb +76 -0
  254. data/lib/contrast/agent/assess/policy/propagator/replace.rb +27 -0
  255. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +38 -0
  256. data/lib/contrast/agent/assess/policy/propagator/select.rb +86 -0
  257. data/lib/contrast/agent/assess/policy/propagator/splat.rb +60 -0
  258. data/lib/contrast/agent/assess/policy/propagator/split.rb +49 -0
  259. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +169 -0
  260. data/lib/contrast/agent/assess/policy/propagator/trim.rb +81 -0
  261. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +79 -0
  262. data/lib/contrast/agent/assess/policy/source_method.rb +209 -0
  263. data/lib/contrast/agent/assess/policy/source_node.rb +62 -0
  264. data/lib/contrast/agent/assess/policy/trigger_method.rb +209 -0
  265. data/lib/contrast/agent/assess/policy/trigger_node.rb +198 -0
  266. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +77 -0
  267. data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +31 -0
  268. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +40 -0
  269. data/lib/contrast/agent/assess/properties.rb +392 -0
  270. data/lib/contrast/agent/assess/rule.rb +18 -0
  271. data/lib/contrast/agent/assess/rule/base.rb +72 -0
  272. data/lib/contrast/agent/assess/rule/csrf.rb +66 -0
  273. data/lib/contrast/agent/assess/rule/csrf/csrf_action.rb +28 -0
  274. data/lib/contrast/agent/assess/rule/csrf/csrf_applicator.rb +69 -0
  275. data/lib/contrast/agent/assess/rule/csrf/csrf_watcher.rb +132 -0
  276. data/lib/contrast/agent/assess/rule/provider.rb +21 -0
  277. data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +62 -0
  278. data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +73 -0
  279. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +121 -0
  280. data/lib/contrast/agent/assess/rule/redos.rb +68 -0
  281. data/lib/contrast/agent/assess/rule/response_scanning_rule.rb +47 -0
  282. data/lib/contrast/agent/assess/rule/response_watcher.rb +36 -0
  283. data/lib/contrast/agent/assess/rule/watcher.rb +36 -0
  284. data/lib/contrast/agent/assess/tag.rb +151 -0
  285. data/lib/contrast/agent/at_exit_hook.rb +33 -0
  286. data/lib/contrast/agent/class_reopener.rb +195 -0
  287. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +26 -0
  288. data/lib/contrast/agent/deadzone/policy/policy.rb +57 -0
  289. data/lib/contrast/agent/disable_reaction.rb +24 -0
  290. data/lib/contrast/agent/exclusion_matcher.rb +190 -0
  291. data/lib/contrast/agent/feature_state.rb +379 -0
  292. data/lib/contrast/agent/inventory/policy/policy.rb +32 -0
  293. data/lib/contrast/agent/inventory/policy/trigger_node.rb +22 -0
  294. data/lib/contrast/agent/logger_manager.rb +116 -0
  295. data/lib/contrast/agent/middleware.rb +352 -0
  296. data/lib/contrast/agent/module_data.rb +16 -0
  297. data/lib/contrast/agent/patching/policy/after_load_patch.rb +37 -0
  298. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +58 -0
  299. data/lib/contrast/agent/patching/policy/method_policy.rb +94 -0
  300. data/lib/contrast/agent/patching/policy/module_policy.rb +116 -0
  301. data/lib/contrast/agent/patching/policy/patch.rb +312 -0
  302. data/lib/contrast/agent/patching/policy/patch_status.rb +192 -0
  303. data/lib/contrast/agent/patching/policy/patcher.rb +310 -0
  304. data/lib/contrast/agent/patching/policy/policy.rb +138 -0
  305. data/lib/contrast/agent/patching/policy/policy_node.rb +80 -0
  306. data/lib/contrast/agent/patching/policy/policy_unpatcher.rb +28 -0
  307. data/lib/contrast/agent/patching/policy/trigger_node.rb +81 -0
  308. data/lib/contrast/agent/protect/policy/policy.rb +37 -0
  309. data/lib/contrast/agent/protect/policy/trigger_node.rb +23 -0
  310. data/lib/contrast/agent/protect/rule.rb +58 -0
  311. data/lib/contrast/agent/protect/rule/base.rb +300 -0
  312. data/lib/contrast/agent/protect/rule/base_service.rb +88 -0
  313. data/lib/contrast/agent/protect/rule/cmd_injection.rb +156 -0
  314. data/lib/contrast/agent/protect/rule/csrf.rb +118 -0
  315. data/lib/contrast/agent/protect/rule/csrf/csrf_evaluator.rb +103 -0
  316. data/lib/contrast/agent/protect/rule/csrf/csrf_token_injector.rb +85 -0
  317. data/lib/contrast/agent/protect/rule/default_scanner.rb +300 -0
  318. data/lib/contrast/agent/protect/rule/deserialization.rb +193 -0
  319. data/lib/contrast/agent/protect/rule/http_method_tampering.rb +80 -0
  320. data/lib/contrast/agent/protect/rule/no_sqli.rb +101 -0
  321. data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +40 -0
  322. data/lib/contrast/agent/protect/rule/path_traversal.rb +143 -0
  323. data/lib/contrast/agent/protect/rule/sqli.rb +101 -0
  324. data/lib/contrast/agent/protect/rule/sqli/default_sql_scanner.rb +16 -0
  325. data/lib/contrast/agent/protect/rule/sqli/mysql_sql_scanner.rb +38 -0
  326. data/lib/contrast/agent/protect/rule/sqli/postgres_sql_scanner.rb +22 -0
  327. data/lib/contrast/agent/protect/rule/sqli/sqlite_sql_scanner.rb +19 -0
  328. data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +20 -0
  329. data/lib/contrast/agent/protect/rule/xss.rb +24 -0
  330. data/lib/contrast/agent/protect/rule/xxe.rb +120 -0
  331. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +82 -0
  332. data/lib/contrast/agent/railtie.rb +30 -0
  333. data/lib/contrast/agent/reaction_processor.rb +47 -0
  334. data/lib/contrast/agent/request.rb +493 -0
  335. data/lib/contrast/agent/request_context.rb +225 -0
  336. data/lib/contrast/agent/require_state.rb +61 -0
  337. data/lib/contrast/agent/response.rb +215 -0
  338. data/lib/contrast/agent/rewriter.rb +244 -0
  339. data/lib/contrast/agent/scope.rb +28 -0
  340. data/lib/contrast/agent/service_heartbeat.rb +37 -0
  341. data/lib/contrast/agent/settings_state.rb +148 -0
  342. data/lib/contrast/agent/socket_client.rb +125 -0
  343. data/lib/contrast/agent/thread.rb +26 -0
  344. data/lib/contrast/agent/tracepoint_hook.rb +51 -0
  345. data/lib/contrast/agent/version.rb +8 -0
  346. data/lib/contrast/api.rb +17 -0
  347. data/lib/contrast/api/.gitkeep +0 -0
  348. data/lib/contrast/api/connection_status.rb +49 -0
  349. data/lib/contrast/api/socket.rb +43 -0
  350. data/lib/contrast/api/speedracer.rb +206 -0
  351. data/lib/contrast/api/tcp_socket.rb +31 -0
  352. data/lib/contrast/api/unix_socket.rb +25 -0
  353. data/lib/contrast/common_agent_configuration.rb +86 -0
  354. data/lib/contrast/components/agent.rb +85 -0
  355. data/lib/contrast/components/app_context.rb +188 -0
  356. data/lib/contrast/components/assess.rb +67 -0
  357. data/lib/contrast/components/config.rb +135 -0
  358. data/lib/contrast/components/contrast_service.rb +113 -0
  359. data/lib/contrast/components/heap_dump.rb +34 -0
  360. data/lib/contrast/components/interface.rb +178 -0
  361. data/lib/contrast/components/inventory.rb +23 -0
  362. data/lib/contrast/components/logger.rb +92 -0
  363. data/lib/contrast/components/protect.rb +38 -0
  364. data/lib/contrast/components/sampling.rb +41 -0
  365. data/lib/contrast/components/scope.rb +106 -0
  366. data/lib/contrast/components/settings.rb +140 -0
  367. data/lib/contrast/config.rb +33 -0
  368. data/lib/contrast/config/agent_configuration.rb +24 -0
  369. data/lib/contrast/config/application_configuration.rb +27 -0
  370. data/lib/contrast/config/assess_configuration.rb +22 -0
  371. data/lib/contrast/config/assess_rules_configuration.rb +18 -0
  372. data/lib/contrast/config/base_configuration.rb +105 -0
  373. data/lib/contrast/config/default_value.rb +16 -0
  374. data/lib/contrast/config/exception_configuration.rb +21 -0
  375. data/lib/contrast/config/heap_dump_configuration.rb +23 -0
  376. data/lib/contrast/config/inventory_configuration.rb +20 -0
  377. data/lib/contrast/config/logger_configuration.rb +20 -0
  378. data/lib/contrast/config/protect_configuration.rb +20 -0
  379. data/lib/contrast/config/protect_rule_configuration.rb +37 -0
  380. data/lib/contrast/config/protect_rules_configuration.rb +30 -0
  381. data/lib/contrast/config/root_configuration.rb +26 -0
  382. data/lib/contrast/config/ruby_configuration.rb +39 -0
  383. data/lib/contrast/config/sampling_configuration.rb +22 -0
  384. data/lib/contrast/config/server_configuration.rb +23 -0
  385. data/lib/contrast/config/service_configuration.rb +22 -0
  386. data/lib/contrast/configuration.rb +214 -0
  387. data/lib/contrast/core_extensions/assess.rb +51 -0
  388. data/lib/contrast/core_extensions/assess/array.rb +58 -0
  389. data/lib/contrast/core_extensions/assess/assess_extension.rb +145 -0
  390. data/lib/contrast/core_extensions/assess/basic_object.rb +15 -0
  391. data/lib/contrast/core_extensions/assess/erb.rb +42 -0
  392. data/lib/contrast/core_extensions/assess/exec_trigger.rb +48 -0
  393. data/lib/contrast/core_extensions/assess/fiber.rb +125 -0
  394. data/lib/contrast/core_extensions/assess/hash.rb +22 -0
  395. data/lib/contrast/core_extensions/assess/kernel.rb +95 -0
  396. data/lib/contrast/core_extensions/assess/module.rb +14 -0
  397. data/lib/contrast/core_extensions/assess/regexp.rb +206 -0
  398. data/lib/contrast/core_extensions/assess/string.rb +75 -0
  399. data/lib/contrast/core_extensions/assess/tilt_template_trigger.rb +73 -0
  400. data/lib/contrast/core_extensions/delegator.rb +14 -0
  401. data/lib/contrast/core_extensions/eval_trigger.rb +52 -0
  402. data/lib/contrast/core_extensions/inventory.rb +22 -0
  403. data/lib/contrast/core_extensions/inventory/datastores.rb +37 -0
  404. data/lib/contrast/core_extensions/module.rb +42 -0
  405. data/lib/contrast/core_extensions/object.rb +27 -0
  406. data/lib/contrast/core_extensions/protect.rb +20 -0
  407. data/lib/contrast/core_extensions/protect/applies_command_injection_rule.rb +70 -0
  408. data/lib/contrast/core_extensions/protect/applies_deserialization_rule.rb +58 -0
  409. data/lib/contrast/core_extensions/protect/applies_no_sqli_rule.rb +81 -0
  410. data/lib/contrast/core_extensions/protect/applies_path_traversal_rule.rb +119 -0
  411. data/lib/contrast/core_extensions/protect/applies_sqli_rule.rb +63 -0
  412. data/lib/contrast/core_extensions/protect/applies_xxe_rule.rb +141 -0
  413. data/lib/contrast/core_extensions/protect/kernel.rb +30 -0
  414. data/lib/contrast/core_extensions/protect/psych.rb +7 -0
  415. data/lib/contrast/core_extensions/thread.rb +31 -0
  416. data/lib/contrast/internal_exception.rb +8 -0
  417. data/lib/contrast/rails_extensions/assess/action_controller_inheritance.rb +48 -0
  418. data/lib/contrast/rails_extensions/assess/active_record.rb +32 -0
  419. data/lib/contrast/rails_extensions/assess/active_record_named.rb +61 -0
  420. data/lib/contrast/rails_extensions/assess/configuration.rb +26 -0
  421. data/lib/contrast/rails_extensions/buffer.rb +30 -0
  422. data/lib/contrast/rails_extensions/rack.rb +45 -0
  423. data/lib/contrast/security_exception.rb +14 -0
  424. data/lib/contrast/sinatra_extensions/assess/cookie.rb +26 -0
  425. data/lib/contrast/sinatra_extensions/inventory/sinatra_base.rb +59 -0
  426. data/lib/contrast/tasks/service.rb +95 -0
  427. data/lib/contrast/utils/assess/sampling_util.rb +96 -0
  428. data/lib/contrast/utils/assess/tracking_util.rb +39 -0
  429. data/lib/contrast/utils/boolean_util.rb +33 -0
  430. data/lib/contrast/utils/cache.rb +69 -0
  431. data/lib/contrast/utils/class_util.rb +58 -0
  432. data/lib/contrast/utils/comment_range.rb +19 -0
  433. data/lib/contrast/utils/data_store_util.rb +23 -0
  434. data/lib/contrast/utils/duck_utils.rb +58 -0
  435. data/lib/contrast/utils/env_configuration_item.rb +52 -0
  436. data/lib/contrast/utils/environment_util.rb +152 -0
  437. data/lib/contrast/utils/freeze_util.rb +36 -0
  438. data/lib/contrast/utils/gemfile_reader.rb +191 -0
  439. data/lib/contrast/utils/hash_digest.rb +148 -0
  440. data/lib/contrast/utils/heap_dump_util.rb +113 -0
  441. data/lib/contrast/utils/invalid_configuration_util.rb +88 -0
  442. data/lib/contrast/utils/inventory_util.rb +126 -0
  443. data/lib/contrast/utils/io_util.rb +61 -0
  444. data/lib/contrast/utils/object_share.rb +117 -0
  445. data/lib/contrast/utils/operating_environment.rb +38 -0
  446. data/lib/contrast/utils/os.rb +49 -0
  447. data/lib/contrast/utils/path_util.rb +151 -0
  448. data/lib/contrast/utils/performs_logging.rb +152 -0
  449. data/lib/contrast/utils/preflight_util.rb +13 -0
  450. data/lib/contrast/utils/prevent_serialization.rb +52 -0
  451. data/lib/contrast/utils/rack_assess_session_cookie.rb +104 -0
  452. data/lib/contrast/utils/rails_assess_configuration.rb +95 -0
  453. data/lib/contrast/utils/random_util.rb +22 -0
  454. data/lib/contrast/utils/resource_loader.rb +23 -0
  455. data/lib/contrast/utils/ruby_ast_rewriter.rb +74 -0
  456. data/lib/contrast/utils/scope_util.rb +99 -0
  457. data/lib/contrast/utils/service_response_util.rb +116 -0
  458. data/lib/contrast/utils/service_sender_util.rb +98 -0
  459. data/lib/contrast/utils/sha256_builder.rb +69 -0
  460. data/lib/contrast/utils/sinatra_helper.rb +49 -0
  461. data/lib/contrast/utils/stack_trace_utils.rb +209 -0
  462. data/lib/contrast/utils/string_utils.rb +72 -0
  463. data/lib/contrast/utils/tag_util.rb +139 -0
  464. data/lib/contrast/utils/thread_tracker.rb +54 -0
  465. data/lib/contrast/utils/timer.rb +78 -0
  466. data/resources/assess/policy.json +1673 -0
  467. data/resources/csrf/inject.js +44 -0
  468. data/resources/deadzone/policy.json +55 -0
  469. data/resources/factory-bot-spec/spec_helper.rb +30 -0
  470. data/resources/inventory/policy.json +110 -0
  471. data/resources/protect/policy.json +417 -0
  472. data/resources/rubocops/kernel/catch_cop.rb +37 -0
  473. data/resources/rubocops/kernel/require_cop.rb +37 -0
  474. data/resources/rubocops/kernel/require_relative_cop.rb +33 -0
  475. data/resources/rubocops/module/autoload_cop.rb +37 -0
  476. data/resources/rubocops/module/const_defined_cop.rb +37 -0
  477. data/resources/rubocops/module/const_get_cop.rb +37 -0
  478. data/resources/rubocops/module/const_set_cop.rb +37 -0
  479. data/resources/rubocops/module/constants_cop.rb +37 -0
  480. data/resources/rubocops/module/name_cop.rb +37 -0
  481. data/resources/rubocops/object/class_cop.rb +37 -0
  482. data/resources/rubocops/object/freeze_cop.rb +37 -0
  483. data/resources/rubocops/object/frozen_cop.rb +37 -0
  484. data/resources/rubocops/object/is_a_cop.rb +37 -0
  485. data/resources/rubocops/object/method_cop.rb +37 -0
  486. data/resources/rubocops/object/respond_to_cop.rb +37 -0
  487. data/resources/rubocops/object/singleton_class_cop.rb +37 -0
  488. data/resources/rubocops/regexp/spelling_cop.rb +44 -0
  489. data/resources/rubocops/thread/new_cop.rb +39 -0
  490. data/resources/ruby-spec/ancestors_spec.rb +70 -0
  491. data/resources/ruby-spec/modulo_spec.rb +831 -0
  492. data/resources/ruby-spec/parameters_spec.rb +261 -0
  493. data/resources/ruby-spec/ruby_spec_spec_helper.rb +35 -0
  494. data/resources/test_marker.txt +1 -0
  495. data/ruby-agent.gemspec +129 -0
  496. data/service_executables/.gitkeep +0 -0
  497. data/service_executables/VERSION +1 -0
  498. data/service_executables/linux/contrast-service +0 -0
  499. data/service_executables/mac/contrast-service +0 -0
  500. 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