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.
- checksums.yaml +7 -0
- data/LICENSE +234 -0
- data/README.md +147 -0
- data/bin/immunio +5 -0
- data/lib/immunio.rb +29 -0
- data/lib/immunio/agent.rb +260 -0
- data/lib/immunio/authentication.rb +96 -0
- data/lib/immunio/blocked_app.rb +38 -0
- data/lib/immunio/channel.rb +432 -0
- data/lib/immunio/cli.rb +39 -0
- data/lib/immunio/context.rb +114 -0
- data/lib/immunio/errors.rb +43 -0
- data/lib/immunio/immunio_ca.crt +45 -0
- data/lib/immunio/logger.rb +87 -0
- data/lib/immunio/plugins/action_dispatch.rb +45 -0
- data/lib/immunio/plugins/action_view.rb +431 -0
- data/lib/immunio/plugins/active_record.rb +707 -0
- data/lib/immunio/plugins/active_record_relation.rb +370 -0
- data/lib/immunio/plugins/authlogic.rb +80 -0
- data/lib/immunio/plugins/csrf.rb +24 -0
- data/lib/immunio/plugins/devise.rb +40 -0
- data/lib/immunio/plugins/environment_reporter.rb +69 -0
- data/lib/immunio/plugins/eval.rb +51 -0
- data/lib/immunio/plugins/exception_handler.rb +55 -0
- data/lib/immunio/plugins/gems_tracker.rb +5 -0
- data/lib/immunio/plugins/haml.rb +36 -0
- data/lib/immunio/plugins/http_finisher.rb +50 -0
- data/lib/immunio/plugins/http_tracker.rb +203 -0
- data/lib/immunio/plugins/io.rb +96 -0
- data/lib/immunio/plugins/redirect.rb +42 -0
- data/lib/immunio/plugins/warden.rb +66 -0
- data/lib/immunio/processor.rb +234 -0
- data/lib/immunio/rails.rb +26 -0
- data/lib/immunio/request.rb +139 -0
- data/lib/immunio/rufus_lua_ext/ref.rb +27 -0
- data/lib/immunio/rufus_lua_ext/state.rb +157 -0
- data/lib/immunio/rufus_lua_ext/table.rb +137 -0
- data/lib/immunio/rufus_lua_ext/utils.rb +13 -0
- data/lib/immunio/version.rb +5 -0
- data/lib/immunio/vm.rb +291 -0
- data/lua-hooks/ext/all.c +78 -0
- data/lua-hooks/ext/bitop/README +22 -0
- data/lua-hooks/ext/bitop/bit.c +189 -0
- data/lua-hooks/ext/extconf.rb +38 -0
- data/lua-hooks/ext/libinjection/COPYING +37 -0
- data/lua-hooks/ext/libinjection/libinjection.h +65 -0
- data/lua-hooks/ext/libinjection/libinjection_html5.c +847 -0
- data/lua-hooks/ext/libinjection/libinjection_html5.h +54 -0
- data/lua-hooks/ext/libinjection/libinjection_sqli.c +2301 -0
- data/lua-hooks/ext/libinjection/libinjection_sqli.h +295 -0
- data/lua-hooks/ext/libinjection/libinjection_sqli_data.h +9349 -0
- data/lua-hooks/ext/libinjection/libinjection_xss.c +531 -0
- data/lua-hooks/ext/libinjection/libinjection_xss.h +21 -0
- data/lua-hooks/ext/libinjection/lualib.c +109 -0
- data/lua-hooks/ext/lpeg/HISTORY +90 -0
- data/lua-hooks/ext/lpeg/lpcap.c +537 -0
- data/lua-hooks/ext/lpeg/lpcap.h +43 -0
- data/lua-hooks/ext/lpeg/lpcode.c +986 -0
- data/lua-hooks/ext/lpeg/lpcode.h +34 -0
- data/lua-hooks/ext/lpeg/lpeg-128.gif +0 -0
- data/lua-hooks/ext/lpeg/lpeg.html +1429 -0
- data/lua-hooks/ext/lpeg/lpprint.c +244 -0
- data/lua-hooks/ext/lpeg/lpprint.h +35 -0
- data/lua-hooks/ext/lpeg/lptree.c +1238 -0
- data/lua-hooks/ext/lpeg/lptree.h +77 -0
- data/lua-hooks/ext/lpeg/lptypes.h +149 -0
- data/lua-hooks/ext/lpeg/lpvm.c +355 -0
- data/lua-hooks/ext/lpeg/lpvm.h +58 -0
- data/lua-hooks/ext/lpeg/makefile +55 -0
- data/lua-hooks/ext/lpeg/re.html +498 -0
- data/lua-hooks/ext/lpeg/test.lua +1409 -0
- data/lua-hooks/ext/lua-cmsgpack/CMakeLists.txt +45 -0
- data/lua-hooks/ext/lua-cmsgpack/README.md +115 -0
- data/lua-hooks/ext/lua-cmsgpack/lua_cmsgpack.c +957 -0
- data/lua-hooks/ext/lua-cmsgpack/test.lua +570 -0
- data/lua-hooks/ext/lua-snapshot/LICENSE +7 -0
- data/lua-hooks/ext/lua-snapshot/Makefile +12 -0
- data/lua-hooks/ext/lua-snapshot/README.md +18 -0
- data/lua-hooks/ext/lua-snapshot/dump.lua +15 -0
- data/lua-hooks/ext/lua-snapshot/snapshot.c +455 -0
- data/lua-hooks/ext/lua/COPYRIGHT +34 -0
- data/lua-hooks/ext/lua/lapi.c +1087 -0
- data/lua-hooks/ext/lua/lapi.h +16 -0
- data/lua-hooks/ext/lua/lauxlib.c +652 -0
- data/lua-hooks/ext/lua/lauxlib.h +174 -0
- data/lua-hooks/ext/lua/lbaselib.c +659 -0
- data/lua-hooks/ext/lua/lcode.c +831 -0
- data/lua-hooks/ext/lua/lcode.h +76 -0
- data/lua-hooks/ext/lua/ldblib.c +398 -0
- data/lua-hooks/ext/lua/ldebug.c +638 -0
- data/lua-hooks/ext/lua/ldebug.h +33 -0
- data/lua-hooks/ext/lua/ldo.c +519 -0
- data/lua-hooks/ext/lua/ldo.h +57 -0
- data/lua-hooks/ext/lua/ldump.c +164 -0
- data/lua-hooks/ext/lua/lfunc.c +174 -0
- data/lua-hooks/ext/lua/lfunc.h +34 -0
- data/lua-hooks/ext/lua/lgc.c +710 -0
- data/lua-hooks/ext/lua/lgc.h +110 -0
- data/lua-hooks/ext/lua/linit.c +38 -0
- data/lua-hooks/ext/lua/liolib.c +556 -0
- data/lua-hooks/ext/lua/llex.c +463 -0
- data/lua-hooks/ext/lua/llex.h +81 -0
- data/lua-hooks/ext/lua/llimits.h +128 -0
- data/lua-hooks/ext/lua/lmathlib.c +263 -0
- data/lua-hooks/ext/lua/lmem.c +86 -0
- data/lua-hooks/ext/lua/lmem.h +49 -0
- data/lua-hooks/ext/lua/loadlib.c +705 -0
- data/lua-hooks/ext/lua/loadlib_rel.c +760 -0
- data/lua-hooks/ext/lua/lobject.c +214 -0
- data/lua-hooks/ext/lua/lobject.h +381 -0
- data/lua-hooks/ext/lua/lopcodes.c +102 -0
- data/lua-hooks/ext/lua/lopcodes.h +268 -0
- data/lua-hooks/ext/lua/loslib.c +243 -0
- data/lua-hooks/ext/lua/lparser.c +1339 -0
- data/lua-hooks/ext/lua/lparser.h +82 -0
- data/lua-hooks/ext/lua/lstate.c +214 -0
- data/lua-hooks/ext/lua/lstate.h +169 -0
- data/lua-hooks/ext/lua/lstring.c +111 -0
- data/lua-hooks/ext/lua/lstring.h +31 -0
- data/lua-hooks/ext/lua/lstrlib.c +871 -0
- data/lua-hooks/ext/lua/ltable.c +588 -0
- data/lua-hooks/ext/lua/ltable.h +40 -0
- data/lua-hooks/ext/lua/ltablib.c +287 -0
- data/lua-hooks/ext/lua/ltm.c +75 -0
- data/lua-hooks/ext/lua/ltm.h +54 -0
- data/lua-hooks/ext/lua/lua.c +392 -0
- data/lua-hooks/ext/lua/lua.def +131 -0
- data/lua-hooks/ext/lua/lua.h +388 -0
- data/lua-hooks/ext/lua/lua.rc +28 -0
- data/lua-hooks/ext/lua/lua_dll.rc +26 -0
- data/lua-hooks/ext/lua/luac.c +200 -0
- data/lua-hooks/ext/lua/luac.rc +1 -0
- data/lua-hooks/ext/lua/luaconf.h +763 -0
- data/lua-hooks/ext/lua/luaconf.h.in +724 -0
- data/lua-hooks/ext/lua/luaconf.h.orig +763 -0
- data/lua-hooks/ext/lua/lualib.h +53 -0
- data/lua-hooks/ext/lua/lundump.c +227 -0
- data/lua-hooks/ext/lua/lundump.h +36 -0
- data/lua-hooks/ext/lua/lvm.c +767 -0
- data/lua-hooks/ext/lua/lvm.h +36 -0
- data/lua-hooks/ext/lua/lzio.c +82 -0
- data/lua-hooks/ext/lua/lzio.h +67 -0
- data/lua-hooks/ext/lua/print.c +227 -0
- data/lua-hooks/ext/luautf8/README.md +152 -0
- data/lua-hooks/ext/luautf8/lutf8lib.c +1274 -0
- data/lua-hooks/ext/luautf8/unidata.h +3064 -0
- data/lua-hooks/lib/boot.lua +254 -0
- data/lua-hooks/lib/encode.lua +4 -0
- data/lua-hooks/lib/lexers/LICENSE +21 -0
- data/lua-hooks/lib/lexers/bash.lua +134 -0
- data/lua-hooks/lib/lexers/bash_dqstr.lua +62 -0
- data/lua-hooks/lib/lexers/css.lua +216 -0
- data/lua-hooks/lib/lexers/html.lua +106 -0
- data/lua-hooks/lib/lexers/javascript.lua +68 -0
- data/lua-hooks/lib/lexers/lexer.lua +1575 -0
- data/lua-hooks/lib/lexers/markers.lua +33 -0
- 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
|