sqreen 0.7.01461158029-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- 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 +143 -0
@@ -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
|