require 'command_mapper/command' module Nmap # # ## Nmap options: # # ### Target Specifications: # # * `-iL path/to/file` - `nmap.target_file = "path/to/file"` # * `-iR 10` - `nmap.random_targets = 10` # * `--exclude host1 --exclude host2` - `nmap.exclude = ["host1", "host2"` # * `--excludefile path/to/file` - `nmap.exclude_file = "path/to/file"` # # ### Host Discovery: # # * `-sL` - `nmap.list = true` # * `-sn` - `nmap.ping = true` # * `-Pn` - `nmap.skip_discovery = true` # * `-PS` - `nmap.syn_discovery = [20..80, 443]` # * `-PA` - `nmap.ack_discovery = [20..80, 443]` # * `-PU` - `nmap.udp_discovery = [20..80, 443]` # * '-PY' - `nmap.sctp_init_ping = [20..80, 443]` # * `-PE` - `nmap.icmp_echo_discovery = true` # * `-PP` - `nmap.icmp_timestamp_discovery = true` # * `-PM` - `nmap.icmp_netmask_discovery = true` # * `-PO` - `nmap.ip_ping = [1, 2, 3, 4, ...]` # * `-PR` - `nmap.arp_ping = true` # * `--traceroute` - `nmap.traceroute = true` # * `-n` - `nmap.disable_dns = true` # * `-R` - `nmap.enable_dns = true` # * `--resolve-all` - `nmap.resolve_all = true` # * `--unique` - `nmap.unique = true` # * `--dns-servers nameserver1,nameserver2` - `nmap.dns_servers = ["nameserver1", "nameserver2"]` # * `--systems-dns` - `nmap.systems_dns = true` # # ### Port Scanning Techniques: # # * `-sS` - `nmap.syn_scan = true` # * `-sT` - `nmap.connect_scan = true` # * `-sU` - `nmap.udp_scan = true` # * `-sY` - `nmap.sctp_init_scan = true` # * `-sN` - `nmap.null_scan = true` # * `-sF` - `nmap.fin_scan = true` # * `-sX` - `nmap.xmas_scan = true` # * `-sA` - `nmap.ack_scan = true` # * `-sW` - `nmap.window_scan = true` # * `-sM` - `nmap.maimon_scan = true` # * `--scanflags` - `nmap.scan_flags = {syn: true, ack: true, rst: true}` / `nmap.scan_flags = [:syn, :ack, :rst]` / `nmap.scan_flags = 9` / `nmap.scan_flags = "SYNACKRST"` # * `-sZ` - `nmap.sctp_cookie_echo_scan = true` # * `-sI zombiehost:probeport` - `nmap.idle_scan = "zombiehost:probeport"` # * `-sO` - `nmap.ip_scan = true` # * `-b ftp.relay-host.com` - `nmap.ftp_bounce_scan = "ftp.relay-host.com"` # # ### Port Specification and Scan Order: # # * `-p 22,80,443,8000-9000` - `nmap.ports = [22, 80, 443, 8000..9000]` # * `--exclude-ports 1-20,1024-2000` - `nmap.exclude_ports = [1..20, 1024..2000]` # * `-F` - `nmap.fast = true` # * `-r` - `nmap.consecutively = true` # * `--top-ports 10` - `nmap.top_ports = 10` # * `--port-ratio 0.5` - `nmap.port_ratio = 0.5` # # ### Service/Version Detection: # # * `-sV` - `nmap.service_scan = true` # * `--allports` - `nmap.all_ports = true` # * `--version-intensity 9` - `nmap.version_intensity = 9` # * `--version-light` - `nmap.version_light = true` # * `--version-all` - `nmap.version_all = true` # * `--version-trace` - `nmap.version_trace = true` # * `-sR` - `nmap.rpc_scan = true` # # ### Script Scan: # # * `-sC` - `nmap.default_script = true` # * `--script script1,script2,script3` - `nmap.script = ["script1", "script2", "script3"]` # * `--script-args=arg1=value,arg2=value2` - `nmap.script_args = {arg1: `value1", arg2: "value2"}` # * `--script-args-file path/to/file` - `nmap.script_args_file = "path/to/file"` # * `--script-help script1,script2,script3` - `nmap.script_help = ["script1", "script2", "script3"]` # * `--script-trace` - `nmap.script_trace = true` # * `--script-updatedb` - `nmap.update_scriptdb = treu` # # ### OS Detection: # # * `-O` - `nmap.os_fingerprint = true` # * `--osscan-limit` - `nmap.limit_os_scan = true` # * `--osscan-guess` - `nmap.max_os_scan = true` # # ### Timing and Performance: # # * `--min-hostgroup 42` - `nmap.min_host_group = 42` # * `--max-hostgroup 42` - `nmap.max_host_group = 42` # * `--min-parallelism 42` - `nmap.min_parallelism = 42` # * `--max-parallelism 42` - `nmap.max_parallelism = 42` # * `--min-rtt-timeout 100ms` - `nmap.min_rtt_timeout = "100ms"` # * `--max-rtt-timeout 500ms` - `nmap.max_rtt_timeout = "500ms"` # * `--initial-rtt-timeout 100ms` - `nmap.initial_rtt_timeout = "100ms"` # * `--max-retries 4` - `nmap.max_retries = 4` # * `--host-timeout 10s` - `nmap.host_timeout = "10s"` # * `--script-timeout 10s` - `nmap.script_timeout = "10s"` # * `--scan-delay 1s` - `nmap.scan_delay = "1s"` # * `--max-scan-delay 42s` - `nmap.max_scan_delay = "42s"` # * `--min-rate 10` - `nmap.min_rate = 10` # * `--max-rate 100` - `nmap.max_rate = 100` # * `--defeat-rst-ratelimit` - `nmap.defeat_rst_ratelimit = true` # * `--defeat-icmp-ratelimit` - `nmap.defeat_icmp_ratelimit = true` # * `--nsock-engine kqueue` - `nmap.nsock_engine = :kqueue` # * `-T polite` - `nmap.timing_template = :polite` # * `-T0` - `nmap.paranoid_timing = true` # * `-T1` - `nmap.sneaky_timing = true` # * `-T2` - `nmap.polite_timing = true` # * `-T3` - `nmap.normal_timing = true` # * `-T4` - `nmap.aggressive_timing = true` # * `-T5` - `nmap.insane_timing = true` # # ### Firewall/IDS Evasion and Spoofing: # # * `-f` - `nmap.packet_fragments = true` # * `--mtu` - `nmap.mtu = true` # * `-D decoy1,decoy2` - `nmap.decoys = ["decoy1", "decoy2"]` # * `-S 8.8.8.8` - `nmap.spoof = "8.8.8.8"` # * `-e eth0` - `nmap.interface = "eth0"` # * `-g 1024` - `nmap.source_port = 1024` # * `--proxies proxy1,proxy2` - `nmap.proxies = ["proxy1", "proxy2"]` # * `--data AABBCCDDEEFF` - `nmap.data = "AABBCCDDEEFF"` # * `--data-string foobar` - `nmap.data_string = "foobar"` # * `--data-length 42` - `nmap.data_length = 42` # * `--ip-options T` - `nmap.ip_options = 'T'` # * `--ttl 42` - `nmap.ttl = 42` # * `--randomize-hosts` - `nmap.randomize_hosts = true` # * `--spoof-mac XX:XX:XX:XX:XX:XX` - `nmap.spoof_mac = "XX:XX:XX:XX:XX:XX"` # * `--badsum` - `nmap.bad_checksum = true` # * `--adler32` - `nmap.sctp_adler32 = true` # # ### Output: # # * `-oN path/to/file` - `nmap.output_normal = "path/to/file"` # * `-oX path/to/file` - `nmap.output_xml = "path/to/file"` # * `-oS path/to/file` - `nmap.output_skiddie = "path/to/file"` # * `-oG path/to/file` - `nmap.output_grepable = "path/to/file"` # * `-oA path/to/basename` - `nmap.output_all = "path/to/basename"` # # ### Verbosity and Debugging: # # * `-v` - `nmap.verbose = true` # * `-v3` - `nmap.verbose = 3` # * `-vv` - `nmap.extra_verbose = true` # * `-v0` - `nmap.quiet = true` # * `-d` - `nmap.debug = true` # * `-d9` - `nmap.debug = 9` # * `--reason` - `nmap.show_reason = true` # * `--stats-every 2s` - `nmap.stats_every = "2s"` # * `--packet-trace` - `nmap.show_packets = true` # * `--open` - `nmap.show_open_ports = true` # * `--iflist` - `nmap.show_interfaces = true` # * `--log-errors` - `nmap.show_log_errors = true` # # ### Miscellaneous Output: # # * `--append-output` - `nmap.append_output = true` # * `--resume` - `nmap.resume = true` # * `--stylesheet path/to/stylesheet.xsl` - `nmap.stylesheet = "path/to/stylesheet.xsl"` # * `--webxml` - `nmap.webxml = true` # * `--no-stylesheet` - `nmap.no_stylesheet = true` # # ### Misc: # # * `-6` - `nmap.ipv6 = true` # * `-A` - `nmap.all = true` # * `--datadir path/to/nmap/dir` - `nmap.nmap_datadir = "path/to/nmap/dir"` # * `--servicedb path/to/services.txt` - `nmap.servicedb = "path/to/services.txt"` # * `--versiondb path/to/versions.txt` - `nmap.versiondb = "path/to/versions.txt"` # * `--send-eth` - `nmap.send_eth = true` # * `--send-ip` - `nmap.send_ip = true` # * `--privileged` - `nmap.privileged = true` # * `--unprivileged` - `nmap.unprivileged = true` # * `--release-memory` - `nmap.release_memory = true` # * `--noninteractive` - `nmap.non_interactive = true` # * `-V` - `nmap.version = true` # * `-h` - `nmap.help = true` # # * `google.com 1.1.1.1 192.168.1-2.*` - `nmap.targets = ["google.com", "1.1.1.1", "192.168.1-2.*"]` # # @see http://nmap.org/book/man.html # class Command < CommandMapper::Command # # Represents a port number. # # @api private # class Port < CommandMapper::Types::Num # Regular expression that validates a port number. PORT_NUMBER_REGEXP = /[1-9][0-9]{0,3}|[1-5][0-9][0-9][0-9][0-9]|6[0-4][0-9][0-9][0-9]|65[0-4][0-9][0-9]|655[0-2][0-9]|6553[0-5]/ # Regular expression that validates a service name. SERVICE_NAME_REGEXP = /[A-Za-z][A-Za-z0-9]*(?:[\/_-][A-Za-z0-9]+)*\*?/ # Regular expression that validates either a port number or service name. PORT_REGEXP = /(?:#{PORT_NUMBER_REGEXP}|#{SERVICE_NAME_REGEXP})/ # Regular expression that validates either a port number or service name. REGEXP = /\A#{PORT_REGEXP}\z/ # # Initializes the port type. # def initialize super(range: 1..65535) end # # Validates the given port number value. # # @param [Object] value # The value to validate. # # @return [true, (false, String)] # Returns true if the value is valid, or `false` and a validation error # message if the value is not compatible. # def validate(value) case value when String if value =~ REGEXP return true else return [false, "must be a valid port number or service name (#{value.inspect})"] end else super(value) end end # # Formats the given value. # # @param [Integer, String] value # The port number value to format. # # @return [String] # The formatted port number. # def format(value) case value when String value else super(value) end end end # # Represents a port range. # # @api private # class PortRange < Port # Regular expression to validate either a port or a port range. PORT_RANGE_REGEXP = /(?:#{PORT_NUMBER_REGEXP})?-(?:#{PORT_NUMBER_REGEXP})?|#{PORT_REGEXP}/ # Regular expression to validate either a port or a port range. REGEXP = /\A#{PORT_RANGE_REGEXP}\z/ # # Validates the given port or port range value. # # @param [Object] value # The port or port range value to validate. # # @return [true, (false, String)] # Returns true if the value is valid, or `false` and a validation error # message if the value is not compatible. # def validate(value) case value when Range valid, message = super(value.begin) unless valid return [valid, message] end valid, message = super(value.end) unless valid return [valid, message] end return true when String if value =~ REGEXP return true else return [false, "must be a valid port number, port range, or service name (#{value.inspect})"] end else super(value) end end # # Formats the given port or port range value. # # @param [Range, Integer, String] value # The port or port range value to format. # # @return [String] # The formatted port or port range. # def format(value) case value when Range "#{value.begin}-#{value.end}" else super(value) end end end # # Represents a list of ports or port ranges. # # @api private # class PortRangeList < CommandMapper::Types::List # Regular expression for validating a port or port range. PORT_RANGE_REGEXP = PortRange::PORT_RANGE_REGEXP # Regular expression for validating an nmap port range. REGEXP = /\A(?:[TUS]:)?#{PORT_RANGE_REGEXP}(?:,(?:[TUS]:)?#{PORT_RANGE_REGEXP})*\z/ # # Initializes the port range list type. # def initialize super(type: PortRange.new) end # # Validates the given port range list value. # # @param [Object] value # The given port range list value to validate. # # @return [true, (false, String)] # Returns true if the value is valid, or `false` and a validation error # message if the value is not compatible. # def validate(value) case value when Hash if value.empty? return [false, "cannot be empty"] end value.each do |protocol,ports| unless PROTOCOL_LETTERS.has_key?(protocol) return [false, "unknown protocol (#{protocol.inspect}) must be :tcp, :udp, or :sctp"] end valid, message = validate(ports) unless valid return [valid, message] end end return true when Range @type.validate(value) when String unless value =~ REGEXP return [false, "not a valid port range list (#{value.inspect})"] end return true else super(value) end end # Mapping of protocol names to single letters used in port range syntax. PROTOCOL_LETTERS = { tcp: 'T', udp: 'U', sctp: 'S' } # # Formats the given port range list value. # # @param [Hash, Range, String, Integer] value # The port range list value. # # @return [String] # The formatted port range list. # def format(value) case value when Hash # format a hash of protocols and port ranges value.map { |protocol,ports| letter = PROTOCOL_LETTERS.fetch(protocol) "#{letter}:#{format(ports)}" }.join(',') when Range # format an individual port range @type.format(value) when String # pass strings directly through value else super(value) end end end # # Represents a list of protocols. # # @api private # ProtocolList = PortRangeList # # Represents a unit of time. # # @api private # class Time < CommandMapper::Types::Str # Regular expression for validating a unit of time. REGEXP = /\A\d+(?:h|m|s|ms)?\z/ # # Validates a time value. # # @param [String, Integer] value # The time value to validate. # # @return [true, (false, String)] # Returns true if the value is considered valid, or false and a # validation message if the value is not valid. # def validate(value) case value when Integer then true else valid, message = super(value) unless valid return [valid, message] end value = value.to_s unless value =~ REGEXP return [false, "must be a number and end with 'ms', 's', 'm', or 'h'"] end return true end end end # # Represents a hex string. # # @api private # class HexString < CommandMapper::Types::Str REGEXP = /\A(?:(?:0x)?[0-9A-F]+|(?:\\x[0-9A-F]{2})+)\z/ # # Validates a hex string value. # # @param [String, #to_s] value # The hex string value to validate. # # @return [true, (false, String)] # Returns true if the value is considered valid, or false and a # validation message if the value is not valid. # def validate(value) valid, message = super(value) unless valid return [valid, message] end value = value.to_s unless value =~ REGEXP return [false, "must be of the format 0xAABBCCDDEEFF..., AABBCCDDEEFF..., or \\xAA\\xBB\\xCC\\xDD\\xEE\\xFF..."] end return true end end # # Represents one or more TCP scan flags. # # @api private # class ScanFlags < CommandMapper::Types::Str # Mapping of symbol scan flags to String values. FLAGS = { urg: 'URG', ack: 'ACK', psh: 'PSH', rst: 'RST', syn: 'SYN', fin: 'FIN' } # Regular expression to validate the given scan flags. REGEXP = /\A(?:\d+|(?:URG|ACK|PSH|RST|SYN|FIN)+)\z/ # # Validates a scanflags value. # # @param [String, Hash{Symbol => Boolean}, #to_s] value # The scanflags value to validate. # # @return [true, (false, String)] # Returns true if the value is considered valid, or false and a # validation message if the value is not valid. # def validate(value) case value when Hash if value.empty? return [false, "Hash value cannot be empty"] end unless value.keys.all? { |key| FLAGS.has_key?(key) } return [false, "Hash must only contain the keys :urg, :ack, :psh, :rst, :syn, or :fin"] end unless value.values.all? { |value| value == nil || value == false || value == true } return [false, "Hash must only contain the values true, false, or nil"] end return true when Array if value.empty? return [false, "Array value cannot be empty"] end unless value.all? { |flag| FLAGS.has_key?(flag) } return [false, "Array must only contain the values :urg, :ack, :psh, :rst, :syn, or :fin"] end return true else valid, message = super(value) unless valid return [valid, message] end value = value.to_s unless value =~ REGEXP return [false, "must only contain URG, ACK, PSH, RST, SYN, or FIN"] end return true end end # # Formats a scanflags value. # # @param [Hash{Symbol => Boolean}, Array, #to_s] value # The scanflags value to format. # # @return [String] # The formatted scanflags value. # def format(value) case value when Hash string = String.new value.each do |key,value| string << FLAGS[key] if value end return string when Array string = String.new value.each do |flag| string << FLAGS[flag] end return string else super(value) end end end command 'nmap' do # TARGET SPECIFICATIONS: option '-iL', name: :target_file, value: {type: InputFile.new} option '-iR', name: :random_targets, value: {type: Num.new} option '--exclude', name: :exclude, value: {type: List.new} option '--excludefile', name: :exclude_file, value: {type: InputFile.new} # HOST DISCOVERY: option '-sL', name: :list option '-sn', name: :ping option '-Pn', name: :skip_discovery option '-PS', name: :syn_discovery, value_in_flag: true, value: {type: PortRangeList.new, required: false} option '-PA', name: :ack_discovery, value_in_flag: true, value: {type: PortRangeList.new, required: false} option '-PU', name: :udp_discovery, value_in_flag: true, value: {type: PortRangeList.new, required: false} option '-PY', name: :sctp_init_ping, value_in_flag: true, value: {type: PortRangeList.new, required: false} option '-PE', name: :icmp_echo_discovery option '-PP', name: :icmp_timestamp_discovery option '-PM', name: :icmp_netmask_discovery option '-PO', name: :ip_ping, value_in_flag: true, value: {type: ProtocolList.new, required: false} option '-PR', name: :arp_ping option '--traceroute', name: :traceroute option '-n', name: :disable_dns option '-R', name: :enable_dns option '--resolve-all' option '--unique' option '--dns-servers', value: {type: List.new} option '--system-dns' # PORT SCANNING TECHNIQUES: option '-sS', name: :syn_scan option '-sT', name: :connect_scan option '-sU', name: :udp_scan option '-sY', name: :sctp_init_scan option '-sN', name: :null_scan option '-sF', name: :fin_scan option '-sX', name: :xmas_scan option '-sA', name: :ack_scan option '-sW', name: :window_scan option '-sM', name: :maimon_scan option '--scanflags', name: :scan_flags, value: {type: ScanFlags.new} option '-sZ', name: :sctp_cookie_echo_scan option '-sI', name: :idle_scan, value: true option '-sO', name: :ip_scan option '-b', name: :ftp_bounce_scan, value: true # PORT SPECIFICATION AND SCAN ORDER: option '-p', name: :ports, value: {type: PortRangeList.new} option '--exclude-ports', value: {type: PortRangeList.new} option '-F', name: :fast option '-r', name: :consecutively option '--top-ports', value: {type: Num.new} option '--port-ratio', value: {type: Dec.new(range: 0.0..1.0)} # SERVICE/VERSION DETECTION: option '-sV', name: :service_scan option '--allports', name: :all_ports option '--version-intensity', value: {type: Num.new(range: 0..9)} option '--version-light' option '--version-all' option '--version-trace' option '-sR', name: :rpc_scan # SCRIPT SCAN: option '-sC', name: :default_script option '--script', value: {type: List.new} option '--script-args', value: {type: KeyValueList.new} option '--script-args-file', value: {type: InputFile.new} option '--script-help', value: {type: List.new} option '--script-trace' option '--script-updatedb', name: :update_scriptdb # OS DETECTION: option '-O', name: :os_fingerprint option '--osscan-limit', name: :limit_os_scan option '--osscan-guess', name: :max_os_scan option '--max-os-tries', name: :max_os_tries # TIMING AND PERFORMANCE: option '--min-hostgroup', name: :min_host_group, value: {type: Num.new} option '--max-hostgroup', name: :max_host_group, value: {type: Num.new} option '--min-parallelism', value: {type: Num.new} option '--max-parallelism', value: {type: Num.new} option '--min-rtt-timeout', value: {type: Time.new} option '--max-rtt-timeout', value: {type: Time.new} option '--initial-rtt-timeout', value: {type: Time.new} option '--max-retries', value: {type: Num.new} option '--host-timeout', value: {type: Time.new} option '--script-timeout', value: {type: Time.new} option '--scan-delay', value: {type: Time.new} option '--max-scan-delay', value: {type: Time.new} option '--min-rate', value: {type: Num.new} option '--max-rate', value: {type: Num.new} option '--defeat-rst-ratelimit' option '--defeat-icmp-ratelimit' option '--nsock-engine', value: {type: Enum[:iocp, :epoll, :kqueue, :poll, :select]} option '-T', name: :timing_template, value: {type: Enum[:paranoid, :sneaky, :polite, :normal, :aggressive, :insane]} option '-T0', name: :paranoid_timing option '-T1', name: :sneaky_timing option '-T2', name: :polite_timing option '-T3', name: :normal_timing option '-T4', name: :aggressive_timing option '-T5', name: :insane_timing # FIREWALL/IDS EVASION AND SPOOFING: option '-f', name: :packet_fragments option '--mtu' option '-D', name: :decoys, value: {type: List.new} option '-S', name: :spoof, value: true option '-e', name: :interface, value: true option '-g', name: :source_port, value: {type: Port.new} option '--proxies', value: {type: List.new} option '--data', value: {type: HexString.new} option '--data-string', value: true option '--data-length', value: {type: Num.new} option '--ip-options', value: true option '--ttl', value: {type: Num.new} option '--randomize-hosts' option '--spoof-mac', value: true option '--badsum', name: :bad_checksum option '--adler32', name: :sctp_adler32 # OUTPUT: option '-oN', name: :output_normal, value: true option '-oX', name: :output_xml, value: true option '-oS', name: :output_skiddie, value: true option '-oG', name: :output_grepable, value: true option '-oA', name: :output_all, value: true # Verbosity and Debugging: option '-v', name: :verbose, value_in_flag: true, value: {type: Num.new, required: false} option '-vv', name: :extra_verbose option '-v0', name: :quiet option '-d', name: :debug, value_in_flag: true, value: {type: Num.new, required: false} option '--reason', name: :show_reason option '--stats-every', value: {type: Time.new} option '--packet-trace', name: :show_packets option '--open', name: :show_open_ports option '--iflist', name: :show_interfaces option '--log-errors', name: :show_log_errors # Miscellaneous output: option '--append-output' option '--resume', value: true option '--stylesheet', value: true option '--webxml', name: :nmap_stylesheet option '--no-stylesheet' # MISC: option '-6', name: :ipv6 option '-A', name: :all option '--datadir', name: :nmap_datadir, value: {type: InputDir.new} option '--servicedb', value: {type: InputFile.new} option '--versiondb', value: {type: InputFile.new} option '--send-eth' option '--send-ip' option '--privileged' option '--unprivileged' option '--release-memory' option '--noninteractive', name: :non_interactive option '-V', name: :version option '-h', name: :help argument :targets, required: false, repeats: true end end end