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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +22 -0
  3. data/README.md +77 -0
  4. data/Rakefile +20 -0
  5. data/lib/sqreen-alt.rb +1 -0
  6. data/lib/sqreen.rb +68 -0
  7. data/lib/sqreen/attack_detected.html +2 -0
  8. data/lib/sqreen/binding_accessor.rb +288 -0
  9. data/lib/sqreen/ca.crt +72 -0
  10. data/lib/sqreen/call_countable.rb +67 -0
  11. data/lib/sqreen/callback_tree.rb +78 -0
  12. data/lib/sqreen/callbacks.rb +100 -0
  13. data/lib/sqreen/capped_queue.rb +23 -0
  14. data/lib/sqreen/condition_evaluator.rb +235 -0
  15. data/lib/sqreen/conditionable.rb +50 -0
  16. data/lib/sqreen/configuration.rb +168 -0
  17. data/lib/sqreen/context.rb +26 -0
  18. data/lib/sqreen/deliveries/batch.rb +84 -0
  19. data/lib/sqreen/deliveries/simple.rb +39 -0
  20. data/lib/sqreen/event.rb +16 -0
  21. data/lib/sqreen/events/attack.rb +61 -0
  22. data/lib/sqreen/events/remote_exception.rb +54 -0
  23. data/lib/sqreen/events/request_record.rb +62 -0
  24. data/lib/sqreen/exception.rb +34 -0
  25. data/lib/sqreen/frameworks.rb +40 -0
  26. data/lib/sqreen/frameworks/generic.rb +446 -0
  27. data/lib/sqreen/frameworks/rails.rb +148 -0
  28. data/lib/sqreen/frameworks/rails3.rb +36 -0
  29. data/lib/sqreen/frameworks/request_recorder.rb +69 -0
  30. data/lib/sqreen/frameworks/sinatra.rb +57 -0
  31. data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
  32. data/lib/sqreen/instrumentation.rb +542 -0
  33. data/lib/sqreen/log.rb +119 -0
  34. data/lib/sqreen/metrics.rb +6 -0
  35. data/lib/sqreen/metrics/average.rb +39 -0
  36. data/lib/sqreen/metrics/base.rb +45 -0
  37. data/lib/sqreen/metrics/collect.rb +22 -0
  38. data/lib/sqreen/metrics/sum.rb +20 -0
  39. data/lib/sqreen/metrics_store.rb +96 -0
  40. data/lib/sqreen/middleware.rb +34 -0
  41. data/lib/sqreen/payload_creator.rb +137 -0
  42. data/lib/sqreen/performance_notifications.rb +86 -0
  43. data/lib/sqreen/performance_notifications/log.rb +36 -0
  44. data/lib/sqreen/performance_notifications/metrics.rb +36 -0
  45. data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
  46. data/lib/sqreen/remote_command.rb +93 -0
  47. data/lib/sqreen/rule_attributes.rb +26 -0
  48. data/lib/sqreen/rule_callback.rb +108 -0
  49. data/lib/sqreen/rules.rb +126 -0
  50. data/lib/sqreen/rules_callbacks.rb +29 -0
  51. data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +77 -0
  52. data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
  53. data/lib/sqreen/rules_callbacks/blacklist_ips.rb +44 -0
  54. data/lib/sqreen/rules_callbacks/count_http_codes.rb +40 -0
  55. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
  56. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +24 -0
  57. data/lib/sqreen/rules_callbacks/custom_error.rb +64 -0
  58. data/lib/sqreen/rules_callbacks/execjs.rb +241 -0
  59. data/lib/sqreen/rules_callbacks/headers_insert.rb +22 -0
  60. data/lib/sqreen/rules_callbacks/inspect_rule.rb +25 -0
  61. data/lib/sqreen/rules_callbacks/matcher_rule.rb +138 -0
  62. data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
  63. data/lib/sqreen/rules_callbacks/record_request_context.rb +39 -0
  64. data/lib/sqreen/rules_callbacks/reflected_xss.rb +254 -0
  65. data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
  66. data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
  67. data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
  68. data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
  69. data/lib/sqreen/rules_signature.rb +151 -0
  70. data/lib/sqreen/runner.rb +365 -0
  71. data/lib/sqreen/runtime_infos.rb +138 -0
  72. data/lib/sqreen/safe_json.rb +60 -0
  73. data/lib/sqreen/sdk.rb +22 -0
  74. data/lib/sqreen/serializer.rb +46 -0
  75. data/lib/sqreen/session.rb +317 -0
  76. data/lib/sqreen/shared_storage.rb +31 -0
  77. data/lib/sqreen/stats.rb +18 -0
  78. data/lib/sqreen/version.rb +5 -0
  79. metadata +148 -0
@@ -0,0 +1,50 @@
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/condition_evaluator'
5
+
6
+ module Sqreen
7
+ # A module that will dynamically had preconditions to the pre/post/failing
8
+ # callbacks
9
+ module Conditionable
10
+ # Hook the necessary callback function
11
+ #
12
+ # @param conditions [Hash] hash of callback names => conditions
13
+ def condition_callbacks(conditions)
14
+ conditions = {} if conditions.nil?
15
+ base = self.class
16
+ %w(pre post failing).each do |cb|
17
+ conds = conditions[cb]
18
+ next unless base.method_defined?(cb)
19
+ send("#{cb}_conditions=", ConditionEvaluator.new(conds)) unless conds.nil?
20
+ defd = base.instance_variable_defined?("@conditional_hooked_#{cb}")
21
+ next if defd && base.instance_variable_get("@conditional_hooked_#{cb}")
22
+ base.send(:alias_method, "#{cb}_without_conditions", cb)
23
+ base.send(:alias_method, cb, "#{cb}_with_conditions")
24
+ base.instance_variable_set("@conditional_hooked_#{cb}", true)
25
+ end
26
+ end
27
+
28
+ def pre_with_conditions(inst, *args, &block)
29
+ eargs = [binding, framework, inst, args, @data, nil]
30
+ return nil if !pre_conditions.nil? && !pre_conditions.evaluate(*eargs)
31
+ pre_without_conditions(inst, *args, &block)
32
+ end
33
+
34
+ def post_with_conditions(rv, inst, *args, &block)
35
+ eargs = [binding, framework, inst, args, @data, rv]
36
+ return nil if !post_conditions.nil? && !post_conditions.evaluate(*eargs)
37
+ post_without_conditions(rv, inst, *args, &block)
38
+ end
39
+
40
+ def failing_with_conditions(rv, inst, *args, &block)
41
+ eargs = [binding, framework, inst, args, @data, rv]
42
+ return nil if !failing_conditions.nil? && !failing_conditions.evaluate(*eargs)
43
+ failing_without_conditions(rv, inst, *args, &block)
44
+ end
45
+
46
+ protected
47
+
48
+ attr_accessor :pre_conditions, :post_conditions, :failing_conditions
49
+ end
50
+ end
@@ -0,0 +1,168 @@
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 'yaml'
5
+ require 'erb'
6
+ require 'sqreen/performance_notifications/newrelic'
7
+
8
+ module Sqreen
9
+ @config = nil
10
+
11
+ def self.config_init(framework = nil)
12
+ @config = Configuration.new(framework)
13
+ @config.load!
14
+ if @config && config_get(:report_perf_newrelic)
15
+ Sqreen::PerformanceNotifications::NewRelic.enable
16
+ end
17
+ @config
18
+ end
19
+
20
+ def self.config_get(name)
21
+ raise 'No configuration defined' if @config.nil?
22
+ @config.get(name)
23
+ end
24
+
25
+ CONFIG_FILE_BY_ENV = 'SQREEN_CONFIG_FILE'.freeze
26
+
27
+ CONFIG_DESCRIPTION = [
28
+ { :env => :SQREEN_DISABLE, :name => :disable,
29
+ :default => false, :convert => :to_bool },
30
+ { :env => :SQREEN_URL, :name => :url,
31
+ :default => 'https://back.sqreen.io' },
32
+ { :env => :SQREEN_TOKEN, :name => :token,
33
+ :default => nil },
34
+ { :env => :SQREEN_RULES, :name => :local_rules,
35
+ :default => nil },
36
+ { :env => :SQREEN_RULES_SIGNATURE, :name => :rules_verify_signature,
37
+ :default => true },
38
+ { :env => :SQREEN_LOG_LEVEL, :name => :log_level,
39
+ :default => 'WARN', :choice => %w[UNKNOWN FATAL ERROR WARN INFO DEBUG] },
40
+ { :env => :SQREEN_LOG_LOCATION, :name => :log_location,
41
+ :default => 'log/sqreen.log' },
42
+ { :env => :SQREEN_RUN_IN_TEST, :name => :run_in_test,
43
+ :default => false, :convert => :to_bool },
44
+ { :env => :SQREEN_BLOCK_ALL_RULES, :name => :block_all_rules,
45
+ :default => nil },
46
+ { :env => :SQREEN_REPORT_PERF_NR, :name => :report_perf_newrelic,
47
+ :default => false, :convert => :to_bool },
48
+ { :env => :SQREEN_INITIAL_FEATURES, :name => :initial_features,
49
+ :default => nil },
50
+
51
+ ].freeze
52
+
53
+ CONFIG_FILE_NAME = 'sqreen.yml'.freeze
54
+
55
+ def self.to_bool(value)
56
+ %w[1 true].include?(value.to_s.downcase.strip)
57
+ end
58
+
59
+ # Class to access configurations variables
60
+ # This try to load environment by different ways.
61
+ # 1. By file:
62
+ # a. From path in environment variable SQREEN_CONFIG_FILE
63
+ # b. From path in #{Rails.root}/config/sqreen.yml
64
+ # c. From home in ~/.sqreen.yml
65
+ # 2. From the environment, which overrides whatever result we found in 1.
66
+ class Configuration
67
+ def initialize(framework = nil)
68
+ @framework = framework
69
+ @config = default_values
70
+ end
71
+
72
+ def load!
73
+ path = find_configuration_file
74
+ if path
75
+ file_config = parse_configuration_file(path)
76
+ @config.merge!(file_config)
77
+ end
78
+
79
+ env_config = from_environment
80
+ @config.merge!(env_config)
81
+ end
82
+
83
+ def get(name)
84
+ @config[name.to_sym]
85
+ end
86
+
87
+ def default_values
88
+ res = {}
89
+ Sqreen::CONFIG_DESCRIPTION.each do |param|
90
+ name = param[:name]
91
+ value = param[:default]
92
+ choices = param[:choices]
93
+ if choices && !choices.include?(value)
94
+ msg = format("Invalid value '%s' for env '%s' (allowed: %s)", value, name, choices)
95
+ raise Sqreen::Exception, msg
96
+ end
97
+ res[name] = param[:convert] ? send(param[:convert], value) : value
98
+ end
99
+ res
100
+ end
101
+
102
+ def from_environment
103
+ res = {}
104
+ Sqreen::CONFIG_DESCRIPTION.each do |param|
105
+ name = param[:name]
106
+ value = ENV[param[:env].to_s]
107
+ next unless value
108
+ res[name] = param[:convert] ? send(param[:convert], value) : value
109
+ end
110
+ res
111
+ end
112
+
113
+ def parse_configuration_file(path)
114
+ yaml = YAML.load(ERB.new(File.read(path)).result)
115
+ return {} unless yaml.is_a?(Hash)
116
+ if @framework
117
+ env = @framework.framework_infos[:environment]
118
+ yaml = yaml[env] if env && yaml[env].is_a?(Hash)
119
+ end
120
+ res = {}
121
+ # hash keys loaded by YAML are strings instead of symbols
122
+ Sqreen::CONFIG_DESCRIPTION.each do |param|
123
+ name = param[:name]
124
+ value = yaml[name.to_s]
125
+ next unless value
126
+ res[name] = param[:convert] ? send(param[:convert], value) : value
127
+ end
128
+ res
129
+ end
130
+
131
+ def find_user_home
132
+ homes = %w[HOME HOMEPATH]
133
+ homes.detect { |h| !ENV[h].nil? }
134
+ end
135
+
136
+ def find_configuration_file
137
+ config_file_from_env || local_config_file || config_file_from_home
138
+ end
139
+
140
+ protected
141
+
142
+ def config_file_from_env
143
+ path = ENV[Sqreen::CONFIG_FILE_BY_ENV]
144
+ return path if path && File.exist?(path)
145
+ end
146
+
147
+ def local_config_file
148
+ if @framework && @framework.root
149
+ path = File.join(@framework.root.to_s, 'config', CONFIG_FILE_NAME)
150
+ return path if File.exist?(path)
151
+ else
152
+ path = File.expand_path(File.join('config', 'sqreen.yml'))
153
+ return path if File.exist?(path)
154
+ end
155
+ end
156
+
157
+ def config_file_from_home
158
+ home = find_user_home
159
+ return unless home
160
+ path = File.join(ENV[home], '.' + CONFIG_FILE_NAME)
161
+ return path if File.exist?(path)
162
+ end
163
+
164
+ def to_bool(value)
165
+ Sqreen::to_bool(value)
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,26 @@
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
+ module Sqreen
5
+ # Context
6
+ class Context
7
+ attr_accessor :bt
8
+
9
+ def self.bt
10
+ Context.new.bt
11
+ end
12
+
13
+ def initialize
14
+ @bt = get_current_backtrace
15
+ end
16
+
17
+ def get_current_backtrace
18
+ # Force caller to be resolved now
19
+ caller.map(&:to_s)
20
+ end
21
+
22
+ def ==(other)
23
+ other.bt == @bt
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,84 @@
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/deliveries/simple'
5
+ require 'sqreen/events/remote_exception'
6
+ module Sqreen
7
+ module Deliveries
8
+ # Simple delivery method that batch event already seen in a batch
9
+ class Batch < Simple
10
+ attr_accessor :max_batch, :max_staleness
11
+ attr_accessor :current_batch, :first_seen
12
+
13
+ def initialize(session,
14
+ max_batch,
15
+ max_staleness,
16
+ randomize_staleness = true)
17
+ super(session)
18
+ self.max_batch = max_batch
19
+ self.max_staleness = max_staleness
20
+ @original_max_staleness = max_staleness
21
+ self.current_batch = []
22
+ @first_seen = {}
23
+ @randomize_staleness = randomize_staleness
24
+ end
25
+
26
+ def post_event(event)
27
+ current_batch.push(event)
28
+ post_batch if post_batch_needed?(event)
29
+ end
30
+
31
+ def drain
32
+ post_batch unless current_batch.empty?
33
+ end
34
+
35
+ def tick
36
+ post_batch if !current_batch.empty? && stale?
37
+ end
38
+
39
+ protected
40
+
41
+ def stale?
42
+ min = @first_seen.values.min
43
+ return false if min.nil?
44
+ (min + max_staleness) < Time.now
45
+ end
46
+
47
+ def post_batch_needed?(event)
48
+ now = Time.now
49
+ event_keys(event).map do |key|
50
+ was = @first_seen[key]
51
+ @first_seen[key] ||= now
52
+ was.nil? || current_batch.size > max_batch || (was + max_staleness) < now
53
+ end.any?
54
+ end
55
+
56
+ def post_batch
57
+ session.post_batch(current_batch)
58
+ current_batch.clear
59
+ now = Time.now
60
+ @first_seen.each_key do |key|
61
+ @first_seen[key] = now
62
+ end
63
+ return unless @randomize_staleness
64
+ self.max_staleness = @original_max_staleness
65
+ # Adds up to 10% of lateness
66
+ self.max_staleness += rand(@original_max_staleness / 10)
67
+ end
68
+
69
+ def event_keys(event)
70
+ return [event_key(event)] unless event.is_a?(Sqreen::RequestRecord)
71
+ event.observed.fetch(:attacks, []).map { |e| "att-#{e[:rule_name]}" } + event.observed.fetch(:sqreen_exceptions, []).map { |e| "rex-#{e[:exception].class}" }
72
+ end
73
+
74
+ def event_key(event)
75
+ case event
76
+ when Sqreen::Attack
77
+ "att-#{event.type}"
78
+ when Sqreen::RemoteException
79
+ "rex-#{event.klass}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,39 @@
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/events/remote_exception'
5
+ require 'sqreen/events/request_record'
6
+
7
+ module Sqreen
8
+ module Deliveries
9
+ # Simple delivery method that directly call session on event
10
+ class Simple
11
+ attr_accessor :session
12
+
13
+ def initialize(session)
14
+ self.session = session
15
+ end
16
+
17
+ def post_event(event)
18
+ case event
19
+ when Sqreen::Attack
20
+ session.post_attack(event)
21
+ when Sqreen::RemoteException
22
+ session.post_sqreen_exception(event)
23
+ when Sqreen::RequestRecord
24
+ session.post_request_record(event)
25
+ else
26
+ session.post_event(event)
27
+ end
28
+ end
29
+
30
+ def drain
31
+ # Since everything is posted at once nothing needs to be done here
32
+ end
33
+
34
+ def tick
35
+ # Since everything is posted at once nothing needs to be done here
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,16 @@
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
+ module Sqreen
5
+ # Master interface for point in time events (e.g. Attack, RemoteException)
6
+ class Event
7
+ attr_reader :payload
8
+ def initialize(payload)
9
+ @payload = payload
10
+ end
11
+
12
+ def to_hash
13
+ payload.to_hash
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,61 @@
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/event'
5
+
6
+ module Sqreen
7
+ # Attack
8
+ # When creating a new attack, it gets automatically pushed to the event's
9
+ # queue.
10
+ class Attack < Event
11
+ def self.record(payload)
12
+ attack = Attack.new(payload)
13
+ attack.enqueue
14
+ end
15
+
16
+ def infos
17
+ payload['infos']
18
+ end
19
+
20
+ def rulespack_id
21
+ return nil unless payload['rule']
22
+ payload['rule']['rulespack_id']
23
+ end
24
+
25
+ def type
26
+ return nil unless payload['rule']
27
+ payload['rule']['name']
28
+ end
29
+
30
+ def time
31
+ return nil unless payload['local']
32
+ payload['local']['time']
33
+ end
34
+
35
+ def backtrace
36
+ return nil unless payload['context']
37
+ payload['context']['backtrace']
38
+ end
39
+
40
+ def enqueue
41
+ Sqreen.queue.push(self)
42
+ end
43
+
44
+ def to_hash
45
+ res = {}
46
+ rule_p = payload['rule']
47
+ request_p = payload['request']
48
+ res[:rule_name] = rule_p['name'] if rule_p && rule_p['name']
49
+ res[:rulespack_id] = rule_p['rulespack_id'] if rule_p && rule_p['rulespack_id']
50
+ res[:test] = rule_p['test'] if rule_p && rule_p['test']
51
+ res[:infos] = payload['infos'] if payload['infos']
52
+ res[:time] = time if time
53
+ res[:client_ip] = request_p[:addr] if request_p && request_p[:addr]
54
+ res[:request] = request_p if request_p
55
+ res[:params] = payload['params'] if payload['params']
56
+ res[:context] = payload['context'] if payload['context']
57
+ res[:headers] = payload['headers'] if payload['headers']
58
+ res
59
+ end
60
+ end
61
+ end