contrast-agent 3.8.4

Sign up to get free protection for your applications and to get access to all the features.
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