# frozen_string_literal: true # # ronin-recon - A micro-framework and tool for performing reconnaissance. # # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) # # ronin-recon is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ronin-recon is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with ronin-recon. If not, see . # require 'ronin/recon/registry' require 'ronin/recon/values' require 'ronin/core/metadata/id' require 'ronin/core/metadata/authors' require 'ronin/core/metadata/summary' require 'ronin/core/metadata/description' require 'ronin/core/metadata/references' require 'ronin/core/params/mixin' require 'async' module Ronin module Recon # # Base class for all recon workers. # # ## Philosophy # # Recon involves performing multiple strategies on input values # (ex: a domain) in order to produce discovered output values # (ex: sub-domains). These recon strategies can be defined as classes # which have a `process` method that accepts certiain input {Values value} # types and yield zero or more output {Values value types}. # # The {Worker} class defines three key parts: # # 1. Metadata - defines information about the recon worker. # 2. [Params] - optional user configurable parameters. # 3. {Worker#process process} - method which receives a {Values Value} class # # [Params]: https://ronin-rb.dev/docs/ronin-core/Ronin/Core/Params/Mixin.html # # ## Example # # require 'ronin/recon/worker' # # module Ronin # module Recon # module DNS # class FooBar # # register 'dns/foo_bar' # # summary 'My DNS recon technique' # description <<~DESC # This recon worker uses the foo-bar technique. # Bla bla bla bla. # DESC # author 'John Smith', email: '...' # # accepts Domain # outputs Host # intensity :passive # # param :wordlist, String, desc: 'Optional wordlist to use' # # def process(value) # # ... # yield Host.new(discovered_host_name) # # ... # end # # end # end # end # end # # ### register # # Registers the worker with {Recon}. # # register 'dns/foo_bar' # # ### accepts # # Defines which {Values Value} types the worker accepts. # # accepts Domain # # Available {Values Value} types are: # # * {Values::Domain Domain} - a domain name (ex: `example.com`). # * {Values::Host Host} - a host-name (ex: `www.example.com`). # * {Values::IP} - a single IP address (ex: `192.168.1.1'). # * {Values::IPRange} - a CIDR IP range (ex: `192.168.1.1/24`). # * {Values::Mailserver} - represents a mailserver for a domain # (ex: `smtp.google.com`). # * {Values::Nameserver} - represents a nameserver for a domain # (ex: `ns1.google.com`). # * {Values::OpenPort} - represents a discovered open port on an IP address. # * {Values::URL} - represents a discovered URL # (ex: `https://example.com/index.html`). # * {Values::Website} - represents a discovered website # (ex: `https://example.com/`). # * {Values::Wildcard} - represent a wildcard host name # (ex: `*.example.com`). # # **Note:** the recon worker may specify that it accepts multiple value # types: # # accepts Domain, Host, IP # # ### outputs # # Similar to `accepts`, but defines the possible output value types of the # worker. # # outputs Host # # **Note:** the recon worker may specify that it can output multiple # different value types: # # outputs Host, IP # # ### intensity # # Indicates the intensity level of the worker class. # # intensity :passive # # The possible intensity levels are: # # * `:passive` - does not send any network traffic to the target system. # * `:active` - sends a moderate amount of network traffic to the target # system. # * `:aggressive` - sends an excessive amount of network traffic to the # target system and may trigger alerts. # # **Note:** if the intensity level of the worker class is not defined, # it will default to `:active`. # # ### summary # # Defines a short one-sentence description of the recon worker. # # summary 'My DNS recon technique' # # ### description # # Defines a longer multi-paragraph description of the recon worker. # # description <<~DESC # This recon worker uses the foo-bar technique. # Bla bla bla bla. # DESC # # **Note:** that `<<~` heredoc, unlike the regular `<<` heredoc, removes # leading whitespace. # # ### author # # Add an author's name and additional information to the recon worker. # # author 'John Smith' # # author 'doctor_doom', email: '...', twitter: '...' # # ### param # # Defines a user configurable param. Params may have a type class, but # default to `String`. Params must have a one-line description. # # param :str, desc: 'A basic string param' # # param :feature_flag, Boolean, desc: 'A boolean param' # # param :enum, Enum[:one, :two, :three], # desc: 'An enum param' # # param :num1, Integer, desc: 'An integer param' # # param :num2, Integer, default: 42, # desc: 'A param with a default value' # # param :num3, Integer, default: ->{ rand(42) }, # desc: 'A param with a dynamic default value' # # param :float, Float, 'Floating point param' # # param :url, URI, desc: 'URL param' # # param :pattern, Regexp, desc: 'Regular Expression param' # # Params may then be accessed in instance methods using `params` Hash. # # param :retries, Integer, default: 4, # desc: 'Number of retries' # # def process(value) # retry_count = 0 # # begin # # ... # rescue => error # retry_count += 1 # # if retry_count < params[:retries] # retry # else # raise(error) # end # end # end # # @api public # class Worker include Core::Metadata::ID include Core::Metadata::Authors include Core::Metadata::Summary include Core::Metadata::Description include Core::Metadata::References include Core::Params::Mixin include Values # # Registers the recon worker with the given name. # # @param [String] worker_id # The recon worker's `id`. # # @example # require 'ronin/recon/worker' # # module Ronin # module Recon # module DNS # class SubdomainBruteforcer < Worker # # register 'dns/subdomain_bruteforcer' # # end # end # end # end # # @api public # def self.register(worker_id) id(worker_id) Recon.register(worker_id,self) end # # Initializes the worker. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # def initialize(**kwargs) super(**kwargs) end # # Gets or sets the value class which the recon worker accepts. # # @param [Array>] value_classes # The optional new value class(es) to accept. # # @return [Array>] # the value class which the recon worker accepts. # # @raise [NotImplementedError] # No value class was defined for the recon worker. # # @example define that the recon worker accepts IP addresses: # accepts IP # def self.accepts(*value_classes) unless value_classes.empty? @accepts = value_classes else @accepts || if superclass < Worker superclass.accepts else raise(NotImplementedError,"#{self} did not set accepts") end end end # # Gets or sets the value class which the recon worker outputs. # # @param [Array>] value_classes # The optional new value class(es) to outputs. # # @return [Array>] # the value class which the recon worker outputs. # # @raise [NotImplementedError] # No value class was defined for the recon worker. # # @example define that the recon worker outputs Host values: # outputs Host # def self.outputs(*value_classes) unless value_classes.empty? @outputs = value_classes else @outputs || if superclass < Worker superclass.outputs else raise(NotImplementedError,"#{self} did not set outputs") end end end # # Gets or sets the worker's default concurrency. # # @param [Integer, nil] new_concurrency # The optional new concurrency to set. # # @return [Integer] # The worker's concurrency. Defaults to `1` if not set. # # @example sets the recon worker's default concurrency: # concurrency 3 # def self.concurrency(new_concurrency=nil) if new_concurrency @concurrency = new_concurrency else @concurrency || if superclass < Worker superclass.concurrency else 1 end end end # # Gets or sets the worker's intensity level. # # @param [:passive, :active, :aggressive, nil] new_intensity # The optional new intensity level to set. # # * `:passive` - does not send any network traffic to the target system. # * `:active` - sends a moderate amount of network traffic to the target # system. # * `:aggressive` - sends an excessive amount of network traffic to the # target system and may trigger alerts. # # @return [:passive, :active, :aggressive] # The worker's intensity level. Defaults to `:active` if not set. # # @raise [ArgumentError] # The new intensity level was not `:passive`, `:active`, or # `:aggressive`. # # @example sets the recon worker's intensity level: # intensity :passive # def self.intensity(new_intensity=nil) if new_intensity case new_intensity when :passive, :active, :aggressive @intensity = new_intensity else raise(ArgumentError,"intensity must be :passive, :active, or :aggressive: #{new_intensity.inspect}") end else @intensity || if superclass < Worker superclass.intensity else :active end end end # # Initializes the worker and runs it with the single value. # # @param [Value] value # The input value to process. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments to initialize the worker with. # # @note # This method is mainly for testing workers and running them # individually. # def self.run(value,**kwargs,&block) worker = new(**kwargs) Async do worker.process(value,&block) end end # # Calls the recon worker with the given input value. # # @param [Value] value # The input value. # # @yield [new_value] # The `call` method can then `yield` one or more newly discovered values # # @yieldparam [Value] new_value # An newly discovered output value from the input value. # # @abstract # def process(value,&block) raise(NotImplementedError,"#{self.class} did not define a #process method") end end end end