sqreen-alt 1.10.0
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/CODE_OF_CONDUCT.md +22 -0
- data/README.md +77 -0
- data/Rakefile +20 -0
- data/lib/sqreen-alt.rb +1 -0
- data/lib/sqreen.rb +68 -0
- data/lib/sqreen/attack_detected.html +2 -0
- data/lib/sqreen/binding_accessor.rb +288 -0
- data/lib/sqreen/ca.crt +72 -0
- data/lib/sqreen/call_countable.rb +67 -0
- data/lib/sqreen/callback_tree.rb +78 -0
- data/lib/sqreen/callbacks.rb +100 -0
- data/lib/sqreen/capped_queue.rb +23 -0
- data/lib/sqreen/condition_evaluator.rb +235 -0
- data/lib/sqreen/conditionable.rb +50 -0
- data/lib/sqreen/configuration.rb +168 -0
- data/lib/sqreen/context.rb +26 -0
- data/lib/sqreen/deliveries/batch.rb +84 -0
- data/lib/sqreen/deliveries/simple.rb +39 -0
- data/lib/sqreen/event.rb +16 -0
- data/lib/sqreen/events/attack.rb +61 -0
- data/lib/sqreen/events/remote_exception.rb +54 -0
- data/lib/sqreen/events/request_record.rb +62 -0
- data/lib/sqreen/exception.rb +34 -0
- data/lib/sqreen/frameworks.rb +40 -0
- data/lib/sqreen/frameworks/generic.rb +446 -0
- data/lib/sqreen/frameworks/rails.rb +148 -0
- data/lib/sqreen/frameworks/rails3.rb +36 -0
- data/lib/sqreen/frameworks/request_recorder.rb +69 -0
- data/lib/sqreen/frameworks/sinatra.rb +57 -0
- data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
- data/lib/sqreen/instrumentation.rb +542 -0
- data/lib/sqreen/log.rb +119 -0
- data/lib/sqreen/metrics.rb +6 -0
- data/lib/sqreen/metrics/average.rb +39 -0
- data/lib/sqreen/metrics/base.rb +45 -0
- data/lib/sqreen/metrics/collect.rb +22 -0
- data/lib/sqreen/metrics/sum.rb +20 -0
- data/lib/sqreen/metrics_store.rb +96 -0
- data/lib/sqreen/middleware.rb +34 -0
- data/lib/sqreen/payload_creator.rb +137 -0
- data/lib/sqreen/performance_notifications.rb +86 -0
- data/lib/sqreen/performance_notifications/log.rb +36 -0
- data/lib/sqreen/performance_notifications/metrics.rb +36 -0
- data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
- data/lib/sqreen/remote_command.rb +93 -0
- data/lib/sqreen/rule_attributes.rb +26 -0
- data/lib/sqreen/rule_callback.rb +108 -0
- data/lib/sqreen/rules.rb +126 -0
- data/lib/sqreen/rules_callbacks.rb +29 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +77 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
- data/lib/sqreen/rules_callbacks/blacklist_ips.rb +44 -0
- data/lib/sqreen/rules_callbacks/count_http_codes.rb +40 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +24 -0
- data/lib/sqreen/rules_callbacks/custom_error.rb +64 -0
- data/lib/sqreen/rules_callbacks/execjs.rb +241 -0
- data/lib/sqreen/rules_callbacks/headers_insert.rb +22 -0
- data/lib/sqreen/rules_callbacks/inspect_rule.rb +25 -0
- data/lib/sqreen/rules_callbacks/matcher_rule.rb +138 -0
- data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
- data/lib/sqreen/rules_callbacks/record_request_context.rb +39 -0
- data/lib/sqreen/rules_callbacks/reflected_xss.rb +254 -0
- data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
- data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
- data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
- data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
- data/lib/sqreen/rules_signature.rb +151 -0
- data/lib/sqreen/runner.rb +365 -0
- data/lib/sqreen/runtime_infos.rb +138 -0
- data/lib/sqreen/safe_json.rb +60 -0
- data/lib/sqreen/sdk.rb +22 -0
- data/lib/sqreen/serializer.rb +46 -0
- data/lib/sqreen/session.rb +317 -0
- data/lib/sqreen/shared_storage.rb +31 -0
- data/lib/sqreen/stats.rb +18 -0
- data/lib/sqreen/version.rb +5 -0
- metadata +148 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rules_callbacks/regexp_rule'
|
5
|
+
require 'sqreen/rules_callbacks/matcher_rule'
|
6
|
+
|
7
|
+
require 'sqreen/rules_callbacks/record_request_context'
|
8
|
+
require 'sqreen/rules_callbacks/rails_parameters'
|
9
|
+
|
10
|
+
require 'sqreen/rules_callbacks/headers_insert'
|
11
|
+
require 'sqreen/rules_callbacks/blacklist_ips'
|
12
|
+
|
13
|
+
require 'sqreen/rules_callbacks/inspect_rule'
|
14
|
+
|
15
|
+
require 'sqreen/rules_callbacks/shell_env'
|
16
|
+
|
17
|
+
require 'sqreen/rules_callbacks/url_matches'
|
18
|
+
require 'sqreen/rules_callbacks/user_agent_matches'
|
19
|
+
require 'sqreen/rules_callbacks/crawler_user_agent_matches'
|
20
|
+
|
21
|
+
require 'sqreen/rules_callbacks/reflected_xss'
|
22
|
+
require 'sqreen/rules_callbacks/execjs'
|
23
|
+
|
24
|
+
require 'sqreen/rules_callbacks/binding_accessor_metrics'
|
25
|
+
require 'sqreen/rules_callbacks/binding_accessor_matcher'
|
26
|
+
require 'sqreen/rules_callbacks/count_http_codes'
|
27
|
+
require 'sqreen/rules_callbacks/crawler_user_agent_matches_metrics'
|
28
|
+
|
29
|
+
require 'sqreen/rules_callbacks/custom_error'
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Copyright (c) 2015 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/binding_accessor'
|
6
|
+
require 'sqreen/rules_callbacks/matcher_rule'
|
7
|
+
|
8
|
+
module Sqreen
|
9
|
+
module Rules
|
10
|
+
# Callback that match on a list or matcher+binding accessor
|
11
|
+
class BindingAccessorMatcherCB < RuleCB
|
12
|
+
attr_reader :rules
|
13
|
+
|
14
|
+
# matcher on one elem
|
15
|
+
class MatcherElem
|
16
|
+
def initialize(expr)
|
17
|
+
prepare([expr])
|
18
|
+
end
|
19
|
+
include Matcher
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(klass, method, rule_hash)
|
23
|
+
super(klass, method, rule_hash)
|
24
|
+
@rules = []
|
25
|
+
if @data.empty? || @data['values'].nil? || @data['values'].empty?
|
26
|
+
msg = "no rules in data (had #{@data.keys})"
|
27
|
+
raise Sqreen::Exception, msg
|
28
|
+
end
|
29
|
+
prepare_rules(@data['values'])
|
30
|
+
end
|
31
|
+
|
32
|
+
def prepare_rules(rules)
|
33
|
+
accessors = Hash.new do |hash, key|
|
34
|
+
hash[key] = BindingAccessor.new(key, true)
|
35
|
+
end
|
36
|
+
@rules = rules.map do |r|
|
37
|
+
if r['binding_accessor'].empty?
|
38
|
+
raise Sqreen::Exception, "no accessors #{r['id']}"
|
39
|
+
end
|
40
|
+
[
|
41
|
+
r['id'],
|
42
|
+
r['binding_accessor'].map { |expression| accessors[expression] },
|
43
|
+
MatcherElem.new(r['matcher']),
|
44
|
+
r['matcher']['value'],
|
45
|
+
]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def pre(inst, *args, &_block)
|
50
|
+
resol_cache = Hash.new do |hash, accessor|
|
51
|
+
hash[accessor] = accessor.resolve(binding, framework, inst, args)
|
52
|
+
end
|
53
|
+
@rules.each do |id, accessors, matcher, matcher_ref|
|
54
|
+
accessors.each do |accessor|
|
55
|
+
val = resol_cache[accessor]
|
56
|
+
val = [val] if val.is_a?(String)
|
57
|
+
next unless val.respond_to?(:each)
|
58
|
+
next if val.respond_to?(:seek)
|
59
|
+
val.each do |v|
|
60
|
+
next if !v.is_a?(String) || (!matcher.min_size.nil? && v.size < matcher.min_size)
|
61
|
+
next if matcher.match(v).nil?
|
62
|
+
infos = {
|
63
|
+
'id' => id,
|
64
|
+
'binding_accessor' => accessor.expression,
|
65
|
+
'matcher' => matcher_ref,
|
66
|
+
'found' => v,
|
67
|
+
}
|
68
|
+
record_event(infos)
|
69
|
+
return advise_action(:raise, :infos => infos)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Copyright (c) 2015 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/binding_accessor'
|
6
|
+
require 'sqreen/events/remote_exception'
|
7
|
+
|
8
|
+
module Sqreen
|
9
|
+
module Rules
|
10
|
+
# Publish metrics about data taken from the binding accessor
|
11
|
+
class BindingAccessorMetrics < RuleCB
|
12
|
+
# Exception thrown when no expression are present
|
13
|
+
class NoExpressions < Sqreen::Exception
|
14
|
+
def initialize(expr)
|
15
|
+
super("No valid expressions defined in #{expr.inspect}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(klass, method, rule_hash)
|
20
|
+
super(klass, method, rule_hash)
|
21
|
+
@expr = {}
|
22
|
+
build_expressions(rule_hash[Attrs::CALLBACKS])
|
23
|
+
end
|
24
|
+
|
25
|
+
PRE_CB = 'pre'.freeze
|
26
|
+
POST_CB = 'post'.freeze
|
27
|
+
FAILING_CB = 'failing'.freeze
|
28
|
+
|
29
|
+
def pre?
|
30
|
+
@expr[PRE_CB]
|
31
|
+
end
|
32
|
+
|
33
|
+
def post?
|
34
|
+
@expr[POST_CB]
|
35
|
+
end
|
36
|
+
|
37
|
+
def failing?
|
38
|
+
@expr[FAILING_CB]
|
39
|
+
end
|
40
|
+
|
41
|
+
def pre(inst, *args, &_block)
|
42
|
+
return unless pre?
|
43
|
+
|
44
|
+
add_metrics(PRE_CB, inst, args)
|
45
|
+
end
|
46
|
+
|
47
|
+
def post(rv, inst, *args, &_block)
|
48
|
+
return unless post?
|
49
|
+
|
50
|
+
add_metrics(POST_CB, inst, args, rv)
|
51
|
+
end
|
52
|
+
|
53
|
+
def failing(exception, inst, *args, &_block)
|
54
|
+
return unless failing?
|
55
|
+
|
56
|
+
add_metrics(FAILING_CB, inst, args, exception)
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def add_metrics(name, inst, args, rv = nil)
|
62
|
+
category, key, value, = @expr[name].map do |accessor|
|
63
|
+
accessor.resolve(binding, framework, inst, args, @data, rv)
|
64
|
+
end
|
65
|
+
record_observation(category, key, value)
|
66
|
+
advise_action(nil)
|
67
|
+
end
|
68
|
+
|
69
|
+
def build_expressions(callbacks)
|
70
|
+
raise NoExpressions, callbacks if callbacks.nil? || callbacks.empty?
|
71
|
+
[PRE_CB, POST_CB, FAILING_CB].each do |c|
|
72
|
+
next if callbacks[c].nil? || callbacks[c].size < 3
|
73
|
+
@expr[c] = callbacks[c].map { |req| BindingAccessor.new(req, true) }
|
74
|
+
end
|
75
|
+
raise NoExpressions, callbacks if @expr.empty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'ipaddr'
|
5
|
+
|
6
|
+
require 'sqreen/rule_callback'
|
7
|
+
|
8
|
+
module Sqreen
|
9
|
+
module Rules
|
10
|
+
# Looks for a blacklisted ip and block
|
11
|
+
class BlacklistIPsCB < RuleCB
|
12
|
+
def initialize(klass, method, rule_hash)
|
13
|
+
super(klass, method, rule_hash)
|
14
|
+
@ips = Hash[@data['values'].map { |v| [v, IPAddr.new(v)] }]
|
15
|
+
raise ArgumentError.new("no ips given") if @ips.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def pre(_inst, *_args, &_block)
|
19
|
+
return unless framework
|
20
|
+
ip = framework.client_ip
|
21
|
+
return unless ip
|
22
|
+
found = find_blacklisted_ip(ip)
|
23
|
+
return unless found
|
24
|
+
Sqreen.log.debug { "Found blacklisted IP #{ip} - found: #{found}" }
|
25
|
+
record_observation('blacklisted', found, 1)
|
26
|
+
advise_action(:raise)
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
# Is this a blacklisted ip?
|
32
|
+
# return the ip blacklisted range that match ip
|
33
|
+
def find_blacklisted_ip(rip)
|
34
|
+
ret = (@ips || {}).find do |_, ip|
|
35
|
+
ip.include?(rip)
|
36
|
+
end
|
37
|
+
return nil unless ret
|
38
|
+
ret.first
|
39
|
+
rescue
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rule_attributes'
|
5
|
+
require 'sqreen/rule_callback'
|
6
|
+
require 'sqreen/safe_json'
|
7
|
+
|
8
|
+
module Sqreen
|
9
|
+
module Rules
|
10
|
+
# Save request context for handling further down
|
11
|
+
class CountHTTPCodes < RuleCB
|
12
|
+
METRIC_CATEGORY = 'http_code'.freeze
|
13
|
+
def post(rv, _inst, *_args, &_block)
|
14
|
+
return unless rv.is_a?(Array) && !rv.empty?
|
15
|
+
record_observation(METRIC_CATEGORY, rv[0], 1)
|
16
|
+
advise_action(nil)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Count 1 for each things located by the binding accessor
|
21
|
+
class BindingAccessorCounter < RuleCB
|
22
|
+
def initialize(klass, method, rule_hash)
|
23
|
+
super(klass, method, rule_hash)
|
24
|
+
@accessors = @data['values'].map do |expr|
|
25
|
+
BindingAccessor.new(expr, true)
|
26
|
+
end
|
27
|
+
@metric_category = rule_hash[Attrs::METRICS].first['name']
|
28
|
+
end
|
29
|
+
|
30
|
+
def post(rv, inst, *args, &_block)
|
31
|
+
return unless rv.is_a?(Array) && !rv.empty?
|
32
|
+
key = @accessors.map do |accessor|
|
33
|
+
accessor.resolve(binding, framework, inst, args, @data, rv)
|
34
|
+
end
|
35
|
+
record_observation(@metric_category, SafeJSON.dump(key), 1)
|
36
|
+
advise_action(nil)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rules_callbacks/matcher_rule'
|
5
|
+
require 'sqreen/frameworks'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
module Rules
|
9
|
+
# FIXME: Factor with UserAgentMatchesCB
|
10
|
+
# Look for crawlers
|
11
|
+
class CrawlerUserAgentMatchesCB < MatcherRuleCB
|
12
|
+
def pre(_inst, *_args, &_block)
|
13
|
+
ua = framework.client_user_agent
|
14
|
+
return unless ua
|
15
|
+
found = match(ua)
|
16
|
+
return unless found
|
17
|
+
Sqreen.log.debug { "Found UA #{ua} - found: #{found}" }
|
18
|
+
infos = { :found => found }
|
19
|
+
record_event(infos)
|
20
|
+
advise_action(nil)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rules_callbacks/matcher_rule'
|
5
|
+
require 'sqreen/frameworks'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
module Rules
|
9
|
+
# Look for crawlers and post them in metrics
|
10
|
+
class CrawlerUserAgentMatchesMetricsCB < MatcherRuleCB
|
11
|
+
CRAWLER_CATEGORY = 'crawler'.freeze
|
12
|
+
|
13
|
+
def pre(_inst, *_args, &_block)
|
14
|
+
ua = framework.client_user_agent
|
15
|
+
return unless ua
|
16
|
+
found = match(ua)
|
17
|
+
return unless found
|
18
|
+
Sqreen.log.debug { "Found UA #{ua} - found: #{found}" }
|
19
|
+
record_observation(CRAWLER_CATEGORY, ua, 1)
|
20
|
+
advise_action(nil)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright (c) 2015 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/exception'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
module Rules
|
9
|
+
# Display sqreen presence
|
10
|
+
class CustomErrorCB < RuleCB
|
11
|
+
attr_reader :status_code, :redirect_url
|
12
|
+
def initialize(klass, method, rule_hash)
|
13
|
+
@redirect_url = nil
|
14
|
+
@status_code = nil
|
15
|
+
super(klass, method, rule_hash)
|
16
|
+
if @data.nil? || @data['values'].empty?
|
17
|
+
raise Sqreen::Exception, 'No data'
|
18
|
+
end
|
19
|
+
configure_custom_error(@data['values'][0])
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure_custom_error(custom_error)
|
23
|
+
case custom_error['type']
|
24
|
+
when 'custom_error_page' then
|
25
|
+
@status_code = custom_error['status_code'].to_i
|
26
|
+
when 'redirection' then
|
27
|
+
@redirect_url = custom_error['redirection_url']
|
28
|
+
@status_code = custom_error.fetch('status_code', 303).to_i
|
29
|
+
else
|
30
|
+
raise Sqreen::Exception, "No custom error #{custom_error['type']}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def failing(except, _inst, *_args, &_block)
|
35
|
+
oexcept = nil
|
36
|
+
if except.respond_to?(:original_exception)
|
37
|
+
oexcept = except.original_exception
|
38
|
+
end
|
39
|
+
if !except.is_a?(Sqreen::AttackBlocked) &&
|
40
|
+
!oexcept.is_a?(Sqreen::AttackBlocked)
|
41
|
+
return advise_action(nil)
|
42
|
+
end
|
43
|
+
if @redirect_url
|
44
|
+
advise_action(:override, :new_return_value => respond_redirect)
|
45
|
+
else
|
46
|
+
advise_action(:override, :new_return_value => respond_page)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def respond_redirect
|
51
|
+
[@status_code, { 'Location' => @redirect_url }, ['']]
|
52
|
+
end
|
53
|
+
|
54
|
+
def respond_page
|
55
|
+
page = open(File.join(File.dirname(__FILE__), '../attack_detected.html'))
|
56
|
+
headers = {
|
57
|
+
'Content-Type' => 'text/html',
|
58
|
+
'Content-Length' => page.size.to_s,
|
59
|
+
}
|
60
|
+
[@status_code, headers, page]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
if defined?(::JRUBY_VERSION)
|
5
|
+
require 'rhino'
|
6
|
+
SQREEN_V8_THREAD = false
|
7
|
+
else
|
8
|
+
begin
|
9
|
+
require 'mini_racer'
|
10
|
+
SQREEN_V8_THREAD = true
|
11
|
+
rescue LoadError
|
12
|
+
require 'therubyracer'
|
13
|
+
SQREEN_V8_THREAD = false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'execjs'
|
18
|
+
|
19
|
+
require 'sqreen/rule_attributes'
|
20
|
+
require 'sqreen/rule_callback'
|
21
|
+
require 'sqreen/condition_evaluator'
|
22
|
+
require 'sqreen/binding_accessor'
|
23
|
+
require 'sqreen/events/remote_exception'
|
24
|
+
|
25
|
+
module Sqreen
|
26
|
+
module Rules
|
27
|
+
# Exec js callbacks
|
28
|
+
class ExecJSCB < RuleCB
|
29
|
+
attr_accessor :restrict_max_depth
|
30
|
+
def initialize(klass, method, rule_hash)
|
31
|
+
super(klass, method, rule_hash)
|
32
|
+
callbacks = @rule[Attrs::CALLBACKS]
|
33
|
+
@conditions = @rule.fetch(Attrs::CONDITIONS, {})
|
34
|
+
|
35
|
+
if callbacks['pre'].nil? &&
|
36
|
+
callbacks['post'].nil? &&
|
37
|
+
callbacks['failing'].nil?
|
38
|
+
raise(Sqreen::Exception, 'no JS CB provided')
|
39
|
+
end
|
40
|
+
|
41
|
+
build_runnable(callbacks)
|
42
|
+
if !SQREEN_V8_THREAD
|
43
|
+
@compiled = ExecJS.compile(@source)
|
44
|
+
end
|
45
|
+
@restrict_max_depth = 20
|
46
|
+
end
|
47
|
+
|
48
|
+
def pre?
|
49
|
+
@js_pre
|
50
|
+
end
|
51
|
+
|
52
|
+
def post?
|
53
|
+
@js_post
|
54
|
+
end
|
55
|
+
|
56
|
+
def failing?
|
57
|
+
@js_failing
|
58
|
+
end
|
59
|
+
|
60
|
+
def pre(inst, *args, &_block)
|
61
|
+
return unless pre?
|
62
|
+
|
63
|
+
call_callback('pre', inst, args)
|
64
|
+
end
|
65
|
+
|
66
|
+
def post(rv, inst, *args, &_block)
|
67
|
+
return unless post?
|
68
|
+
|
69
|
+
call_callback('post', inst, args, rv)
|
70
|
+
end
|
71
|
+
|
72
|
+
def failing(rv, inst, *args, &_block)
|
73
|
+
return unless failing?
|
74
|
+
|
75
|
+
call_callback('failing', inst, args, rv)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.hash_val_included(needed, haystack, min_length = 8, max_depth = 20)
|
79
|
+
new_obj = {}
|
80
|
+
insert = []
|
81
|
+
to_do = haystack.map { |k, v| [new_obj, k, v, 0] }
|
82
|
+
until to_do.empty?
|
83
|
+
where, key, value, deepness = to_do.pop
|
84
|
+
safe_key = key.kind_of?(Integer) ? key : key.to_s
|
85
|
+
if value.is_a?(Hash) && deepness < max_depth
|
86
|
+
val = {}
|
87
|
+
insert << [where, safe_key, val]
|
88
|
+
to_do += value.map { |k, v| [val, k, v, deepness + 1] }
|
89
|
+
elsif value.is_a?(Array) && deepness < max_depth
|
90
|
+
val = []
|
91
|
+
insert << [where, safe_key, val]
|
92
|
+
i = -1
|
93
|
+
to_do += value.map { |v| [val, i += 1, v, deepness + 1] }
|
94
|
+
elsif deepness >= max_depth # if we are after max_depth don't try to filter
|
95
|
+
insert << [where, safe_key, value]
|
96
|
+
else
|
97
|
+
v = value.to_s
|
98
|
+
if v.size >= min_length && ConditionEvaluator.str_include?(needed.to_s, v)
|
99
|
+
case where
|
100
|
+
when Array
|
101
|
+
where << value
|
102
|
+
else
|
103
|
+
where[safe_key] = value
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
insert.reverse.each do |wh, ikey, ival|
|
109
|
+
case wh
|
110
|
+
when Array
|
111
|
+
wh << ival unless ival.respond_to?(:empty?) && ival.empty?
|
112
|
+
else
|
113
|
+
wh[ikey] = ival unless ival.respond_to?(:empty?) && ival.empty?
|
114
|
+
end
|
115
|
+
end
|
116
|
+
new_obj
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
|
121
|
+
def record_and_continue?(ret)
|
122
|
+
case ret
|
123
|
+
when NilClass
|
124
|
+
return false
|
125
|
+
when Hash
|
126
|
+
ret.keys.each do |k|
|
127
|
+
ret[(begin
|
128
|
+
k.to_sym
|
129
|
+
rescue
|
130
|
+
k
|
131
|
+
end)] = ret[k] end
|
132
|
+
record_event(ret[:record]) unless ret[:record].nil?
|
133
|
+
unless ret['observations'].nil?
|
134
|
+
ret['observations'].each do |obs|
|
135
|
+
obs[3] = Time.parse(obs[3]) if obs.size >= 3 && obs[3].is_a?(String)
|
136
|
+
record_observation(*obs)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
return !ret[:call].nil?
|
140
|
+
else
|
141
|
+
raise Sqreen::Exception, "Invalid return type #{ret.inspect}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def call_callback(name, inst, args, rv = nil)
|
146
|
+
if SQREEN_V8_THREAD
|
147
|
+
Thread.current["SQREEN_EXECJS_SOURCE#{self.object_id}"] ||= ExecJS.compile(@source)
|
148
|
+
end
|
149
|
+
ret = nil
|
150
|
+
args_override = nil
|
151
|
+
arguments = nil
|
152
|
+
loop do
|
153
|
+
arguments = (args_override || @argument_requirements[name]).map do |accessor|
|
154
|
+
accessor.resolve(binding, framework, inst, args, @data, rv)
|
155
|
+
end
|
156
|
+
arguments = restrict(name, arguments) if @conditions.key?(name)
|
157
|
+
Sqreen.log.debug { [name, arguments].inspect }
|
158
|
+
if SQREEN_V8_THREAD
|
159
|
+
ret = Thread.current["SQREEN_EXECJS_SOURCE#{self.object_id}"].call(name, *arguments)
|
160
|
+
else
|
161
|
+
ret = @compiled.call(name, *arguments)
|
162
|
+
end
|
163
|
+
unless record_and_continue?(ret)
|
164
|
+
return nil if ret.nil?
|
165
|
+
return advise_action(ret[:status], ret)
|
166
|
+
end
|
167
|
+
name = ret[:call]
|
168
|
+
rv = ret[:data]
|
169
|
+
args_override = ret[:args]
|
170
|
+
args_override = build_accessor(args_override) if args_override
|
171
|
+
end
|
172
|
+
rescue => e
|
173
|
+
Sqreen.log.warn "we catch a JScb exception: #{e.inspect}"
|
174
|
+
Sqreen.log.debug e.backtrace
|
175
|
+
record_exception(e, :cb => name, :args => arguments)
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
|
179
|
+
def each_hash_val_include(condition, depth = 10)
|
180
|
+
return if depth <= 0
|
181
|
+
condition.each do |key, values|
|
182
|
+
if key == ConditionEvaluator::HASH_INC_OPERATOR
|
183
|
+
yield values
|
184
|
+
else
|
185
|
+
values.map do |v|
|
186
|
+
each_hash_val_include(v, depth - 1) { |vals| yield vals } if v.is_a?(Hash)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def restrict(cbname, arguments)
|
193
|
+
condition = @conditions[cbname]
|
194
|
+
return arguments if condition.nil? or @argument_requirements[cbname].nil?
|
195
|
+
|
196
|
+
each_hash_val_include(condition) do |needle, haystack, min_length|
|
197
|
+
# We could actually run the binding accessor expression here.
|
198
|
+
needed_idx = @argument_requirements[cbname].map(&:expression).index(needle)
|
199
|
+
next unless needed_idx
|
200
|
+
|
201
|
+
haystack_idx = @argument_requirements[cbname].map(&:expression).index(haystack)
|
202
|
+
next unless haystack_idx
|
203
|
+
|
204
|
+
arguments[haystack_idx] = ExecJSCB.hash_val_included(
|
205
|
+
arguments[needed_idx],
|
206
|
+
arguments[haystack_idx],
|
207
|
+
min_length.to_i,
|
208
|
+
@restrict_max_depth
|
209
|
+
)
|
210
|
+
end
|
211
|
+
|
212
|
+
arguments
|
213
|
+
end
|
214
|
+
|
215
|
+
def build_accessor(reqs)
|
216
|
+
reqs.map do |req|
|
217
|
+
BindingAccessor.new(req, true)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def build_runnable(callbacks)
|
222
|
+
@argument_requirements = {}
|
223
|
+
@source = ''
|
224
|
+
@js_pre = !callbacks['pre'].nil?
|
225
|
+
@js_post = !callbacks['post'].nil?
|
226
|
+
@js_failing = !callbacks['failing'].nil?
|
227
|
+
callbacks.each do |name, args_or_func|
|
228
|
+
@source << "var #{name} = "
|
229
|
+
if args_or_func.is_a?(Array)
|
230
|
+
@source << args_or_func.pop
|
231
|
+
@argument_requirements[name] = build_accessor(args_or_func)
|
232
|
+
else
|
233
|
+
@source << args_or_func
|
234
|
+
@argument_requirements[name] = []
|
235
|
+
end
|
236
|
+
@source << ";\n"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|