Class: Arachni::Framework

Inherits:
Object
  • Object
show all
Includes:
UI::Output
Defined in:
lib/framework.rb

Overview

Arachni::Framework class

The Framework class ties together all the components.
It should be wrapped by a UI class.

It’s the brains of the operation, it bosses the rest of the classes around.
It runs the audit, loads modules and reports and runs them according to the supplied options.

@author: Anastasios “Zapotek” Laskos

                                     <tasos.laskos@gmail.com>
                                     <zapotek@segfault.gr>

@version: 0.1-pre

Constant Summary

VERSION =
'0.1-pre'
REVISION =
'$Rev: 375 $'
REPORT_EXT =
'.afr'

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Methods included from UI::Output

#debug!, #debug?, #only_positives!, #only_positives?, #print_debug, #print_debug_backtrace, #print_debug_pp, #print_error, #print_info, #print_line, #print_ok, #print_status, #print_verbose, #verbose!, #verbose?

Constructor Details

- (Framework) initialize(opts)

Initializes all the system components.

Parameters:



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/framework.rb', line 80

def initialize( opts )
    
    Encoding.default_external = "BINARY"
    Encoding.default_internal = "BINARY"
    
    @opts = opts
        
    @modreg = Arachni::Module::Registry.new( @opts.dir['modules'] )
    @repreg = Arachni::Report::Registry.new( @opts.dir['reports'] )
    
    parse_opts( )
    prepare_user_agent( )
    
    @spider   = Arachni::Spider.new( @opts )
    @analyzer = Arachni::Analyzer.new( @opts )
    
    # trap Ctrl+C interrupts
    $_interrupted = false
    trap( 'INT' ) { $_interrupted = true }
    
    # deep copy the redundancy rules to preserve their counter
    # for the reports
    @orig_redundant = deep_clone( @opts.redundant )
end

Instance Attribute Details

- (Hash) opts (readonly)

Instance options

Returns:

  • (Hash)


73
74
75
# File 'lib/framework.rb', line 73

def opts
  @opts
end

Instance Method Details

- (Page) analyze(url, html, headers)

Analyzes the html code for elements and returns a page object

Parameters:

  • (String) url
  • (String) html
  • (Array) headers

Returns:



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/framework.rb', line 195

def analyze( url, html, headers )
    
    elements = Hash.new
    
    # analyze each page the crawler returns and save its elements
    elements = @analyzer.run( url, html, headers ).clone

    elements['cookies'] =
        merge_with_cookiejar( elements['cookies'] )

    # get the variables of the url query as an array of hashes
    query_vars = @analyzer.get_link_vars( url )
    
    # if url query has variables in it append them to the page elements
    if( query_vars.size > 0 )
        elements['links'] << {
            'href'  => url,
            'vars'  => query_vars
        }
    end
    
    if( elements['headers'] )
        request_headers = elements['headers'].clone
#            elements.delete( 'headers' )
    end
    
    return Page.new( {
        :url         => url,
        :query_vars  => query_vars,
        :html        => html,
        :headers     => headers,
        :request_headers => request_headers,
        :elements    => elements,
        :cookiejar   => @opts.cookies
    } )

end

- (Object) audit

Audits the site.

Runs the spider, analyzes each page and runs the loaded modules.



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/framework.rb', line 158

def audit
    pages = []
        
    # initiates the crawl
    @sitemap = @spider.run {
        | url, html, headers |

        page = analyze( url, html, headers )
        
        # if the user wants to run the modules against each page
        # the crawler finds do it now...
        if( !@opts.mods_run_last )
            run_mods( page )
        else
            # ..else handle any interrupts that may occur... 
            handle_interrupt( )
            # ... and save the page data for later.
            pages << page
        end

    }

    # if the user opted to run the modules after the crawl/analysis
    # do it now.
    pages.each { |page| run_mods( page ) } if( @opts.mods_run_last )
        
end

- (AuditStore) audit_store_get

Returns the results of the audit as an Arachni::AuditStore instance

Returns:

See Also:



240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/framework.rb', line 240

def audit_store_get
    
    # restore the original redundacy rules and their counters
    @opts.redundant = @orig_redundant

    return AuditStore.new( {
        :version  => VERSION,
        :revision => REVISION,
        :options  => @opts.to_h,
        :sitemap  => @sitemap.sort,
        :vulns    => deep_clone( Arachni::Module::Registry.get_results( ) )
     } )
end

- (Object) audit_store_load(file)

Loads an Arachni::AuditStore object

Parameters:

  • (String) file

    location of the dump file

See Also:



396
397
398
# File 'lib/framework.rb', line 396

def audit_store_load( file )
    return AuditStore.load( file )
end

- (Object) audit_store_save(file)

Saves an AuditStore instance in file

Parameters:

  • (String) file


377
378
379
380
381
382
383
384
385
386
387
# File 'lib/framework.rb', line 377

def audit_store_save( file )
    
    file += REPORT_EXT
    
    print_line( )
    print_status( 'Dumping audit results in \'' + file  + '\'.' )
    
    audit_store_get( ).save( file )
    
    print_status( 'Done!' )
end

- (Array<Class>) ls_loaded_mods

Returns an array of all loaded modules

Returns:

  • (Array<Class>)


259
260
261
# File 'lib/framework.rb', line 259

def ls_loaded_mods
    @modreg.ls_loaded( )
end

- (Array<Class>) ls_loaded_reps

Returns an array of all loaded reports

Returns:

  • (Array<Class>)


268
269
270
# File 'lib/framework.rb', line 268

def ls_loaded_reps
    @repreg.ls_loaded( )
end

- (Array<Hash>) lsmod

Returns an array of hashes with information about all available modules

Returns:

  • (Array<Hash>)


406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/framework.rb', line 406

def lsmod
    
    i = 0
    mod_info = []
    
    @modreg.ls_available().each_pair {
        |mod_name, path|

        @modreg.mod_load( mod_name )

        info = @modreg.mod_info( i )

        info["mod_name"]    = mod_name
        info["Name"]        = info["Name"].strip
        info["Description"] = info["Description"].strip
        
        if( !info["Dependencies"] )
            info["Dependencies"] = []
        end
        
        info["Author"]    = info["Author"].strip
        info["Version"]   = info["Version"].strip 
        info["Path"]      = path['path'].strip
        
        i+=1
        
        mod_info << info
    }
    
    # clean the registry inloading all modules
    Arachni::Module::Registry.clean( )
    
    return mod_info

end

- (Array<Hash>) lsrep

Returns an array of hashes with information about all available reports

Returns:

  • (Array<Hash>)


448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/framework.rb', line 448

def lsrep
    
    i = 0
    rep_info = []
    
    @repreg.ls_available( ).each_pair {
        |rep_name, path|

        @repreg.rep_load( rep_name )

        info = @repreg.info( i )

        info["rep_name"]    = rep_name
        info["Path"]        = path['path'].strip
        
        i+=1
        
        rep_info << info
    }
    return rep_info
end

- (Object) mod_load(mods = '*')

Loads modules

Parameters:

  • (Array) mods (defaults to: '*')

    Array of modules to load



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/framework.rb', line 277

def mod_load( mods = '*' )

    if( mods[0] != "*" )

        avail_mods  = @modreg.ls_available(  )
        
        mods.each {
            |mod_name|
            if( !avail_mods[mod_name] )
                  raise( Arachni::Exceptions::ModNotFound,
                      "Error: Module #{mod_name} wasn't found." )
            end
        }

        
        sorted_mods = []
        
        # discovery modules should be loaded before audit ones
        # and ls_available() ownors that
        avail_mods.map {
            |mod|
            sorted_mods << mod[0] if mods.include?( mod[0] )
        }
    else
        sorted_mods = ["*"]
    end
    
    #
    # Check the validity of user provided module names
    #
    sorted_mods.each {
        |mod_name|
        
        # if the mod name is '*' load all modules
        # and replace it with the actual module names
        if( mod_name == '*' )
            
            @opts.mods = []
            
            @modreg.ls_available(  ).keys.each {
                |mod|
                @opts.mods << mod
                @modreg.mod_load( mod )
            }
            
            # and we're done..
            break
        end
        
        # ...and load the module passing all exceptions to the UI.
        begin
            @modreg.mod_load( mod_name )
        rescue Exception => e
            raise e
        end
    }
end

- (Object) rep_convert(file)

Converts a saved AuditStore to a report.

It basically loads a serialized AuditStore,
passes it to a the loaded Reports and runs the reports.

Parameters:

  • (String) file

    location of the saved AuditStore



367
368
369
370
# File 'lib/framework.rb', line 367

def rep_convert( file )
    run_reps( audit_store_load( file ) )
    exit 0
end

- (Object) rep_load(reports = ['stdout'])

Loads reports

Parameters:

  • (Array) reports (defaults to: ['stdout'])

    Array of reports to load



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/framework.rb', line 340

def rep_load( reports = ['stdout'] )

    reports.each {
        |report|
        
        if( !@repreg.ls_available(  )[report] )
            raise( Arachni::Exceptions::ReportNotFound,
                "Error: Report #{report} wasn't found." )
        end

        begin
            # load the report
            @repreg.rep_load( report )
        rescue Exception => e
            raise e
        end
    }
end

- (String) report_ext

Returns the extension of the report files

Returns:

  • (String)


493
494
495
# File 'lib/framework.rb', line 493

def report_ext
    REPORT_EXT
end

- (String) revision

Returns the SVN revision of the framework

Returns:

  • (String)


484
485
486
# File 'lib/framework.rb', line 484

def revision
    REVISION
end

- (Object) run

Runs the system

It parses the instanse options and runs the audit



110
111
112
113
114
115
116
117
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
146
147
148
149
150
151
# File 'lib/framework.rb', line 110

def run
    
    # pass all exceptions to the UI
    begin
        validate_opts
    rescue
        raise
    end
    
    @opts.start_datetime = Time.now
        
    # start the audit
    audit( )
    
    @opts.finish_datetime = Time.now
    @opts.delta_time = @opts.finish_datetime - @opts.start_datetime
    
    # run reports
    if( @opts.reports )
        begin
            run_reps( audit_store_get( ).clone )
        rescue Exception => e
            print_error( e.to_s )
            print_debug_backtrace( e )
            print_line
            exit 0
        end
    end
    
    # save the AuditStore in a file 
    if( @opts.repsave && !@opts.repload )
        begin
            audit_store_save( @opts.repsave )
        rescue Exception => e
            print_error( e.to_s )
            print_debug_backtrace( e )
            print_line
            exit 0
        end
    end
    
end

- (String) version

Returns the version of the framework

Returns:

  • (String)


475
476
477
# File 'lib/framework.rb', line 475

def version
    VERSION
end