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,198 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Agent
6
+ module Assess
7
+ module Policy
8
+ # This class functions to translate our policy.json into an actionable
9
+ # Ruby object, allowing for dynamic patching over hardcoded patching,
10
+ # specifically for those methods which result in the trigger of a
11
+ # vulnerability (indicate points in the application where uncontrolled
12
+ # user input can do damage).
13
+ class TriggerNode < PolicyNode
14
+ JSON_BAD_VALUE = 'bad_value'
15
+ JSON_GOOD_VALUE = 'good_value'
16
+ JSON_DISALLOWED_TAGS = 'disallowed_tags'
17
+ JSON_REQUIRED_TAGS = 'required_tags'
18
+ JSON_NODES = 'nodes'
19
+ JSON_RULE_NAME = 'name'
20
+ JSON_CUSTOM_PATCH = 'custom_patch'
21
+
22
+ attr_reader :rule_id
23
+ attr_accessor :required_tags, :disallowed_tags, :good_value, :bad_value
24
+
25
+ def initialize trigger_hash = {}, rule_hash = {}
26
+ super(trigger_hash)
27
+ good_value = trigger_hash[JSON_GOOD_VALUE]
28
+ bad_value = trigger_hash[JSON_BAD_VALUE]
29
+ @good_value = Regexp.new(good_value, true) if good_value
30
+ @bad_value = Regexp.new(bad_value, true) if bad_value
31
+ @regexp = !@dataflow && (@good_value || @bad_value)
32
+ @custom_patch = trigger_hash.fetch(JSON_CUSTOM_PATCH, false)
33
+ @rule_id = rule_hash.fetch(JSON_RULE_NAME) # raises KeyError exception if not found
34
+ @dataflow = rule_hash.fetch(JSON_DATAFLOW, true)
35
+ @required_tags = populate_tags(rule_hash[JSON_REQUIRED_TAGS])
36
+ @disallowed_tags = populate_disallowed(rule_hash[JSON_DISALLOWED_TAGS])
37
+ @trigger_class = trigger_hash['trigger_class']
38
+ @trigger_method = trigger_hash['trigger_method']
39
+ @trigger_method = @trigger_method.to_sym if @trigger_method
40
+ validate
41
+ end
42
+
43
+ TRIGGER = 'Trigger'
44
+ def node_class
45
+ TRIGGER
46
+ end
47
+
48
+ def apply_custom_trigger context, trigger_node, source, object, ret, invoked, *args
49
+ custom_trigger_class.send(@trigger_method, context, trigger_node, source, object, ret, invoked, *args)
50
+ end
51
+
52
+ def custom_trigger_class
53
+ @_custom_trigger_class ||= Object.cs__const_get(@trigger_class)
54
+ end
55
+
56
+ def custom_trigger?
57
+ @trigger_class && @trigger_method
58
+ end
59
+
60
+ def custom_patch?
61
+ @custom_patch
62
+ end
63
+
64
+ def node_type
65
+ :TYPE_METHOD
66
+ end
67
+
68
+ def rule_disabled?
69
+ Contrast::Agent::FeatureState.instance.assess_disabled_rules.include?(rule_id)
70
+ end
71
+
72
+ # Indicate if this is a dataflow based trigger, meaning it has a proper
73
+ # synch that requires a tainted source to reach it. If this returns
74
+ # false, this rule is for method validation, ensuring that an insecure
75
+ # method, such as a non-cryptographically secure random, is not invoked
76
+ def dataflow?
77
+ @dataflow
78
+ end
79
+
80
+ # Indicate if this is a regexp based trigger, meaning it has a proper
81
+ # synch that requires a source to reach it. While this type of rule
82
+ # does not require the source to be tainted, it does validate it with
83
+ # a regular expression to determine if the method is being invoked
84
+ # safely.
85
+ def regexp_rule?
86
+ @regexp
87
+ end
88
+
89
+ # the name of the rule, in capital & underscore format
90
+ # used to make it match enum things in TeamServer
91
+ def loud_name
92
+ @_loud_name ||= rule_id.upcase.gsub(Contrast::Utils::ObjectShare::DASH,
93
+ Contrast::Utils::ObjectShare::UNDERSCORE)
94
+ end
95
+
96
+ # Determine if a dataflow rule violation has occurred
97
+ def violated? source
98
+ # if the source isn't tracked, there can't be a violation
99
+ # this condition may not hold true forever, but for now it's
100
+ # a nice optimization
101
+ return false unless source.cs__tracked?
102
+
103
+ # find the ranges that violate the rule (untrusted, etc)
104
+ vulnerable_ranges = find_ranges_by_tag(source.cs__properties, required_tags)
105
+ # if there aren't any vulnerable ranges, nope out
106
+ return false if vulnerable_ranges.empty?
107
+
108
+ # find the ranges that are exempt from the rule
109
+ # (validated, sanitized, etc)
110
+ secure_ranges = find_ranges_by_tag(source.cs__properties, disallowed_tags)
111
+ # if there are vulnerable ranges and no secure, report
112
+ return true if secure_ranges.empty?
113
+
114
+ # figure out if there are any vulnerable ranges that aren't
115
+ # covered by a secure one. if there are, the rule was violated
116
+ !Contrast::Utils::TagUtil.covered?(vulnerable_ranges, secure_ranges)
117
+ end
118
+
119
+ # Standard validation + TS trace version two rules:
120
+ # Must have source
121
+ def validate
122
+ super
123
+ # If this isn't a dataflow rule, it can't have a source
124
+ return unless dataflow?
125
+ raise(ArgumentError, "Trigger #{ id } did not have a proper source. Unable to create.") unless sources&.any?
126
+ end
127
+
128
+ private
129
+
130
+ # By default, any rule will be triggered if the source
131
+ # of the rule event has an untrusted tag range that is
132
+ # not covered by one of its disallowed tags.
133
+ UNTRUSTED = 'UNTRUSTED'
134
+ def populate_tags required_tags
135
+ return unless dataflow?
136
+
137
+ validate_rule_tags(required_tags)
138
+ @required_tags = required_tags || []
139
+ @required_tags << UNTRUSTED
140
+ end
141
+
142
+ ENCODER_START = 'CUSTOM_ENCODED_'
143
+ VALIDATOR_START = 'CUSTOM_VALIDATED_'
144
+ # If a level 1 rule comes from TeamServer, it will have the
145
+ # tag 'custom-encoder-#{ name }' or 'custom-validator-#{ name }'.
146
+ # All rules should take this into account.
147
+ # Additionally, if something is marked 'limited-chars' it means
148
+ # it has been properly vetted to not contain dangerous input.
149
+ LIMITED_CHARS = 'LIMITED_CHARS'
150
+ CUSTOM_ENCODED = 'CUSTOM_ENCODED'
151
+ CUSTOM_VALIDATED = 'CUSTOM_VALIDATED'
152
+ def populate_disallowed disallowed_tags
153
+ return unless dataflow?
154
+
155
+ validate_rule_tags(disallowed_tags)
156
+ @disallowed_tags = disallowed_tags || []
157
+ @disallowed_tags << LIMITED_CHARS
158
+ @disallowed_tags << CUSTOM_ENCODED
159
+ @disallowed_tags << CUSTOM_VALIDATED
160
+ @disallowed_tags << ENCODER_START + loud_name
161
+ @disallowed_tags << VALIDATOR_START + loud_name
162
+ end
163
+
164
+ ENCODED_MARKER = '_ENCODED'
165
+ def validate_rule_tags tags
166
+ return unless tags
167
+
168
+ tags.each do |tag|
169
+ raise(ArgumentError, "Rule #{ rule_id } had an invalid tag. #{ tag } is not a known value.") unless
170
+ Contrast::Agent::Assess::Policy::PolicyNode::VALID_TAGS.include?(tag) ||
171
+ Contrast::Agent::Assess::Policy::PolicyNode::VALID_SOURCE_TAGS.include?(tag)
172
+ end
173
+ end
174
+
175
+ def find_ranges_by_tag cs__properties, tags
176
+ ranges = []
177
+
178
+ # if there aren't any all_tags or tags, break early
179
+ return ranges unless cs__properties.tracked?
180
+ return ranges unless tags&.any?
181
+
182
+ # find all tags that match the desired ones
183
+ tags.each do |desired|
184
+ found = cs__properties.fetch_tag(desired)
185
+ next unless found
186
+
187
+ # we need to dup here so that we don't change the tags if target is
188
+ # used in another trace
189
+ ranges = Contrast::Utils::TagUtil.ordered_merge(ranges, found.dup)
190
+ end
191
+
192
+ ranges
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,77 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Agent
6
+ module Assess
7
+ module Policy
8
+ module TriggerValidation
9
+ # Validator used to assert a SSRF finding is actually vulnerable
10
+ # before serializing that finding as a DTM to report to the service.
11
+ module SSRFValidator
12
+ SSRF_RULE = 'ssrf'
13
+ URL_PATTERN =
14
+ %r{(?<protocol>http|https|ftp|sftp|telnet|gopher|rtsp|rtsps|ssh|svn)://(?<host>[^/?]+)(?<path>/?[^?]*)(?<query_string>\?.*)?}i.cs__freeze
15
+ # The Net::HTTP class validates host format on instantiation. Since
16
+ # our triggers for that class are on the instance, they already
17
+ # have this validation done for them. We do not need to apply the
18
+ # validation in this case.
19
+ PATH_ONLY_PATCH_MARKER = 'Assess:Trigger:Net::HTTP#'
20
+
21
+ # A finding is valid for SSRF if the source of the trigger event is
22
+ # a valid URL in which the User controls a section prior to the
23
+ # querystring
24
+ # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/server_side_request_forgery.md
25
+ def self.valid? patcher, _object, _ret, args
26
+ return true unless SSRF_RULE == patcher&.rule_id
27
+ return true if patcher.id.to_s.start_with?(PATH_ONLY_PATCH_MARKER)
28
+
29
+ url = args[0].to_s
30
+ match = url.match(URL_PATTERN)
31
+ return false unless match
32
+
33
+ # It is dangerous for the user to control a section of the URL
34
+ # between the start of the protocol and the beginning of the
35
+ # querystring. If there is no querystring, then the entire URL is
36
+ # dangerous for the User to control.
37
+ start = match.begin(:protocol)
38
+ finish = match.begin(:query_string)
39
+ finish ||= url.length
40
+
41
+ args[0].cs__properties.any_tags_between?(start, finish)
42
+ end
43
+
44
+ # Some SSRF triggers take multiple parameters to build the URL.
45
+ # For the net_http_# sources, if the first parameter is a String,
46
+ # the URL is built by appending the first and second parameters.
47
+ def self.composite? patcher, args
48
+ id = patcher.id.to_s
49
+ return false unless id.start_with?(PATH_ONLY_PATCH_MARKER)
50
+
51
+ args[0].is_a?(String)
52
+ end
53
+
54
+ def self.build_single_source args
55
+ return nil unless args[0].cs__respond_to?(:cs__properties)
56
+
57
+ args[0].to_s
58
+ end
59
+
60
+ def self.build_composite_source args
61
+ if args.length > 1
62
+ return nil unless args[0].cs__respond_to?(:cs__properties) ||
63
+ args[1].cs__respond_to?(:cs__properties)
64
+
65
+ args[0] + args[1]
66
+ else
67
+ return nil unless args[0].cs__respond_to?(:cs__properties)
68
+
69
+ args[0].to_s
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,31 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ cs__scoped_require 'contrast/agent/assess/policy/trigger_validation/ssrf_validator'
5
+ cs__scoped_require 'contrast/agent/assess/policy/trigger_validation/xss_validator'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Assess
10
+ module Policy
11
+ # Some of our triggers require transformation or validation prior to
12
+ # reporting in order to account for false positives or other aberrant
13
+ # conditions. This provides a single place from which those validations
14
+ # can be called.
15
+ module TriggerValidation
16
+ VALIDATORS = [
17
+ Contrast::Agent::Assess::Policy::TriggerValidation::SSRFValidator,
18
+ Contrast::Agent::Assess::Policy::TriggerValidation::XSSValidator
19
+ ].cs__freeze
20
+
21
+ def self.valid? patcher, object, ret, args
22
+ VALIDATORS.each do |validator|
23
+ return false unless validator.valid?(patcher, object, ret, args)
24
+ end
25
+ true
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Agent
6
+ module Assess
7
+ module Policy
8
+ module TriggerValidation
9
+ # Validator used to assert a Reflected XSS finding is actually
10
+ # vulnerable before serializing that finding as a DTM to report to
11
+ # the service.
12
+ module XSSValidator
13
+ XSS_RULE = 'reflected-xss'
14
+ SAFE_CONTENT_TYPES = %w[
15
+ /csv
16
+ /javascript
17
+ /json
18
+ /pdf
19
+ /x-javascript
20
+ /x-json
21
+ ].cs__freeze
22
+
23
+ # A finding is valid for XSS if the response type is not one of
24
+ # those assumed to be safe
25
+ # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/reflected_xss.md
26
+ def self.valid? patcher, _object, _ret, _args
27
+ return true unless XSS_RULE == patcher&.rule_id
28
+
29
+ content_type = Contrast::Agent::REQUEST_TRACKER.current&.response&.content_type
30
+ return true unless content_type
31
+
32
+ content_type = content_type.downcase
33
+ SAFE_CONTENT_TYPES.none? { |safe_type| content_type.index(safe_type) }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,392 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ cs__scoped_require 'set'
5
+ cs__scoped_require 'base64'
6
+ cs__scoped_require 'contrast/utils/prevent_serialization'
7
+ cs__scoped_require 'contrast/utils/tag_util'
8
+ cs__scoped_require 'contrast/agent/assess/contrast_event'
9
+
10
+ module Contrast
11
+ module Agent
12
+ module Assess
13
+ # Properties associated with a tracked String. If String is monkey
14
+ # patched this object is lazily generated on affected Strings.
15
+ #
16
+ # This class acts as a holder for the Assess information we need in order
17
+ # to properly convey the events that lead up to the state of the tracked
18
+ # user input.
19
+ class Properties
20
+ include Contrast::Utils::PreventSerialization
21
+
22
+ # CONTRAST-36937
23
+ # Creating these on Properties is expensive. We want to delay this for
24
+ # as long as possible.
25
+ def properties
26
+ @_properties ||= {}
27
+ end
28
+
29
+ def events
30
+ @_events ||= []
31
+ end
32
+
33
+ def tracked?
34
+ tags? && tags.any?
35
+ end
36
+
37
+ def tagged? label
38
+ tags? && tags.key?(label)
39
+ end
40
+
41
+ def add_properties hash
42
+ return unless hash
43
+
44
+ properties.merge!(hash)
45
+ end
46
+
47
+ def add_property name, value
48
+ return unless name && value
49
+
50
+ properties[name] = value
51
+ end
52
+
53
+ def any_tags_between? start, finish
54
+ return false unless tags?
55
+
56
+ range = Contrast::Agent::Assess::Tag.new(start + finish, start)
57
+ tags.each_value do |tag_array|
58
+ return true if tag_array.any? { |tag| tag.overlaps?(range) }
59
+ end
60
+ false
61
+ end
62
+
63
+ # Find all of the ranges that span a given index. This is used
64
+ # in propagation when we need to shift tags about. For instance, in
65
+ # the append method when we need to see if any tag at the end needs
66
+ # to be expanded out to the size of the new String.
67
+ #
68
+ # Note: Tags do not know their key, so this is only the range covered
69
+ def tags_at idx
70
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags?
71
+
72
+ at = []
73
+ tags.each_value do |tag_array|
74
+ tag_array.each do |tag|
75
+ if tag.covers?(idx)
76
+ at << tag
77
+ elsif tag.above?(idx)
78
+ break
79
+ end
80
+ end
81
+ end
82
+ at
83
+ end
84
+
85
+ # given a range, select all tags in that range the selected tags are
86
+ # shifted such that the start index of the new tag (0) aligns with the
87
+ # given start index in the range
88
+ #
89
+ # current tags: 5-15
90
+ # range : 5-10
91
+ # result : 0-05
92
+ def tags_at_range range
93
+ return Contrast::Utils::ObjectShare::EMPTY_HASH unless tags?
94
+
95
+ at = Hash.new { |h, k| h[k] = [] }
96
+ length = range.stop - range.start
97
+ tags.each_pair do |key, value|
98
+ add = []
99
+ value.each do |tag|
100
+ comparison = tag.compare_range(range.start, range.stop)
101
+ # BELOW and ABOVE are applicable to this check and are removed.
102
+ case comparison
103
+ # part of the tag is being selected
104
+ when Contrast::Agent::Assess::Tag::LOW_SPAN
105
+ add << Contrast::Agent::Assess::Tag.new(length)
106
+ # the tag exists in the requested range, figure out the boundaries
107
+ when Contrast::Agent::Assess::Tag::WITHIN
108
+ start = tag.start_idx - range.start
109
+ finish = length - start
110
+ add << Contrast::Agent::Assess::Tag.new(finish, start)
111
+ # the tag spans the requested range.
112
+ when Contrast::Agent::Assess::Tag::WITHOUT
113
+ add << Contrast::Agent::Assess::Tag.new(length)
114
+ # part of the tag is being selected
115
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN
116
+ add << Contrast::Agent::Assess::Tag.new(length)
117
+ end
118
+ end
119
+ next if add.empty?
120
+
121
+ at[key] = add
122
+ end
123
+ at
124
+ end
125
+
126
+ # Given a tag name and range object, add a new tag to this
127
+ # collection. If the given range touches an existing tag,
128
+ # we'll combine the two, adjusting the existing one and
129
+ # dropping this new one.
130
+ def add_tag label, range
131
+ length = range.stop - range.start
132
+ tag = Contrast::Agent::Assess::Tag.new(length, range.start)
133
+ existing = fetch_tag(label)
134
+ tags[label] = Contrast::Utils::TagUtil.ordered_merge(existing, tag)
135
+ end
136
+
137
+ def set_tags label, tag_ranges
138
+ tags[label] = tag_ranges
139
+ end
140
+
141
+ # Remove all tags with a given label
142
+ def delete_tags label
143
+ return unless tags?
144
+
145
+ tags.delete(label)
146
+ end
147
+
148
+ # Reset the tag hash
149
+ def clear_tags
150
+ return unless tags?
151
+
152
+ tags.clear
153
+ end
154
+
155
+ # Returns a list of all current tag labels, most likely to be used for
156
+ # a splat operation
157
+ def tag_keys
158
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags?
159
+
160
+ tags.keys
161
+ end
162
+
163
+ # Calls merge to combine touching or overlapping tags
164
+ # Deletes empty tags
165
+ def cleanup_tags
166
+ return unless tags?
167
+
168
+ Contrast::Utils::TagUtil.merge_tags(tags)
169
+ tags.delete_if { |_, value| value.empty? }
170
+ end
171
+
172
+ # We'll use this as a helper method to retrieve tags from the hash.
173
+ # Because the hash auto-populates an empty array when we try to access
174
+ # a tag in it, we cannot use the [] method without side effect. To get
175
+ # around this, we'll use a fetch work around.
176
+ def fetch_tag label
177
+ return unless tags?
178
+
179
+ tags.fetch(label, nil)
180
+ end
181
+
182
+ # Convert the tags of this object into the TraceTaintRange requried to
183
+ # be sent to the service
184
+ def tags_to_dtm
185
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags?
186
+
187
+ ranges = []
188
+ tags.each_pair do |key, value|
189
+ next if value.empty?
190
+
191
+ value.each do |tag|
192
+ range = Contrast::Api::Dtm::TraceTaintRange.new
193
+ range.tag = Contrast::Utils::StringUtils.protobuf_safe_string(key)
194
+ range.range = tag.start_idx.to_s + Contrast::Utils::ObjectShare::COLON + tag.end_idx.to_s
195
+ ranges << range
196
+ end
197
+ end
198
+ ranges
199
+ end
200
+
201
+ # Remove all tags within the given ranges.
202
+ # This does not delete an entire tag if part of that tag is
203
+ # outside this range, meaning we may reduce sizes of tags
204
+ # or split them.
205
+ #
206
+ # If shift is true, it is assumed the characters at those ranges were
207
+ # removed. If shift is false, it is assumed those ranges were replaced
208
+ # by the same number of characters and no shift is needed.
209
+ #
210
+ # current tags: 0-15
211
+ # range: 5-10
212
+ # result: 0-5, 10-15
213
+ def delete_tags_at_ranges ranges, shift = true
214
+ return unless tags?
215
+
216
+ # Stage one - delete the tags w/o changing their
217
+ # location.
218
+ ranges.each do |range|
219
+ remove_tags(range)
220
+ end
221
+
222
+ return unless shift
223
+
224
+ # the amount we've already removed from the string
225
+ shift = 0
226
+ # Stage two - shift the tags to the left to account
227
+ # for the sections that were deleted.
228
+ ranges.each do |range|
229
+ shift_tags_for_deletion(range, shift)
230
+ shift += (range.stop - range.start)
231
+ end
232
+
233
+ # Clean up and merge any touching tags
234
+ Contrast::Utils::TagUtil.merge_tags(tags)
235
+ end
236
+
237
+ # Shift all the tags in this object by the given ranges.
238
+ # This method assumes the ranges are sorted, meaning
239
+ # the leftmost (lowest) range is first
240
+ #
241
+ # current tags: 0-15
242
+ # range: 5-10
243
+ # result: 0-5, 10-20
244
+ def shift_tags ranges
245
+ return unless tags?
246
+
247
+ ranges.each do |range|
248
+ shift_tags_for_insertion(range)
249
+ end
250
+ end
251
+
252
+ # Add an event to these properties. It will be used to build
253
+ # a trace if this object ends up in a trigger.
254
+ def add_event event
255
+ events << event
256
+ self
257
+ end
258
+
259
+ def build_event policy_node, tagged, object, ret, args, invoked = 0, source_type = nil, source_name = nil
260
+ event = Contrast::Agent::Assess::ContrastEvent.new(policy_node, tagged, object, ret, args, invoked, source_type, source_name)
261
+ add_event(event)
262
+ report_sources(tagged, event)
263
+ end
264
+
265
+ private
266
+
267
+ def report_sources tagged, event
268
+ return unless tagged && !tagged.to_s.empty?
269
+ return unless event&.source_type
270
+
271
+ current_request = Contrast::Agent::REQUEST_TRACKER.current
272
+ return unless current_request
273
+ return if current_request.observed_route.sources.any? { |source| source.type == event.forced_source_type && source.name == event.forced_source_name }
274
+
275
+ event_source_dtm = event.build_event_source_dtm
276
+ return unless event_source_dtm
277
+
278
+ current_request.observed_route.sources << event_source_dtm
279
+ end
280
+
281
+ # Because of the auto-fill thing, we should not allow direct access to
282
+ # the tags hash. Instead, the methods above should be used to do
283
+ # operations like add, delete, and fetch.
284
+ #
285
+ # CONTRAST-22914
286
+ # please do NOT expose this w/ an attr_reader / accessor. there are
287
+ # helper methods in this class that safely access the hash. the tags
288
+ # method is private to avoid the side effect of a direct lookup with
289
+ # `[]` adding an empty array to the hash.
290
+ def tags
291
+ @_tags ||= Hash.new { |h, k| h[k] = [] }
292
+ end
293
+
294
+ # Creating Tags is expensive and we check for Tags all the time on
295
+ # untracked things. ALWAYS!!! call this method before checking if
296
+ # an object has tags
297
+ def tags?
298
+ instance_variable_defined?(:@_tags)
299
+ end
300
+
301
+ # Remove the tag ranges covering the given range
302
+ def remove_tags range
303
+ return unless tags?
304
+
305
+ full_delete = []
306
+ tags.each_pair do |key, value|
307
+ remove = []
308
+ add = []
309
+ value.each do |tag|
310
+ comparison = tag.compare_range(range.start, range.stop)
311
+ # ABOVE and BELOW are not affected by this check
312
+ case comparison
313
+ when Contrast::Agent::Assess::Tag::LOW_SPAN
314
+ tag.update_end(range.start)
315
+ when Contrast::Agent::Assess::Tag::WITHIN
316
+ remove << tag
317
+ when Contrast::Agent::Assess::Tag::WITHOUT
318
+ new_tag = tag.clone
319
+ new_tag.update_start(range.stop)
320
+ add << new_tag
321
+ tag.update_end(range.start)
322
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN
323
+ tag.update_start(range.stop)
324
+ end
325
+ end
326
+ value.delete_if { |tag| remove.include?(tag) }
327
+ Contrast::Utils::TagUtil.ordered_merge(value, add)
328
+ full_delete << key if value.empty?
329
+ end
330
+ full_delete.each { |key| tags.delete(key) }
331
+ end
332
+
333
+ # Shift the tag ranges covering the given range
334
+ # We assume this is for a deletion, meaning we
335
+ # have to move tags to the left
336
+ def shift_tags_for_deletion range, shift
337
+ return unless tags?
338
+
339
+ tags.each_value do |value|
340
+ value.each do |tag|
341
+ comparison = tag.compare_range(range.start - shift, range.stop - shift)
342
+ length = range.stop - range.start
343
+ case comparison
344
+ # this is really the only thing we need to shift
345
+ when Contrast::Agent::Assess::Tag::ABOVE
346
+ tag.shift(0 - length)
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ # Shift the tag ranges covering the given range
353
+ # We assume this is for a insertion, meaning we
354
+ # have to move tags to the right
355
+ def shift_tags_for_insertion range
356
+ return unless tags?
357
+
358
+ tags.each_value do |value|
359
+ add = []
360
+ value.each do |tag|
361
+ comparison = tag.compare_range(range.start, range.stop)
362
+ length = range.stop - range.start
363
+ # BELOW is not affected by this check
364
+ case comparison
365
+ # part of the tag is being inserted on
366
+ when Contrast::Agent::Assess::Tag::LOW_SPAN
367
+ new_tag = tag.clone
368
+ new_tag.update_start(range.start)
369
+ new_tag.shift(length)
370
+ add << new_tag
371
+ tag.update_end(range.start)
372
+ # the tag exists in the inserted range. it is partially shifted
373
+ when Contrast::Agent::Assess::Tag::WITHIN
374
+ tag.shift(length)
375
+ # the tag spans the range. leave the part below alone
376
+ when Contrast::Agent::Assess::Tag::WITHOUT
377
+ new_tag = tag.clone
378
+ new_tag.update_start(range.start)
379
+ new_tag.shift(length)
380
+ add << new_tag
381
+ tag.update_end(range.start)
382
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE
383
+ tag.shift(length)
384
+ end
385
+ end
386
+ Contrast::Utils::TagUtil.ordered_merge(value, add)
387
+ end
388
+ end
389
+ end
390
+ end
391
+ end
392
+ end