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
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
|