immunio 0.15.2

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 (157) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +234 -0
  3. data/README.md +147 -0
  4. data/bin/immunio +5 -0
  5. data/lib/immunio.rb +29 -0
  6. data/lib/immunio/agent.rb +260 -0
  7. data/lib/immunio/authentication.rb +96 -0
  8. data/lib/immunio/blocked_app.rb +38 -0
  9. data/lib/immunio/channel.rb +432 -0
  10. data/lib/immunio/cli.rb +39 -0
  11. data/lib/immunio/context.rb +114 -0
  12. data/lib/immunio/errors.rb +43 -0
  13. data/lib/immunio/immunio_ca.crt +45 -0
  14. data/lib/immunio/logger.rb +87 -0
  15. data/lib/immunio/plugins/action_dispatch.rb +45 -0
  16. data/lib/immunio/plugins/action_view.rb +431 -0
  17. data/lib/immunio/plugins/active_record.rb +707 -0
  18. data/lib/immunio/plugins/active_record_relation.rb +370 -0
  19. data/lib/immunio/plugins/authlogic.rb +80 -0
  20. data/lib/immunio/plugins/csrf.rb +24 -0
  21. data/lib/immunio/plugins/devise.rb +40 -0
  22. data/lib/immunio/plugins/environment_reporter.rb +69 -0
  23. data/lib/immunio/plugins/eval.rb +51 -0
  24. data/lib/immunio/plugins/exception_handler.rb +55 -0
  25. data/lib/immunio/plugins/gems_tracker.rb +5 -0
  26. data/lib/immunio/plugins/haml.rb +36 -0
  27. data/lib/immunio/plugins/http_finisher.rb +50 -0
  28. data/lib/immunio/plugins/http_tracker.rb +203 -0
  29. data/lib/immunio/plugins/io.rb +96 -0
  30. data/lib/immunio/plugins/redirect.rb +42 -0
  31. data/lib/immunio/plugins/warden.rb +66 -0
  32. data/lib/immunio/processor.rb +234 -0
  33. data/lib/immunio/rails.rb +26 -0
  34. data/lib/immunio/request.rb +139 -0
  35. data/lib/immunio/rufus_lua_ext/ref.rb +27 -0
  36. data/lib/immunio/rufus_lua_ext/state.rb +157 -0
  37. data/lib/immunio/rufus_lua_ext/table.rb +137 -0
  38. data/lib/immunio/rufus_lua_ext/utils.rb +13 -0
  39. data/lib/immunio/version.rb +5 -0
  40. data/lib/immunio/vm.rb +291 -0
  41. data/lua-hooks/ext/all.c +78 -0
  42. data/lua-hooks/ext/bitop/README +22 -0
  43. data/lua-hooks/ext/bitop/bit.c +189 -0
  44. data/lua-hooks/ext/extconf.rb +38 -0
  45. data/lua-hooks/ext/libinjection/COPYING +37 -0
  46. data/lua-hooks/ext/libinjection/libinjection.h +65 -0
  47. data/lua-hooks/ext/libinjection/libinjection_html5.c +847 -0
  48. data/lua-hooks/ext/libinjection/libinjection_html5.h +54 -0
  49. data/lua-hooks/ext/libinjection/libinjection_sqli.c +2301 -0
  50. data/lua-hooks/ext/libinjection/libinjection_sqli.h +295 -0
  51. data/lua-hooks/ext/libinjection/libinjection_sqli_data.h +9349 -0
  52. data/lua-hooks/ext/libinjection/libinjection_xss.c +531 -0
  53. data/lua-hooks/ext/libinjection/libinjection_xss.h +21 -0
  54. data/lua-hooks/ext/libinjection/lualib.c +109 -0
  55. data/lua-hooks/ext/lpeg/HISTORY +90 -0
  56. data/lua-hooks/ext/lpeg/lpcap.c +537 -0
  57. data/lua-hooks/ext/lpeg/lpcap.h +43 -0
  58. data/lua-hooks/ext/lpeg/lpcode.c +986 -0
  59. data/lua-hooks/ext/lpeg/lpcode.h +34 -0
  60. data/lua-hooks/ext/lpeg/lpeg-128.gif +0 -0
  61. data/lua-hooks/ext/lpeg/lpeg.html +1429 -0
  62. data/lua-hooks/ext/lpeg/lpprint.c +244 -0
  63. data/lua-hooks/ext/lpeg/lpprint.h +35 -0
  64. data/lua-hooks/ext/lpeg/lptree.c +1238 -0
  65. data/lua-hooks/ext/lpeg/lptree.h +77 -0
  66. data/lua-hooks/ext/lpeg/lptypes.h +149 -0
  67. data/lua-hooks/ext/lpeg/lpvm.c +355 -0
  68. data/lua-hooks/ext/lpeg/lpvm.h +58 -0
  69. data/lua-hooks/ext/lpeg/makefile +55 -0
  70. data/lua-hooks/ext/lpeg/re.html +498 -0
  71. data/lua-hooks/ext/lpeg/test.lua +1409 -0
  72. data/lua-hooks/ext/lua-cmsgpack/CMakeLists.txt +45 -0
  73. data/lua-hooks/ext/lua-cmsgpack/README.md +115 -0
  74. data/lua-hooks/ext/lua-cmsgpack/lua_cmsgpack.c +957 -0
  75. data/lua-hooks/ext/lua-cmsgpack/test.lua +570 -0
  76. data/lua-hooks/ext/lua-snapshot/LICENSE +7 -0
  77. data/lua-hooks/ext/lua-snapshot/Makefile +12 -0
  78. data/lua-hooks/ext/lua-snapshot/README.md +18 -0
  79. data/lua-hooks/ext/lua-snapshot/dump.lua +15 -0
  80. data/lua-hooks/ext/lua-snapshot/snapshot.c +455 -0
  81. data/lua-hooks/ext/lua/COPYRIGHT +34 -0
  82. data/lua-hooks/ext/lua/lapi.c +1087 -0
  83. data/lua-hooks/ext/lua/lapi.h +16 -0
  84. data/lua-hooks/ext/lua/lauxlib.c +652 -0
  85. data/lua-hooks/ext/lua/lauxlib.h +174 -0
  86. data/lua-hooks/ext/lua/lbaselib.c +659 -0
  87. data/lua-hooks/ext/lua/lcode.c +831 -0
  88. data/lua-hooks/ext/lua/lcode.h +76 -0
  89. data/lua-hooks/ext/lua/ldblib.c +398 -0
  90. data/lua-hooks/ext/lua/ldebug.c +638 -0
  91. data/lua-hooks/ext/lua/ldebug.h +33 -0
  92. data/lua-hooks/ext/lua/ldo.c +519 -0
  93. data/lua-hooks/ext/lua/ldo.h +57 -0
  94. data/lua-hooks/ext/lua/ldump.c +164 -0
  95. data/lua-hooks/ext/lua/lfunc.c +174 -0
  96. data/lua-hooks/ext/lua/lfunc.h +34 -0
  97. data/lua-hooks/ext/lua/lgc.c +710 -0
  98. data/lua-hooks/ext/lua/lgc.h +110 -0
  99. data/lua-hooks/ext/lua/linit.c +38 -0
  100. data/lua-hooks/ext/lua/liolib.c +556 -0
  101. data/lua-hooks/ext/lua/llex.c +463 -0
  102. data/lua-hooks/ext/lua/llex.h +81 -0
  103. data/lua-hooks/ext/lua/llimits.h +128 -0
  104. data/lua-hooks/ext/lua/lmathlib.c +263 -0
  105. data/lua-hooks/ext/lua/lmem.c +86 -0
  106. data/lua-hooks/ext/lua/lmem.h +49 -0
  107. data/lua-hooks/ext/lua/loadlib.c +705 -0
  108. data/lua-hooks/ext/lua/loadlib_rel.c +760 -0
  109. data/lua-hooks/ext/lua/lobject.c +214 -0
  110. data/lua-hooks/ext/lua/lobject.h +381 -0
  111. data/lua-hooks/ext/lua/lopcodes.c +102 -0
  112. data/lua-hooks/ext/lua/lopcodes.h +268 -0
  113. data/lua-hooks/ext/lua/loslib.c +243 -0
  114. data/lua-hooks/ext/lua/lparser.c +1339 -0
  115. data/lua-hooks/ext/lua/lparser.h +82 -0
  116. data/lua-hooks/ext/lua/lstate.c +214 -0
  117. data/lua-hooks/ext/lua/lstate.h +169 -0
  118. data/lua-hooks/ext/lua/lstring.c +111 -0
  119. data/lua-hooks/ext/lua/lstring.h +31 -0
  120. data/lua-hooks/ext/lua/lstrlib.c +871 -0
  121. data/lua-hooks/ext/lua/ltable.c +588 -0
  122. data/lua-hooks/ext/lua/ltable.h +40 -0
  123. data/lua-hooks/ext/lua/ltablib.c +287 -0
  124. data/lua-hooks/ext/lua/ltm.c +75 -0
  125. data/lua-hooks/ext/lua/ltm.h +54 -0
  126. data/lua-hooks/ext/lua/lua.c +392 -0
  127. data/lua-hooks/ext/lua/lua.def +131 -0
  128. data/lua-hooks/ext/lua/lua.h +388 -0
  129. data/lua-hooks/ext/lua/lua.rc +28 -0
  130. data/lua-hooks/ext/lua/lua_dll.rc +26 -0
  131. data/lua-hooks/ext/lua/luac.c +200 -0
  132. data/lua-hooks/ext/lua/luac.rc +1 -0
  133. data/lua-hooks/ext/lua/luaconf.h +763 -0
  134. data/lua-hooks/ext/lua/luaconf.h.in +724 -0
  135. data/lua-hooks/ext/lua/luaconf.h.orig +763 -0
  136. data/lua-hooks/ext/lua/lualib.h +53 -0
  137. data/lua-hooks/ext/lua/lundump.c +227 -0
  138. data/lua-hooks/ext/lua/lundump.h +36 -0
  139. data/lua-hooks/ext/lua/lvm.c +767 -0
  140. data/lua-hooks/ext/lua/lvm.h +36 -0
  141. data/lua-hooks/ext/lua/lzio.c +82 -0
  142. data/lua-hooks/ext/lua/lzio.h +67 -0
  143. data/lua-hooks/ext/lua/print.c +227 -0
  144. data/lua-hooks/ext/luautf8/README.md +152 -0
  145. data/lua-hooks/ext/luautf8/lutf8lib.c +1274 -0
  146. data/lua-hooks/ext/luautf8/unidata.h +3064 -0
  147. data/lua-hooks/lib/boot.lua +254 -0
  148. data/lua-hooks/lib/encode.lua +4 -0
  149. data/lua-hooks/lib/lexers/LICENSE +21 -0
  150. data/lua-hooks/lib/lexers/bash.lua +134 -0
  151. data/lua-hooks/lib/lexers/bash_dqstr.lua +62 -0
  152. data/lua-hooks/lib/lexers/css.lua +216 -0
  153. data/lua-hooks/lib/lexers/html.lua +106 -0
  154. data/lua-hooks/lib/lexers/javascript.lua +68 -0
  155. data/lua-hooks/lib/lexers/lexer.lua +1575 -0
  156. data/lua-hooks/lib/lexers/markers.lua +33 -0
  157. metadata +308 -0
@@ -0,0 +1,96 @@
1
+ require_relative '../context'
2
+
3
+ module Immunio
4
+ module IOHooks
5
+
6
+ def self.inject(mod, name, methods)
7
+ mod.class_eval <<-EOF
8
+ def self.extended(base) # def self.extended(base)
9
+ #{methods.inspect}.each do |method| # ["read", "binread"].each do |method|
10
+ base.singleton_class.alias_method_chain method, :immunio # base.singleton_class.alias_method_chain method, :immunio
11
+ end # end
12
+ end # end
13
+
14
+ def self.included(base) # def self.included(base)
15
+ #{methods.inspect}.each do |method| # ["read", "binread"].each do |method|
16
+ base.alias_method_chain method, :immunio # base.alias_method_chain method, :immunio
17
+ end # end
18
+ end # end
19
+ EOF
20
+ Immunio.logger.debug "IO: successfully chained #{name} #{methods}"
21
+ methods.each do |method|
22
+ mod.class_eval <<-EOF
23
+ def #{method}_with_immunio(*args, &block) # def read_with_immunio(*args, &block)
24
+ Request.time "plugin", "IO::#{method}" do #
25
+ strict_context, loose_context, stack, loose_stack = Immunio::Context.context()
26
+ Immunio.run_hook! "io", "file_io", # Immunio.run_hook! "io", "open",
27
+ method: "#{name}#{method}", # open_method: "IO.read",
28
+ parameters: args, # parameters: args
29
+ stack: loose_stack, #
30
+ context_key: loose_context, #
31
+ cwd: Dir.pwd
32
+ Request.pause "plugin", "IO::#{method}" do #
33
+ #{method}_without_immunio(*args, &block) # read_without_immunio(*args, &block)
34
+ end
35
+ end
36
+ end # end
37
+ EOF
38
+ Immunio.logger.debug "IO: successfully created hook for #{name} #{method}"
39
+ end
40
+ end
41
+ end
42
+
43
+ module IOClassHooks
44
+ IOHooks.inject self, "IO.", %w( read write binread binwrite readlines sysopen copy_stream popen )
45
+ end
46
+ Immunio.logger.debug "IO: IOClassHooks created: #{IOClassHooks}"
47
+
48
+ module KernelModuleHooks
49
+ # exec() is not included currently as it replaces the running process
50
+ # and would never get primed correctly
51
+ IOHooks.inject self, "Kernel.", %w( open system spawn )
52
+
53
+ # Special handling for hooking the backtick character
54
+ # In *theory* this could be rolled into self.inject above.
55
+ # In practice it's fugly and breaks the world.
56
+ Kernel.class_eval <<-EOF
57
+ define_method :backtick_with_immunio do |cmd|
58
+ Request.time "plugin", "Shell::backtick" do
59
+ strict_context, loose_context, stack, loose_stack = Immunio::Context.context()
60
+ Immunio.run_hook! "io", "file_io",
61
+ method: "Kernel.backtick",
62
+ parameters: [cmd],
63
+ stack: loose_stack,
64
+ context_key: loose_context,
65
+ cwd: Dir.pwd
66
+ Request.pause "plugin", "Shell::backtick" do
67
+ Kernel.send(:backtick_without_immunio, cmd)
68
+ end
69
+ end
70
+ end
71
+ EOF
72
+ Kernel.send :alias_method, :backtick_without_immunio, :`
73
+ Kernel.send :alias_method, :`, :backtick_with_immunio
74
+ end
75
+ Immunio.logger.debug "Shell: KernelModuleHooks created: #{KernelModuleHooks}"
76
+
77
+ module FileClassHooks
78
+ IOHooks.inject self, "File.", %w( new open )
79
+ end
80
+ Immunio.logger.debug "IO: FileClassHooks created: #{FileClassHooks}"
81
+ end
82
+
83
+ # Add FileIO hooks if enabled
84
+ if Immunio.agent.plugin_enabled?("file_io")
85
+ IO.extend Immunio::IOClassHooks
86
+ File.extend Immunio::FileClassHooks
87
+ Immunio.logger.debug "IO: All hooks installed."
88
+ end
89
+
90
+ # Add Kernel hooks if enabled
91
+ if Immunio.agent.plugin_enabled?("shell_command")
92
+ # Both are necessary to hook calling both Kernel.open() and open() etc.
93
+ Kernel.send :include, Immunio::KernelModuleHooks
94
+ Kernel.extend Immunio::KernelModuleHooks
95
+ Immunio.logger.debug "Shell: All hooks installed."
96
+ end
@@ -0,0 +1,42 @@
1
+ require_relative '../context'
2
+
3
+ module Immunio
4
+ module RedirectHook
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ alias_method_chain :redirect_to, :immunio if method_defined? :redirect_to
9
+ end
10
+
11
+ protected
12
+ def redirect_to_with_immunio(options = {}, response_status = {})
13
+ Immunio.logger.debug "ActiveSupport checking redirect."
14
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
15
+ # redirect_to excepts a variety of argument types
16
+ # but the only one that creates a absolute URL redirect
17
+ # is a string, so we only call the hook in that case.
18
+ # However we have to call Proc arguments to determine
19
+ # if they return a string...
20
+ loptions = options
21
+ if loptions.is_a? Proc then
22
+ loptions = loptions.call
23
+ end
24
+ if loptions.is_a? String then
25
+ strict_context, loose_context, stack, loose_stack = Immunio::Context.context() # rubocop:disable Lint/UselessAssignment
26
+ Immunio.run_hook! "redirect", "framework_redirect",
27
+ destination_url: loptions,
28
+ context_key: loose_context,
29
+ stack: loose_stack
30
+ end
31
+ Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
32
+ redirect_to_without_immunio( options, response_status )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ if Immunio::agent.plugin_enabled?("redirect") then
40
+ ActionController::Base.send :include, Immunio::RedirectHook
41
+ Immunio.logger.debug "Redirect: All hooks installed."
42
+ end
@@ -0,0 +1,66 @@
1
+ # Register callbacks to Warden (https://github.com/hassox/warden), the backend of Devise.
2
+
3
+ begin
4
+ require "warden"
5
+ rescue LoadError # rubocop:disable Lint/HandleExceptions
6
+ # Ignore
7
+ end
8
+
9
+ if defined?(Warden)
10
+ Warden::Manager.after_authentication do |user|
11
+ Immunio::Request.time "plugin", "Warden::Manager.after_authentication" do
12
+ Immunio.logger.debug "Warden instrumentation fired for after_authentication"
13
+ Immunio.login user_record: user, plugin: "warden"
14
+ end
15
+ end
16
+
17
+ Warden::Manager.before_failure do |env|
18
+ Immunio::Request.time "plugin", "Warden::Manager.before_failure" do
19
+ info = {plugin: "warden"}
20
+
21
+ # Devise uses these specific form fields for authentication by default
22
+ [:username, :email].each do |attr|
23
+ value = env.fetch("rack.request.form_hash", {}).fetch("user", {})[attr.to_s]
24
+ info[attr] = value if value
25
+ end
26
+
27
+ Immunio.logger.debug "Warden instrumentation fired for before_failure"
28
+ Immunio.failed_login info
29
+ end
30
+ end
31
+
32
+ Warden::Manager.after_set_user do |user|
33
+ Immunio::Request.time "plugin", "Warden::Manager.after_set_user" do
34
+ Immunio.logger.debug "Warden instrumentation fired for after_set_user"
35
+ Immunio.set_user user_record: user, plugin: "warden"
36
+ end
37
+ end
38
+
39
+ Warden::Manager.before_logout do |user|
40
+ Immunio::Request.time "plugin", "Warden::Manager.before_logout" do
41
+ Immunio.logger.debug "Warden instrumentation fired for before_logout"
42
+ Immunio.logout user_record: user, plugin: "warden"
43
+ end
44
+ end
45
+
46
+ # Force lookup of user info for all requests.
47
+ module Immunio
48
+ class WardenUserCaller
49
+ def initialize(app)
50
+ @app = app
51
+ end
52
+
53
+ def call(env)
54
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
55
+ # This will end up calling Warden::Manager.after_set_user above if
56
+ # a valid session associated with a user is seen.
57
+ env['warden'].user
58
+
59
+ Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
60
+ @app.call(env)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,234 @@
1
+ require_relative "request"
2
+ require_relative "vm"
3
+
4
+ module Immunio
5
+ class Processor
6
+ attr_accessor :environment # Holds environment info for next channel transmission
7
+
8
+ def initialize(channel, vmfactory, options)
9
+ @channel = channel
10
+ @vmfactory = vmfactory
11
+
12
+ @dev_mode = options.fetch(:dev_mode, false)
13
+ @debug_mode = options.fetch(:debug_mode, false)
14
+ @log_timings = options.fetch(:log_timings, false)
15
+
16
+ # This hash is not in sync with the one in the VM. It is sent to the VM on initialization.
17
+ @serverdata = {}
18
+
19
+ # List of hook handlers. hook => Lua function.
20
+ # Stored in the request on first execution.
21
+ @hook_handlers = {}
22
+
23
+ @timings = Hash.new do |timings, type|
24
+ timings[type] = Hash.new do |type_timings, name|
25
+ type_timings[name] = {
26
+ "total_duration" => 0,
27
+ "count" => 0
28
+ }
29
+ end
30
+ end
31
+ @timings_mutex = Mutex.new
32
+
33
+ # Package up aggregated timings to send to backend
34
+ @channel.on_sending do
35
+ @timings_mutex.synchronize do
36
+ Immunio.logger.debug {"Aggregated timings since last agentmanager transmission: #{@timings}"}
37
+ timings = @timings.clone
38
+ @timings.clear
39
+
40
+ { timings: timings }.tap do |info|
41
+ next unless @environment
42
+ Immunio.logger.debug {"Reporting environment info: #{@environment}"}
43
+ info[:environment] = @environment
44
+ @environment = nil
45
+ end
46
+ end
47
+ end
48
+
49
+ puts "[IMMUNIO] Dev mode activated!" if @dev_mode
50
+ end
51
+
52
+ def new_request(request)
53
+ # Start channel on first request
54
+ @channel.start unless @channel.started?
55
+
56
+ # Wait until we've received all the hooks before continuing (if
57
+ # ready_timeout is set)
58
+ @channel.wait_until_ready!
59
+
60
+ ActiveSupport::Notifications.publish "immunio.new_request", request
61
+
62
+ # Don't process request unless channel is ready (meaning we've loaded all
63
+ # the hooks) or we're in dev_mode and hooks are loaded from files
64
+ Request.current = request if (@channel.ready? || @dev_mode)
65
+ end
66
+
67
+ def aggregate_timings(timings)
68
+ log_pieces = []
69
+ log_pieces << "\nTimings for request (in ms):" if @log_timings
70
+
71
+ request_total = timings["request"]["total"][:total_duration]
72
+ log_pieces << "\tTotal request time: #{request_total}" if @log_timings
73
+
74
+ @timings_mutex.synchronize do
75
+ timings.each do |type, type_timings|
76
+ log_pieces << "\tType: #{type}" if @log_timings && type != "request"
77
+
78
+ type_total = 0
79
+ type_timings.each do |name, timing|
80
+ if @log_timings && type != "request"
81
+ log_pieces << "\t\t#{name}: #{timing[:total_duration]} (#{timing[:count]})"
82
+ end
83
+
84
+ @timings[type][name]["total_duration"] += timing[:total_duration]
85
+ @timings[type][name]["count"] += timing[:count]
86
+
87
+ type_total += timing[:total_duration]
88
+ end
89
+
90
+ if @log_timings && type != "request"
91
+ log_pieces << "\tTotal time for type #{type}: #{type_total.round(3)}/#{request_total}"
92
+ end
93
+ end
94
+ end
95
+
96
+ Immunio.logger.info { log_pieces.join("\n") } if @log_timings
97
+ end
98
+
99
+ def finish_request
100
+ request = Request.current
101
+ if request
102
+ Immunio.logger.debug "Finishing request #{request.id}"
103
+ aggregate_timings(request.timings)
104
+ ActiveSupport::Notifications.publish "immunio.finish_request", request
105
+ @channel.send_encoded_message request.encode if request.should_report?
106
+ end
107
+ rescue StandardError => e
108
+ log_and_send_error e, "Error finishing request", request_id: request.try(:id)
109
+ ensure
110
+ Request.current = nil
111
+ end
112
+
113
+ # Run the `hook` and return a hash eg.: `{ "allow": true }`.
114
+ def run_hook(plugin, hook, meta={})
115
+ request = Request.current
116
+
117
+ # Hooks called outside of a request are ignored since they are triggered while the framework is loaded.
118
+ return {} unless request
119
+
120
+ # Notify about the hook. This has no perf cost if there are no subscribers.
121
+ # Used to test and debug the agent in the test Rails apps.
122
+ ActiveSupport::Notifications.publish "immunio.hook", plugin, hook, meta
123
+
124
+ timestamp = Time.now.utc.iso8601(6)
125
+
126
+ # The VM & handlers are changed on code update.
127
+ # So we ensure the request uses the same VM & hook handlers for all hooks.
128
+ request.vm ||= @vmfactory.new_vm
129
+
130
+ # If there is no registered handler, just log the hook and return.
131
+ unless request.vm.has_function? hook
132
+ Immunio.logger.debug "No hook code for '#{hook}' to run for request #{request.id}"
133
+ return {}
134
+ end
135
+
136
+ # Converts the request data to a Lua table to speedup future calls.
137
+ request.data = request.vm.create_object(request.data)
138
+
139
+ globals = {
140
+ "agent_type" => AGENT_TYPE,
141
+ "agent_version" => VERSION,
142
+ "timestamp" => timestamp,
143
+ "plugin" => plugin,
144
+ "hook" => hook,
145
+ "meta" => meta,
146
+ "request" => request.data,
147
+ }
148
+
149
+ begin
150
+ Immunio.logger.debug "Running #{hook} hook for request #{request.id} with global values: #{globals}"
151
+ rescue Encoding::CompatibilityError
152
+ Immunio.logger.debug "Running #{hook} hook for request #{request.id} (can't log global values due to encoding incompatibility)"
153
+ end
154
+
155
+ # Run the hook code in the VM and time the execution.
156
+ result = Request.time "hook", hook do
157
+ request.vm.call hook, globals
158
+ end
159
+
160
+ # result.to_h can be expensive, so put it in a block so it only runs when needed
161
+ begin
162
+ Immunio.logger.debug { "Result from #{hook} hook: #{result ? result.to_h : {}}" }
163
+ rescue Encoding::CompatibilityError
164
+ Immunio.logger.debug { "Result from #{hook} hook: (can't log result due to encoding incompatibility)" }
165
+ end
166
+
167
+ result || {}
168
+
169
+ # Previosuly this only caught VMErrors, however other exceptions can cause 500s
170
+ # so to be on the safe side make sure we catch anything raised within the VM call --ol
171
+ rescue StandardError => e
172
+ # Log and discard VM errors
173
+
174
+ # Some versions of rails, like 4.2.0, fail to JSONify some objects properly.
175
+ safe_meta = {}
176
+ meta.each do |key, value|
177
+ if value.is_a?(Numeric) || value.is_a?(String) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
178
+ safe_meta[key] = value
179
+ else
180
+ safe_meta[key] = value.inspect
181
+ end
182
+ end
183
+
184
+ log_and_send_error e, "Error running hook #{hook}",
185
+ request_id: request.id,
186
+ timestamp: timestamp,
187
+ plugin: plugin,
188
+ hook: hook,
189
+ meta: safe_meta,
190
+ vmcode_version: request.vm.code_version,
191
+ vmdata_version: request.vm.data_version
192
+
193
+ {} # Return empty result.
194
+ end
195
+
196
+ # Run the hook and raise a RequestBlocked error if the request should be blocked.
197
+ def run_hook!(*args)
198
+ result = run_hook(*args)
199
+
200
+ # Raise if not allowed (default to allow)
201
+ if !result.fetch("allow", true)
202
+ Immunio.logger.debug "Blocking request due to hook response"
203
+ raise RequestBlocked, "The request was blocked by the Immunio agent"
204
+ end
205
+
206
+ # Check result for a response override.
207
+ if result.has_key?(:override_status)
208
+ raise OverrideResponse.new(result.fetch(:override_status), result.fetch(:override_headers, []), result.fetch(:override_body, ""))
209
+ end
210
+
211
+ result
212
+ end
213
+
214
+ def log_and_send_error(e, message="Error", info={})
215
+ Immunio.logger.warn "#{message}: #{e.message}"
216
+ Immunio.logger.warn "Stack: #{e.backtrace}"
217
+
218
+ # Re-raise in dev mode before we send it to the backend.
219
+ raise e if @dev_mode
220
+
221
+ default_info = {
222
+ type: "engine.exception",
223
+ exception: e.message,
224
+ traceback: e.backtrace,
225
+ agent_version: Immunio::VERSION
226
+ }
227
+
228
+ @channel.send_message default_info.merge(info)
229
+
230
+ # Re-raise error in test mode so we know when something is broken in hook handlers.
231
+ raise e if Rails.env.test?
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,26 @@
1
+ require_relative "plugins/exception_handler"
2
+ require_relative "plugins/environment_reporter"
3
+ require_relative "plugins/gems_tracker"
4
+ require_relative "plugins/http_finisher"
5
+ require_relative "plugins/http_tracker"
6
+ require_relative "plugins/warden"
7
+
8
+ module Immunio
9
+ class Engine < ::Rails::Engine
10
+ config.app_middleware.insert 0, HTTPFinisher
11
+ config.app_middleware.insert_before ActionDispatch::ShowExceptions, HTTPTracker
12
+ config.app_middleware.insert_after ActionDispatch::DebugExceptions, ExceptionHandler
13
+ config.app_middleware.insert_after Warden::Manager, WardenUserCaller if defined? Warden::Manager
14
+ config.app_middleware.use EnvironmentReporter
15
+
16
+ config.action_dispatch.rescue_responses.merge!('Immunio::RequestBlocked' => :forbidden)
17
+
18
+ if Immunio::agent.plugin_enabled?("sqli") then
19
+ initializer "immunio.active_record", after: "active_record.initialize_database" do
20
+ ActiveSupport.on_load(:active_record) do
21
+ require_relative "plugins/active_record"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end