Class: SCNR::Introspector
- Inherits:
-
Object
- Object
- SCNR::Introspector
show all
- Includes:
- Rack::Utils
- Defined in:
- lib/scnr/introspector.rb,
lib/scnr/introspector/error.rb,
lib/scnr/introspector/scope.rb,
lib/scnr/introspector/version.rb,
lib/scnr/introspector/coverage.rb,
lib/scnr/introspector/data_flow.rb,
lib/scnr/introspector/data_flow/sink.rb,
lib/scnr/introspector/execution_flow.rb,
lib/scnr/introspector/data_flow/scope.rb,
lib/scnr/introspector/coverage/resource.rb,
lib/scnr/introspector/execution_flow/point.rb,
lib/scnr/introspector/execution_flow/scope.rb,
lib/scnr/introspector/coverage/resource/line.rb
Defined Under Namespace
Modules: Overloads
Classes: Coverage, DataFlow, Error, ExecutionFlow, Scope
Constant Summary
collapse
- OVERLOAD =
[
[:erb, :Templates],
[:test, [:SCNR, :Introspector, :Test]]
]
- VERSION =
IO.read( File.dirname( __FILE__ ) + '/version' ).strip
Class Method Summary
collapse
Instance Method Summary
collapse
Constructor Details
#initialize(app, options = {}) ⇒ Introspector
Returns a new instance of Introspector.
174
175
176
177
178
179
180
181
182
183
184
|
# File 'lib/scnr/introspector.rb', line 174
def initialize( app, options = {} )
@app = app
@options = options
puts "[INTROSPECTOR] Codename SCNR Introspector Initialized."
overload_application
overload_rails if rails?
@mutex = Mutex.new
end
|
Class Method Details
.data_flows ⇒ Object
68
69
70
|
# File 'lib/scnr/introspector.rb', line 68
def data_flows
Thread.current[:data_flows] ||= {}
end
|
.filter_caller(a) ⇒ Object
88
89
90
91
92
93
|
# File 'lib/scnr/introspector.rb', line 88
def filter_caller( a )
dir = File.dirname( __FILE__ )
a.reject do |c|
c.start_with?( dir ) || c.include?( 'trace_point' )
end
end
|
.find_and_log_taint(object, method, method_source_location, args) ⇒ Object
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
# File 'lib/scnr/introspector.rb', line 95
def find_and_log_taint( object, method, method_source_location, args )
taint = self.taint_seed
return if !taint
tainted = find_taint_in_arguments( taint, args )
return if !tainted
sink = DataFlow::Sink.new(
object: object.to_s,
method_name: method.to_s,
arguments: args,
tainted_argument_index: tainted[0],
tainted_value: tainted[1].to_s,
backtrace: filter_caller( Kernel.caller[1..-1] ),
method_source_location: method_source_location
)
log_sinks( taint, sink )
end
|
.find_taint_in_arguments(taint, args) ⇒ Object
114
115
116
117
118
119
120
121
122
123
|
# File 'lib/scnr/introspector.rb', line 114
def find_taint_in_arguments( taint, args )
args.each.with_index do |arg, i|
value = find_taint_recursively( taint, arg, i )
next if !value
return [i, value]
end
nil
end
|
.find_taint_recursively(taint, object, depth) ⇒ Object
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
# File 'lib/scnr/introspector.rb', line 125
def find_taint_recursively( taint, object, depth )
case object
when Hash
object.each do |k, v|
t = find_taint_recursively( taint, v, depth )
return t if t
end
when Array
object.each do |v|
t = find_taint_recursively( taint, v, depth )
return t if t
end
when String
return object if object.include? taint
else
nil
end
nil
end
|
.flush_sinks(taint) ⇒ Object
82
83
84
85
86
|
# File 'lib/scnr/introspector.rb', line 82
def flush_sinks( taint )
synchronize do
self.data_flows.delete taint
end
end
|
.log_sinks(taint, sink) ⇒ Object
76
77
78
79
80
|
# File 'lib/scnr/introspector.rb', line 76
def log_sinks( taint, sink )
synchronize do
(self.data_flows[taint] ||= DataFlow.new).sinks << sink
end
end
|
.overload(object, m) ⇒ Object
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
# File 'lib/scnr/introspector.rb', line 30
def overload( object, m )
method_source_location = object.allocate.method(m).source_location
rnd = SecureRandom.hex(10)
msg = "[INTROSPECTOR] Injecting trace code for #{object}##{m}"
if method_source_location
msg << " in #{method_source_location.join(':')}"
end
puts msg
ov = <<EORUBY
module Overloads
module #{object.to_s.split( '::' ).join}#{rnd}Overload
def #{m}( *args )
SCNR::Introspector.find_and_log_taint( #{object}, :#{m}, #{method_source_location.inspect}, args )
super *args
end
end
end
#{object}.prepend Overloads::#{object.to_s.split( '::' ).join}#{rnd}Overload
EORUBY
eval ov
rescue => e
end
|
.synchronize(&block) ⇒ Object
72
73
74
|
# File 'lib/scnr/introspector.rb', line 72
def synchronize( &block )
@mutex.synchronize( &block )
end
|
.taint_seed ⇒ Object
64
65
66
|
# File 'lib/scnr/introspector.rb', line 64
def taint_seed
Thread.current[:taint]
end
|
.taint_seed=(t) ⇒ Object
60
61
62
|
# File 'lib/scnr/introspector.rb', line 60
def taint_seed=( t )
Thread.current[:taint] = t
end
|
Instance Method Details
#call(env) ⇒ Object
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
|
# File 'lib/scnr/introspector.rb', line 218
def call( env )
info = Set.new
info << :platforms
if env.delete( 'HTTP_X_SCNR_INTROSPECTOR_TRACE' )
info << :execution_flow
end
if env['HTTP_X_SCNR_INTROSPECTOR_TAINT']
info << :data_flow
end
inject( env, info )
rescue => e
pp e
pp e.backtrace
end
|
#db ⇒ Object
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
|
# File 'lib/scnr/introspector.rb', line 327
def db
return if !rails?
case ActiveRecord::Base.connection.adapter_name
when 'PostgreSQL'
:pgsql
when 'MySQL'
:mysql
when 'SQLite3'
:sqlite
else
nil
end
end
|
#inject(env, info = []) ⇒ Object
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
|
# File 'lib/scnr/introspector.rb', line 237
def inject( env, info = [] )
self.class.taint_seed = env.delete( 'HTTP_X_SCNR_INTROSPECTOR_TAINT' )
if self.class.taint_seed
self.class.taint_seed = Base64.decode64( self.class.taint_seed )
self.class.taint_seed = nil if self.class.taint_seed.empty?
end
seed = env.delete( 'HTTP_X_SCNR_ENGINE_SCAN_SEED' )
data = {}
response = nil
if info.include? :execution_flow
execution_flow = nil
synchronize do
execution_flow = ExecutionFlow.new @options do
response = @app.call( env )
end
end
data['execution_flow'] = execution_flow.to_rpc_data
else
response = @app.call( env )
end
if info.include? :platforms
data['platforms'] = self.platforms
end
if info.include?( :coverage ) && Coverage.enabled?
data['coverage'] = Coverage.new( @options ).retrieve_results
end
if info.include?( :data_flow ) && self.class.taint_seed
data['data_flow'] = self.class.flush_sinks( self.class.taint_seed )&.to_rpc_data
end
code = response.shift
= response.shift
body = response.shift
if ['Content-Type'] && ['Content-Type'].include?( 'html' )
body = body.respond_to?( :body ) ? body.body : body
body = [body].flatten
body << "<!-- #{seed}\n#{JSON.dump( data )}\n#{seed} -->"
['Content-Length'] = body.map(&:bytesize).inject(:+)
end
[code, , [body].flatten ]
rescue => e
pp e
pp e.backtrace
end
|
#os ⇒ Symbol
Returns OS platform type to use for Options#platforms.
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
|
# File 'lib/scnr/introspector.rb', line 304
def os
@os ||= (
host_os = RbConfig::CONFIG['host_os']
case host_os
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
:windows
when /linux/
:linux
when /darwin|mac os|bsd/
:bsd
when /solaris/
:solaris
else
nil
end
)
end
|
#overload_application ⇒ Object
186
187
188
|
# File 'lib/scnr/introspector.rb', line 186
def overload_application
overload_class @app.class
end
|
#overload_class(klass) ⇒ Object
206
207
208
209
210
211
212
|
# File 'lib/scnr/introspector.rb', line 206
def overload_class( klass )
k = klass.allocate
k.methods.each do |m|
next if k.method( m ).parameters.empty?
self.class.overload( klass, m )
end
end
|
#overload_rails ⇒ Object
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
# File 'lib/scnr/introspector.rb', line 190
def overload_rails
Rails.application.eager_load!
klasses = [
ActionController::Base,
ActiveRecord::Base
]
descendants = klasses.map do |k|
ObjectSpace.each_object( Class ).select { |klass| klass < k }
end.flatten.reject { |k| k.to_s.start_with? '#' }
descendants.each do |klass|
overload_class klass
end
end
|
293
294
295
296
297
298
299
|
# File 'lib/scnr/introspector.rb', line 293
def platforms
platforms = [:ruby, os, db]
if rails?
platforms << :rails
end
platforms.compact
end
|
#rails? ⇒ Boolean
346
347
348
|
# File 'lib/scnr/introspector.rb', line 346
def rails?
!!defined?( Rails )
end
|
#synchronize(&block) ⇒ Object
214
215
216
|
# File 'lib/scnr/introspector.rb', line 214
def synchronize( &block )
@mutex.synchronize( &block )
end
|