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.
- checksums.yaml +4 -4
- data/CODE_OF_CONDUCT.md +22 -0
- data/README.md +77 -0
- data/Rakefile +40 -0
- data/lib/sqreen.rb +67 -0
- data/lib/sqreen/binding_accessor.rb +184 -0
- data/lib/sqreen/ca.crt +72 -0
- data/lib/sqreen/callback_tree.rb +78 -0
- data/lib/sqreen/callbacks.rb +120 -0
- data/lib/sqreen/capped_queue.rb +23 -0
- data/lib/sqreen/condition_evaluator.rb +169 -0
- data/lib/sqreen/conditionable.rb +50 -0
- data/lib/sqreen/configuration.rb +151 -0
- data/lib/sqreen/context.rb +22 -0
- data/lib/sqreen/deliveries/batch.rb +80 -0
- data/lib/sqreen/deliveries/simple.rb +36 -0
- data/lib/sqreen/detect.rb +14 -0
- data/lib/sqreen/detect/shell_injection.rb +61 -0
- data/lib/sqreen/detect/sql_injection.rb +115 -0
- data/lib/sqreen/event.rb +16 -0
- data/lib/sqreen/events/attack.rb +60 -0
- data/lib/sqreen/events/remote_exception.rb +53 -0
- data/lib/sqreen/exception.rb +31 -0
- data/lib/sqreen/frameworks.rb +40 -0
- data/lib/sqreen/frameworks/generic.rb +243 -0
- data/lib/sqreen/frameworks/rails.rb +155 -0
- data/lib/sqreen/frameworks/rails3.rb +36 -0
- data/lib/sqreen/frameworks/sinatra.rb +34 -0
- data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
- data/lib/sqreen/instrumentation.rb +504 -0
- data/lib/sqreen/log.rb +116 -0
- data/lib/sqreen/metrics.rb +6 -0
- data/lib/sqreen/metrics/average.rb +39 -0
- data/lib/sqreen/metrics/base.rb +41 -0
- data/lib/sqreen/metrics/collect.rb +22 -0
- data/lib/sqreen/metrics/sum.rb +20 -0
- data/lib/sqreen/metrics_store.rb +94 -0
- data/lib/sqreen/parsers/sql.rb +98 -0
- data/lib/sqreen/parsers/sql_tokenizer.rb +266 -0
- data/lib/sqreen/parsers/unix.rb +110 -0
- data/lib/sqreen/payload_creator.rb +132 -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 +82 -0
- data/lib/sqreen/rule_attributes.rb +25 -0
- data/lib/sqreen/rule_callback.rb +97 -0
- data/lib/sqreen/rules.rb +116 -0
- data/lib/sqreen/rules_callbacks.rb +29 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
- data/lib/sqreen/rules_callbacks/count_http_codes.rb +18 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +25 -0
- data/lib/sqreen/rules_callbacks/execjs.rb +136 -0
- data/lib/sqreen/rules_callbacks/headers_insert.rb +20 -0
- data/lib/sqreen/rules_callbacks/inspect_rule.rb +20 -0
- data/lib/sqreen/rules_callbacks/matcher_rule.rb +103 -0
- data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
- data/lib/sqreen/rules_callbacks/record_request_context.rb +23 -0
- data/lib/sqreen/rules_callbacks/reflected_xss.rb +40 -0
- data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
- data/lib/sqreen/rules_callbacks/shell.rb +33 -0
- data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
- data/lib/sqreen/rules_callbacks/sql.rb +41 -0
- data/lib/sqreen/rules_callbacks/system_shell.rb +25 -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 +142 -0
- data/lib/sqreen/runner.rb +312 -0
- data/lib/sqreen/runtime_infos.rb +127 -0
- data/lib/sqreen/session.rb +340 -0
- data/lib/sqreen/stats.rb +18 -0
- data/lib/sqreen/version.rb +6 -0
- 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
|