sqreen-alt 1.10.0
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 +7 -0
- data/CODE_OF_CONDUCT.md +22 -0
- data/README.md +77 -0
- data/Rakefile +20 -0
- data/lib/sqreen-alt.rb +1 -0
- data/lib/sqreen.rb +68 -0
- data/lib/sqreen/attack_detected.html +2 -0
- data/lib/sqreen/binding_accessor.rb +288 -0
- data/lib/sqreen/ca.crt +72 -0
- data/lib/sqreen/call_countable.rb +67 -0
- data/lib/sqreen/callback_tree.rb +78 -0
- data/lib/sqreen/callbacks.rb +100 -0
- data/lib/sqreen/capped_queue.rb +23 -0
- data/lib/sqreen/condition_evaluator.rb +235 -0
- data/lib/sqreen/conditionable.rb +50 -0
- data/lib/sqreen/configuration.rb +168 -0
- data/lib/sqreen/context.rb +26 -0
- data/lib/sqreen/deliveries/batch.rb +84 -0
- data/lib/sqreen/deliveries/simple.rb +39 -0
- data/lib/sqreen/event.rb +16 -0
- data/lib/sqreen/events/attack.rb +61 -0
- data/lib/sqreen/events/remote_exception.rb +54 -0
- data/lib/sqreen/events/request_record.rb +62 -0
- data/lib/sqreen/exception.rb +34 -0
- data/lib/sqreen/frameworks.rb +40 -0
- data/lib/sqreen/frameworks/generic.rb +446 -0
- data/lib/sqreen/frameworks/rails.rb +148 -0
- data/lib/sqreen/frameworks/rails3.rb +36 -0
- data/lib/sqreen/frameworks/request_recorder.rb +69 -0
- data/lib/sqreen/frameworks/sinatra.rb +57 -0
- data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
- data/lib/sqreen/instrumentation.rb +542 -0
- data/lib/sqreen/log.rb +119 -0
- data/lib/sqreen/metrics.rb +6 -0
- data/lib/sqreen/metrics/average.rb +39 -0
- data/lib/sqreen/metrics/base.rb +45 -0
- data/lib/sqreen/metrics/collect.rb +22 -0
- data/lib/sqreen/metrics/sum.rb +20 -0
- data/lib/sqreen/metrics_store.rb +96 -0
- data/lib/sqreen/middleware.rb +34 -0
- data/lib/sqreen/payload_creator.rb +137 -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 +93 -0
- data/lib/sqreen/rule_attributes.rb +26 -0
- data/lib/sqreen/rule_callback.rb +108 -0
- data/lib/sqreen/rules.rb +126 -0
- data/lib/sqreen/rules_callbacks.rb +29 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +77 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
- data/lib/sqreen/rules_callbacks/blacklist_ips.rb +44 -0
- data/lib/sqreen/rules_callbacks/count_http_codes.rb +40 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +24 -0
- data/lib/sqreen/rules_callbacks/custom_error.rb +64 -0
- data/lib/sqreen/rules_callbacks/execjs.rb +241 -0
- data/lib/sqreen/rules_callbacks/headers_insert.rb +22 -0
- data/lib/sqreen/rules_callbacks/inspect_rule.rb +25 -0
- data/lib/sqreen/rules_callbacks/matcher_rule.rb +138 -0
- data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
- data/lib/sqreen/rules_callbacks/record_request_context.rb +39 -0
- data/lib/sqreen/rules_callbacks/reflected_xss.rb +254 -0
- data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
- data/lib/sqreen/rules_callbacks/shell_env.rb +32 -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 +151 -0
- data/lib/sqreen/runner.rb +365 -0
- data/lib/sqreen/runtime_infos.rb +138 -0
- data/lib/sqreen/safe_json.rb +60 -0
- data/lib/sqreen/sdk.rb +22 -0
- data/lib/sqreen/serializer.rb +46 -0
- data/lib/sqreen/session.rb +317 -0
- data/lib/sqreen/shared_storage.rb +31 -0
- data/lib/sqreen/stats.rb +18 -0
- data/lib/sqreen/version.rb +5 -0
- metadata +148 -0
@@ -0,0 +1,54 @@
|
|
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 'json'
|
5
|
+
require 'sqreen/event'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
# When an exception arise it is automatically pushed to the event queue
|
9
|
+
class RemoteException < Sqreen::Event
|
10
|
+
def self.record(payload_or_exception)
|
11
|
+
exception = RemoteException.new(payload_or_exception)
|
12
|
+
exception.enqueue
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(payload_or_exception)
|
16
|
+
payload = if payload_or_exception.is_a?(Hash)
|
17
|
+
payload_or_exception
|
18
|
+
else
|
19
|
+
{ 'exception' => payload_or_exception }
|
20
|
+
end
|
21
|
+
super(payload)
|
22
|
+
end
|
23
|
+
|
24
|
+
def enqueue
|
25
|
+
Sqreen.queue.push(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def klass
|
29
|
+
payload['exception'].class.name
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_hash
|
33
|
+
exception = payload['exception']
|
34
|
+
ev = {
|
35
|
+
:klass => exception.class.name,
|
36
|
+
:message => exception.message,
|
37
|
+
:params => payload['request_params'],
|
38
|
+
:time => payload['time'],
|
39
|
+
:infos => {
|
40
|
+
:client_ip => payload['client_ip'],
|
41
|
+
},
|
42
|
+
:request => payload['request_infos'],
|
43
|
+
:headers => payload['headers'],
|
44
|
+
:rule_name => payload['rule_name'],
|
45
|
+
:rulespack_id => payload['rulespack_id'],
|
46
|
+
}
|
47
|
+
|
48
|
+
ev[:infos].merge!(payload['infos']) if payload['infos']
|
49
|
+
return ev unless exception.backtrace
|
50
|
+
ev[:context] = { :backtrace => exception.backtrace.map(&:to_s) }
|
51
|
+
ev
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,62 @@
|
|
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 'json'
|
5
|
+
require 'sqreen/event'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
# When a request is deeemed worthy of being sent to the backend
|
9
|
+
class RequestRecord < Sqreen::Event
|
10
|
+
def observed
|
11
|
+
(payload && payload[:observed]) || {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
res = { :version => '20171208' }
|
16
|
+
if payload[:observed]
|
17
|
+
res[:observed] = payload[:observed].dup
|
18
|
+
rulespack = nil
|
19
|
+
if observed[:attacks]
|
20
|
+
res[:observed][:attacks] = observed[:attacks].map do |att|
|
21
|
+
natt = att.dup
|
22
|
+
rulespack = natt.delete(:rulespack_id) || rulespack
|
23
|
+
natt
|
24
|
+
end
|
25
|
+
end
|
26
|
+
if observed[:sqreen_exceptions]
|
27
|
+
res[:observed][:sqreen_exceptions] = observed[:sqreen_exceptions].map do |exc|
|
28
|
+
nex = exc.dup
|
29
|
+
excp = nex.delete(:exception)
|
30
|
+
if excp
|
31
|
+
nex[:message] = excp.message
|
32
|
+
nex[:klass] = excp.class.name
|
33
|
+
end
|
34
|
+
rulespack = nex.delete(:rulespack_id) || rulespack
|
35
|
+
nex
|
36
|
+
end
|
37
|
+
end
|
38
|
+
res[:rulespack_id] = rulespack unless rulespack.nil?
|
39
|
+
if observed[:observations]
|
40
|
+
res[:observed][:observations] = observed[:observations].map do |cat, key, value, time|
|
41
|
+
{ :category => cat, :key => key, :value => value, :time => time }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
if observed[:sdk]
|
45
|
+
res[:observed][:sdk] = observed[:sdk].map do |meth, time, *args|
|
46
|
+
{ :name => meth, :time => time, :args => args }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
res[:local] = payload['local'] if payload['local']
|
51
|
+
if payload['request']
|
52
|
+
res[:request] = payload['request'].dup
|
53
|
+
res[:client_ip] = res[:request].delete(:client_ip) if res[:request][:client_ip]
|
54
|
+
else
|
55
|
+
res[:request] = {}
|
56
|
+
end
|
57
|
+
res[:request][:parameters] = payload['params'] if payload['params']
|
58
|
+
res[:request][:headers] = payload['headers'] if payload['headers']
|
59
|
+
res
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,34 @@
|
|
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
|
+
|
32
|
+
class InvalidSignatureException < Exception
|
33
|
+
end
|
34
|
+
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) && defined?(::Rails::VERSION)
|
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,446 @@
|
|
1
|
+
# Copyright (c) 2015 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
require 'sqreen/events/remote_exception'
|
6
|
+
require 'sqreen/callbacks'
|
7
|
+
require 'sqreen/exception'
|
8
|
+
require 'sqreen/log'
|
9
|
+
require 'sqreen/frameworks/request_recorder'
|
10
|
+
|
11
|
+
module Sqreen
|
12
|
+
module Frameworks
|
13
|
+
# This is the base class for framework specific code
|
14
|
+
class GenericFramework
|
15
|
+
include RequestRecorder
|
16
|
+
attr_accessor :sqreen_configuration
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
if defined?(Rack::Builder)
|
20
|
+
hook_rack_builder
|
21
|
+
else
|
22
|
+
to_app_done(Process.pid)
|
23
|
+
end
|
24
|
+
clean_request_record
|
25
|
+
end
|
26
|
+
|
27
|
+
# What kind of database is this
|
28
|
+
def db_settings(_options = {})
|
29
|
+
raise Sqreen::NotImplementedYet
|
30
|
+
end
|
31
|
+
|
32
|
+
# More information about the current framework
|
33
|
+
def framework_infos
|
34
|
+
raise Sqreen::NotImplementedYet unless ensure_rack_loaded
|
35
|
+
{
|
36
|
+
:framework_type => 'Rack',
|
37
|
+
:framework_version => Rack.version,
|
38
|
+
:environment => ENV['RACK_ENV'],
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def development?
|
43
|
+
ENV['RACK_ENV'] == 'development'
|
44
|
+
end
|
45
|
+
|
46
|
+
PREFFERED_IP_HEADERS = %w(HTTP_X_FORWARDED_FOR HTTP_X_REAL_IP
|
47
|
+
HTTP_CLIENT_IP HTTP_X_FORWARDED
|
48
|
+
HTTP_X_CLUSTER_CLIENT_IP HTTP_FORWARDED_FOR
|
49
|
+
HTTP_FORWARDED HTTP_VIA).freeze
|
50
|
+
|
51
|
+
def ip_headers
|
52
|
+
req = request
|
53
|
+
return [] unless req
|
54
|
+
ips = []
|
55
|
+
(PREFFERED_IP_HEADERS + ['REMOTE_ADDR']).each do |header|
|
56
|
+
v = req.env[header]
|
57
|
+
ips << [header, v] unless v.nil?
|
58
|
+
end
|
59
|
+
ips << ['rack.ip', req.ip] if req.respond_to?(:ip)
|
60
|
+
ips
|
61
|
+
end
|
62
|
+
|
63
|
+
# What is the current client IP as seen by rack
|
64
|
+
def rack_client_ip
|
65
|
+
req = request
|
66
|
+
return nil unless req
|
67
|
+
return req.ip if req.respond_to?(:ip)
|
68
|
+
req.env['REMOTE_ADDR']
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sourced from rack:Request#trusted_proxy?
|
72
|
+
TRUSTED_PROXIES = /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
|
73
|
+
LOCALHOST = /\A127\.0\.0\.1\Z|\A::1\Z|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
|
74
|
+
|
75
|
+
# What is the current client IP
|
76
|
+
def client_ip
|
77
|
+
req = request
|
78
|
+
return nil unless req
|
79
|
+
# Look for an external address being forwarded
|
80
|
+
split_ips = []
|
81
|
+
PREFFERED_IP_HEADERS.each do |header_name|
|
82
|
+
forwarded = req.env[header_name]
|
83
|
+
ips = split_ip_addresses(forwarded)
|
84
|
+
lip = ips.find { |ip| (ip !~ TRUSTED_PROXIES) && valid_ip?(ip) }
|
85
|
+
split_ips << ips unless ips.empty?
|
86
|
+
return lip unless lip.nil?
|
87
|
+
end
|
88
|
+
# Else fall back to declared remote addr
|
89
|
+
r = req.env['REMOTE_ADDR']
|
90
|
+
# If this is localhost get the last hop before
|
91
|
+
if r.nil? || r =~ LOCALHOST
|
92
|
+
split_ips.each do |ips|
|
93
|
+
lip = ips.find { |ip| (ip !~ LOCALHOST) && valid_ip?(ip) }
|
94
|
+
return lip unless lip.nil?
|
95
|
+
end
|
96
|
+
end
|
97
|
+
r
|
98
|
+
end
|
99
|
+
|
100
|
+
# Get a header by name
|
101
|
+
def header(name)
|
102
|
+
req = request
|
103
|
+
return nil unless req
|
104
|
+
req.env[name]
|
105
|
+
end
|
106
|
+
|
107
|
+
def http_headers
|
108
|
+
req = request
|
109
|
+
return nil unless req
|
110
|
+
req.env.select { |k, _| k.to_s.start_with?('HTTP_') }
|
111
|
+
end
|
112
|
+
|
113
|
+
def hostname
|
114
|
+
req = request
|
115
|
+
return nil unless req
|
116
|
+
http_host = req.env['HTTP_HOST']
|
117
|
+
return http_host if http_host && !http_host.empty?
|
118
|
+
req.env['SERVER_NAME']
|
119
|
+
end
|
120
|
+
|
121
|
+
def request_id
|
122
|
+
req = request
|
123
|
+
return nil unless req
|
124
|
+
req.env['HTTP_X_REQUEST_ID']
|
125
|
+
end
|
126
|
+
|
127
|
+
# Summary of known request infos
|
128
|
+
def request_infos
|
129
|
+
req = request
|
130
|
+
return {} unless req
|
131
|
+
# FIXME: Use frozen string keys
|
132
|
+
{
|
133
|
+
:rid => request_id,
|
134
|
+
:user_agent => client_user_agent,
|
135
|
+
:scheme => req.scheme,
|
136
|
+
:verb => req.env['REQUEST_METHOD'],
|
137
|
+
:host => hostname,
|
138
|
+
:port => req.env['SERVER_PORT'],
|
139
|
+
:referer => req.env['HTTP_REFERER'],
|
140
|
+
:path => request_path,
|
141
|
+
:remote_port => req.env['REMOTE_PORT'],
|
142
|
+
:remote_ip => remote_addr,
|
143
|
+
:client_ip => client_ip,
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
# Request URL path
|
148
|
+
def request_path
|
149
|
+
req = request
|
150
|
+
return nil unless req
|
151
|
+
req.script_name + req.path_info
|
152
|
+
end
|
153
|
+
|
154
|
+
# request user agent
|
155
|
+
def client_user_agent
|
156
|
+
req = request
|
157
|
+
return nil unless req
|
158
|
+
req.env['HTTP_USER_AGENT']
|
159
|
+
end
|
160
|
+
|
161
|
+
# Application root
|
162
|
+
def root
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
|
166
|
+
# Main entry point for sqreen.
|
167
|
+
# launch whenever we are ready
|
168
|
+
def on_start
|
169
|
+
yield self
|
170
|
+
end
|
171
|
+
|
172
|
+
# Should the agent not be starting up?
|
173
|
+
def prevent_startup
|
174
|
+
return :irb if $0 == 'irb'
|
175
|
+
return if sqreen_configuration.nil?
|
176
|
+
disable = sqreen_configuration.get(:disable)
|
177
|
+
return :config_disable if disable == true || disable.to_s.to_i == 1
|
178
|
+
end
|
179
|
+
|
180
|
+
# Instrument with our rules when the framework as finished loading
|
181
|
+
def instrument_when_ready!(instrumentor, rules)
|
182
|
+
wait_for_to_app do
|
183
|
+
instrumentor.instrument!(rules, self)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def to_app_done(val)
|
188
|
+
return if @to_app_done
|
189
|
+
@to_app_done = val
|
190
|
+
return unless @wait
|
191
|
+
@wait.each(&:call)
|
192
|
+
@wait.clear
|
193
|
+
end
|
194
|
+
|
195
|
+
def wait_for_to_app(&block)
|
196
|
+
yield && return if @to_app_done
|
197
|
+
@wait ||= []
|
198
|
+
@wait << block
|
199
|
+
end
|
200
|
+
|
201
|
+
# Does the parameters value include this value
|
202
|
+
def params_include?(value)
|
203
|
+
params = request_params
|
204
|
+
return false if params.nil?
|
205
|
+
each_value_for_hash(params) do |param|
|
206
|
+
return true if param == value
|
207
|
+
end
|
208
|
+
false
|
209
|
+
end
|
210
|
+
|
211
|
+
# Does the parameters key/value include this value
|
212
|
+
def full_params_include?(value)
|
213
|
+
params = request_params
|
214
|
+
return false if params.nil?
|
215
|
+
each_key_value_for_hash(params) do |param|
|
216
|
+
return true if param == value
|
217
|
+
end
|
218
|
+
false
|
219
|
+
end
|
220
|
+
|
221
|
+
# Fetch and store the current request object
|
222
|
+
# Nota: cleanup should be performed at end of request (see clean_request)
|
223
|
+
def store_request(object)
|
224
|
+
return unless ensure_rack_loaded
|
225
|
+
SharedStorage.set(:request, Rack::Request.new(object))
|
226
|
+
SharedStorage.inc(:stored_requests)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Get the currently stored request
|
230
|
+
def request
|
231
|
+
SharedStorage.get(:request)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Cleanup request context
|
235
|
+
def clean_request
|
236
|
+
return unless SharedStorage.dec(:stored_requests) <= 0
|
237
|
+
payload_creator = Sqreen::PayloadCreator.new(self)
|
238
|
+
close_request_record(Sqreen.queue, Sqreen.observations_queue, payload_creator)
|
239
|
+
SharedStorage.set(:request, nil)
|
240
|
+
end
|
241
|
+
|
242
|
+
def request_params
|
243
|
+
self.class.parameters_from_request(request)
|
244
|
+
end
|
245
|
+
|
246
|
+
def filtered_request_params
|
247
|
+
params = request_params
|
248
|
+
params.delete('cookies')
|
249
|
+
params
|
250
|
+
end
|
251
|
+
|
252
|
+
%w(form query cookies).each do |section|
|
253
|
+
define_method("#{section}_params") do
|
254
|
+
self.class.send("#{section}_params", request)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
P_FORM = 'form'.freeze
|
259
|
+
P_QUERY = 'query'.freeze
|
260
|
+
P_COOKIE = 'cookies'.freeze
|
261
|
+
P_GRAPE = 'grape_params'.freeze
|
262
|
+
P_RACK_ROUTING = 'rack_routing'.freeze
|
263
|
+
|
264
|
+
def self.form_params(request)
|
265
|
+
return nil unless request
|
266
|
+
begin
|
267
|
+
request.POST
|
268
|
+
rescue => e
|
269
|
+
Sqreen.log.debug("POST Parameters are invalid #{e.inspect}")
|
270
|
+
nil
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def self.cookies_params(request)
|
275
|
+
return nil unless request
|
276
|
+
begin
|
277
|
+
request.cookies
|
278
|
+
rescue => e
|
279
|
+
Sqreen.log.debug("cookies are invalid #{e.inspect}")
|
280
|
+
nil
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def self.query_params(request)
|
285
|
+
return nil unless request
|
286
|
+
begin
|
287
|
+
request.GET
|
288
|
+
rescue => e
|
289
|
+
Sqreen.log.debug("GET Parameters are invalid #{e.inspect}")
|
290
|
+
nil
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.parameters_from_request(request)
|
295
|
+
return {} unless request
|
296
|
+
|
297
|
+
r = {
|
298
|
+
P_FORM => form_params(request),
|
299
|
+
P_QUERY => query_params(request),
|
300
|
+
P_COOKIE => cookies_params(request),
|
301
|
+
}
|
302
|
+
# Add grape parameters if seen
|
303
|
+
p = request.env['grape.request.params']
|
304
|
+
r[P_GRAPE] = p if p
|
305
|
+
p = request.env['rack.routing_args']
|
306
|
+
if p
|
307
|
+
r[P_RACK_ROUTING] = p.dup
|
308
|
+
r[P_RACK_ROUTING].delete :route_info
|
309
|
+
r[P_RACK_ROUTING].delete :version
|
310
|
+
end
|
311
|
+
r
|
312
|
+
end
|
313
|
+
|
314
|
+
# Expose current working directory
|
315
|
+
def cwd
|
316
|
+
Dir.getwd
|
317
|
+
end
|
318
|
+
|
319
|
+
WHITELIST_KEY = 'sqreen.whitelisted_request'.freeze
|
320
|
+
|
321
|
+
# Return the current item that whitelist this request
|
322
|
+
# returns nil if request is not whitelisted
|
323
|
+
def whitelisted_match
|
324
|
+
return nil unless request
|
325
|
+
return request.env[WHITELIST_KEY] if request.env.key?(WHITELIST_KEY)
|
326
|
+
request.env[WHITELIST_KEY] = whitelisted_ip || whitelisted_path
|
327
|
+
end
|
328
|
+
|
329
|
+
# Returns the current path that whitelist the request
|
330
|
+
def whitelisted_path
|
331
|
+
path = request_path
|
332
|
+
return nil unless path
|
333
|
+
find_whitelisted_path(path)
|
334
|
+
end
|
335
|
+
|
336
|
+
# Returns the current path that whitelist the request
|
337
|
+
def whitelisted_ip
|
338
|
+
ip = client_ip
|
339
|
+
return nil unless ip
|
340
|
+
find_whitelisted_ip(ip)
|
341
|
+
rescue
|
342
|
+
nil
|
343
|
+
end
|
344
|
+
|
345
|
+
def remote_addr
|
346
|
+
return nil unless request
|
347
|
+
request.env['REMOTE_ADDR']
|
348
|
+
end
|
349
|
+
|
350
|
+
protected
|
351
|
+
|
352
|
+
# Is this a whitelisted path?
|
353
|
+
# return the path witelisted prefix that match path
|
354
|
+
def find_whitelisted_path(rpath)
|
355
|
+
(Sqreen.whitelisted_paths || []).find do |path|
|
356
|
+
rpath.start_with?(path)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
# Is this a whitelisted ip?
|
361
|
+
# return the ip witelisted range that match ip
|
362
|
+
def find_whitelisted_ip(rip)
|
363
|
+
ret = (Sqreen.whitelisted_ips || {}).find do |_, ip|
|
364
|
+
ip.include?(rip)
|
365
|
+
end
|
366
|
+
return nil unless ret
|
367
|
+
ret.first
|
368
|
+
end
|
369
|
+
|
370
|
+
def hook_rack_request(klass)
|
371
|
+
@calling_pid = Process.pid
|
372
|
+
klass.class_eval do
|
373
|
+
define_method(:call_with_sqreen) do |*args, &block|
|
374
|
+
rv = call_without_sqreen(*args, &block)
|
375
|
+
if Sqreen.framework.instance_variable_get('@calling_pid') != Process.pid
|
376
|
+
Sqreen.framework.instance_variable_set('@calling_pid', Process.pid)
|
377
|
+
yield Sqreen.framework
|
378
|
+
end
|
379
|
+
rv
|
380
|
+
end
|
381
|
+
alias_method :call_without_sqreen, :call
|
382
|
+
alias_method :call, :call_with_sqreen
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def hook_rack_builder
|
387
|
+
Rack::Builder.class_eval do
|
388
|
+
define_method(:to_app_with_sqreen) do |*args, &block|
|
389
|
+
Sqreen.framework.to_app_done(Process.pid)
|
390
|
+
to_app_without_sqreen(*args, &block)
|
391
|
+
end
|
392
|
+
alias_method :to_app_without_sqreen, :to_app
|
393
|
+
alias_method :to_app, :to_app_with_sqreen
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# FIXME: Extract to another object (utils?)
|
398
|
+
# FIXME: protect against cycles ?
|
399
|
+
def each_value_for_hash(params, &block)
|
400
|
+
case params
|
401
|
+
when Hash then params.each { |_k, v| each_value_for_hash(v, &block) }
|
402
|
+
when Array then params.each { |v| each_value_for_hash(v, &block) }
|
403
|
+
else
|
404
|
+
yield params
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def each_key_value_for_hash(params, &block)
|
409
|
+
case params
|
410
|
+
when Hash then params.each do |k, v|
|
411
|
+
yield k
|
412
|
+
each_key_value_for_hash(v, &block)
|
413
|
+
end
|
414
|
+
when Array then params.each { |v| each_key_value_for_hash(v, &block) }
|
415
|
+
else
|
416
|
+
yield params
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def ensure_rack_loaded
|
421
|
+
@cannot_load_rack ||= false
|
422
|
+
return false if @cannot_load_rack
|
423
|
+
require 'rack' unless defined?(Rack)
|
424
|
+
true
|
425
|
+
rescue LoadError => e
|
426
|
+
# FIXME: find a nice way to test this branch
|
427
|
+
Sqreen::RemoteException.record(e)
|
428
|
+
@cannot_load_rack = true
|
429
|
+
false
|
430
|
+
end
|
431
|
+
|
432
|
+
private
|
433
|
+
|
434
|
+
def split_ip_addresses(ip_addresses)
|
435
|
+
ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
|
436
|
+
end
|
437
|
+
|
438
|
+
def valid_ip?(ip)
|
439
|
+
IPAddr.new(ip)
|
440
|
+
true
|
441
|
+
rescue
|
442
|
+
false
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|