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.
167
168
169
170
171
172
173
174
175
|
# File 'lib/scnr/introspector.rb', line 167
def initialize( app, options = {} )
@app = app
@options = options
overload_application
overload_rails if rails?
@mutex = Mutex.new
end
|
Class Method Details
.data_flows ⇒ Object
61
62
63
|
# File 'lib/scnr/introspector.rb', line 61
def data_flows
Thread.current[:data_flows] ||= {}
end
|
.filter_caller(a) ⇒ Object
81
82
83
84
85
86
|
# File 'lib/scnr/introspector.rb', line 81
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
# File 'lib/scnr/introspector.rb', line 88
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
107
108
109
110
111
112
113
114
115
116
|
# File 'lib/scnr/introspector.rb', line 107
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
# File 'lib/scnr/introspector.rb', line 118
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
75
76
77
78
79
|
# File 'lib/scnr/introspector.rb', line 75
def flush_sinks( taint )
synchronize do
self.data_flows.delete taint
end
end
|
.log_sinks(taint, sink) ⇒ Object
69
70
71
72
73
|
# File 'lib/scnr/introspector.rb', line 69
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
|
# File 'lib/scnr/introspector.rb', line 30
def overload( object, m )
method_source_location = object.allocate.method(m).source_location
rnd = SecureRandom.hex(10)
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
65
66
67
|
# File 'lib/scnr/introspector.rb', line 65
def synchronize( &block )
@mutex.synchronize( &block )
end
|
.taint_seed ⇒ Object
57
58
59
|
# File 'lib/scnr/introspector.rb', line 57
def taint_seed
Thread.current[:taint]
end
|
.taint_seed=(t) ⇒ Object
53
54
55
|
# File 'lib/scnr/introspector.rb', line 53
def taint_seed=( t )
Thread.current[:taint] = t
end
|
Instance Method Details
#call(env) ⇒ Object
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
|
# File 'lib/scnr/introspector.rb', line 209
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
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
|
# File 'lib/scnr/introspector.rb', line 318
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
228
229
230
231
232
233
234
235
236
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
|
# File 'lib/scnr/introspector.rb', line 228
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.
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
|
# File 'lib/scnr/introspector.rb', line 295
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
177
178
179
|
# File 'lib/scnr/introspector.rb', line 177
def overload_application
overload_class @app.class
end
|
#overload_class(klass) ⇒ Object
197
198
199
200
201
202
203
|
# File 'lib/scnr/introspector.rb', line 197
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
# File 'lib/scnr/introspector.rb', line 181
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
|
284
285
286
287
288
289
290
|
# File 'lib/scnr/introspector.rb', line 284
def platforms
platforms = [:ruby, os, db]
if rails?
platforms << :rails
end
platforms.compact
end
|
#rails? ⇒ Boolean
337
338
339
|
# File 'lib/scnr/introspector.rb', line 337
def rails?
!!defined?( Rails )
end
|
#synchronize(&block) ⇒ Object
205
206
207
|
# File 'lib/scnr/introspector.rb', line 205
def synchronize( &block )
@mutex.synchronize( &block )
end
|