sqreen 1.11.3-java → 1.12.0-java
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 +4 -4
- data/lib/sqreen/actions.rb +203 -0
- data/lib/sqreen/binding_accessor.rb +73 -4
- data/lib/sqreen/callbacks.rb +55 -0
- data/lib/sqreen/deliveries/batch.rb +7 -1
- data/lib/sqreen/instrumentation.rb +15 -1
- data/lib/sqreen/remote_command.rb +14 -3
- data/lib/sqreen/rule_callback.rb +1 -52
- data/lib/sqreen/rules_callbacks/run_req_start_actions.rb +61 -0
- data/lib/sqreen/runner.rb +59 -0
- data/lib/sqreen/sdk.rb +34 -0
- data/lib/sqreen/session.rb +7 -2
- data/lib/sqreen/version.rb +1 -1
- metadata +13 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a79999b0619b1ce3b8fd31a8ae18744a8afee94f8ca52af3c62c16cb7dbd0c9
|
4
|
+
data.tar.gz: b51e44c2dffcc337120c349a99f71b0dcde9bbb4b092701fe6802cb8b7b5a910
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7af93806486c4dc0140660d8b2df9e0fac024c1590e4eb7141271d8909543b05e5d548d92c7df6f4831ae5381f9fa87a2973c85fd232796ca3364a2e208dab73
|
7
|
+
data.tar.gz: 5cdc6cb90cf0ceb885cbd6d0aedff61d8aa18962bfd0eebead7f6556cad046dae81b4c4c60ec97ab124007e1fa2938b02cc3f6b01cde85142f3a18af11c54edf
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# Copyright (c) 2018 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'ipaddr'
|
5
|
+
require 'sqreen/log'
|
6
|
+
require 'sqreen/exception'
|
7
|
+
require 'sqreen/sdk'
|
8
|
+
require 'sqreen/frameworks'
|
9
|
+
|
10
|
+
module Sqreen
|
11
|
+
# Implements actions (behavior taken in response to agent signals)
|
12
|
+
module Actions
|
13
|
+
# Exception for when an unknown action type is gotten from the server
|
14
|
+
class UnknownActionType < ::Sqreen::Exception
|
15
|
+
attr_reader :action_type
|
16
|
+
def initialize(action_type)
|
17
|
+
super("no such action type: #{action_type}. Must be one of #{Base.known_types}")
|
18
|
+
@action_type = action_type
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Where the currently loaded actions are stored. Singleton
|
23
|
+
class Repository
|
24
|
+
include Singleton
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@actions = {} # indexed by subclass
|
28
|
+
@actions.default_proc = proc { |h, k| h[k] = [] }
|
29
|
+
end
|
30
|
+
|
31
|
+
def <<(action)
|
32
|
+
@actions[action.class] << action
|
33
|
+
end
|
34
|
+
|
35
|
+
def [](action_class)
|
36
|
+
@actions[action_class]
|
37
|
+
end
|
38
|
+
|
39
|
+
def clear
|
40
|
+
@actions.clear
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Sqreen::Actions::Base]
|
45
|
+
def self.deserialize_action(hash)
|
46
|
+
action_type = hash['action']
|
47
|
+
raise 'no action type available' unless action_type
|
48
|
+
|
49
|
+
subclass = Base.get_type_class(action_type)
|
50
|
+
raise UnknownActionType, action_type unless subclass
|
51
|
+
|
52
|
+
id = hash['action_id']
|
53
|
+
raise 'no action id available' unless id
|
54
|
+
|
55
|
+
duration = hash['duration']
|
56
|
+
if !duration.nil? && duration <= 0
|
57
|
+
Sqreen.log.debug "Action #{id} is already expired"
|
58
|
+
return nil
|
59
|
+
end
|
60
|
+
|
61
|
+
subclass.new(id, duration, hash['parameters'] || {})
|
62
|
+
end
|
63
|
+
|
64
|
+
class Base
|
65
|
+
attr_reader :id, :expiry
|
66
|
+
|
67
|
+
def initialize(id, duration)
|
68
|
+
@id = id
|
69
|
+
@expiry = Time.new + duration unless duration.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
# See Sqreen::CB for return values
|
73
|
+
def run(*args)
|
74
|
+
return if expiry && Time.new > expiry
|
75
|
+
ret = do_run *args
|
76
|
+
unless ret.nil?
|
77
|
+
Sqreen.internal_track(event_name,
|
78
|
+
'properties' => event_properties(*args).
|
79
|
+
merge('action_id' => id))
|
80
|
+
end
|
81
|
+
ret
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def do_run(*_args)
|
87
|
+
raise ::Sqreen::NotImplementedYet, "do_run not implemented in #{self.class}"
|
88
|
+
# implement in subclasses
|
89
|
+
end
|
90
|
+
|
91
|
+
def event_properties(*_run_args)
|
92
|
+
raise ::Sqreen::NotImplementedYet, "event_properties not implemented in #{self.class}"
|
93
|
+
# implement in subclasses
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def event_name
|
99
|
+
"sq.action.#{self.class.type_name}"
|
100
|
+
end
|
101
|
+
|
102
|
+
@@subclasses = {}
|
103
|
+
class << self
|
104
|
+
private :new
|
105
|
+
|
106
|
+
attr_reader :type_name
|
107
|
+
|
108
|
+
def get_type_class(name)
|
109
|
+
@@subclasses[name]
|
110
|
+
end
|
111
|
+
|
112
|
+
def known_types
|
113
|
+
@@subclasses.keys
|
114
|
+
end
|
115
|
+
|
116
|
+
def inherited(subclass)
|
117
|
+
class << subclass
|
118
|
+
public :new
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
def type_name=(name)
|
125
|
+
@type_name = name
|
126
|
+
@@subclasses[name] = self
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
module IpRanges
|
132
|
+
attr_reader :ranges
|
133
|
+
|
134
|
+
def parse_ip_ranges(params)
|
135
|
+
ranges = params['ip_cidr']
|
136
|
+
unless ranges && ranges.is_a?(Array) && !ranges.empty?
|
137
|
+
raise 'no non-empty ip_cidr array present'
|
138
|
+
end
|
139
|
+
|
140
|
+
@ranges = ranges.map &IPAddr.method(:new)
|
141
|
+
end
|
142
|
+
|
143
|
+
def matches_ip?(client_ip)
|
144
|
+
parsed_ip = IPAddr.new client_ip
|
145
|
+
found = ranges.find { |r| r.include? parsed_ip }
|
146
|
+
return false unless found
|
147
|
+
|
148
|
+
Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
|
149
|
+
true
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Block a list of IP address ranges. Standard "raise" behavior.
|
154
|
+
class BlockIp < Base
|
155
|
+
include IpRanges
|
156
|
+
self.type_name = 'block_ip'
|
157
|
+
|
158
|
+
def initialize(id, duration, params = {})
|
159
|
+
super(id, duration)
|
160
|
+
parse_ip_ranges params
|
161
|
+
end
|
162
|
+
|
163
|
+
def do_run(client_ip)
|
164
|
+
return nil unless matches_ip? client_ip
|
165
|
+
e = Sqreen::AttackBlocked.new("Blocked client's IP (action: #{id}). No action is required")
|
166
|
+
{ :status => :raise, :exception => e }
|
167
|
+
end
|
168
|
+
|
169
|
+
def event_properties(client_ip)
|
170
|
+
{ 'ip_address' => client_ip }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Block a list of IP address ranges by forcefully redirecting the user
|
175
|
+
# to a specific URL.
|
176
|
+
class RedirectIp < Base
|
177
|
+
include IpRanges
|
178
|
+
self.type_name = 'redirect_ip'
|
179
|
+
|
180
|
+
attr_reader :redirect_url
|
181
|
+
|
182
|
+
def initialize(id, duration, params = {})
|
183
|
+
super(id, duration)
|
184
|
+
@redirect_url = params['url']
|
185
|
+
raise "no url provided for action #{id}" unless @redirect_url
|
186
|
+
parse_ip_ranges params
|
187
|
+
end
|
188
|
+
|
189
|
+
def do_run(client_ip)
|
190
|
+
return nil unless matches_ip? client_ip
|
191
|
+
Sqreen.log.info "Will request redirect for client with IP #{client_ip} (action: #{id}). "
|
192
|
+
{
|
193
|
+
:status => :skip,
|
194
|
+
:new_return_value => [303, { 'Location' => @redirect_url }, ['']],
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
def event_properties(client_ip)
|
199
|
+
{ 'ip_address' => client_ip, 'url' => @redirect_url }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -9,13 +9,14 @@ module Sqreen
|
|
9
9
|
# the value located at the given binding
|
10
10
|
class BindingAccessor
|
11
11
|
PathElem = Struct.new(:kind, :value)
|
12
|
-
attr_reader :path, :expression, :final_transform
|
12
|
+
attr_reader :path, :expression, :final_transform, :transform_args
|
13
13
|
|
14
14
|
# Expression to be accessed
|
15
15
|
# @param expression [String] expression to read
|
16
16
|
# @param convert [Boolean] wheter to convert objects to
|
17
17
|
# simpler types (Array, Hash, String...)
|
18
18
|
def initialize(expression, convert = false)
|
19
|
+
@transform_args = []
|
19
20
|
@final_transform = nil
|
20
21
|
@expression = expression
|
21
22
|
@path = []
|
@@ -41,6 +42,19 @@ module Sqreen
|
|
41
42
|
value
|
42
43
|
end
|
43
44
|
|
45
|
+
# implement eql? and hash for uniq
|
46
|
+
def hash
|
47
|
+
expression.hash ^ @convert.hash
|
48
|
+
end
|
49
|
+
|
50
|
+
def eql?(other)
|
51
|
+
self.class == other.class &&
|
52
|
+
expression == other.expression &&
|
53
|
+
@convert == other.instance_variable_get('@convert')
|
54
|
+
end
|
55
|
+
|
56
|
+
alias == eql?
|
57
|
+
|
44
58
|
protected
|
45
59
|
|
46
60
|
STRING_KIND = 'string'.freeze
|
@@ -138,8 +152,17 @@ module Sqreen
|
|
138
152
|
parts.join('|').rstrip
|
139
153
|
end
|
140
154
|
|
155
|
+
TRANSFORM_ARGS_REGEXP = /\(([^)]*)\)\z/
|
141
156
|
def final_transform=(transform)
|
142
157
|
transform.strip!
|
158
|
+
|
159
|
+
transform.sub!(TRANSFORM_ARGS_REGEXP, '')
|
160
|
+
@transform_args = if Regexp.last_match
|
161
|
+
Regexp.last_match(1).split(/,\s*/)
|
162
|
+
else
|
163
|
+
[]
|
164
|
+
end
|
165
|
+
|
143
166
|
unless KNOWN_TRANSFORMS.include?(transform)
|
144
167
|
raise Sqreen::Exception, "Invalid transform #{transform}"
|
145
168
|
end
|
@@ -277,12 +300,58 @@ module Sqreen
|
|
277
300
|
end
|
278
301
|
values
|
279
302
|
end
|
280
|
-
|
303
|
+
|
304
|
+
def concat_keys_and_values(value, max_size = nil)
|
305
|
+
return nil if value.nil?
|
306
|
+
values = Set.new
|
307
|
+
max_size = max_size.to_i if max_size
|
308
|
+
res = ''
|
309
|
+
descend(value) do |x|
|
310
|
+
next unless values.add?(x)
|
311
|
+
x = x.to_s
|
312
|
+
return res if max_size && res.size + x.size + 1 > max_size
|
313
|
+
res << "\n" unless res.empty?
|
314
|
+
res << x
|
315
|
+
end
|
316
|
+
res
|
317
|
+
end
|
318
|
+
|
319
|
+
private
|
320
|
+
|
321
|
+
def descend(value, max_iter = 1000)
|
322
|
+
seen = Set.new
|
323
|
+
look_into = [value]
|
324
|
+
idx = 0
|
325
|
+
until look_into.empty? || max_iter <= idx
|
326
|
+
idx += 1
|
327
|
+
val = look_into.pop
|
328
|
+
|
329
|
+
case val
|
330
|
+
when Hash
|
331
|
+
next unless seen.add?(val.object_id)
|
332
|
+
look_into.concat(val.keys)
|
333
|
+
look_into.concat(val.values)
|
334
|
+
when Array
|
335
|
+
next unless seen.add?(val.object_id)
|
336
|
+
look_into.concat(val)
|
337
|
+
else
|
338
|
+
next if val.respond_to?(:seek)
|
339
|
+
if val.respond_to?(:each)
|
340
|
+
next unless seen.add?(val.object_id)
|
341
|
+
val.each { |v| look_into << v }
|
342
|
+
else
|
343
|
+
yield val
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end # end module Transforms
|
349
|
+
|
281
350
|
include Transforms
|
282
351
|
KNOWN_TRANSFORMS = Transforms.public_instance_methods.map(&:to_s)
|
283
352
|
|
284
353
|
def transform(value)
|
285
|
-
send(@final_transform, value) if @final_transform
|
354
|
+
send(@final_transform, value, *@transform_args) if @final_transform
|
286
355
|
end
|
287
|
-
end
|
356
|
+
end # end class BindingAccessor
|
288
357
|
end
|
data/lib/sqreen/callbacks.rb
CHANGED
@@ -109,4 +109,59 @@ module Sqreen
|
|
109
109
|
@block.call
|
110
110
|
end
|
111
111
|
end
|
112
|
+
|
113
|
+
# Framework-aware callback
|
114
|
+
class FrameworkCB < CB
|
115
|
+
attr_accessor :framework
|
116
|
+
|
117
|
+
def whitelisted?
|
118
|
+
whitelisted = SharedStorage.get(:whitelisted)
|
119
|
+
return whitelisted unless whitelisted.nil?
|
120
|
+
framework && !framework.whitelisted_match.nil?
|
121
|
+
end
|
122
|
+
|
123
|
+
# Record an attack event into Sqreen system
|
124
|
+
# @param infos [Hash] Additional information about request
|
125
|
+
def record_event(infos, at = Time.now.utc)
|
126
|
+
return unless framework
|
127
|
+
payload = {
|
128
|
+
:infos => infos,
|
129
|
+
:rulespack_id => rulespack_id,
|
130
|
+
:rule_name => rule_name,
|
131
|
+
:test => test,
|
132
|
+
:time => at,
|
133
|
+
}
|
134
|
+
if payload_tpl.include?('context')
|
135
|
+
payload[:backtrace] = Sqreen::Context.new.bt
|
136
|
+
end
|
137
|
+
framework.observe(:attacks, payload, payload_tpl)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Record a metric observation
|
141
|
+
# @param category [String] Name of the metric observed
|
142
|
+
# @param key [String] aggregation key
|
143
|
+
# @param observation [Object] data observed
|
144
|
+
# @param at [Time] time when observation was made
|
145
|
+
def record_observation(category, key, observation, at = Time.now.utc)
|
146
|
+
return unless framework
|
147
|
+
framework.observe(:observations, [category, key, observation, at], [], false)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Record an exception that just occurred
|
151
|
+
# @param exception [Exception] Exception to send over
|
152
|
+
# @param infos [Hash] Additional contextual information
|
153
|
+
def record_exception(exception, infos = {}, at = Time.now.utc)
|
154
|
+
return unless framework
|
155
|
+
payload = {
|
156
|
+
:exception => exception,
|
157
|
+
:infos => infos,
|
158
|
+
:rulespack_id => rulespack_id,
|
159
|
+
:rule_name => rule_name,
|
160
|
+
:test => test,
|
161
|
+
:time => at,
|
162
|
+
:backtrace => exception.backtrace || Sqreen::Context.bt,
|
163
|
+
}
|
164
|
+
framework.observe(:sqreen_exceptions, payload)
|
165
|
+
end
|
166
|
+
end
|
112
167
|
end
|
@@ -68,7 +68,13 @@ module Sqreen
|
|
68
68
|
|
69
69
|
def event_keys(event)
|
70
70
|
return [event_key(event)] unless event.is_a?(Sqreen::RequestRecord)
|
71
|
-
|
71
|
+
res = []
|
72
|
+
res += event.observed.fetch(:attacks, []).map { |e| "att-#{e[:rule_name]}" }
|
73
|
+
res += event.observed.fetch(:sqreen_exceptions, []).map { |e| "rex-#{e[:exception].class}" }
|
74
|
+
res += event.observed.fetch(:sdk, []).select { |e|
|
75
|
+
e[0] == :track
|
76
|
+
}.map { |e| "sdk-track".freeze }
|
77
|
+
return res
|
72
78
|
end
|
73
79
|
|
74
80
|
def event_key(event)
|
@@ -10,6 +10,7 @@ require 'sqreen/events/remote_exception'
|
|
10
10
|
require 'sqreen/rules_signature'
|
11
11
|
require 'sqreen/shared_storage'
|
12
12
|
require 'sqreen/rules_callbacks/record_request_context'
|
13
|
+
require 'sqreen/rules_callbacks/run_req_start_actions'
|
13
14
|
require 'set'
|
14
15
|
|
15
16
|
# How to override a class method:
|
@@ -310,6 +311,7 @@ module Sqreen
|
|
310
311
|
args = ret[:args]
|
311
312
|
when :raise, 'raise'
|
312
313
|
Thread.current[:sqreen_in_use] = false
|
314
|
+
raise ret[:exception] if ret.key?(:exception)
|
313
315
|
raise Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
|
314
316
|
end
|
315
317
|
end
|
@@ -659,9 +661,16 @@ module Sqreen
|
|
659
661
|
|
660
662
|
attr_accessor :metrics_engine
|
661
663
|
|
664
|
+
# @return [Array<Sqreen::CB>]
|
665
|
+
def hardcoded_callbacks(framework)
|
666
|
+
[
|
667
|
+
Sqreen::Rules::RunReqStartActions.new(framework)
|
668
|
+
]
|
669
|
+
end
|
670
|
+
|
662
671
|
# Instrument the application code using the rules
|
663
672
|
# @param rules [Array<Hash>] Rules to instrument
|
664
|
-
# @param
|
673
|
+
# @param framework [Sqreen::Frameworks::GenericFramework]
|
665
674
|
def instrument!(rules, framework)
|
666
675
|
verifier = nil
|
667
676
|
if Sqreen.features['rules_signature'] &&
|
@@ -671,13 +680,18 @@ module Sqreen
|
|
671
680
|
else
|
672
681
|
Sqreen.log.debug('Rules signature is not enabled')
|
673
682
|
end
|
683
|
+
|
674
684
|
remove_all_callbacks # Force cb tree to be empty before instrumenting
|
685
|
+
|
675
686
|
rules.each do |rule|
|
676
687
|
rcb = Sqreen::Rules.cb_from_rule(rule, self, metrics_engine, verifier)
|
677
688
|
next unless rcb
|
678
689
|
rcb.framework = framework
|
679
690
|
add_callback(rcb)
|
680
691
|
end
|
692
|
+
|
693
|
+
hardcoded_callbacks(framework).each { |cb| add_callback(cb) }
|
694
|
+
|
681
695
|
Sqreen.instrumentation_ready = true
|
682
696
|
end
|
683
697
|
|
@@ -10,6 +10,7 @@ module Sqreen
|
|
10
10
|
:instrumentation_enable => :setup_instrumentation,
|
11
11
|
:instrumentation_remove => :remove_instrumentation,
|
12
12
|
:rules_reload => :reload_rules,
|
13
|
+
:actions_reload => :reload_actions,
|
13
14
|
:features_get => :features,
|
14
15
|
:features_change => :change_features,
|
15
16
|
:force_logout => :shutdown,
|
@@ -19,6 +20,14 @@ module Sqreen
|
|
19
20
|
:performance_budget => :change_performance_budget,
|
20
21
|
}.freeze
|
21
22
|
|
23
|
+
# wraps output returned by a command that should also result in status: false
|
24
|
+
class FailureOutput
|
25
|
+
attr_reader :wrapped_output
|
26
|
+
def initialize(output)
|
27
|
+
@wrapped_output = output
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
22
31
|
attr_reader :uuid
|
23
32
|
|
24
33
|
def initialize(json_desc)
|
@@ -83,11 +92,13 @@ module Sqreen
|
|
83
92
|
def format_output(output)
|
84
93
|
case output
|
85
94
|
when NilClass
|
86
|
-
|
95
|
+
{ :status => false, :reason => 'nil returned' }
|
87
96
|
when TrueClass
|
88
|
-
|
97
|
+
{ :status => true }
|
98
|
+
when FailureOutput
|
99
|
+
{ :status => false, :output => output.wrapped_output }
|
89
100
|
else
|
90
|
-
|
101
|
+
{ :status => true, :output => output }
|
91
102
|
end
|
92
103
|
end
|
93
104
|
end
|
data/lib/sqreen/rule_callback.rb
CHANGED
@@ -14,7 +14,7 @@ require 'sqreen/payload_creator'
|
|
14
14
|
module Sqreen
|
15
15
|
module Rules
|
16
16
|
# Base class for callback that are initialized by rules from Sqreen
|
17
|
-
class RuleCB <
|
17
|
+
class RuleCB < FrameworkCB
|
18
18
|
include Conditionable
|
19
19
|
include CallCountable
|
20
20
|
# If nothing was asked by the rule we will ask for all sections available
|
@@ -23,7 +23,6 @@ module Sqreen
|
|
23
23
|
attr_reader :test
|
24
24
|
attr_reader :payload_tpl
|
25
25
|
attr_reader :block
|
26
|
-
attr_accessor :framework
|
27
26
|
|
28
27
|
# @params klass [String] class instrumented
|
29
28
|
# @params method [String] method that was instrumented
|
@@ -48,12 +47,6 @@ module Sqreen
|
|
48
47
|
@rule[Attrs::RULESPACK_ID]
|
49
48
|
end
|
50
49
|
|
51
|
-
def whitelisted?
|
52
|
-
whitelisted = SharedStorage.get(:whitelisted)
|
53
|
-
return whitelisted unless whitelisted.nil?
|
54
|
-
framework && !framework.whitelisted_match.nil?
|
55
|
-
end
|
56
|
-
|
57
50
|
# Recommend taking an action (optionnally adding more data/context)
|
58
51
|
#
|
59
52
|
# This will format the requested action and optionnally
|
@@ -63,50 +56,6 @@ module Sqreen
|
|
63
56
|
additional_data.merge(:status => action)
|
64
57
|
end
|
65
58
|
|
66
|
-
# Record an attack event into Sqreen system
|
67
|
-
# @param infos [Hash] Additional information about request
|
68
|
-
def record_event(infos, at = Time.now.utc)
|
69
|
-
return unless framework
|
70
|
-
payload = {
|
71
|
-
:infos => infos,
|
72
|
-
:rulespack_id => rulespack_id,
|
73
|
-
:rule_name => rule_name,
|
74
|
-
:test => test,
|
75
|
-
:time => at,
|
76
|
-
}
|
77
|
-
if payload_tpl.include?('context')
|
78
|
-
payload[:backtrace] = Sqreen::Context.new.bt
|
79
|
-
end
|
80
|
-
framework.observe(:attacks, payload, payload_tpl)
|
81
|
-
end
|
82
|
-
|
83
|
-
# Record a metric observation
|
84
|
-
# @param category [String] Name of the metric observed
|
85
|
-
# @param key [String] aggregation key
|
86
|
-
# @param observation [Object] data observed
|
87
|
-
# @param at [Time] time when observation was made
|
88
|
-
def record_observation(category, key, observation, at = Time.now.utc)
|
89
|
-
return unless framework
|
90
|
-
framework.observe(:observations, [category, key, observation, at], [], false)
|
91
|
-
end
|
92
|
-
|
93
|
-
# Record an exception that just occurred
|
94
|
-
# @param exception [Exception] Exception to send over
|
95
|
-
# @param infos [Hash] Additional contextual information
|
96
|
-
def record_exception(exception, infos = {}, at = Time.now.utc)
|
97
|
-
return unless framework
|
98
|
-
payload = {
|
99
|
-
:exception => exception,
|
100
|
-
:infos => infos,
|
101
|
-
:rulespack_id => rulespack_id,
|
102
|
-
:rule_name => rule_name,
|
103
|
-
:test => test,
|
104
|
-
:time => at,
|
105
|
-
:backtrace => exception.backtrace || Sqreen::Context.bt,
|
106
|
-
}
|
107
|
-
framework.observe(:sqreen_exceptions, payload)
|
108
|
-
end
|
109
|
-
|
110
59
|
def overtime!
|
111
60
|
return false unless @overtimeable
|
112
61
|
Sqreen.log.debug { "rulecb #{self} is overtime!" }
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Copyright (c) 2018 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rule_callback'
|
5
|
+
require 'sqreen/actions'
|
6
|
+
require 'sqreen/middleware'
|
7
|
+
|
8
|
+
module Sqreen
|
9
|
+
module Rules
|
10
|
+
# Runs actions concerned with whether the request ought to be served
|
11
|
+
class RunReqStartActions < FrameworkCB
|
12
|
+
def initialize(framework)
|
13
|
+
if defined?(Sqreen::Frameworks::SinatraFramework) &&
|
14
|
+
framework.is_a?(Sqreen::Frameworks::SinatraFramework)
|
15
|
+
super(Sinatra::ExtendedRack, :call)
|
16
|
+
elsif defined?(Sqreen::Frameworks::RailsFramework) &&
|
17
|
+
framework.is_a?(Sqreen::Frameworks::RailsFramework)
|
18
|
+
super(Sqreen::RailsMiddleware, :call)
|
19
|
+
else
|
20
|
+
# last resort; we won't get nice errors
|
21
|
+
super(Sqreen::Middleware, :call)
|
22
|
+
end
|
23
|
+
|
24
|
+
self.framework = framework
|
25
|
+
end
|
26
|
+
|
27
|
+
def whitelisted?
|
28
|
+
whitelisted = SharedStorage.get(:whitelisted)
|
29
|
+
return whitelisted unless whitelisted.nil?
|
30
|
+
framework && !framework.whitelisted_match.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def pre(_inst, _args, _budget = nil, &_block)
|
34
|
+
return unless framework
|
35
|
+
ip = framework.client_ip
|
36
|
+
return unless ip
|
37
|
+
|
38
|
+
actions = actions_repo[Sqreen::Actions::BlockIp] +
|
39
|
+
actions_repo[Sqreen::Actions::RedirectIp]
|
40
|
+
|
41
|
+
actions.each do |act|
|
42
|
+
res = run_client_ip_action(act, ip)
|
43
|
+
return res unless res.nil?
|
44
|
+
end
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# @param action [Sqreen::Actions::Base]
|
51
|
+
def run_client_ip_action(action, client_ip)
|
52
|
+
action.run client_ip
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Sqreen::Actions::Repository]
|
56
|
+
def actions_repo
|
57
|
+
Sqreen::Actions::Repository.instance
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/sqreen/runner.rb
CHANGED
@@ -214,6 +214,7 @@ module Sqreen
|
|
214
214
|
def remove_instrumentation(_context_infos = {})
|
215
215
|
Sqreen.log.debug 'removing instrumentation'
|
216
216
|
instrumenter.remove_all_callbacks
|
217
|
+
Sqreen::Actions::Repository.instance.clear
|
217
218
|
true
|
218
219
|
end
|
219
220
|
|
@@ -221,12 +222,43 @@ module Sqreen
|
|
221
222
|
Sqreen.log.debug 'Reloading rules'
|
222
223
|
rulespack_id, rules = load_rules
|
223
224
|
instrumenter.remove_all_callbacks
|
225
|
+
Sqreen::Actions::Repository.instance.clear
|
224
226
|
|
225
227
|
@framework.instrument_when_ready!(instrumenter, rules)
|
226
228
|
Sqreen.log.debug 'Rules reloaded'
|
227
229
|
rulespack_id.to_s
|
228
230
|
end
|
229
231
|
|
232
|
+
def reload_actions(_context_infos = {})
|
233
|
+
Sqreen.log.debug 'Reloading actions'
|
234
|
+
|
235
|
+
data = session.get_actionspack
|
236
|
+
|
237
|
+
unless data.respond_to?(:[]) && data['status']
|
238
|
+
Sqreen.log.warn('Could not load actions')
|
239
|
+
return RemoteCommand::FailureOutput.new(
|
240
|
+
:error => 'Could not load actions from /actionspack'
|
241
|
+
)
|
242
|
+
end
|
243
|
+
|
244
|
+
action_hashes = data['actions']
|
245
|
+
unless action_hashes.respond_to? :each
|
246
|
+
Sqreen.log.warn('No action definitions in response')
|
247
|
+
return RemoteCommand::FailureOutput.new(
|
248
|
+
:error => 'No action definitions in response'
|
249
|
+
)
|
250
|
+
end
|
251
|
+
Sqreen.log.debug("Loading actions from hashes #{action_hashes}")
|
252
|
+
|
253
|
+
unsupported = load_actions(action_hashes)
|
254
|
+
|
255
|
+
if unsupported.empty?
|
256
|
+
true
|
257
|
+
else
|
258
|
+
RemoteCommand::FailureOutput.new(:unsupported_actions => unsupported.to_a)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
230
262
|
def process_commands(commands, context_infos = {})
|
231
263
|
return if commands.nil? || commands.empty?
|
232
264
|
res = RemoteCommand.process_list(self, commands, context_infos)
|
@@ -380,5 +412,32 @@ module Sqreen
|
|
380
412
|
end
|
381
413
|
end
|
382
414
|
end
|
415
|
+
|
416
|
+
private
|
417
|
+
|
418
|
+
def load_actions(hashes)
|
419
|
+
unsupported = Set.new
|
420
|
+
|
421
|
+
actions = hashes.map do |h|
|
422
|
+
begin
|
423
|
+
Sqreen::Actions.deserialize_action(h)
|
424
|
+
rescue Sqreen::Actions::UnknownActionType => e
|
425
|
+
Sqreen.log.warn("Unsupported action type: #{e.action_type}")
|
426
|
+
unsupported << e.action_type
|
427
|
+
nil
|
428
|
+
rescue => e
|
429
|
+
raise Sqreen::Exception, "Invalid action hash: #{h}: #{e.message}"
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
actions = actions.reject(&:nil?)
|
434
|
+
Sqreen.log.debug("Will add #{actions.size} valid actions")
|
435
|
+
|
436
|
+
repos = Sqreen::Actions::Repository.instance
|
437
|
+
repos.clear
|
438
|
+
actions.each { |action| repos << action }
|
439
|
+
|
440
|
+
unsupported
|
441
|
+
end
|
383
442
|
end
|
384
443
|
end
|
data/lib/sqreen/sdk.rb
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
|
4
4
|
# Sqreen Namespace
|
5
5
|
module Sqreen
|
6
|
+
|
7
|
+
SDK_RESERVED_PREFIX = 'sq.'.freeze
|
8
|
+
TRACK_PAYLOAD_DATA = ['request'.freeze, 'params'.freeze, 'headers'.freeze].freeze
|
9
|
+
|
6
10
|
# Sqreen SDK
|
7
11
|
class << self
|
8
12
|
# Authentication tracking method
|
@@ -18,5 +22,35 @@ module Sqreen
|
|
18
22
|
[], false
|
19
23
|
)
|
20
24
|
end
|
25
|
+
|
26
|
+
def track(event_name, options = {})
|
27
|
+
return unless Sqreen.framework
|
28
|
+
if event_name.start_with? SDK_RESERVED_PREFIX
|
29
|
+
Sqreen.log.warn("Event names starting with '#{SDK_RESERVED_PREFIX}' " \
|
30
|
+
'are reserved. Event ignored.')
|
31
|
+
return false
|
32
|
+
end
|
33
|
+
internal_track(event_name, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
# For internal usage. Users are to call track() instead.
|
37
|
+
def internal_track(event_name, options = {})
|
38
|
+
properties = options[:properties]
|
39
|
+
authentication_keys = options[:user_identifiers]
|
40
|
+
timestamp = options[:timestamp] || Time.now.utc
|
41
|
+
# Not in SDK v0
|
42
|
+
# request = options[:request]
|
43
|
+
|
44
|
+
args = {}
|
45
|
+
args[:authentication_keys] = authentication_keys if authentication_keys
|
46
|
+
args[:properties] = properties if properties
|
47
|
+
|
48
|
+
Sqreen.framework.observe(
|
49
|
+
:sdk,
|
50
|
+
[:track, timestamp, event_name, :args => args],
|
51
|
+
TRACK_PAYLOAD_DATA, true
|
52
|
+
)
|
53
|
+
true
|
54
|
+
end
|
21
55
|
end
|
22
56
|
end
|
data/lib/sqreen/session.rb
CHANGED
@@ -159,8 +159,9 @@ module Sqreen
|
|
159
159
|
|
160
160
|
def do_http_request(method, path, data, headers = {}, max_retry = 2)
|
161
161
|
connect unless connected?
|
162
|
+
now = Time.now.utc
|
162
163
|
headers['X-Session-Key'] = @session_id if @session_id
|
163
|
-
headers['X-Sqreen-Time'] =
|
164
|
+
headers['X-Sqreen-Time'] = now.to_f.to_s
|
164
165
|
headers['User-Agent'] = "sqreen-ruby/#{Sqreen::VERSION}"
|
165
166
|
headers['X-Sqreen-Beta'] = format('pid=%d;tid=%s;nb=%d;t=%f',
|
166
167
|
Process.pid,
|
@@ -206,7 +207,7 @@ module Sqreen
|
|
206
207
|
Sqreen.log.debug 'warning: empty return value'
|
207
208
|
end
|
208
209
|
end
|
209
|
-
Sqreen.log.debug format('%s %s (DONE)', method, path)
|
210
|
+
Sqreen.log.debug format('%s %s (DONE in %f ms)', method, path, (Time.now.utc - now) * 1000)
|
210
211
|
res
|
211
212
|
end
|
212
213
|
|
@@ -264,6 +265,10 @@ module Sqreen
|
|
264
265
|
'dependencies' => dependencies)
|
265
266
|
end
|
266
267
|
|
268
|
+
def get_actionspack
|
269
|
+
resilient_get('actionspack')
|
270
|
+
end
|
271
|
+
|
267
272
|
def post_request_record(request_record)
|
268
273
|
resilient_post('request_record', request_record.to_hash)
|
269
274
|
end
|
data/lib/sqreen/version.rb
CHANGED
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqreen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.12.0
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- Sqreen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: execjs
|
15
|
-
version_requirements: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 0.3.0
|
20
14
|
requirement: !ruby/object:Gem::Requirement
|
21
15
|
requirements:
|
22
16
|
- - ">="
|
23
17
|
- !ruby/object:Gem::Version
|
24
18
|
version: 0.3.0
|
19
|
+
name: execjs
|
25
20
|
prerelease: false
|
26
21
|
type: :runtime
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: therubyrhino
|
29
22
|
version_requirements: !ruby/object:Gem::Requirement
|
30
23
|
requirements:
|
31
24
|
- - ">="
|
32
25
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
26
|
+
version: 0.3.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
34
28
|
requirement: !ruby/object:Gem::Requirement
|
35
29
|
requirements:
|
36
30
|
- - ">="
|
37
31
|
- !ruby/object:Gem::Version
|
38
32
|
version: '0'
|
33
|
+
name: therubyrhino
|
39
34
|
prerelease: false
|
40
35
|
type: :runtime
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
41
|
description: Sqreen is a SaaS based Application protection and monitoring platform that integrates directly into your Ruby applications. Learn more at https://sqreen.io.
|
42
42
|
email: contact@sqreen.io
|
43
43
|
executables: []
|
@@ -49,6 +49,7 @@ files:
|
|
49
49
|
- Rakefile
|
50
50
|
- lib/sqreen-alt.rb
|
51
51
|
- lib/sqreen.rb
|
52
|
+
- lib/sqreen/actions.rb
|
52
53
|
- lib/sqreen/attack_detected.html
|
53
54
|
- lib/sqreen/binding_accessor.rb
|
54
55
|
- lib/sqreen/ca.crt
|
@@ -109,6 +110,7 @@ files:
|
|
109
110
|
- lib/sqreen/rules_callbacks/record_request_context.rb
|
110
111
|
- lib/sqreen/rules_callbacks/reflected_xss.rb
|
111
112
|
- lib/sqreen/rules_callbacks/regexp_rule.rb
|
113
|
+
- lib/sqreen/rules_callbacks/run_req_start_actions.rb
|
112
114
|
- lib/sqreen/rules_callbacks/shell_env.rb
|
113
115
|
- lib/sqreen/rules_callbacks/url_matches.rb
|
114
116
|
- lib/sqreen/rules_callbacks/user_agent_matches.rb
|