Class: Arachni::Platform::Manager

Inherits:
Object
  • Object
show all
Extended by:
UI::Output, Utilities
Includes:
UI::Output, Utilities, Enumerable
Defined in:
lib/arachni/platform/manager.rb

Overview

Represents a collection of platform lists.

It also holds a DB of all fingerprints per URI as a class variable and provides helper method for accessing and manipulating it.

Author:

  • Tasos “Zapotek” Laskos <tasos.laskos@arachni-scanner.com>

Constant Summary collapse

TYPES =
{
    os:         'Operating systems',
    db:         'Databases',
    servers:    'Web servers',
    languages:  'Programming languages',
    frameworks: 'Frameworks'
}
OS =
{
    # Generic *nix, flavor couldn't be identified.
    unix:    {
        linux:   {},

        # Generic BSD, flavor couldn't be identified.
        bsd:     {},
        aix:     {},
        solaris: {}
    },
    windows: {}
}
DB =
{
    sql: {
        mysql:      {},
        pgsql:      {},
        mssql:      {},
        oracle:     {},
        sqlite:     {},
        ingres:     {},
        emc:        {},
        db2:        {},
        interbase:  {},
        informix:   {},
        firebird:   {},
        maxdb:      {},
        sybase:     {},
        frontbase:  {},
        hsqldb:     {},
        access:     {},
    },
    nosql: {
        mongodb:    {}
    }
}
SERVERS =
[
    :apache,
    :nginx,
    :tomcat,
    :iis,
    :jetty,
    :gunicorn
]
LANGUAGES =
[
    :php,
    :java,
    :python,
    :ruby,
    :asp,
    :aspx,
    :perl
]
FRAMEWORKS =

WebApp frameworks.

[
    :rack,
    :rails,
    :cakephp,
    :symfony,
    :nette,
    :django,
    :aspx_mvc,
    :jsf,
    :cherrypy
]
PLATFORM_NAMES =
{
    # Operating systems
    unix:       'Generic Unix family',
    linux:      'Linux',
    bsd:        'Generic BSD family',
    aix:        'IBM AIX',
    solaris:    'Solaris',
    windows:    'MS Windows',

    # Databases
    sql:        'Generic SQL family',
    mysql:      'MySQL',
    pgsql:      'Postgresql',
    mssql:      'MSSQL',
    oracle:     'Oracle',
    sqlite:     'SQLite',
    emc:        'EMC',
    db2:        'DB2',
    interbase:  'InterBase',
    informix:   'Informix',
    firebird:   'Firebird',
    maxdb:      'SaP Max DB',
    sybase:     'Sybase',
    frontbase:  'Frontbase',
    ingres:     'IngresDB',
    hsqldb:     'HSQLDB',
    access:     'MS Access',
    nosql:      'Generic NoSQL family',
    mongodb:    'MongoDB',

    # Web servers
    apache:     'Apache',
    nginx:      'Nginx',
    tomcat:     'TomCat',
    iis:        'IIS',
    jetty:      'Jetty',
    gunicorn:   'Gunicorn',

    # Programming languages
    php:    'PHP',
    java:   'Java',
    python: 'Python',
    ruby:   'Ruby',
    asp:    'ASP',
    aspx:   'ASP.NET',
    perl:   'Perl',

    # Web frameworks
    rack:     'Rack',
    django:   'Django',
    cakephp:  'CakePHP',
    nette:    'Nette',
    symfony:  'Symfony',
    rails:    'Ruby on Rails',
    aspx_mvc: 'ASP.NET MVC',
    jsf:      'JavaServer Faces',
    cherrypy: 'CherryPy'
}
PLATFORM_CACHE_SIZE =
500

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utilities

available_port, available_port_mutex, bytes_to_kilobytes, bytes_to_megabytes, caller_name, caller_path, cookie_decode, cookie_encode, cookies_from_file, cookies_from_parser, cookies_from_response, exception_jail, exclude_path?, follow_protocol?, form_decode, form_encode, forms_from_parser, forms_from_response, full_and_absolute_url?, generate_token, get_path, hms_to_seconds, html_decode, html_encode, include_path?, links_from_parser, links_from_response, normalize_url, page_from_response, page_from_url, parse_set_cookie, path_in_domain?, path_too_deep?, port_available?, rand_port, random_seed, redundant_path?, regexp_array_match, remove_constants, request_parse_body, seconds_to_hms, skip_page?, skip_path?, skip_resource?, skip_response?, to_absolute, uri_decode, uri_encode, uri_parse, uri_parse_query, uri_parser, uri_rewrite

Methods included from UI::Output

debug?, debug_level_1?, debug_level_2?, debug_level_3?, debug_level_4?, debug_off, debug_on, disable_only_positives, included, mute, muted?, only_positives, only_positives?, print_bad, print_debug, print_debug_backtrace, print_debug_level_1, print_debug_level_2, print_debug_level_3, print_debug_level_4, print_error, print_error_backtrace, print_exception, print_info, print_line, print_ok, print_status, print_verbose, reroute_to_file, reroute_to_file?, reset_output_options, unmute, verbose?, verbose_on

Constructor Details

#initialize(platforms = []) ⇒ Manager

Returns a new instance of Manager.

Parameters:

  • platforms (Array<String, Symbol>) (defaults to: [])

    Platforms with which to initialize the lists.



367
368
369
370
371
372
373
374
375
# File 'lib/arachni/platform/manager.rb', line 367

def initialize( platforms = [] )
    @platforms = {}
    TYPES.keys.each do |type|
        @platforms[type] =
            List.new( self.class.const_get( type.to_s.upcase.to_sym ) )
    end

    update platforms
end

Class Method Details

.[](uri) ⇒ Manager

Returns Platform for the given `uri`.

Parameters:

Returns:

  • (Manager)

    Platform for the given `uri`



284
285
286
287
288
289
290
291
292
293
294
# File 'lib/arachni/platform/manager.rb', line 284

def self.[]( uri )
    # If fingerprinting is disabled there's no point in filling the cache
    # with the same object over and over, create an identical one for all
    # URLs and return that always.
    if !Options.fingerprint?
        return @default ||= new_from_options
    end

    return new_from_options if !(key = make_key( uri ))
    synchronize { @platforms.fetch(key) { new_from_options } }
end

.[]=(uri, platforms) ⇒ Manager

Sets platform manager for the given `uri`.

Parameters:

Returns:

Raises:



305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/arachni/platform/manager.rb', line 305

def self.[]=( uri, platforms )
    # For some reason we failed to make a key, try to salvage the situation.
    if !(key = make_key( uri ))
        return new_from_options( platforms )
    end

    synchronize do
        @platforms[key] =
            platforms.is_a?( self ) ?
                platforms :
                new_from_options( platforms )
    end
end

.any?Boolean

Returns `true` if there are platforms fingerprints, `false` otherwise.

Returns:

  • (Boolean)

    `true` if there are platforms fingerprints, `false` otherwise.



352
353
354
# File 'lib/arachni/platform/manager.rb', line 352

def self.any?
    !empty?
end

.clearObject

Clears global platforms DB.



220
221
222
# File 'lib/arachni/platform/manager.rb', line 220

def self.clear
    @platforms.clear
end

.empty?Boolean

Returns `true` if there are no platforms fingerprints, `false` otherwise.

Returns:

  • (Boolean)

    `true` if there are no platforms fingerprints, `false` otherwise.



346
347
348
# File 'lib/arachni/platform/manager.rb', line 346

def self.empty?
    @platforms.empty?
end

.find_type(platform) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/arachni/platform/manager.rb', line 184

def self.find_type( platform )
    @find_type ||= {}

    if @find_type.empty?
        TYPES.keys.each do |type|

            platforms = const_get( type.to_s.upcase.to_sym )
            platforms = platforms.find_symbol_keys_recursively if platforms.is_a?( Hash )

            platforms.each do |p|
                @find_type[p] = type
            end
        end
    end

    @find_type[platform]
end

.fingerprint(page) ⇒ Manager

Runs all fingerprinters against the given `page`.

Parameters:

  • page (Page)

    Page to fingerprint.

Returns:



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/arachni/platform/manager.rb', line 259

def self.fingerprint( page )
    synchronize do
        return page if !fingerprint? page

        fingerprinters.available.each do |name|
            exception_jail( false ) do
                fingerprinters[name].new( page ).run
            end
        end

        # We do this to flag the resource as checked even if no platforms
        # were identified. We don't want to keep checking a resource that
        # yields nothing over and over.
        update( page.url, [] )
    end

    # Fingerprinting will have resulted in element parsing, clear the element
    # caches to keep RAM consumption down.
    page.clear_cache
end

.fingerprint?(resource) ⇒ Bool

Returns `true` if the resource should be fingerprinted, `false` otherwise.

Parameters:

Returns:

  • (Bool)

    `true` if the resource should be fingerprinted, `false` otherwise.



247
248
249
250
# File 'lib/arachni/platform/manager.rb', line 247

def self.fingerprint?( resource )
    !(!Options.fingerprint? || resource.code != 200 || !resource.text? ||
        include?( resource.url ) || resource.scope.out?)
end

.fingerprintersObject



236
237
238
239
240
# File 'lib/arachni/platform/manager.rb', line 236

def self.fingerprinters
    @manager ||=
        Component::Manager.new( Options.paths.fingerprinters,
                                Platform::Fingerprinters )
end

.include?(uri) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


324
325
326
# File 'lib/arachni/platform/manager.rb', line 324

def self.include?( uri )
    @platforms.include?( make_key( uri ) )
end

.make_key(uri) ⇒ Object



356
357
358
359
# File 'lib/arachni/platform/manager.rb', line 356

def self.make_key( uri )
    return if !(parsed = Arachni::URI( uri ))
    parsed.without_query
end

.new_from_options(platforms = []) ⇒ Object



361
362
363
# File 'lib/arachni/platform/manager.rb', line 361

def self.new_from_options( platforms = [] )
    new( platforms | Options.platforms )
end

.resetObject

Empties the global platform fingerprints.



225
226
227
228
229
230
231
232
233
# File 'lib/arachni/platform/manager.rb', line 225

def self.reset
    set Hash.new
    @manager.clear if @manager
    @manager = nil

    @mutex  = Monitor.new

    self
end

.set(platforms) ⇒ Object

Sets global platforms fingerprints



213
214
215
216
217
# File 'lib/arachni/platform/manager.rb', line 213

def self.set( platforms )
    @platforms = Support::Cache::LeastRecentlyPushed.new( PLATFORM_CACHE_SIZE )
    platforms.each { |k, v| @platforms[k] = v }
    @platforms
end

.sizeObject



319
320
321
# File 'lib/arachni/platform/manager.rb', line 319

def self.size
    @platforms.size
end

.synchronize(&block) ⇒ Object



180
181
182
# File 'lib/arachni/platform/manager.rb', line 180

def self.synchronize( &block )
    @mutex.synchronize( &block )
end

.update(uri, platforms) ⇒ Manager

Updates the `platforms` for the given `uri`.

Parameters:

Returns:

Raises:



338
339
340
341
342
# File 'lib/arachni/platform/manager.rb', line 338

def self.update( uri, platforms )
    synchronize do
        self[uri].update platforms
    end
end

.validObject



202
203
204
# File 'lib/arachni/platform/manager.rb', line 202

def self.valid
    @valid ||= Set.new( PLATFORM_NAMES.keys )
end

.valid?(platforms) ⇒ Boolean

Returns:

  • (Boolean)


206
207
208
209
# File 'lib/arachni/platform/manager.rb', line 206

def self.valid?( platforms )
    platforms = [platforms].flatten.compact
    (valid & platforms).to_a == platforms
end

Instance Method Details

#<<(platform) ⇒ Manager

Returns `self`.

Parameters:

  • platform (Symbol, String)

    Platform to add to the appropriate list.

Returns:

Raises:



550
551
552
553
# File 'lib/arachni/platform/manager.rb', line 550

def <<( platform )
    find_list( platform ) << platform
    self
end

#any?Boolean

Returns `true` if there are applicable platforms, `false` otherwise.

Returns:

  • (Boolean)

    `true` if there are applicable platforms, `false` otherwise.



521
522
523
# File 'lib/arachni/platform/manager.rb', line 521

def any?
    !empty?
end

#clearObject



525
526
527
# File 'lib/arachni/platform/manager.rb', line 525

def clear
    @platforms.clear
end

#dbList

Returns Platform list for databases.

Returns:

  • (List)

    Platform list for databases.

See Also:



# File 'lib/arachni/platform/manager.rb', line 383

#each(&block) ⇒ Enumerator, Manager

Returns `Enumerator` if no `block` is given, `self` otherwise.

Parameters:

  • block (Block)

    Block to be passed each platform.

Returns:

  • (Enumerator, Manager)

    `Enumerator` if no `block` is given, `self` otherwise.



495
496
497
498
499
# File 'lib/arachni/platform/manager.rb', line 495

def each( &block )
    return enum_for( __method__ ) if !block_given?
    @platforms.map { |_, p| p.to_a }.flatten.each( &block )
    self
end

#empty?Boolean

Returns `true` if there are no applicable platforms, `false` otherwise.

Returns:

  • (Boolean)

    `true` if there are no applicable platforms, `false` otherwise.



515
516
517
# File 'lib/arachni/platform/manager.rb', line 515

def empty?
    !@platforms.map { |_, p| p.empty? }.include?( false )
end

#find_list(platform) ⇒ List

Returns Platform list.

Parameters:

  • platform (String, Symbol)

    Platform whose list to find.

Returns:

  • (List)

    Platform list.



568
569
570
# File 'lib/arachni/platform/manager.rb', line 568

def find_list( platform )
    @platforms[find_type( normalize( platform ) )]
end

#find_type(platform) ⇒ Symbol

Returns Platform type.

Parameters:

  • platform (String, Symbol)

    Platform whose type to find

Returns:

  • (Symbol)

    Platform type.



559
560
561
# File 'lib/arachni/platform/manager.rb', line 559

def find_type( platform )
    self.class.find_type( platform )
end

#frameworksList

Returns Platform list for frameworks.

Returns:

  • (List)

    Platform list for frameworks.

See Also:



407
408
409
410
411
# File 'lib/arachni/platform/manager.rb', line 407

[:os, :db, :servers, :languages, :frameworks].each do |type|
    define_method type do
        @platforms[type]
    end
end

#fullname(platform) ⇒ String

Converts a platform shortname to a full name.

Parameters:

  • platform (String, Symbol)

    Platform shortname.

Returns:

Raises:



423
424
425
# File 'lib/arachni/platform/manager.rb', line 423

def fullname( platform )
    PLATFORM_NAMES[normalize( platform )]
end

#include?(platform) ⇒ Boolean

Returns `true` if one of the lists contains the `platform`, `false` otherwise.

Parameters:

  • platform (Symbol, String)

    Platform to check.

Returns:

  • (Boolean)

    `true` if one of the lists contains the `platform`, `false` otherwise.

Raises:



509
510
511
# File 'lib/arachni/platform/manager.rb', line 509

def include?( platform )
    find_list( platform ).include?( platform )
end

#invalid?(platform) ⇒ Boolean

Returns `true` if platform is invalid (i.e. not in #valid), `false` otherwise.

Parameters:

  • platform (Symbol, String)

    Platform to check.

Returns:

  • (Boolean)

    `true` if platform is invalid (i.e. not in #valid), `false` otherwise.

See Also:



486
487
488
# File 'lib/arachni/platform/manager.rb', line 486

def invalid?( platform )
    !valid?( platform )
end

#languagesList

Returns Platform list for languages.

Returns:

  • (List)

    Platform list for languages.

See Also:



# File 'lib/arachni/platform/manager.rb', line 395

#osList

Returns Platform list for operating systems.

Returns:

  • (List)

    Platform list for operating systems.

See Also:



# File 'lib/arachni/platform/manager.rb', line 377

#pick(data_per_platform) ⇒ Hash

Selects appropriate data, depending on the applicable platforms, from `data_per_platform`.

Parameters:

  • data_per_platform (Hash{<Symbol, String> => Object})

    Hash with platform names as keys and arbitrary data as values.

Returns:

  • (Hash)

    `data_per_platform` with non-applicable entries (for non-empty platform lists) removed. Data for platforms whose list is empty will not be removed.

Raises:



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/arachni/platform/manager.rb', line 439

def pick( data_per_platform )
    data_per_list = {}
    data_per_platform.each do |platform, value|
        list = find_list( platform )
        data_per_list[list]           ||= {}
        data_per_list[list][platform]   = value
    end

    picked = {}
    data_per_list.each do |list, data|
        # If a platform list is empty pass the given data without picking...
        if list.empty?
            picked.merge! data
            next
        end

        # ...otherwise enforce its platform restrictions.
        picked.merge! list.pick( data )
    end

    picked
end

#serversList

Returns Platform list for web servers.

Returns:

  • (List)

    Platform list for web servers.

See Also:



# File 'lib/arachni/platform/manager.rb', line 389

#update(enum) ⇒ Manager

Returns Updated `self`.

Parameters:

  • enum (Enumerable)

    Enumerable object containing platforms.

Returns:

Raises:



537
538
539
540
# File 'lib/arachni/platform/manager.rb', line 537

def update( enum )
    enum.each { |p| self << p }
    self
end

#validSet<Symbol>

Returns List of valid platforms.

Returns:

  • (Set<Symbol>)

    List of valid platforms.



464
465
466
# File 'lib/arachni/platform/manager.rb', line 464

def valid
    self.class.valid
end

#valid?(platform) ⇒ Boolean

Returns `true` if platform is valid (i.e. in #valid), `false` otherwise.

Parameters:

  • platform (Symbol, String)

    Platform to check.

Returns:

  • (Boolean)

    `true` if platform is valid (i.e. in #valid), `false` otherwise.

See Also:



475
476
477
# File 'lib/arachni/platform/manager.rb', line 475

def valid?( platform )
    valid.include? platform
end