sqreen-alt 1.10.0
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 +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
|