sqreen-alt 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
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