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,40 @@
|
|
|
1
|
+
# Register callbacks using Devise internals.
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "devise"
|
|
5
|
+
rescue LoadError # rubocop:disable Lint/HandleExceptions
|
|
6
|
+
# Ignore
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
if defined? Devise
|
|
10
|
+
module Immunio
|
|
11
|
+
# Hook into password recovery feature to trigger the `framework_password_reset` hook.
|
|
12
|
+
module DeviseRecoverableHooks
|
|
13
|
+
extend ActiveSupport::Concern
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
alias_method_chain :send_reset_password_instructions, :immunio
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def send_reset_password_instructions_with_immunio(attributes={})
|
|
20
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
21
|
+
Immunio.logger.debug "Devise instrumentation fired for send_reset_password_instructions"
|
|
22
|
+
|
|
23
|
+
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
|
|
24
|
+
|
|
25
|
+
if recoverable.persisted? # Found
|
|
26
|
+
Immunio.password_reset user_record: recoverable, plugin: 'devise'
|
|
27
|
+
else
|
|
28
|
+
Immunio.failed_password_reset email: recoverable.email, plugin: 'devise'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
32
|
+
send_reset_password_instructions_without_immunio(attributes)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
Devise::Models::Recoverable::ClassMethods.send :include, Immunio::DeviseRecoverableHooks
|
|
40
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Report environment data on the first request.
|
|
2
|
+
#
|
|
3
|
+
# XXX: Completely untested outside of MRI (official Ruby runtime) 1.9.3 and up!
|
|
4
|
+
|
|
5
|
+
module Immunio
|
|
6
|
+
class EnvironmentReporter
|
|
7
|
+
def initialize(app)
|
|
8
|
+
@app = app
|
|
9
|
+
|
|
10
|
+
# No need for mutex, reporting environment is idempotent
|
|
11
|
+
@reported = false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(env)
|
|
15
|
+
response = @app.call(env)
|
|
16
|
+
|
|
17
|
+
# Report gem usage at the end so that everything as been loaded.
|
|
18
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
19
|
+
report if !@reported
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
response
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def runtime_name
|
|
26
|
+
if Object.const_defined?(:RUBY_DESCRIPTION) && RUBY_DESCRIPTION =~ /Enterprise/
|
|
27
|
+
'ree'
|
|
28
|
+
elsif Object.const_defined? :RUBY_ENGINE
|
|
29
|
+
RUBY_ENGINE.to_s
|
|
30
|
+
else
|
|
31
|
+
'unknown'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def runtime_version
|
|
36
|
+
case runtime_name
|
|
37
|
+
when 'ruby' then RUBY_VERSION
|
|
38
|
+
when 'jruby' then JRUBY_VERSION
|
|
39
|
+
when 'rbx' then Rubinius::VERSION
|
|
40
|
+
else 'unknown'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def report
|
|
45
|
+
@reported = true
|
|
46
|
+
|
|
47
|
+
info = {
|
|
48
|
+
runtime: {
|
|
49
|
+
name: runtime_name,
|
|
50
|
+
version: runtime_version
|
|
51
|
+
},
|
|
52
|
+
language: {
|
|
53
|
+
name: 'ruby',
|
|
54
|
+
version: RUBY_VERSION
|
|
55
|
+
},
|
|
56
|
+
platform: {
|
|
57
|
+
description: RUBY_PLATFORM
|
|
58
|
+
},
|
|
59
|
+
dependencies: {}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Gem.loaded_specs.each_pair do |name, spec|
|
|
63
|
+
info[:dependencies][name] = spec.version.to_s
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
Immunio.agent.environment = info
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require_relative '../context'
|
|
2
|
+
# We use DebugInspector to execute eval() in the callers frame.
|
|
3
|
+
|
|
4
|
+
require 'binding_of_caller'
|
|
5
|
+
module Immunio
|
|
6
|
+
module KernelEvalHook
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
def self.extended(base)
|
|
10
|
+
base.singleton_class.alias_method_chain :eval, :immunio
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.included(base)
|
|
14
|
+
base.alias_method_chain :eval, :immunio
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
protected
|
|
18
|
+
def eval_with_immunio(*args)
|
|
19
|
+
Immunio.logger.debug {"Eval_with_immunio with args: #{args}"}
|
|
20
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
21
|
+
strict_context, loose_context, stack, loose_stack = Immunio::Context.context()
|
|
22
|
+
Immunio.run_hook! "eval", "eval",
|
|
23
|
+
parameters: args,
|
|
24
|
+
context_key: loose_context,
|
|
25
|
+
stack: loose_stack
|
|
26
|
+
Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
27
|
+
if args.length == 1 then
|
|
28
|
+
# eval call did not include a binding, so we eval it in the caller's binding
|
|
29
|
+
# NOTE: the 7 here is the number of frames above us the caller was. Changing
|
|
30
|
+
# the call stack will break this...
|
|
31
|
+
rval = eval_without_immunio(args[0], binding.of_caller(7))
|
|
32
|
+
# XXX We can and should log here, but it breaks a ruby test (atom_feed_helper)
|
|
33
|
+
#Immunio.logger.debug {"Eval_without_immunio (binding 7) returned: #{rval.inspect}\nEval stack was: #{stack}"}
|
|
34
|
+
else
|
|
35
|
+
# eval call included a binding, so we still eval in the binding
|
|
36
|
+
rval = eval_without_immunio(*args)
|
|
37
|
+
# XXX We can and should log here, but it breaks a ruby test (atom_feed_helper)
|
|
38
|
+
#Immunio.logger.debug {"Eval_without_immunio returned: #{rval.inspect}"}
|
|
39
|
+
end
|
|
40
|
+
rval
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if Immunio::agent.plugin_enabled?("eval") then
|
|
48
|
+
Kernel.send :include, Immunio::KernelEvalHook
|
|
49
|
+
Kernel.extend Immunio::KernelEvalHook
|
|
50
|
+
Immunio.logger.debug "Eval: All hooks installed."
|
|
51
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require_relative "../blocked_app"
|
|
2
|
+
require_relative "../errors"
|
|
3
|
+
|
|
4
|
+
module Immunio
|
|
5
|
+
# Rack middleware trigger proper hook whenever an exception is raised in the app.
|
|
6
|
+
class ExceptionHandler
|
|
7
|
+
def initialize(app)
|
|
8
|
+
@app = app
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(env)
|
|
12
|
+
@app.call(env)
|
|
13
|
+
rescue RequestBlocked
|
|
14
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}[RequestBlocked]" do
|
|
15
|
+
# Avoid bubbling exception to logging middlewares.
|
|
16
|
+
Immunio.blocked_app.call(env)
|
|
17
|
+
end
|
|
18
|
+
rescue OverrideResponse => override
|
|
19
|
+
status, headers, body = Immunio.override_response.call(env, override)
|
|
20
|
+
|
|
21
|
+
Immunio.run_hook "http_tracker", "http_response_start",
|
|
22
|
+
status: status, headers: headers
|
|
23
|
+
[status, headers, body]
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}[#{e.class}]" do
|
|
26
|
+
# Check for template, DB errors and such generated by the agent.
|
|
27
|
+
original_exception = unwrap_exception(e)
|
|
28
|
+
if RequestBlocked === original_exception || original_exception.message =~ /^Immunio::RequestBlocked:/
|
|
29
|
+
return Immunio.blocked_app.call(env)
|
|
30
|
+
end
|
|
31
|
+
if OverrideResponse === original_exception # We can't do anything with just the message:
|
|
32
|
+
status, headers, body = Immunio.override_response.call(env, original_exception)
|
|
33
|
+
|
|
34
|
+
Immunio.run_hook "http_tracker", "http_response_start",
|
|
35
|
+
status: status, headers: headers
|
|
36
|
+
return [status, headers, body]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# A real app exception, not generated by the agent.
|
|
40
|
+
Immunio.run_hook! "exception_handler", "exception", exception: "#{e.class.name}: #{e}"
|
|
41
|
+
|
|
42
|
+
# Re-raise
|
|
43
|
+
raise
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Unwrap the innermost original exception.
|
|
48
|
+
def unwrap_exception(e)
|
|
49
|
+
while e.respond_to? :original_exception
|
|
50
|
+
e = e.original_exception
|
|
51
|
+
end
|
|
52
|
+
e
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Haml compiles templates to something like:
|
|
2
|
+
#
|
|
3
|
+
#begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, {:autoclose=>["area", "base", "basefont", "br", "col", "command", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"], :preserve=>["textarea", "pre", "code"], :attr_wrapper=>"'", :ugly=>true, :format=>:html5, :encoding=>"UTF-8", :escape_html=>true, :escape_attrs=>true, :hyphenate_data_attrs=>true, :cdata=>false});_erbout = _hamlout.buffer;@output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil;;_hamlout.buffer << "<script>console.log('test')</script>\n";::Haml::Util.html_safe(_erbout);ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;
|
|
4
|
+
#
|
|
5
|
+
# In there is a call to ::Haml::Util.html_safe, which calls the real html_safe.
|
|
6
|
+
# But lots of haml code paths call ::Haml::Util.html_safe, so we can't mark it
|
|
7
|
+
# as safe. Instead, we need to modify the compiled template code to call the
|
|
8
|
+
# method in a marked-safe context.
|
|
9
|
+
#
|
|
10
|
+
# But haml doesn't make it easy, as it alias-method-chains its own methods. This
|
|
11
|
+
# module continues the chain to subvert the
|
|
12
|
+
# precompiled_method_return_value_with_haml_xss method and instead call our own
|
|
13
|
+
# method, which calls html_safe while in a safe context.
|
|
14
|
+
|
|
15
|
+
module Immunio
|
|
16
|
+
module Haml
|
|
17
|
+
module Compiler
|
|
18
|
+
extend ActiveSupport::Concern
|
|
19
|
+
|
|
20
|
+
included do
|
|
21
|
+
alias_method :precompiled_method_return_value_without_haml_xss_without_immunio, :precompiled_method_return_value_without_haml_xss
|
|
22
|
+
alias_method :precompiled_method_return_value, :precompiled_method_return_value_with_haml_xss_with_immunio
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.immunio_html_safe(string)
|
|
26
|
+
Immunio::UnsafeBufferDetection.in_safe_context { ::Haml::Util.html_safe(string) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def precompiled_method_return_value_with_haml_xss_with_immunio
|
|
30
|
+
"Immunio::Haml::Compiler.immunio_html_safe(#{precompiled_method_return_value_without_haml_xss_without_immunio})"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
::Haml::Compiler.send :include, Immunio::Haml::Compiler if defined? ::Haml::Compiler
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require "delegate"
|
|
2
|
+
|
|
3
|
+
module Immunio
|
|
4
|
+
# Rack middleware running at the very end of the stack to finish HTTP requests.
|
|
5
|
+
class HTTPFinisher
|
|
6
|
+
def initialize(app)
|
|
7
|
+
@app = app
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(env)
|
|
11
|
+
status, headers, body = @app.call(env)
|
|
12
|
+
if Request.current
|
|
13
|
+
Immunio.logger.debug "Finishing request in HTTPFinisher"
|
|
14
|
+
[status, headers, BodyWrapper.new(body)]
|
|
15
|
+
else
|
|
16
|
+
[status, headers, body]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class BodyWrapper < SimpleDelegator
|
|
22
|
+
def initialize(body)
|
|
23
|
+
super body
|
|
24
|
+
@body = body
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def each
|
|
28
|
+
begin
|
|
29
|
+
@body.each do |chunk|
|
|
30
|
+
Immunio.run_hook "http_finisher", "http_response_body_chunk",
|
|
31
|
+
chunk: chunk
|
|
32
|
+
yield chunk
|
|
33
|
+
end
|
|
34
|
+
rescue RequestBlocked
|
|
35
|
+
# We need to catch this exception here as access to static files is
|
|
36
|
+
# deferred. If one of our hooks, such as file_io throws when the Rake
|
|
37
|
+
# stack has unwound this far it will kill the request with a 500
|
|
38
|
+
yield "Request Blocked"
|
|
39
|
+
end
|
|
40
|
+
Immunio.run_hook "http_finisher", "http_request_finish"
|
|
41
|
+
|
|
42
|
+
# Reset current request
|
|
43
|
+
Immunio.finish_request
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def respond_to?(*args)
|
|
47
|
+
@body.respond_to?(*args)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
require_relative "../blocked_app"
|
|
2
|
+
require_relative "../errors"
|
|
3
|
+
|
|
4
|
+
module Immunio
|
|
5
|
+
# Rack middleware tracking HTTP requests and responses and triggers the proper hooks.
|
|
6
|
+
class HTTPTracker
|
|
7
|
+
def initialize(app)
|
|
8
|
+
@app = app
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(env)
|
|
12
|
+
request = Request.new(env)
|
|
13
|
+
request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
14
|
+
Immunio.logger.debug "Creating new request in HTTPTracker"
|
|
15
|
+
Immunio.new_request(request)
|
|
16
|
+
|
|
17
|
+
Immunio.run_hook! "http_tracker", "http_request_start", meta_from_env(env)
|
|
18
|
+
|
|
19
|
+
env['rack.input'] = InputWrapper.new(env['rack.input'])
|
|
20
|
+
|
|
21
|
+
status, headers, body = Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
22
|
+
@app.call(env)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Run hook for the session only if it was loaded
|
|
26
|
+
session = env["rack.session"]
|
|
27
|
+
if session_was_loaded?(session)
|
|
28
|
+
Immunio.run_hook! "http_tracker", "framework_session",
|
|
29
|
+
session_id: extract_session_id(session)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Immunio expects response headers as a list of tuples.
|
|
33
|
+
list_headers = headers_to_list(headers)
|
|
34
|
+
|
|
35
|
+
result = Immunio.run_hook! "http_tracker", "http_response_start",
|
|
36
|
+
status: status, headers: list_headers
|
|
37
|
+
|
|
38
|
+
# If new headers are specified, convert them back to the Ruby hash format.
|
|
39
|
+
if result["headers"] != nil
|
|
40
|
+
# The new_headers completely replace the originals.
|
|
41
|
+
headers = list_to_headers(result["headers"].to_a)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Send the response
|
|
45
|
+
[status, headers, body]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
rescue RequestBlocked
|
|
49
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}[RequestBlocked]" do
|
|
50
|
+
status, headers, body = Immunio.blocked_app.call(env)
|
|
51
|
+
# Do not allow blocking the request here
|
|
52
|
+
Immunio.run_hook "http_tracker", "http_response_start",
|
|
53
|
+
status: status, headers: headers
|
|
54
|
+
|
|
55
|
+
[status, headers, body]
|
|
56
|
+
end
|
|
57
|
+
rescue OverrideResponse => override
|
|
58
|
+
status, headers, body = Immunio.override_response.call(env, override)
|
|
59
|
+
|
|
60
|
+
Immunio.run_hook "http_tracker", "http_response_start",
|
|
61
|
+
status: status, headers: headers
|
|
62
|
+
[status, headers, body]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
def headers_to_list(headers)
|
|
67
|
+
list_headers = []
|
|
68
|
+
headers.each do |name, value|
|
|
69
|
+
# Ruby treats the `Set-Cookie` header specially. If there are multiple
|
|
70
|
+
# Set-Cookie headers to send, they are joined into a single field,
|
|
71
|
+
# separated by line-feeds.
|
|
72
|
+
if name == "Set-Cookie"
|
|
73
|
+
value.split("\n").each do |cookie_val|
|
|
74
|
+
list_headers.push(["Set-Cookie", cookie_val])
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
list_headers.push([name, value])
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
list_headers
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def list_to_headers(list)
|
|
84
|
+
new_headers = {}
|
|
85
|
+
list.each do |name, value|
|
|
86
|
+
# If this header is already in `new_headers`, append to the
|
|
87
|
+
# existing value with a linefeed separator.
|
|
88
|
+
if new_headers.has_key?(name)
|
|
89
|
+
new_headers[name] += ("\n" + value)
|
|
90
|
+
else
|
|
91
|
+
new_headers[name] = value
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
new_headers
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def meta_from_env(env)
|
|
98
|
+
request = Rack::Request.new(env)
|
|
99
|
+
|
|
100
|
+
# Extract request headers from `env`.
|
|
101
|
+
headers = env.select { |k| k.starts_with? "HTTP_" }.
|
|
102
|
+
each_with_object({}) { |(k, v), h| h.store k[5..-1].downcase.tr('_', '-'), v }
|
|
103
|
+
|
|
104
|
+
# Determine scheme (http://www.rubydoc.info/github/rack/rack/master/file/SPEC)
|
|
105
|
+
# There are also some HTTP headers from proxies that may affect the
|
|
106
|
+
# scheme seen by the end user. We process those in the hooks.
|
|
107
|
+
scheme = env["rack.url_scheme"]
|
|
108
|
+
if env["HTTPS"] == "on"
|
|
109
|
+
# Some servers will set the HTTPS var explicity. If set, use it
|
|
110
|
+
scheme = "https"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Determine the route name in controller#action format:
|
|
114
|
+
route_name = nil
|
|
115
|
+
|
|
116
|
+
if defined?(Rails.application)
|
|
117
|
+
begin
|
|
118
|
+
path = request.env['PATH_INFO']
|
|
119
|
+
method = request.env['REQUEST_METHOD'].downcase.to_sym
|
|
120
|
+
url = Rails.application.routes.recognize_path(path, method: method)
|
|
121
|
+
route_name = "#{url[:controller]}##{url[:action]}"
|
|
122
|
+
rescue ActionController::RoutingError
|
|
123
|
+
route_name = nil
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
{
|
|
128
|
+
protocol: env["SERVER_PROTOCOL"],
|
|
129
|
+
scheme: scheme,
|
|
130
|
+
uri: env["REQUEST_URI"],
|
|
131
|
+
server_name: env["SERVER_NAME"],
|
|
132
|
+
# SERVER_ADDR is non-standard, but rack uses it as a fallback, so
|
|
133
|
+
# include it here as well so we can access it from Lua.
|
|
134
|
+
server_addr: env["SERVER_ADDR"],
|
|
135
|
+
server_port: env["SERVER_PORT"],
|
|
136
|
+
route_name: route_name,
|
|
137
|
+
querystring: request.query_string,
|
|
138
|
+
method: request.request_method,
|
|
139
|
+
path: request.path_info,
|
|
140
|
+
socket_ip: request.ip,
|
|
141
|
+
socket_port: request.port,
|
|
142
|
+
headers: headers
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def session_was_loaded?(session)
|
|
147
|
+
session && (session.respond_to?(:loaded?) ? session.loaded? : true)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def extract_session_id(session)
|
|
151
|
+
case
|
|
152
|
+
when session.respond_to?(:id)
|
|
153
|
+
session.id
|
|
154
|
+
when session[:id]
|
|
155
|
+
session[:id]
|
|
156
|
+
when session[:session_id]
|
|
157
|
+
session[:session_id]
|
|
158
|
+
else
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
class InputWrapper < SimpleDelegator
|
|
165
|
+
def initialize(input)
|
|
166
|
+
super input
|
|
167
|
+
@input = input
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def gets(*)
|
|
171
|
+
v = super
|
|
172
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
173
|
+
report_chunk v
|
|
174
|
+
end
|
|
175
|
+
v
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def read(*)
|
|
179
|
+
v = super
|
|
180
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
181
|
+
report_chunk v
|
|
182
|
+
end
|
|
183
|
+
v
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def each
|
|
187
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
188
|
+
@input.each do |chunk|
|
|
189
|
+
report_chunk chunk
|
|
190
|
+
Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
|
191
|
+
yield chunk
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
def report_chunk(chunk)
|
|
199
|
+
Immunio.run_hook! "http_tracker", "http_request_body_chunk",
|
|
200
|
+
chunk: chunk
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|