Module: Threatstack::Instrumentation
- Includes:
- Constants
- Defined in:
- lib/instrumentation/common.rb,
lib/instrumentation/instrumenter.rb,
lib/instrumentation/frameworks/rails.rb,
lib/instrumentation/frameworks/kernel.rb,
lib/instrumentation/frameworks/random.rb
Defined Under Namespace
Modules: Frameworks
Classes: Instrumenter
Constant Summary
collapse
- @@logger =
Threatstack::Utils::TSLogger.create 'CommonInstrumentation'
- @@submitter =
Threatstack::Jobs::EventSubmitter.instance
Constants included
from Constants
Constants::AGENT_ID, Constants::AGENT_INSTANCE_ID, Constants::AGENT_NAME, Constants::AGENT_VERSION, Constants::APPSEC_BASE_URL, Constants::APPSEC_EVENTS_URL, Constants::ATTACK, Constants::AWS_METADATA_URL, Constants::BLOCK_PATH_TRAVERSAL, Constants::BLOCK_SQLI, Constants::BLOCK_XSS, Constants::CGI_VARIABLES, Constants::DEPENDENCIES, Constants::DETECTED_NOT_BLOCKED, Constants::DETECT_ATTACKS_ONLY, Constants::DETECT_PATH_TRAVERSAL, Constants::DISABLED, Constants::DROP_FIELDS, Constants::ENVIRONMENT, Constants::EVENTS_PER_REQ, Constants::FILTER_BY_PATH, Constants::INSTRUMENTATION, Constants::IPV4, Constants::IPV6, Constants::JOB_INTERVAL, Constants::LOG_COLORS, Constants::LOG_LEVEL, Constants::MANUAL_INIT, Constants::MAX_QUEUED_EVENTS, Constants::PATH_TRAVERSAL, Constants::REDACTED, Constants::REQUEST_BLOCKED, Constants::ROOT_DIR, Constants::RUBY, Constants::SQLI, Constants::TRUTHY, Constants::XSS
Class Method Summary
collapse
-
.check_parameters(params, location, request, headers, backtrace, include_payload = true) ⇒ Object
-
.check_pathtraversal_payload(param, name = nil) ⇒ Object
-
.check_sqli_payload(param, name = nil) ⇒ Object
-
.check_xss_payload(param, name = nil) ⇒ Object
-
.const_exist?(name) ⇒ Boolean
-
.create_attack_event(payload, type, location, request, headers, backtrace) ⇒ Object
-
.create_instrumentation_event(module_name, method_name, file_path, line_num, arguments) ⇒ Object
-
.drop_sensitive_fields(obj) ⇒ Object
-
.extract_instrumentation_params(params) ⇒ Object
-
.flatten(obj) ⇒ Object
-
.is_filtered_event(file_path) ⇒ Object
-
.resolve_const(name) ⇒ Object
Methods included from Constants
app_root_dir, env, is_truthy
Class Method Details
.check_parameters(params, location, request, headers, backtrace, include_payload = true) ⇒ Object
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
# File 'lib/instrumentation/common.rb', line 182
def self.check_parameters(params, location, request, , backtrace, include_payload = true)
if params.nil? || !params.is_a?(Hash)
@@logger.debug "Skipping param check for: #{params}"
return nil
end
flattened = flatten params
sqli_found, xss_found, pathtraversal_found = false
flattened.each do |key, val|
sqli_found = check_sqli_payload(val, key) unless sqli_found
xss_found = check_xss_payload(val, key) unless xss_found
pathtraversal_found = check_pathtraversal_payload(val, key) unless pathtraversal_found
end
payload = include_payload ? params : nil
create_attack_event(payload, SQLI, location, request, , backtrace) if sqli_found
create_attack_event(payload, XSS, location, request, , backtrace) if xss_found
create_attack_event(payload, PATH_TRAVERSAL, location, request, , backtrace) if pathtraversal_found
{ :sqli => sqli_found, :xss => xss_found, :path_traversal => pathtraversal_found }
end
|
.check_pathtraversal_payload(param, name = nil) ⇒ Object
168
169
170
171
172
173
174
175
176
177
178
179
180
|
# File 'lib/instrumentation/common.rb', line 168
def self.check_pathtraversal_payload(param, name = nil)
return false unless DETECT_PATH_TRAVERSAL
if param.nil? || !param.kind_of?(String)
@@logger.debug "Path Traversal Check skipped for: #{name}" unless name.nil?
return false
end
match = (Libinjection.libinjection_pathtraversal(param, param.length) === 1 ? true : false)
@@logger.send(match ? :warn : :debug, "Path Traversal Check #{match ? 'POSITIVE' : 'negative'} for: #{name}=#{param}") unless name.nil?
match
end
|
.check_sqli_payload(param, name = nil) ⇒ Object
147
148
149
150
151
152
153
154
155
|
# File 'lib/instrumentation/common.rb', line 147
def self.check_sqli_payload(param, name = nil)
if param.nil? || !param.kind_of?(String)
@@logger.debug "SQLI Check skipped for: #{name}" unless name.nil?
return false
end
match = (Libinjection.libinjection_sqli(param, param.length, '') === 1 ? true : false)
@@logger.send(match ? :warn : :debug, "SQLI Check #{match ? 'POSITIVE' : 'negative'} for: #{name}=#{param}") unless name.nil?
match
end
|
.check_xss_payload(param, name = nil) ⇒ Object
157
158
159
160
161
162
163
164
165
166
|
# File 'lib/instrumentation/common.rb', line 157
def self.check_xss_payload(param, name = nil)
if param.nil? || !param.kind_of?(String)
@@logger.debug "XSS Check skipped for: #{name}" unless name.nil?
return false
end
match = (Libinjection.libinjection_xss(param, param.length) === 1 ? true : false)
@@logger.send(match ? :warn : :debug, "XSS Check #{match ? 'POSITIVE' : 'negative'} for: #{name}=#{param}") unless name.nil?
match
end
|
.const_exist?(name) ⇒ Boolean
68
69
70
71
72
|
# File 'lib/instrumentation/common.rb', line 68
def self.const_exist?(name)
resolve_const(name) && true
rescue NameError, ArgumentError
false
end
|
.create_attack_event(payload, type, location, request, headers, backtrace) ⇒ Object
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
# File 'lib/instrumentation/common.rb', line 43
def self.create_attack_event(payload, type, location, request, , backtrace)
is_blocked = (type == SQLI && BLOCK_SQLI) || (type == XSS && BLOCK_XSS) || (type == PATH_TRAVERSAL && BLOCK_PATH_TRAVERSAL)
data = {
:timestamp => Time.now.utc.strftime('%FT%T.%3NZ'),
:module_name => AGENT_NAME,
:request_ip => request.remote_ip,
:request_headers => ,
:request_url => request.path,
:request_method => request.request_method,
:attack_message => is_blocked ? REQUEST_BLOCKED : DETECTED_NOT_BLOCKED,
:attack_stack => backtrace,
:attack_details => {
:details => [{ :signature => nil, :value => drop_sensitive_fields(payload) }],
:in => location,
:type => type,
:isBlocked => is_blocked,
:action => 'process_action'
}
}
@@logger.debug "Creating attack event with data: #{data}"
@@submitter.queue_event Threatstack::Events::AttackEvent.new(drop_sensitive_fields(data))
end
|
.create_instrumentation_event(module_name, method_name, file_path, line_num, arguments) ⇒ Object
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
# File 'lib/instrumentation/common.rb', line 28
def self.create_instrumentation_event(module_name, method_name, file_path, line_num, arguments)
return if is_filtered_event(file_path)
data = {
:module_name => module_name,
:method_name => method_name,
:file_path => file_path,
:line_num => line_num,
:arguments => drop_sensitive_fields(arguments)
}
@@logger.debug "Creating instrumentation event with data: #{data}"
@@submitter.queue_event Threatstack::Events::InstrumentationEvent.new(drop_sensitive_fields(data))
end
|
.drop_sensitive_fields(obj) ⇒ Object
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
# File 'lib/instrumentation/common.rb', line 118
def self.drop_sensitive_fields(obj)
return obj if DROP_FIELDS.nil?
if obj.is_a?(Hash)
obj.each_with_object({}) do |(k, v), h|
if DROP_FIELDS[k] || DROP_FIELDS[k.to_s]
h[k] = REDACTED
next
end
if v.is_a?(Hash) || v.is_a?(Array)
h[k] = drop_sensitive_fields(v)
else
h[k] = v
end
end
elsif obj.is_a?(Array)
obj.each_with_object([]) do |v, arr|
if v.is_a?(Hash) || v.is_a?(Array)
arr.push drop_sensitive_fields(v)
else
arr.push v
end
end
else
obj
end
end
|
108
109
110
111
112
113
114
115
116
|
# File 'lib/instrumentation/common.rb', line 108
def self.(params)
module_name = params[:target_class].to_s.downcase
method_name = params[:method_name].downcase
called_by = params[:caller_loc] ? params[:caller_loc].first : nil
file_path = called_by ? called_by.absolute_path : nil
line_num = called_by ? called_by.lineno : nil
args = params[:args] ? params[:args] : []
return module_name, method_name, file_path, line_num, args
end
|
.flatten(obj) ⇒ Object
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
# File 'lib/instrumentation/common.rb', line 80
def self.flatten(obj)
if obj.is_a?(Hash)
obj.each_with_object({}) do |(k, v), h|
if v.is_a?(Hash) || v.is_a?(Array)
flatten(v).map do |h_k, h_v|
h["#{k}.#{h_k}".to_sym] = h_v
end
else
h[k] = v
end
end
elsif obj.is_a?(Array)
h = {}
obj.each_with_index do |v, index|
if v.is_a?(Hash) || v.is_a?(Array)
flatten(v).map do |h_k, h_v|
h["#{index}.#{h_k}".to_sym] = h_v
end
else
h[index.to_s] = v
end
end
h
else
obj
end
end
|
.is_filtered_event(file_path) ⇒ Object
17
18
19
20
21
22
23
24
25
26
|
# File 'lib/instrumentation/common.rb', line 17
def self.is_filtered_event(file_path)
return false if FILTER_BY_PATH.nil?
return false if file_path.nil?
filtered = FILTER_BY_PATH.any? do |path|
file_path.include? path
end
filtered
end
|
.resolve_const(name) ⇒ Object
74
75
76
77
78
|
# File 'lib/instrumentation/common.rb', line 74
def self.resolve_const(name)
raise ArgumentError if name.nil? || name.empty?
name.to_s.split('::').inject(Object) { |a, e| a.const_get(e) }
end
|