sqreen 0.1.0.pre → 0.7.01461158029

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CODE_OF_CONDUCT.md +22 -0
  3. data/README.md +77 -0
  4. data/Rakefile +40 -0
  5. data/lib/sqreen.rb +67 -0
  6. data/lib/sqreen/binding_accessor.rb +184 -0
  7. data/lib/sqreen/ca.crt +72 -0
  8. data/lib/sqreen/callback_tree.rb +78 -0
  9. data/lib/sqreen/callbacks.rb +120 -0
  10. data/lib/sqreen/capped_queue.rb +23 -0
  11. data/lib/sqreen/condition_evaluator.rb +169 -0
  12. data/lib/sqreen/conditionable.rb +50 -0
  13. data/lib/sqreen/configuration.rb +151 -0
  14. data/lib/sqreen/context.rb +22 -0
  15. data/lib/sqreen/deliveries/batch.rb +80 -0
  16. data/lib/sqreen/deliveries/simple.rb +36 -0
  17. data/lib/sqreen/detect.rb +14 -0
  18. data/lib/sqreen/detect/shell_injection.rb +61 -0
  19. data/lib/sqreen/detect/sql_injection.rb +115 -0
  20. data/lib/sqreen/event.rb +16 -0
  21. data/lib/sqreen/events/attack.rb +60 -0
  22. data/lib/sqreen/events/remote_exception.rb +53 -0
  23. data/lib/sqreen/exception.rb +31 -0
  24. data/lib/sqreen/frameworks.rb +40 -0
  25. data/lib/sqreen/frameworks/generic.rb +243 -0
  26. data/lib/sqreen/frameworks/rails.rb +155 -0
  27. data/lib/sqreen/frameworks/rails3.rb +36 -0
  28. data/lib/sqreen/frameworks/sinatra.rb +34 -0
  29. data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
  30. data/lib/sqreen/instrumentation.rb +504 -0
  31. data/lib/sqreen/log.rb +116 -0
  32. data/lib/sqreen/metrics.rb +6 -0
  33. data/lib/sqreen/metrics/average.rb +39 -0
  34. data/lib/sqreen/metrics/base.rb +41 -0
  35. data/lib/sqreen/metrics/collect.rb +22 -0
  36. data/lib/sqreen/metrics/sum.rb +20 -0
  37. data/lib/sqreen/metrics_store.rb +94 -0
  38. data/lib/sqreen/parsers/sql.rb +98 -0
  39. data/lib/sqreen/parsers/sql_tokenizer.rb +266 -0
  40. data/lib/sqreen/parsers/unix.rb +110 -0
  41. data/lib/sqreen/payload_creator.rb +132 -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 +82 -0
  47. data/lib/sqreen/rule_attributes.rb +25 -0
  48. data/lib/sqreen/rule_callback.rb +97 -0
  49. data/lib/sqreen/rules.rb +116 -0
  50. data/lib/sqreen/rules_callbacks.rb +29 -0
  51. data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
  52. data/lib/sqreen/rules_callbacks/count_http_codes.rb +18 -0
  53. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
  54. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +25 -0
  55. data/lib/sqreen/rules_callbacks/execjs.rb +136 -0
  56. data/lib/sqreen/rules_callbacks/headers_insert.rb +20 -0
  57. data/lib/sqreen/rules_callbacks/inspect_rule.rb +20 -0
  58. data/lib/sqreen/rules_callbacks/matcher_rule.rb +103 -0
  59. data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
  60. data/lib/sqreen/rules_callbacks/record_request_context.rb +23 -0
  61. data/lib/sqreen/rules_callbacks/reflected_xss.rb +40 -0
  62. data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
  63. data/lib/sqreen/rules_callbacks/shell.rb +33 -0
  64. data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
  65. data/lib/sqreen/rules_callbacks/sql.rb +41 -0
  66. data/lib/sqreen/rules_callbacks/system_shell.rb +25 -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 +142 -0
  70. data/lib/sqreen/runner.rb +312 -0
  71. data/lib/sqreen/runtime_infos.rb +127 -0
  72. data/lib/sqreen/session.rb +340 -0
  73. data/lib/sqreen/stats.rb +18 -0
  74. data/lib/sqreen/version.rb +6 -0
  75. metadata +95 -34
@@ -0,0 +1,31 @@
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/log'
5
+
6
+ module Sqreen
7
+ # Base exeception class for sqreen
8
+ class Exception < ::StandardError
9
+ def initialize(msg = nil, *args)
10
+ super(msg, *args)
11
+ Sqreen.log.error msg if msg
12
+ end
13
+ end
14
+
15
+ # When the token is not found
16
+ class TokenNotFoundException < Exception
17
+ end
18
+
19
+ # When the token is invalid
20
+ class TokenInvalidException < Exception
21
+ end
22
+
23
+ # This exception name is particularly important since it is often seen by
24
+ # Sqreen users when watching their logs. It should not raise any concern to
25
+ # them.
26
+ class AttackBlocked < Exception
27
+ end
28
+
29
+ class NotImplementedYet < Exception
30
+ end
31
+ 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
+ module Sqreen
5
+ @@framework = nil
6
+
7
+ def self::set_framework(fwk)
8
+ @@framework = fwk
9
+ end
10
+
11
+ def self::framework
12
+ return @@framework if @@framework
13
+ klass = case
14
+ when defined?(::Rails)
15
+ case Rails::VERSION::MAJOR.to_i
16
+ when 4, 5
17
+ require 'sqreen/frameworks/rails'
18
+ Sqreen::Frameworks::RailsFramework
19
+ when 3
20
+ require 'sqreen/frameworks/rails3'
21
+ Sqreen::Frameworks::Rails3Framework
22
+ else
23
+ raise "Rails version #{Rails.version} not supported"
24
+ end
25
+ when defined?(::Sinatra)
26
+ require 'sqreen/frameworks/sinatra'
27
+ Sqreen::Frameworks::SinatraFramework
28
+ when defined?(::SqreenTest)
29
+ require 'sqreen/frameworks/sqreen_test'
30
+ Sqreen::Frameworks::SqreenTestFramework
31
+ else
32
+ # FIXME: use sqreen logger before configuration?
33
+ STDERR.puts "Error: cannot find any framework\n"
34
+ require 'sqreen/frameworks/generic'
35
+ Sqreen::Frameworks::GenericFramework
36
+ end
37
+ fwk = klass.new
38
+ Sqreen.set_framework(fwk)
39
+ end
40
+ end
@@ -0,0 +1,243 @@
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
+
6
+ module Sqreen
7
+ module Frameworks
8
+ # This is the base class for framework specific code
9
+ class GenericFramework
10
+ attr_accessor :sqreen_configuration
11
+
12
+ # What kind of database is this
13
+ def db_settings(_options = {})
14
+ raise Sqreen::NotImplementedYet
15
+ end
16
+
17
+ # More information about the current framework
18
+ def framework_infos
19
+ raise Sqreen::NotImplementedYet unless ensure_rack_loaded
20
+ {
21
+ :framework_type => 'Rack',
22
+ :framework_version => Rack.version,
23
+ :environment => ENV['RACK_ENV'],
24
+ }
25
+ end
26
+
27
+ # What is the current client IP
28
+ def client_ip
29
+ req = request
30
+ return nil unless req
31
+ return req.ip if req.respond_to?(:ip)
32
+ req.env['REMOTE_ADDR']
33
+ end
34
+
35
+ def hostname
36
+ req = request
37
+ return nil unless req
38
+ http_host = req.env['HTTP_HOST']
39
+ return http_host if http_host && !http_host.empty?
40
+ req.env['SERVER_NAME']
41
+ end
42
+
43
+ def request_id
44
+ req = request
45
+ return nil unless req
46
+ req.env['HTTP_X_REQUEST_ID']
47
+ end
48
+
49
+ # Summary of known request infos
50
+ def request_infos
51
+ req = request
52
+ return {} unless req
53
+ # FIXME: Use frozen string keys
54
+ {
55
+ :rid => request_id,
56
+ :user_agent => client_user_agent,
57
+ :scheme => req.scheme,
58
+ :verb => req.env['REQUEST_METHOD'],
59
+ :host => hostname,
60
+ :port => req.env['SERVER_PORT'],
61
+ :rport => req.env['REMOTE_PORT'],
62
+ :referer => req.env['HTTP_REFERER'],
63
+ :path => request_path,
64
+ :addr => client_ip,
65
+ }
66
+ end
67
+
68
+ # Request URL path
69
+ def request_path
70
+ req = request
71
+ return nil unless req
72
+ req.script_name + req.path_info
73
+ end
74
+
75
+ # request user agent
76
+ def client_user_agent
77
+ req = request
78
+ return nil unless req
79
+ req.env['HTTP_USER_AGENT']
80
+ end
81
+
82
+ # Application root
83
+ def root
84
+ nil
85
+ end
86
+
87
+ # Main entry point for sqreen.
88
+ # launch whenever we are ready
89
+ def on_start
90
+ yield self
91
+ end
92
+
93
+ # Should the agent not be starting up?
94
+ def prevent_startup
95
+ :irb if $0 == 'irb'
96
+ end
97
+
98
+ # Instrument with our rules when the framework as finished loading
99
+ def instrument_when_ready!(instrumentor, rules)
100
+ done = false
101
+ # FIXME: why is this called twice
102
+ unless defined?(Rack::Builder)
103
+ done = true
104
+ return
105
+ end
106
+ cb = Sqreen::RunWhenCalledCB.new(Rack::Builder, :to_app) do
107
+ if done
108
+ Sqreen.log.debug('Already instrumented to_app')
109
+ else
110
+ instrumentor.instrument!(rules, self)
111
+ done = true
112
+ end
113
+ end
114
+ instrumentor.add_callback(cb)
115
+ end
116
+
117
+ # Does the parameters include this value
118
+ def params_include?(value)
119
+ params = request_params
120
+ return false if params.nil?
121
+ each_value_for_hash(params) do |param|
122
+ return true if param == value
123
+ end
124
+ false
125
+ end
126
+
127
+ # Fetch and store the current request object
128
+ # Nota: cleanup should be performed at end of request (see clean_request)
129
+ def store_request(object)
130
+ return unless ensure_rack_loaded
131
+ SharedStorage.set(:request, Rack::Request.new(object))
132
+ SharedStorage.inc(:stored_requests)
133
+ end
134
+
135
+ # Get the currently stored request
136
+ def request
137
+ SharedStorage.get(:request)
138
+ end
139
+
140
+ # Cleanup request context
141
+ def clean_request
142
+ return unless SharedStorage.dec(:stored_requests) <= 0
143
+ SharedStorage.set(:request, nil)
144
+ end
145
+
146
+ def request_params
147
+ self.class.parameters_from_request(request)
148
+ end
149
+
150
+ def filtered_request_params
151
+ params = request_params
152
+ params.delete('cookies')
153
+ params
154
+ end
155
+
156
+ %w(form query cookies).each do |section|
157
+ define_method("#{section}_params") do
158
+ self.class.send("#{section}_params", request)
159
+ end
160
+ end
161
+
162
+ P_FORM = 'form'.freeze
163
+ P_QUERY = 'query'.freeze
164
+ P_COOKIE = 'cookies'.freeze
165
+ P_GRAPE = 'grape_params'.freeze
166
+ P_RACK_ROUTING = 'rack_routing'.freeze
167
+
168
+ def self.form_params(request)
169
+ return nil unless request
170
+ begin
171
+ request.POST
172
+ rescue => e
173
+ Sqreen.log.debug("POST Parameters are invalid #{e.inspect}")
174
+ nil
175
+ end
176
+ end
177
+
178
+ def self.cookies_params(request)
179
+ return nil unless request
180
+ begin
181
+ request.cookies
182
+ rescue => e
183
+ Sqreen.log.debug("cookies are invalid #{e.inspect}")
184
+ nil
185
+ end
186
+ end
187
+
188
+ def self.query_params(request)
189
+ return nil unless request
190
+ begin
191
+ request.GET
192
+ rescue => e
193
+ Sqreen.log.debug("GET Parameters are invalid #{e.inspect}")
194
+ nil
195
+ end
196
+ end
197
+
198
+ def self.parameters_from_request(request)
199
+ return {} unless request
200
+
201
+ r = {
202
+ P_FORM => form_params(request),
203
+ P_QUERY => query_params(request),
204
+ P_COOKIE => cookies_params(request),
205
+ }
206
+ # Add grape parameters if seen
207
+ p = request.env['grape.request.params']
208
+ r[P_GRAPE] = p if p
209
+ p = request.env['rack.routing_args']
210
+ if p
211
+ r[P_RACK_ROUTING] = p.dup
212
+ r[P_RACK_ROUTING].delete :route_info
213
+ r[P_RACK_ROUTING].delete :version
214
+ end
215
+ r
216
+ end
217
+
218
+ protected
219
+
220
+ # FIXME: Extract to another object (utils?)
221
+ # FIXME: protect against cycles ?
222
+ def each_value_for_hash(params, &block)
223
+ case params
224
+ when Hash then params.each { |_k, v| each_value_for_hash(v, &block) }
225
+ when Array then params.each { |v| each_value_for_hash(v, &block) }
226
+ else
227
+ yield params
228
+ end
229
+ end
230
+
231
+ def ensure_rack_loaded
232
+ return false if @cannot_load_rack
233
+ require 'rack' unless defined?(Rack)
234
+ true
235
+ rescue LoadError => e
236
+ # FIXME find a nice way to test this branch
237
+ Sqreen::RemoteException.record(e)
238
+ @cannot_load_rack = true
239
+ false
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
3
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
4
+
5
+ require 'sqreen/frameworks/generic'
6
+
7
+ module Sqreen
8
+ module Frameworks
9
+ # Rails related framework code
10
+ class RailsFramework < GenericFramework
11
+ DB_MAPPING = {
12
+ 'SQLite' => :sqlite,
13
+ 'MySQL' => :mysql,
14
+ 'Mysql2' => :mysql,
15
+ }.freeze
16
+
17
+ def framework_infos
18
+ {
19
+ :framework_type => 'Rails',
20
+ :framework_version => Rails::VERSION::STRING,
21
+ :environment => Rails.env.to_s,
22
+ }
23
+ end
24
+
25
+ def db_settings(options = {})
26
+ adapter = options[:connection_adapter]
27
+ return nil unless adapter
28
+
29
+ begin
30
+ adapter_name = adapter.adapter_name
31
+ rescue
32
+ # FIXME: we may want to log that
33
+ Sqreen.log.error 'cannot find ADAPTER_NAME'
34
+ return nil
35
+ end
36
+ db_type = DB_MAPPING[adapter_name]
37
+ db_infos = { :name => adapter_name }
38
+ [db_type, db_infos]
39
+ end
40
+
41
+ def client_ip
42
+ request = SharedStorage.get :request
43
+ return unless request && request.env
44
+ remote_ip = request.env['action_dispatch.remote_ip']
45
+ return super unless remote_ip
46
+ # FIXME: - this exist only since Rails 3.2.1
47
+ # http://apidock.com/rails/v3.2.1/ActionDispatch/RemoteIp/GetIp/calculate_ip
48
+ if remote_ip.respond_to?(:calculate_ip)
49
+ return remote_ip.calculate_ip
50
+ else
51
+ # This might not return the same value as calculate IP
52
+ return remote_ip.to_s
53
+ end
54
+ end
55
+
56
+ def request_id
57
+ req = request
58
+ return super unless req
59
+ req.env['action_dispatch.request_id'] || super
60
+ end
61
+
62
+ def root
63
+ return nil unless @application
64
+ @application.root
65
+ end
66
+
67
+ # Register a new initializer in rails to ba called when we are starting up
68
+ class Init < ::Rails::Railtie
69
+ def self.startup
70
+ initializer 'sqreen.startup' do |app|
71
+ yield app
72
+ end
73
+ end
74
+ end
75
+
76
+ def hook_rack_request(app)
77
+ saved_meth_name = :call_without_sqreen_hooked
78
+ new_method = :call_with_sqreen_hooked
79
+
80
+ app.class.class_eval do
81
+ alias_method saved_meth_name, :call
82
+
83
+ define_method(new_method) do |*args, &cblock|
84
+ rv = send(saved_meth_name, *args, &cblock)
85
+ if Sqreen.framework.instance_variable_get('@calling_pid') != Process.pid
86
+ Sqreen.framework.instance_variable_set('@calling_pid', Process.pid)
87
+ yield Sqreen.framework
88
+ end
89
+ rv
90
+ end
91
+
92
+ alias_method :call, new_method
93
+ end
94
+ end
95
+
96
+ def on_start(&block)
97
+ @calling_pid = Process.pid
98
+ Init.startup do |app|
99
+ hook_rack_request(app, &block)
100
+ app.config.after_initialize do
101
+ yield self
102
+ end
103
+ end
104
+ end
105
+
106
+ def prevent_startup
107
+ res = super
108
+ return res if res
109
+ run_in_test = sqreen_configuration.get(:run_in_test)
110
+ return :rails_test if !run_in_test && Rails.env.test?
111
+
112
+ # SQREEN-880 - prevent Sqreen startup on Sidekiq workers
113
+ return :sidekiq_cli if defined?(Sidekiq::CLI)
114
+
115
+ # Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
116
+ return :rake if !run_in_test && $0.end_with?('rake')
117
+
118
+ return nil unless defined?(Rails::CommandsTasks)
119
+ return nil if defined?(Rails::Server)
120
+ return :rails_console if defined?(Rails::Console)
121
+ return :rails_dbconsole if defined?(Rails::DBConsole)
122
+ return :rails_generators if defined?(Rails::Generators)
123
+ nil
124
+ end
125
+
126
+ def instrument_when_ready!(instrumentor, rules)
127
+ instrumentor.instrument!(rules, self)
128
+ end
129
+
130
+ def rails_params
131
+ self.class.rails_params(request)
132
+ end
133
+
134
+ def self.rails_params(request)
135
+ return nil unless request
136
+ other = request.env['action_dispatch.request.parameters']
137
+ return nil unless other
138
+ # Remove Rails created parameters:
139
+ other = other.dup
140
+ other.delete :action
141
+ other.delete :controller
142
+ other
143
+ end
144
+
145
+ P_OTHER = 'other'.freeze
146
+
147
+ def self.parameters_from_request(request)
148
+ return {} unless request
149
+ ret = super(request)
150
+ ret[P_OTHER] = rails_params(request)
151
+ ret
152
+ end
153
+ end
154
+ end
155
+ end