sqreen 0.1.0.pre → 0.7.01461158029

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