immunio 0.15.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/immunio/cli.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Immunio
|
5
|
+
class CLI < Thor
|
6
|
+
desc 'init', 'Initializes an Immunio configuration in the current app'
|
7
|
+
method_option :key,
|
8
|
+
type: :string,
|
9
|
+
desc: 'The key generated for your app in Immunio'
|
10
|
+
method_option :secret,
|
11
|
+
type: :string,
|
12
|
+
desc: 'The secret generated for your app in Immunio'
|
13
|
+
def init
|
14
|
+
if File.exist?(config_file) && File.read(config_file) =~ /key:\s+\w+|secret:\s+\w+/
|
15
|
+
say 'Immunio already initialized.', :green
|
16
|
+
Kernel.exit 0
|
17
|
+
end
|
18
|
+
|
19
|
+
key = options[:key] || ask('Enter the key generated for your app in Immunio:')
|
20
|
+
secret = options[:secret] || ask('Enter the secret generated for your app in Immunio:')
|
21
|
+
|
22
|
+
FileUtils.mkdir_p(File.dirname(config_file))
|
23
|
+
|
24
|
+
File.open(config_file, 'a') do |f|
|
25
|
+
f.puts "key: #{key}"
|
26
|
+
f.puts "secret: #{secret}"
|
27
|
+
end
|
28
|
+
|
29
|
+
say "Credentials written to #{config_file}", :green
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def config_file
|
35
|
+
root = defined?(Rails) ? Rails.root : Dir.pwd
|
36
|
+
File.join(root, 'config', 'immunio.yml')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Immunio
|
2
|
+
module Context
|
3
|
+
RAILS_TEMPLATE_FILTER = Regexp.new("(.*(_erb|_haml))__+\\d+_\\d+(.*)")
|
4
|
+
# Cache for contexts (named in tribute to our buddy Adam Back who invented proof of work)
|
5
|
+
@@hash_cache = {}
|
6
|
+
|
7
|
+
# Calculate context hashes and a stack trace. Additional data, in the form
|
8
|
+
# of a String, may be provided to mix into the strict context hash.
|
9
|
+
def self.context(additional_data=nil)
|
10
|
+
# We can filter out at least the top two frames
|
11
|
+
cache_key = Digest::SHA1.hexdigest(caller(2).join())
|
12
|
+
if @@hash_cache.has_key?(cache_key) then
|
13
|
+
loose_context = @@hash_cache[cache_key]["loose_context"]
|
14
|
+
strict_context = @@hash_cache[cache_key]["strict_context"]
|
15
|
+
stack = @@hash_cache[cache_key]["stack"]
|
16
|
+
loose_stack = @@hash_cache[cache_key]["stack"]
|
17
|
+
|
18
|
+
if Immunio.agent.config.log_context_data
|
19
|
+
Immunio.logger.info {"Stack contexts from cache"}
|
20
|
+
end
|
21
|
+
else
|
22
|
+
# Use ropes as they're faster than string concatenation
|
23
|
+
loose_stack_rope = []
|
24
|
+
loose_context_rope = []
|
25
|
+
stack_rope = []
|
26
|
+
strict_context_rope = []
|
27
|
+
|
28
|
+
# drop the top frame as it's us, but retain the rest. Immunio frames
|
29
|
+
# are filtered by the Gem regex.
|
30
|
+
locations = caller(1).map do |frame|
|
31
|
+
frame = frame.split(":", 3)
|
32
|
+
{path: frame[0], line: frame[1], label: frame[2]}
|
33
|
+
end
|
34
|
+
|
35
|
+
locations.each do |frame|
|
36
|
+
|
37
|
+
# Filter frame names from template rendering to remove generated random bits
|
38
|
+
matchdata = RAILS_TEMPLATE_FILTER.match(frame[:label])
|
39
|
+
if matchdata != nil then
|
40
|
+
frame[:label] = matchdata[1] + matchdata[3]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Reduce paths to be relative to root if possible, to allow
|
44
|
+
# relocation. If there's no rails root, or the path doesn't start with
|
45
|
+
# the rails root, just use the filename part.
|
46
|
+
if defined?(Rails) && defined?(Rails.root) &&
|
47
|
+
Rails.root && frame[:path].start_with?(Rails.root.to_s)
|
48
|
+
strict_path = frame[:path].sub(Rails.root.to_s, '')
|
49
|
+
else
|
50
|
+
strict_path = File.basename(frame[:path])
|
51
|
+
end
|
52
|
+
|
53
|
+
stack_rope << "\n" unless stack_rope.empty?
|
54
|
+
stack_rope << frame[:path]
|
55
|
+
stack_rope << ":"
|
56
|
+
stack_rope << frame[:line]
|
57
|
+
stack_rope << ":"
|
58
|
+
stack_rope << frame[:label]
|
59
|
+
|
60
|
+
strict_context_rope << "\n" unless strict_context_rope.empty?
|
61
|
+
strict_context_rope << strict_path
|
62
|
+
strict_context_rope << ":"
|
63
|
+
strict_context_rope << frame[:line]
|
64
|
+
strict_context_rope << ":"
|
65
|
+
strict_context_rope << frame[:label]
|
66
|
+
|
67
|
+
# Remove pathname from the loose context. The goal here is to prevent
|
68
|
+
# upgrading gem versions from changing the loose context key, so for instance
|
69
|
+
# users don't have to rebuild their whitelists every time they update a gem
|
70
|
+
loose_context_rope << "\n" unless loose_context_rope.empty?
|
71
|
+
loose_context_rope << File.basename(frame[:path])
|
72
|
+
loose_context_rope << ":"
|
73
|
+
loose_context_rope << frame[:label]
|
74
|
+
|
75
|
+
# build a second seperate rope for the stack that determines ou loose context key
|
76
|
+
# This includes filenames for usability -- just method names not being very good
|
77
|
+
# for display purposes...
|
78
|
+
loose_stack_rope << "\n" unless loose_stack_rope.empty?
|
79
|
+
loose_stack_rope << frame[:path]
|
80
|
+
loose_stack_rope << ":"
|
81
|
+
loose_stack_rope << frame[:label]
|
82
|
+
|
83
|
+
end
|
84
|
+
stack = stack_rope.join()
|
85
|
+
strict_stack = strict_context_rope.join()
|
86
|
+
loose_stack = loose_stack_rope.join()
|
87
|
+
|
88
|
+
if Immunio.agent.config.log_context_data
|
89
|
+
Immunio.logger.info {"Strict context stack:\n#{strict_stack}"}
|
90
|
+
Immunio.logger.info {"Loose context stack:\n#{loose_stack}"}
|
91
|
+
end
|
92
|
+
|
93
|
+
strict_context = Digest::SHA1.hexdigest(strict_stack)
|
94
|
+
loose_context = Digest::SHA1.hexdigest(loose_context_rope.join())
|
95
|
+
@@hash_cache[cache_key] = {
|
96
|
+
"strict_context" => strict_context,
|
97
|
+
"loose_context" => loose_context,
|
98
|
+
"stack" => stack,
|
99
|
+
"loose_stack" => loose_stack
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
# Mix in additional context data
|
104
|
+
unless additional_data.nil?
|
105
|
+
if Immunio.agent.config.log_context_data
|
106
|
+
Immunio.logger.info {"Additional context data:\n#{additional_data}"}
|
107
|
+
end
|
108
|
+
strict_context = Digest::SHA1.hexdigest(strict_context + additional_data)
|
109
|
+
end
|
110
|
+
|
111
|
+
return strict_context, loose_context, stack, loose_stack
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Immunio
|
4
|
+
# General agent error
|
5
|
+
class Error < RuntimeError; end
|
6
|
+
|
7
|
+
# Error to block a request in progress
|
8
|
+
# Ruby's `SecurityError` is outside the `StandardError` hierarchy, so it will
|
9
|
+
# only be caught if somebody decides to catch it explicitly.
|
10
|
+
#
|
11
|
+
# `StandardError` and `RuntimeError` are both caught by a default rescue
|
12
|
+
# block. This makes it too easy for a developer to catch and ignore one of
|
13
|
+
# our blocking errors unintentionally.
|
14
|
+
class BlockError < SecurityError; end
|
15
|
+
|
16
|
+
# Request was not allowed by hook
|
17
|
+
class RequestBlocked < BlockError
|
18
|
+
class Result
|
19
|
+
include Singleton
|
20
|
+
|
21
|
+
# PG activerecord exception results must have an error_field method before Rails 4.x
|
22
|
+
def error_field(_field)
|
23
|
+
""
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# PG activerecord exceptions must have a result method before Rails 4.x
|
28
|
+
def result
|
29
|
+
Result.instance
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Response overridden by hook
|
34
|
+
class OverrideResponse < BlockError
|
35
|
+
attr_reader :status, :headers, :body
|
36
|
+
|
37
|
+
def initialize(status, headers, body)
|
38
|
+
@status = status
|
39
|
+
@headers = headers
|
40
|
+
@body = body
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
|
3
|
+
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
4
|
+
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
|
5
|
+
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
|
6
|
+
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
|
7
|
+
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
|
8
|
+
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
|
9
|
+
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
|
10
|
+
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
|
11
|
+
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
|
12
|
+
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
|
13
|
+
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
|
14
|
+
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
|
15
|
+
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
|
16
|
+
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
|
17
|
+
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
|
18
|
+
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
|
19
|
+
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
|
20
|
+
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
|
21
|
+
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
|
22
|
+
-----END CERTIFICATE-----
|
23
|
+
|
24
|
+
-----BEGIN CERTIFICATE-----
|
25
|
+
MIIDljCCAn6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBcMQswCQYDVQQGEwJDQTEW
|
26
|
+
MBQGA1UEChMNSW1tdW4uaW8gSW5jLjEVMBMGA1UECxMMd3d3LmltbXVuLmlvMR4w
|
27
|
+
HAYDVQQDExVJbW11bi5pbyBJbmMuIFJvb3QgQ0EwHhcNMTQwMzI1MTQ0NTI3WhcN
|
28
|
+
MjQwMzIyMTQ0NTI3WjBcMQswCQYDVQQGEwJDQTEWMBQGA1UEChMNSW1tdW4uaW8g
|
29
|
+
SW5jLjEVMBMGA1UECxMMd3d3LmltbXVuLmlvMR4wHAYDVQQDExVJbW11bi5pbyBJ
|
30
|
+
bmMuIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDomhh6
|
31
|
+
lGHL6IsOlK7TmFikinZ0ShPQQ8WHFbwoLiELGJSNGFKdiSnQICOkjTI6kdXCiOgk
|
32
|
+
TYARs8Ty7e1rbCTiBZUV2d2Q1qktS+wHv6GYEukjkX+/yLLf65XsNQlbPheFuBCG
|
33
|
+
Tvy8qU8PbdXQu35zLuCwpq8DAanCEXWANOAIYXOaIa0DMqRrVG5QeSfi5mxeXL5q
|
34
|
+
Xb4FhqQbMwX7xkQiIB3NQCmVtplDSdMPfCvg/T97rC14XR8jKteRe2OkaLFeGHZp
|
35
|
+
mROT2k4dTY4r8h2dV3EW8N4pQizDVpUfzNrgwoaKGccg6JiLLVeMQT7BJxjEi3Tt
|
36
|
+
tSt7gf0cWGwMWHbbAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
|
37
|
+
BAQDAgEGMB0GA1UdDgQWBBRUsJtzljk4lIWaEpQXfTR48b70SDAfBgNVHSMEGDAW
|
38
|
+
gBRUsJtzljk4lIWaEpQXfTR48b70SDANBgkqhkiG9w0BAQUFAAOCAQEAS0PyWyXj
|
39
|
+
1oF/pWKhY6g71UlcnnISXoutIEQxK0cxqzwjCd/62W5SlgxpSFHJmeuhMHKgT1XF
|
40
|
+
PgretLnGSXft1ZmpAtUmHXJYxSFbHz8kv/S5rYW7GeJRTUEqJNGly9nPiAkbY2Uk
|
41
|
+
nZSF2uY62CztoDTtESunAcSPN3BxoEEQ3GLg+PFJVFVApyXfjdK5Jc9lfMGH6vIX
|
42
|
+
hnW1BIYyyHqaerL2VSFJBaxqDk9NeUmt3w77EdJ0CpRo03Gbw8g19aIrXr9u25lW
|
43
|
+
Zm2+58fpRuZ2pdclnTWfjyKb00WCkqfInFLeMvXIEhA60TirhlPBRZ5+8D42I5Sd
|
44
|
+
AcWwGci7HgH1+A==
|
45
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Immunio
|
5
|
+
# Subclass global Logger class to add TRACE level
|
6
|
+
class Logger < ::Logger
|
7
|
+
module Severity
|
8
|
+
TRACE = -1
|
9
|
+
end
|
10
|
+
include Severity
|
11
|
+
|
12
|
+
def trace?; @level <= TRACE; end
|
13
|
+
|
14
|
+
def trace(progname = nil, &block)
|
15
|
+
add(TRACE, nil, progname, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def format_severity(severity)
|
19
|
+
SEV_LABEL[severity] || 'ANY'
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
SEV_LABEL = Array.new(::Logger::SEV_LABEL)
|
24
|
+
SEV_LABEL[-1] = 'TRACE'
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :logger
|
28
|
+
|
29
|
+
def self.create_startup_logger
|
30
|
+
@startup_messages = StringIO.new
|
31
|
+
@logger = Logger.new @startup_messages
|
32
|
+
|
33
|
+
setup_logger_formatter
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.setup_logger_formatter
|
37
|
+
logger.formatter = proc do |severity, datetime, _progname, msg|
|
38
|
+
"[#{datetime}] #{severity}: #{msg}\n"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.switch_to_real_logger(log_file, log_level)
|
43
|
+
# Have we already switched to real logger?
|
44
|
+
return if !defined?(@startup_messages)
|
45
|
+
|
46
|
+
if log_file == "STDOUT"
|
47
|
+
@logger = Logger.new $stdout
|
48
|
+
elsif log_file == "STDERR"
|
49
|
+
@logger = Logger.new $stderr
|
50
|
+
else
|
51
|
+
path = Pathname.new(log_file)
|
52
|
+
begin
|
53
|
+
FileUtils.mkdir_p path.dirname unless File.exist? path.dirname
|
54
|
+
|
55
|
+
file = File.open path, 'a'
|
56
|
+
file.binmode
|
57
|
+
file.sync = true
|
58
|
+
|
59
|
+
@logger = Logger.new file
|
60
|
+
log_file = path.realpath
|
61
|
+
rescue StandardError => e
|
62
|
+
logger.warn "Failed to open #{log_file} (#{path.realdirpath}) for logging (#{e.message})"
|
63
|
+
@logger = Logger.new $stderr
|
64
|
+
log_file = "STDERR"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Dump saved log messages during startup to real log
|
69
|
+
logger << @startup_messages.string
|
70
|
+
remove_instance_variable(:@startup_messages)
|
71
|
+
|
72
|
+
setup_logger_formatter
|
73
|
+
|
74
|
+
begin
|
75
|
+
logger.level = Logger.const_get(log_level.to_s.upcase)
|
76
|
+
rescue
|
77
|
+
logger.level = Logger::DEBUG
|
78
|
+
logger.debug "Failed to interpret log level #{log_level}, falling back to debug"
|
79
|
+
end
|
80
|
+
|
81
|
+
logger.debug "Logging to #{log_file}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.logger
|
85
|
+
@logger
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Immunio
|
2
|
+
module CookieHooks
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
# TODO: should anything be checked to make sure @parent_jar exists
|
7
|
+
if method_defined? :[] # Not sure when this wouldn't exist.
|
8
|
+
# The following won't work because of the names:
|
9
|
+
# alias_method_chain :[], :immunio if method_defined? :[]
|
10
|
+
alias_method :lookup_without_immunio, :[]
|
11
|
+
alias_method :[], :lookup_with_immunio
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def lookup_with_immunio(name)
|
17
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
18
|
+
raw_cookie_value = @parent_jar[name]
|
19
|
+
cookie_value = Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
20
|
+
lookup_without_immunio(name)
|
21
|
+
end
|
22
|
+
if !raw_cookie_value.nil? and cookie_value.nil?
|
23
|
+
Immunio.run_hook! "action_dispatch", "bad_cookie", key: name,
|
24
|
+
value: raw_cookie_value
|
25
|
+
end
|
26
|
+
cookie_value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class ActionDispatch::Cookies
|
33
|
+
if defined? SignedCookieJar
|
34
|
+
SignedCookieJar.send :include, Immunio::CookieHooks
|
35
|
+
end
|
36
|
+
if defined? UpgradeLegacySignedCookieJar
|
37
|
+
UpgradeLegacySignedCookieJar.send :include, Immunio::CookieHooks
|
38
|
+
end
|
39
|
+
if defined? EncryptedCookieJar
|
40
|
+
EncryptedCookieJar.send :include, Immunio::CookieHooks
|
41
|
+
end
|
42
|
+
if defined? UpgradeLegacyEncryptedCookieJar
|
43
|
+
UpgradeLegacyEncryptedCookieJar.send :include, Immunio::CookieHooks
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,431 @@
|
|
1
|
+
# Hook into ActionView rendering to inject Immunio's hooks.
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module Immunio
|
5
|
+
# Renders templates by filtering them through Immunio's hook handlers.
|
6
|
+
class Template
|
7
|
+
attr_accessor :vars
|
8
|
+
|
9
|
+
def initialize(template)
|
10
|
+
@template = template
|
11
|
+
@next_var_id = 0
|
12
|
+
@next_template_id = 0
|
13
|
+
@vars = {}
|
14
|
+
@scheduled_fragments_writes = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def id
|
18
|
+
(@template.respond_to?(:virtual_path) && @template.virtual_path) || (@template.respond_to?(:source) && @template.source)
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
self.class === other && id == other.id
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_source?
|
26
|
+
@template.respond_to?(:source) && !@template.source.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_text?
|
30
|
+
@template.formats.first == :text
|
31
|
+
end
|
32
|
+
|
33
|
+
def load_source(context)
|
34
|
+
return if !@template.respond_to?(:source) || !@template.source.nil?
|
35
|
+
|
36
|
+
# @template is a virtual template that doesn't contain the source. We need
|
37
|
+
# to try to load the source. But, the virtual template doesn't know the
|
38
|
+
# original format of the source template file. Grab the original format
|
39
|
+
# from the view context and override the default of just :html when
|
40
|
+
# when looking up the template.
|
41
|
+
old_formats = context.lookup_context.formats
|
42
|
+
begin
|
43
|
+
context.lookup_context.formats = @template.formats
|
44
|
+
refreshed = @template.refresh(context)
|
45
|
+
ensure
|
46
|
+
context.lookup_context.formats = old_formats
|
47
|
+
end
|
48
|
+
|
49
|
+
return if refreshed.nil?
|
50
|
+
|
51
|
+
@template.instance_variable_set :@source, refreshed.source
|
52
|
+
end
|
53
|
+
|
54
|
+
def template_sha
|
55
|
+
# A template might have a source but it might be nil.
|
56
|
+
@template_sha ||= begin
|
57
|
+
Digest::SHA1.hexdigest(@template.source) if has_source?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def compiled?
|
62
|
+
@template.instance_variable_get :@compiled
|
63
|
+
end
|
64
|
+
|
65
|
+
# Generate the next var unique ID to be used in a template.
|
66
|
+
def next_var_id
|
67
|
+
id = @next_var_id
|
68
|
+
@next_var_id += 1
|
69
|
+
id
|
70
|
+
end
|
71
|
+
|
72
|
+
def next_template_id
|
73
|
+
id = @next_template_id
|
74
|
+
@next_template_id += 1
|
75
|
+
id
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_nonce
|
79
|
+
# Generate a two byte CSRNG nonce to make our substitutions unpreictable
|
80
|
+
# Why only 2 bytes? The nonce is per render, so the odds of guessing it are very low
|
81
|
+
# and entropy is finite so we don't want to drain the random pool unnecessarily
|
82
|
+
@nonce ||= SecureRandom.hex(2)
|
83
|
+
end
|
84
|
+
|
85
|
+
def mark_var(content, code, template_id, file, line, escape)
|
86
|
+
id = Template.next_var_id
|
87
|
+
nonce = Template.get_nonce
|
88
|
+
Template.vars[id.to_s] = {
|
89
|
+
template_sha: template_sha,
|
90
|
+
template_id: template_id.to_s,
|
91
|
+
nonce: nonce,
|
92
|
+
code: code,
|
93
|
+
file: file,
|
94
|
+
line: line
|
95
|
+
}
|
96
|
+
|
97
|
+
rval = ""
|
98
|
+
# NOTE: What happens here is pretty funky to preserve the html_safe SafeBuffer behaviour in ruby.
|
99
|
+
# If escaped is true we directly concatenate the content between two SafeBuffers. This will cause
|
100
|
+
# escaping if content is not itself a SafeBuffer.
|
101
|
+
# Otherwise we explicitly convert to a string, and convert that to a SafeBuffer to ensure that
|
102
|
+
# for instance no escaping is performed on the contents of a <%== %> Erubis interpolation.
|
103
|
+
if escape and not is_text? then
|
104
|
+
# explicitly convert (w/ escapes) and mark safe things that aren't String (SafeBuffer is_a String also)
|
105
|
+
# `to_s` is used to render any object passed to a template.
|
106
|
+
# It is called internally when appending to ActionView::OutputBuffer.
|
107
|
+
# We force rendering to get the actual string.
|
108
|
+
# This has no impact if `rendered` is already a string.
|
109
|
+
content = content.to_s.html_safe unless content.is_a? String
|
110
|
+
# As a failsafe, just return the content if it already contains our markers. This can occur when
|
111
|
+
# a helper calls render partial to generate a component of a page. Both render calls are root level
|
112
|
+
# templates from our perspective.
|
113
|
+
if content =~ /\{immunio-var:\d+:#{nonce}\}/ then
|
114
|
+
# don't add markers.
|
115
|
+
Immunio.logger.debug {"WARNING: ActionView not marking interpolation which already contains markers: \"#{content}\""}
|
116
|
+
rval = content
|
117
|
+
else
|
118
|
+
rval = "{immunio-var:#{id}:#{nonce}}".html_safe + content + "{/immunio-var:#{id}:#{nonce}}".html_safe
|
119
|
+
end
|
120
|
+
else
|
121
|
+
content = "" if content.nil?
|
122
|
+
# See comment above
|
123
|
+
if content =~ /\{immunio-var:\\d+:#{nonce}\}/ then
|
124
|
+
# don't add markers.
|
125
|
+
Immunio.logger.debug {"WARNING: ActionView not marking interpolation which already contains markers: \"#{content}\""}
|
126
|
+
rval = content.html_safe
|
127
|
+
else
|
128
|
+
rval = "{immunio-var:#{id}:#{nonce}}".html_safe + content.html_safe + "{/immunio-var:#{id}:#{nonce}}".html_safe
|
129
|
+
end
|
130
|
+
end
|
131
|
+
rval
|
132
|
+
end
|
133
|
+
|
134
|
+
def mark_and_defer_fragment_write(key, content, options)
|
135
|
+
id = @scheduled_fragments_writes.size
|
136
|
+
nonce = Template.get_nonce
|
137
|
+
@scheduled_fragments_writes << [key, content, options]
|
138
|
+
"{immunio-fragment:#{id}:#{nonce}}#{content}{/immunio-fragment:#{id}:#{nonce}}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def render(context)
|
142
|
+
load_source context
|
143
|
+
# Don't handle templates with no source (inline text templates).
|
144
|
+
if not has_source? then
|
145
|
+
rendered = yield
|
146
|
+
rendered.instance_variable_set("@__immunio_processed", true)
|
147
|
+
return rendered
|
148
|
+
end
|
149
|
+
|
150
|
+
begin
|
151
|
+
root = true if rendering_stack.length == 0
|
152
|
+
|
153
|
+
rendering_stack.push self
|
154
|
+
# Calculate SHA1 of this template.
|
155
|
+
template_sha
|
156
|
+
Immunio.logger.debug {"ActionView rendering template with sha #{@template_sha}, root: #{root}"}
|
157
|
+
rendered = yield
|
158
|
+
rendered.instance_variable_set("@__immunio_processed", true)
|
159
|
+
|
160
|
+
if root
|
161
|
+
# This is the root template. Let ActionView render it, and then look
|
162
|
+
# for XSS.
|
163
|
+
rendered = rendered.to_str
|
164
|
+
# Rendering done!
|
165
|
+
result = run_hook! "template_render_done", rendered: rendered, vars: @vars
|
166
|
+
|
167
|
+
# We use the return value from the hook handler if present.
|
168
|
+
rendered = result.fetch("rendered") { rendered.dup }
|
169
|
+
|
170
|
+
remove_var_markers! rendered
|
171
|
+
|
172
|
+
# If some fragments were marked to be cached, commit their content to cache.
|
173
|
+
write_and_remove_fragments! context, rendered
|
174
|
+
|
175
|
+
rendered.html_safe
|
176
|
+
else
|
177
|
+
# This is a partial template. Just render it.
|
178
|
+
rendered
|
179
|
+
end
|
180
|
+
ensure
|
181
|
+
top_template = rendering_stack.pop
|
182
|
+
unless top_template == self
|
183
|
+
raise Error, "Unexpected Immunio::Template on rendering stack. Expected #{id}, got #{top_template.try :id}."
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Generate code injected in templates to wrap everything inside `<%= ... %>`.
|
189
|
+
def self.generate_render_var_code(code, escape)
|
190
|
+
template = Template.current
|
191
|
+
if template
|
192
|
+
template_id = template.next_template_id
|
193
|
+
"(__immunio_result = (#{code}); Immunio::Template.render_var(#{code.strip.inspect}, __immunio_result, #{template_id}, __FILE__, __LINE__, #{escape}))"
|
194
|
+
else
|
195
|
+
code
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.render_var(code, rendered, template_id, file, line, escape)
|
200
|
+
if rendered.instance_variable_get("@__immunio_processed") then
|
201
|
+
# Ignore buffers marked as __immunio_processed in render as these are full templates or partials
|
202
|
+
return rendered
|
203
|
+
elsif code =~ /yield( .*)?/
|
204
|
+
# Ignore yielded blocks inside layouts
|
205
|
+
return rendered
|
206
|
+
end
|
207
|
+
template = Template.current
|
208
|
+
if template
|
209
|
+
rendered = template.mark_var rendered, code, template_id, file, line, escape
|
210
|
+
end
|
211
|
+
rendered.html_safe
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.current
|
215
|
+
rendering_stack.last
|
216
|
+
end
|
217
|
+
|
218
|
+
def self.next_var_id
|
219
|
+
rendering_stack.first.next_var_id
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.vars
|
223
|
+
rendering_stack.first.vars
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.get_nonce
|
227
|
+
rendering_stack.first.get_nonce
|
228
|
+
end
|
229
|
+
|
230
|
+
# Save fragment info to the root template only
|
231
|
+
def self.mark_and_defer_fragment_write(*args)
|
232
|
+
rendering_stack.first.mark_and_defer_fragment_write(*args)
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
# Stack of the templates currently being rendered.
|
237
|
+
def self.rendering_stack
|
238
|
+
Thread.current["immunio.rendering_stack"] ||= []
|
239
|
+
end
|
240
|
+
|
241
|
+
def rendering_stack
|
242
|
+
self.class.rendering_stack
|
243
|
+
end
|
244
|
+
|
245
|
+
def run_hook!(name, meta={})
|
246
|
+
default_meta = {
|
247
|
+
template_sha: template_sha,
|
248
|
+
name: (@template.respond_to?(:virtual_path) && @template.virtual_path) || nil,
|
249
|
+
origin: @template.identifier,
|
250
|
+
nonce: Template.get_nonce
|
251
|
+
}
|
252
|
+
Immunio.run_hook! "action_view", name, default_meta.merge(meta)
|
253
|
+
end
|
254
|
+
|
255
|
+
def write_and_remove_fragments!(context, content)
|
256
|
+
# Rails tests do use the context as the view context sometimes.
|
257
|
+
if context.is_a? ActionController::Base
|
258
|
+
controller = context
|
259
|
+
elsif context.respond_to? :controller
|
260
|
+
controller = context.controller
|
261
|
+
else
|
262
|
+
# Some rails unit tests don't have a controller...
|
263
|
+
remove_all_markers! content
|
264
|
+
return
|
265
|
+
end
|
266
|
+
|
267
|
+
# Iterate to handle nested fragments. Child fragments have lower ids than their parents.
|
268
|
+
nonce = Template.get_nonce
|
269
|
+
@scheduled_fragments_writes.each_with_index do |(key, _, options), id|
|
270
|
+
# Remove the markers ...
|
271
|
+
content.sub!(/\{immunio-fragment:#{id}:#{nonce}\}(.*)\{\/immunio-fragment:#{id}:#{nonce}\}/m) do
|
272
|
+
# The escaped content inside the markers ($1), is written to cache.
|
273
|
+
output = $1
|
274
|
+
remove_all_markers! output
|
275
|
+
controller.write_fragment_without_immunio key, output, options
|
276
|
+
output
|
277
|
+
end
|
278
|
+
end
|
279
|
+
# To be extra safe strip all markers from content
|
280
|
+
remove_all_markers! content
|
281
|
+
end
|
282
|
+
|
283
|
+
def remove_var_markers!(input)
|
284
|
+
nonce = Template.get_nonce
|
285
|
+
# TODO is this the fastest way to remove the markers? Needs benchmarking ...
|
286
|
+
input.gsub!(/\{\/?immunio-var:\d+:#{nonce}\}/, "")
|
287
|
+
end
|
288
|
+
|
289
|
+
def remove_all_markers!(input)
|
290
|
+
input.gsub!(/\{\/?immunio-(fragment|var):\d+:[a-zA-Z0-9]+\}/, "")
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Regexp to test for blocks (... do) in the Ruby code of templates.
|
295
|
+
BLOCK_EXPR = ActionView::Template::Handlers::Erubis::BLOCK_EXPR
|
296
|
+
|
297
|
+
# Hooks for the ERB template engine.
|
298
|
+
# (Default one used in Rails).
|
299
|
+
module ErubisHooks
|
300
|
+
extend ActiveSupport::Concern
|
301
|
+
|
302
|
+
included do
|
303
|
+
alias_method_chain :add_expr, :immunio
|
304
|
+
end
|
305
|
+
|
306
|
+
def add_expr_with_immunio(src, code, indicator)
|
307
|
+
# Wrap expressions in the templates to track their rendered value.
|
308
|
+
# Do not wrap expressions with blocks, eg.: <%= form_tag do %>
|
309
|
+
# TODO should we support blocks?
|
310
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
311
|
+
unless code =~ BLOCK_EXPR
|
312
|
+
# escape unless we see the == indicator
|
313
|
+
escape = !(indicator == '==')
|
314
|
+
code = Immunio::Template.generate_render_var_code(code, escape)
|
315
|
+
end
|
316
|
+
Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
317
|
+
add_expr_without_immunio(src, code, indicator)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Hooks for the HAML template engine.
|
324
|
+
module HamlHooks
|
325
|
+
extend ActiveSupport::Concern
|
326
|
+
|
327
|
+
included do
|
328
|
+
alias_method_chain :push_script, :immunio
|
329
|
+
end
|
330
|
+
|
331
|
+
def push_script_with_immunio(code, opts = {}, &block)
|
332
|
+
# Wrap expressions in the templates to track their rendered value.
|
333
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
334
|
+
if code !~ BLOCK_EXPR
|
335
|
+
# escape if we're told to by HAML
|
336
|
+
code = Immunio::Template.generate_render_var_code(code, opts[:escape_html])
|
337
|
+
end
|
338
|
+
Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
339
|
+
push_script_without_immunio(code, opts, &block)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Hook for the `ActionView::TemplateRenderer`. These are called for root
|
346
|
+
# templates.
|
347
|
+
module TemplateRendererHooks
|
348
|
+
extend ActiveSupport::Concern
|
349
|
+
|
350
|
+
included do
|
351
|
+
alias_method_chain :render_template, :immunio
|
352
|
+
end
|
353
|
+
|
354
|
+
def render_template_with_immunio(template, *args)
|
355
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
356
|
+
renderer = Template.new(template)
|
357
|
+
|
358
|
+
renderer.render @view do
|
359
|
+
Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
360
|
+
render_template_without_immunio(template, *args)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Hook for the `ActionView::Template`. These are called for non-root
|
368
|
+
# templates.
|
369
|
+
module TemplateHooks
|
370
|
+
extend ActiveSupport::Concern
|
371
|
+
|
372
|
+
included do
|
373
|
+
alias_method_chain :render, :immunio
|
374
|
+
end
|
375
|
+
|
376
|
+
def render_with_immunio(context, *args, &block)
|
377
|
+
Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
378
|
+
renderer = Template.new(self)
|
379
|
+
|
380
|
+
renderer.render context do
|
381
|
+
Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
|
382
|
+
render_without_immunio(context, *args, &block)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
# Hook for `ActionController::Caching::Fragments` responsible for handling the `<% cache do %>...` in templates.
|
390
|
+
module FragmentCachingHooks
|
391
|
+
extend ActiveSupport::Concern
|
392
|
+
|
393
|
+
included do
|
394
|
+
alias_method_chain :write_fragment, :immunio
|
395
|
+
end
|
396
|
+
|
397
|
+
def write_fragment_with_immunio(key, content, options = nil)
|
398
|
+
return content unless cache_configured?
|
399
|
+
|
400
|
+
template = Template.current
|
401
|
+
if template
|
402
|
+
# We're rendering a template. Defer caching 'till we get the escaped content from the hook handler.
|
403
|
+
content = Template.mark_and_defer_fragment_write(key, content, options)
|
404
|
+
else
|
405
|
+
# Not rendering a template. Ignore.
|
406
|
+
# Shouldn't happen. But, just to be safe in case fragment caching is used in the controller for something else.
|
407
|
+
content = write_fragment_without_immunio(key, content, options)
|
408
|
+
end
|
409
|
+
|
410
|
+
content
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
# Add XSS hooks if enabled
|
416
|
+
if Immunio::agent.plugin_enabled?("xss") then
|
417
|
+
# Hook into template engines.
|
418
|
+
ActionView::Template::Handlers::Erubis.send :include, Immunio::ErubisHooks
|
419
|
+
|
420
|
+
ActiveSupport.on_load(:after_initialize) do
|
421
|
+
# Wait after Rails initialization to patch custom template engines.
|
422
|
+
if defined? Haml::Compiler
|
423
|
+
Haml::Compiler.send :include, Immunio::HamlHooks
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
# Hook into rendering process of Rails.
|
428
|
+
ActionView::TemplateRenderer.send :include, Immunio::TemplateRendererHooks
|
429
|
+
ActionView::Template.send :include, Immunio::TemplateHooks
|
430
|
+
ActionController::Caching::Fragments.send :include, Immunio::FragmentCachingHooks
|
431
|
+
end
|