ruby-masscan 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f03fa94f7c501646be712a24b98fe07a6dd156c054c7ae27714cac80b1fbb5e8
4
- data.tar.gz: a346c7b055da689cad8571a38383b43a5e797889d6206b85ca7d2a64e25b904f
3
+ metadata.gz: 064d7200b60b80a1ed4f0459239235d21f96587a3199833988dd5953728cd168
4
+ data.tar.gz: 619757cb909fcec538c63ef5d37d2cb1f1029509d71e957be9aa6f74726ad4ea
5
5
  SHA512:
6
- metadata.gz: ecdea1cc3b62cf1d0dbb2b7771807c05f2e6339b573f6208dcfb21e94fdaad160e51adc077e158ec6c4b4bc9426c7925dfd376d53d455922c5e318e6ee999936
7
- data.tar.gz: 88bbf9a1332946ae7688fae852a1a3565269a853eff17434dc8e0b1fd7eae2c3e14b09cc64fa3fc80a06781f3fd644b97e5f97fcc52ccd2aa71cbc2a32b15f3c
6
+ metadata.gz: 9d9fa47ab80653e00c4d3f16b02e1cbe824575d0979b43596f7a52c703b9eda0616882e8bd7effdb77a3f9bfafe40b50043ace08a06f4725b59335dab724fee2
7
+ data.tar.gz: ad559b68f9af88c2c2e1b66711a93b3bceda9962cd0b3f1f478400a0f1dd7d79a7a87eab81eef4ff9c1cca426c135732d05437affb757fdc3d1ebcfc64b681b2
@@ -9,8 +9,6 @@ jobs:
9
9
  fail-fast: false
10
10
  matrix:
11
11
  ruby:
12
- - 2.4
13
- - 2.5
14
12
  - 2.6
15
13
  - 2.7
16
14
  - 3.0
data/ChangeLog.md CHANGED
@@ -1,3 +1,16 @@
1
+ ### 0.2.1 / 2023-03-15
2
+
3
+ * Unescape `\\xXX` hex escaped characters in payload strings parsed from `.list`
4
+ masscan files.
5
+
6
+ ### 0.2.0 / 2021-11-30
7
+
8
+ * Replaced the `rprogram` dependency with [command_mapper].
9
+ * Fixed a typo in the mapping of the `-oJ` option flag.
10
+ * Added {Masscan::OutputFile#to_s}.
11
+
12
+ [command_mapper]: https://github.com/postmodern/command_mapper.rb#readme
13
+
1
14
  ### 0.1.1 / 2021-09-09
2
15
 
3
16
  * Added missing {Masscan::Banner#ttl}.
data/Gemfile CHANGED
@@ -2,14 +2,16 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ # gem 'command_mapper', '~> 0.1', github: 'postmodern/command_mapper.rb'
6
+
5
7
  group :development do
6
8
  gem 'rake'
7
9
  gem 'rubygems-tasks', '~> 0.2'
8
10
  gem 'rspec', '~> 3.0'
9
-
10
- gem 'json'
11
11
  gem 'simplecov', '~> 0.7'
12
+
12
13
  gem 'kramdown'
14
+ gem 'redcarpet', platform: :mri
13
15
  gem 'yard', '~> 0.9'
14
16
  gem 'yard-spellcheck', require: false
15
17
  end
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2021 Hal Brodigan
1
+ Copyright (c) 2021-2023 Hal Brodigan
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -15,17 +15,22 @@ file formats.
15
15
 
16
16
  ## Features
17
17
 
18
- * Provides a Ruby interface for running the `masscan` command.
19
- * Supports parsing masscan Binary, List, and JSON output files.
18
+ * Provides a [Ruby interface][Masscan::Command] for running the `masscan`
19
+ utility.
20
+ * Supports [parsing][Masscan::OutputFile] masscan Binary, List, and JSON output
21
+ files.
22
+
23
+ [Masscan::Command]: https://rubydoc.info/gems/ruby-masscan/Masscan/Command
24
+ [Masscan::OutputFile]: https://rubydoc.info/gems/ruby-masscan/Masscan/OutputFile
20
25
 
21
26
  ## Examples
22
27
 
23
28
  Run `sudo masscan` from Ruby:
24
29
 
25
30
  ```ruby
26
- require 'masscan/program'
31
+ require 'masscan/command'
27
32
 
28
- Masscan::Program.sudo_scan do |masscan|
33
+ Masscan::Command.sudo do |masscan|
29
34
  masscan.output_format = :list
30
35
  masscan.output_file = 'masscan.txt'
31
36
 
@@ -108,7 +113,7 @@ end
108
113
 
109
114
  * [ruby] >= 2.0.0
110
115
  * [masscan] >= 1.0.0
111
- * [rprogram] ~> 0.3
116
+ * [command_mapper] ~> 0.1
112
117
 
113
118
  ## Install
114
119
 
@@ -119,21 +124,21 @@ $ gem install ruby-masscan
119
124
  ### gemspec
120
125
 
121
126
  ```ruby
122
- gemspec.add_dependency 'ruby-masscan', '~> 0.1'
127
+ gemspec.add_dependency 'ruby-masscan', '~> 0.2'
123
128
  ```
124
129
 
125
130
  ### Gemfile
126
131
 
127
132
  ```ruby
128
- gem 'ruby-masscan', '~> 0.1'
133
+ gem 'ruby-masscan', '~> 0.2'
129
134
  ```
130
135
 
131
136
  ## License
132
137
 
133
- Copyright (c) 2021 Hal Brodigan
138
+ Copyright (c) 2021-2023 Hal Brodigan
134
139
 
135
140
  See {file:LICENSE.txt} for license information.
136
141
 
137
142
  [masscan]: https://github.com/robertdavidgraham/masscan#readme
138
143
  [ruby]: https://www.ruby-lang.org/
139
- [rprogram]: https://github.com/postmodern/rprogram#readme
144
+ [command_mapper]: https://github.com/postmodern/command_mapper.rb#readme
data/gemspec.yml CHANGED
@@ -16,13 +16,14 @@ metadata:
16
16
  source_code_uri: https://github.com/postmodern/ruby-masscan
17
17
  bug_tracker_uri: https://github.com/postmodern/ruby-masscan/issues
18
18
  changelog_uri: https://github.com/postmodern/ruby-masscan/blob/master/ChangeLog.md
19
+ rubygems_mfa_required: 'true'
19
20
 
20
21
  required_ruby_version: ">= 2.0.0"
21
22
 
22
23
  requirements: masscan >= 1.0.0
23
24
 
24
25
  dependencies:
25
- rprogram: ~> 0.3
26
+ command_mapper: ~> 0.1
26
27
 
27
28
  development_dependencies:
28
29
  bundler: ~> 2.0
@@ -0,0 +1,240 @@
1
+ require 'command_mapper/command'
2
+
3
+ module Masscan
4
+ #
5
+ # Provides an interface for invoking the `masscan` utility.
6
+ #
7
+ # ## Example
8
+ #
9
+ # require 'masscan/command'
10
+ #
11
+ # Masscan::Command.sudo do |masscan|
12
+ # masscan.output_format = :list
13
+ # masscan.output_file = 'masscan.txt'
14
+ #
15
+ # masscan.ips = '192.168.1.1/24'
16
+ # masscan.ports = [20,21,22,23,25,80,110,443,512,522,8080,1080]
17
+ # end
18
+ #
19
+ # ## `masscan` options:
20
+ #
21
+ # * `--range` - `masscan.range`
22
+ # * `-p` - `masscan.ports`
23
+ # * `--banners` - `masscan.banners`
24
+ # * `--rate` - `masscan.rate`
25
+ # * `--conf` - `masscan.config_file`
26
+ # * `--resume` - `masscan.resume`
27
+ # * `--echo` - `masscan.echo`
28
+ # * `--adapter` - `masscan.adapter`
29
+ # * `--adapter-ip` - `masscan.adapter_ip`
30
+ # * `--adapter-port` - `masscan.adapter_port`
31
+ # * `--adapter-mac` - `masscan.adapter_mac`
32
+ # * `--adapter-vlan` - `masscan.adapter_vlan`
33
+ # * `--router-mac` - `masscan.router_mac`
34
+ # * `--ping` - `masscan.ping`
35
+ # * `--exclude` - `masscan.exclude`
36
+ # * `--excludefile` - `masscan.exclude_file`
37
+ # * `--includefile` - `masscan.include_file`
38
+ # * `--append-output` - `masscan.append_output`
39
+ # * `--iflist` - `masscan.list_interfaces`
40
+ # * `--retries` - `masscan.retries`
41
+ # * `--nmap` - `masscan.nmap_help`
42
+ # * `--pcap-payloads` - `masscan.pcap_payloads`
43
+ # * `--nmap-payloads` - `masscan.nmap_payloads`
44
+ # * `--http-method` - `masscan.http_method`
45
+ # * `--http-url` - `masscan.http_url`
46
+ # * `--http-version` - `masscan.http_version`
47
+ # * `--http-host` - `masscan.http_host`
48
+ # * `--http-user-agent` - `masscan.http_user_agent`
49
+ # * `--http-field` - `masscan.http_field`
50
+ # * `--http-field-remove` - `masscan.http_field_remove`
51
+ # * `--http-cookie` - `masscan.http_cookie`
52
+ # * `--http-payload` - `masscan.http_payload`
53
+ # * `--show` - `masscan.show`
54
+ # * `--noshow` - `masscan.hide`
55
+ # * `--pcap` - `masscan.pcap`
56
+ # * `--packet-trace` - `masscan.packet_trace`
57
+ # * `--pfring` - `masscan.pfring`
58
+ # * `--resume-index` - `masscan.resume_index`
59
+ # * `--resume-count` - `masscan.resume_count`
60
+ # * `--shards` - `masscan.shards`
61
+ # * `--rotate` - `masscan.rotate`
62
+ # * `--rotate-offset` - `masscan.rotate_offset`
63
+ # * `--rotate-size` - `masscan.rotate_size`
64
+ # * `--rotate-dir` - `masscan.rotate_dir`
65
+ # * `--seed` - `masscan.seed`
66
+ # * `--regress` - `masscan.regress`
67
+ # * `--ttl` - `masscan.ttl`
68
+ # * `--wait` - `masscan.wait`
69
+ # * `--offline` - `masscan.offline`
70
+ # * `-sL` - `masscan.print_list`
71
+ # * `--interactive` - `masscan.interactive`
72
+ # * `--output-format` - `masscan.output_format`
73
+ # * `--output-filename` - `masscan.output_file`
74
+ # * `-oB` - `masscan.output_binary`
75
+ # * `-oX` - `masscan.output_xml`
76
+ # * `-oG` - `masscan.output_grepable`
77
+ # * ` -oJ` - `masscan.output_json`
78
+ # * `-oL` - `masscan.output_list`
79
+ # * `--readscan` - `masscan.read_scan`
80
+ # * `-V` - `masscan.version`
81
+ # * `-h` - `masscan.help`
82
+ #
83
+ # @see https://github.com/robertdavidgraham/masscan/blob/master/doc/masscan.8.markdown
84
+ #
85
+ # @since 0.2.0
86
+ #
87
+ class Command < CommandMapper::Command
88
+
89
+ class PortList < CommandMapper::Types::Num
90
+
91
+ def validate(value)
92
+ case value
93
+ when Array
94
+ value.each do |element|
95
+ valid, message = validate(element)
96
+
97
+ unless valid
98
+ return [valid, message]
99
+ end
100
+ end
101
+
102
+ return true
103
+ when Range
104
+ valid, message = super(value.begin)
105
+
106
+ unless valid
107
+ return [valid, message]
108
+ end
109
+
110
+ valid, message = super(value.end)
111
+
112
+ unless valid
113
+ return [valid, message]
114
+ end
115
+
116
+ return true
117
+ else
118
+ super(value)
119
+ end
120
+ end
121
+
122
+ def format(value)
123
+ case value
124
+ when Array
125
+ value.map(&method(:format)).join(',')
126
+ when Range
127
+ "#{value.begin}-#{value.end}"
128
+ else
129
+ super(value)
130
+ end
131
+ end
132
+
133
+ end
134
+
135
+ class Shards < CommandMapper::Types::Str
136
+
137
+ def validate(value)
138
+ case value
139
+ when Array
140
+ if value.length > 2
141
+ return [false, "cannot contain more tha two elements (#{value.inspect})"]
142
+ end
143
+
144
+ return true
145
+ else
146
+ super(value)
147
+ end
148
+ end
149
+
150
+ def format(value)
151
+ case value
152
+ when Array
153
+ "#{value[0]}/#{value[1]}"
154
+ else
155
+ super(value)
156
+ end
157
+ end
158
+
159
+ end
160
+
161
+ command "masscan" do
162
+ option '--range', name: :range, value: {type: List.new}
163
+ option '-p', name: :ports, value: {type: PortList.new}
164
+ option '--banners', name: :banners
165
+ option '--rate', name: :rate, value: {type: Num.new}
166
+ option '--conf', name: :config_file, value: {type: InputFile.new}
167
+ option '--resume', name: :resume, value: {type: InputFile.new}
168
+ option '--echo', name: :echo, value: true
169
+ option '--adapter', name: :adapter, value: true
170
+ option '--adapter-ip', name: :adapter_ip, value: true
171
+ option '--adapter-port', name: :adapter_port, value: {type: Num.new}
172
+ option '--adapter-mac', name: :adapter_mac, value: true
173
+ option '--adapter-vlan', name: :adapter_vlan, value: true
174
+ option '--router-mac', name: :router_mac, value: true
175
+ option '--ping', name: :ping
176
+ option '--exclude', name: :exclude, value: {type: List.new}
177
+ option '--excludefile', name: :exclude_file, value: {type: InputFile.new}
178
+ option '--includefile', name: :include_file, value: {type: InputFile.new}
179
+ option '--append-output', name: :append_output
180
+ option '--iflist', name: :list_interfaces
181
+ option '--retries', name: :retries
182
+ option '--nmap', name: :nmap_help
183
+ option '--pcap-payloads', name: :pcap_payloads
184
+ option '--nmap-payloads', name: :nmap_payloads, value: {type: InputFile.new}
185
+
186
+ option '--http-method', name: :http_method, value: true
187
+ option '--http-url', name: :http_url, value: true
188
+ option '--http-version', name: :http_version, value: true
189
+ option '--http-host', name: :http_host, value: true
190
+ option '--http-user-agent', name: :http_user_agent, value: true
191
+
192
+ option '--http-field', value: {type: KeyValue.new}, repeats: true
193
+
194
+ option '--http-field-remove', name: :http_field_remove
195
+ option '--http-cookie', name: :http_cookie
196
+ option '--http-payload', name: :http_payload
197
+
198
+ option '--show', name: :show
199
+ option '--noshow', name: :hide
200
+ option '--pcap', name: :pcap, value: true
201
+ option '--packet-trace', name: :packet_trace
202
+ option '--pfring', name: :pfring
203
+ option '--resume-index', name: :resume_index
204
+ option '--resume-count', name: :resume_count
205
+ option '--shards', name: :shards, value: {type: Shards.new}
206
+ option '--rotate', name: :rotate, value: true
207
+ option '--rotate-offset', name: :rotate_offset, value: true
208
+ option '--rotate-size', name: :rotate_size, value: true
209
+ option '--rotate-dir', name: :rotate_dir, value: {type: InputDir.new}
210
+ option '--seed', name: :seed, value: {type: Num.new}
211
+ option '--regress', name: :regress
212
+ option '--ttl', name: :ttl, value: {type: Num.new}
213
+ option '--wait', name: :wait, value: {type: Num.new}
214
+ option '--offline', name: :offline
215
+ option '-sL', name: :print_list
216
+ option '--interactive', name: :interactive
217
+ option '--output-format', name: :output_format, value: {
218
+ type: Enum[
219
+ :xml,
220
+ :binary,
221
+ :grepable,
222
+ :list,
223
+ :JSON
224
+ ]
225
+ }
226
+ option '--output-filename', name: :output_file, value: true
227
+ option '-oB', name: :output_binary, value: true
228
+ option '-oX', name: :output_xml, value: true
229
+ option '-oG', name: :output_grepable, value: true
230
+ option '-oJ', name: :output_json, value: true
231
+ option '-oL', name: :output_list, value: true
232
+ option '--readscan', name: :read_scan, value: {type: InputFile.new}
233
+ option '-V', name: :version
234
+ option '-h', name: :help
235
+
236
+ argument :ips, repeats: true
237
+ end
238
+
239
+ end
240
+ end
@@ -6,8 +6,18 @@ module Masscan
6
6
  #
7
7
  # Represents an output file.
8
8
  #
9
+ # ## Example
10
+ #
11
+ # output_file = Masscan::OutputFile.new('masscan.json')
12
+ # output_file.each do |record|
13
+ # p record
14
+ # end
15
+ #
9
16
  class OutputFile
10
17
 
18
+ # Mapping of formats to parsers.
19
+ #
20
+ # @api semipublic
11
21
  PARSERS = {
12
22
  binary: Parsers::Binary,
13
23
  list: Parsers::List,
@@ -29,6 +39,8 @@ module Masscan
29
39
  # The parser for the output file format.
30
40
  #
31
41
  # @return [Parsers::Binary, Parsers::JSON, Parsers::List]
42
+ #
43
+ # @api private
32
44
  attr_reader :parser
33
45
 
34
46
  #
@@ -52,6 +64,22 @@ module Masscan
52
64
  end
53
65
  end
54
66
 
67
+ # Mapping of file extensions to formats
68
+ #
69
+ # @api semipublic
70
+ FILE_FORMATS = {
71
+ '.bin' => :binary,
72
+ '.dat' => :binary,
73
+
74
+ '.txt' => :list,
75
+ '.list' => :list,
76
+
77
+ '.json' => :json,
78
+ '.ndjson' => :ndjson,
79
+
80
+ '.xml' => :xml
81
+ }
82
+
55
83
  #
56
84
  # Infers the format from the output file's extension name.
57
85
  #
@@ -64,14 +92,10 @@ module Masscan
64
92
  # @raise [ArgumentError]
65
93
  # The output format could not be inferred from the file's name.
66
94
  #
95
+ # @api semipublic
96
+ #
67
97
  def self.infer_format(path)
68
- case File.extname(path)
69
- when '.bin', '.dat' then :binary
70
- when '.txt', '.list' then :list
71
- when '.json' then :json
72
- when '.ndjson' then :ndjson
73
- when '.xml' then :xml
74
- else
98
+ FILE_FORMATS.fetch(File.extname(path)) do
75
99
  raise(ArgumentError,"could not infer format of #{path}")
76
100
  end
77
101
  end
@@ -96,5 +120,17 @@ module Masscan
96
120
  end
97
121
  end
98
122
 
123
+ #
124
+ # Converts the output file to a String.
125
+ #
126
+ # @return [String]
127
+ # The path to the output file.
128
+ #
129
+ # @since 0.2.0
130
+ #
131
+ def to_s
132
+ @path
133
+ end
134
+
99
135
  end
100
136
  end
@@ -64,7 +64,7 @@ module Masscan
64
64
  timestamp: parse_timestamp(timestamp)
65
65
  )
66
66
  elsif line.start_with?('banner ')
67
- type, ip_proto, port, ip, timestamp, app_proto, banner = line.split(' ',7)
67
+ type, ip_proto, port, ip, timestamp, app_proto, payload = line.split(' ',7)
68
68
 
69
69
  yield Banner.new(
70
70
  protocol: parse_ip_protocol(ip_proto),
@@ -72,11 +72,28 @@ module Masscan
72
72
  ip: parse_ip(ip),
73
73
  timestamp: parse_timestamp(timestamp),
74
74
  app_protocol: parse_app_protocol(app_proto),
75
- payload: banner
75
+ payload: parse_payload(payload)
76
76
  )
77
77
  end
78
78
  end
79
79
  end
80
+
81
+ #
82
+ # Parses a payload string and removes any `\\xXX` hex escaped characters.
83
+ #
84
+ # @param [String] payload
85
+ # The payload string to unescape.
86
+ #
87
+ # @return [String]
88
+ # The raw payload string.
89
+ #
90
+ # @api private
91
+ #
92
+ def self.parse_payload(payload)
93
+ payload.gsub(/\\x[0-9a-f]{2}/) do |hex_escape|
94
+ hex_escape[2..].to_i(16).chr
95
+ end
96
+ end
80
97
  end
81
98
  end
82
99
  end
@@ -1,99 +1,50 @@
1
- require 'masscan/task'
2
-
3
- require 'rprogram/program'
1
+ require 'masscan/command'
4
2
 
5
3
  module Masscan
6
4
  #
7
- # Represents the `masscan` program.
5
+ # @deprecated Please use {Command} instead.
8
6
  #
9
- class Program < RProgram::Program
10
-
11
- name_program 'masscan'
7
+ class Program < Command
12
8
 
13
9
  #
14
- # Finds the `masscan` program and performs a scan.
10
+ # Runs `masscan`.
15
11
  #
16
12
  # @param [Hash{Symbol => Object}] options
17
13
  # Additional options for masscan.
18
14
  #
19
- # @param [Hash{Symbol => Object}] exec_options
20
- # Additional exec-options.
21
- #
22
- # @yield [task]
23
- # If a block is given, it will be passed a task object
15
+ # @yield [masscan]
16
+ # If a block is given, it will be passed the new masscan instance
24
17
  # used to specify options for masscan.
25
18
  #
26
- # @yieldparam [Task] task
27
- # The masscan task object.
19
+ # @yieldparam [Masscan] masscan
20
+ # The masscan instance.
28
21
  #
29
22
  # @return [Boolean]
30
23
  # Specifies whether the command exited normally.
31
24
  #
32
- # @example Specifying `masscan` options via a Hash.
33
- # Masscan::Program.scan(
25
+ # @example Specifying `masscan` options via a Hash:
26
+ # Masscan::Command.scan(
34
27
  # ips: '192.168.1.1/24',
35
28
  # ports: [22, 80, 443],
36
29
  # )
37
30
  #
38
- # @example Specifying `masscan` options via a {Task} object.
39
- # Masscan::Program.scan do |masscan|
31
+ # @example Specifying `masscan` options via a block:
32
+ # Masscan::Command.scan do |masscan|
40
33
  # masscan.ips = '192.168.1.1/24'
41
34
  # masscan.ports = [22, 80, 443]
42
35
  # end
43
36
  #
44
- # @see #scan
45
- #
46
- def self.scan(options={},exec_options={},&block)
47
- find.scan(options,exec_options,&block)
37
+ def self.scan(options={},&block)
38
+ run(options,&block)
48
39
  end
49
40
 
50
41
  #
51
- # Finds the `masscan` program and performs a scan, but runs `masscan` under
52
- # `sudo`.
42
+ # Runs `masscan` but under `sudo`.
53
43
  #
54
44
  # @see scan
55
45
  #
56
- # @since 0.8.0
57
- #
58
- def self.sudo_scan(options={},exec_options={},&block)
59
- find.sudo_scan(options,exec_options,&block)
60
- end
61
-
62
- #
63
- # Performs a scan.
64
- #
65
- # @param [Hash{Symbol => Object}] options
66
- # Additional options for masscan.
67
- #
68
- # @param [Hash{Symbol => Object}] exec_options
69
- # Additional exec-options.
70
- #
71
- # @yield [task]
72
- # If a block is given, it will be passed a task object
73
- # used to specify options for masscan.
74
- #
75
- # @yieldparam [Task] task
76
- # The masscan task object.
77
- #
78
- # @return [Boolean]
79
- # Specifies whether the command exited normally.
80
- #
81
- # @see http://rubydoc.info/gems/rprogram/0.3.0/RProgram/Program#run-instance_method
82
- # For additional exec-options.
83
- #
84
- def scan(options={},exec_options={},&block)
85
- run_task(Task.new(options,&block),exec_options)
86
- end
87
-
88
- #
89
- # Performs a scan and runs `masscan` under `sudo`.
90
- #
91
- # @see #scan
92
- #
93
- # @since 0.8.0
94
- #
95
- def sudo_scan(options={},exec_options={},&block)
96
- sudo_task(Task.new(options,&block),exec_options)
46
+ def self.sudo_scan(options={},&block)
47
+ sudo(options,&block)
97
48
  end
98
49
 
99
50
  end
@@ -1,4 +1,4 @@
1
1
  module Masscan
2
2
  # ruby-masscan version
3
- VERSION = '0.1.1'
3
+ VERSION = '0.2.1'
4
4
  end
data/lib/masscan.rb CHANGED
@@ -1,2 +1,3 @@
1
+ require 'masscan/command'
1
2
  require 'masscan/program'
2
3
  require 'masscan/output_file'
@@ -0,0 +1,142 @@
1
+ require 'spec_helper'
2
+ require 'masscan/command'
3
+
4
+ describe Masscan::Command do
5
+ describe described_class::PortList do
6
+ describe "#validate" do
7
+ context "when given a single port number" do
8
+ let(:value) { 443 }
9
+
10
+ it "must return true" do
11
+ expect(subject.validate(value)).to be(true)
12
+ end
13
+ end
14
+
15
+ context "when given a Range of port numbers" do
16
+ let(:value) { (1..1024) }
17
+
18
+ it "must return true" do
19
+ expect(subject.validate(value)).to be(true)
20
+ end
21
+ end
22
+
23
+ context "when given an Array of port numbers" do
24
+ let(:value) { [80, 443] }
25
+
26
+ it "must return true" do
27
+ expect(subject.validate(value)).to be(true)
28
+ end
29
+
30
+ context "and the Array contains Ranges" do
31
+ let(:value) { [80, (1..42), 443] }
32
+
33
+ it "must return true" do
34
+ expect(subject.validate(value)).to be(true)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "#format" do
41
+ context "when given a single port number" do
42
+ let(:value) { 443 }
43
+
44
+ it "must return the formatted port number" do
45
+ expect(subject.format(value)).to eq(value.to_s)
46
+ end
47
+ end
48
+
49
+ context "when given a Range of port numbers" do
50
+ let(:value) { (1..1024) }
51
+
52
+ it "must return the formatted port number range (ex: 1-102)" do
53
+ expect(subject.format(value)).to eq("#{value.begin}-#{value.end}")
54
+ end
55
+ end
56
+
57
+ context "when given an Array of port numbers" do
58
+ let(:value) { [80, 443] }
59
+
60
+ it "must return the formatted list of port numbers" do
61
+ expect(subject.format(value)).to eq(value.join(','))
62
+ end
63
+
64
+ context "and the Array contains Ranges" do
65
+ let(:value) { [80, (1..42), 443] }
66
+
67
+ it "must return the formatted list of port numbers and port ranges" do
68
+ expect(subject.format(value)).to eq("#{value[0]},#{value[1].begin}-#{value[1].end},#{value[2]}")
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ describe described_class::Shards do
76
+ describe "#validate" do
77
+ context "when given a Rational value" do
78
+ let(:value) { (1/2r) }
79
+
80
+ it "must return true" do
81
+ expect(subject.validate(value)).to be(true)
82
+ end
83
+ end
84
+
85
+ context "when given an Array value" do
86
+ let(:value) { [1, 2] }
87
+
88
+ it "must return true" do
89
+ expect(subject.validate(value)).to be(true)
90
+ end
91
+
92
+ context "but the Array length is > 2" do
93
+ let(:value) { [1,2,3] }
94
+
95
+ it "must return a validation error" do
96
+ expect(subject.validate(value)).to eq(
97
+ [false, "cannot contain more tha two elements (#{value.inspect})"]
98
+ )
99
+ end
100
+ end
101
+ end
102
+
103
+ context "otherwise" do
104
+ let(:value) { :"1/2" }
105
+
106
+ it "must return true" do
107
+ expect(subject.validate(value)).to be(true)
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "#format" do
113
+ context "when given a Rational value" do
114
+ let(:value) { (1/2r) }
115
+
116
+ it "must format it into \#{numerator}/\#{denominator}" do
117
+ expect(subject.format(value)).to eq(
118
+ "#{value.numerator}/#{value.denominator}"
119
+ )
120
+ end
121
+ end
122
+
123
+ context "when given an Array value" do
124
+ let(:value) { [1, 2] }
125
+
126
+ it "must format it into \#{array[0]}/\#{array[1]}" do
127
+ expect(subject.format(value)).to eq(
128
+ "#{value[0]}/#{value[1]}"
129
+ )
130
+ end
131
+ end
132
+
133
+ context "otherwise" do
134
+ let(:value) { :"1/2" }
135
+
136
+ it "must convert the value to a String" do
137
+ expect(subject.format(value)).to eq(value.to_s)
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -68,6 +68,38 @@ describe Masscan::OutputFile do
68
68
  end
69
69
  end
70
70
 
71
+ describe "FILE_FORMATS" do
72
+ subject { described_class::FILE_FORMATS }
73
+
74
+ describe ".bin" do
75
+ it { expect(subject['.bin']).to be(:binary) }
76
+ end
77
+
78
+ describe ".dat" do
79
+ it { expect(subject['.dat']).to be(:binary) }
80
+ end
81
+
82
+ describe ".txt" do
83
+ it { expect(subject['.txt']).to be(:list) }
84
+ end
85
+
86
+ describe ".list" do
87
+ it { expect(subject['.list']).to be(:list) }
88
+ end
89
+
90
+ describe ".json" do
91
+ it { expect(subject['.json']).to be(:json) }
92
+ end
93
+
94
+ describe ".ndjson" do
95
+ it { expect(subject['.ndjson']).to be(:ndjson) }
96
+ end
97
+
98
+ describe ".xml" do
99
+ it { expect(subject['.xml']).to be(:xml) }
100
+ end
101
+ end
102
+
71
103
  describe "#initialize" do
72
104
  let(:path) { "/path/to/file.json" }
73
105
 
@@ -100,7 +132,9 @@ describe Masscan::OutputFile do
100
132
  end
101
133
  end
102
134
 
103
- subject { described_class.new(Fixtures.join('masscan.list')) }
135
+ let(:path) { Fixtures.join('masscan.list') }
136
+
137
+ subject { described_class.new(path) }
104
138
 
105
139
  describe "#each" do
106
140
  context "when given a block" do
@@ -132,4 +166,10 @@ describe Masscan::OutputFile do
132
166
  end
133
167
  end
134
168
  end
169
+
170
+ describe "#to_s" do
171
+ it "must return #path" do
172
+ expect(subject.to_s).to eq(path)
173
+ end
174
+ end
135
175
  end
@@ -104,6 +104,31 @@ describe Masscan::Parsers::List do
104
104
  expect(yielded_banner.service).to eq(service_keyword)
105
105
  expect(yielded_banner.payload).to eq(payload)
106
106
  end
107
+
108
+ context "when the payload field contains '\\xXX' hex escaped characters" do
109
+ let(:escaped_payload) do
110
+ "HTTP/1.0 404 Not Found\\x0d\\x0aContent-Type: text/html\\x0d\\x0aDate: Thu, 26 Aug 2021 06:47:52 GMT\\x0d\\x0aServer: ECS (sec/974D)\\x0d\\x0aContent-Length: 345\\x0d\\x0aConnection: close\\x0d\\x0a\\x0d"
111
+ end
112
+ let(:unescaped_payload) do
113
+ "HTTP/1.0 404 Not Found\r\nContent-Type: text/html\r\nDate: Thu, 26 Aug 2021 06:47:52 GMT\r\nServer: ECS (sec/974D)\r\nContent-Length: 345\r\nConnection: close\r\n\r"
114
+ end
115
+
116
+ let(:line) do
117
+ "banner #{protocol} #{port} #{ip} #{timestamp.to_i} #{service_name} #{escaped_payload}"
118
+ end
119
+
120
+ it "must unescape the '\\xXX' hex escaped characters" do
121
+ yielded_records = []
122
+
123
+ subject.parse(io) do |record|
124
+ yielded_records << record
125
+ end
126
+
127
+ yielded_banner = yielded_records.first
128
+
129
+ expect(yielded_banner.payload).to eq(unescaped_payload)
130
+ end
131
+ end
107
132
  end
108
133
  end
109
134
  end
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require 'rspec'
2
2
  require 'simplecov'
3
3
  SimpleCov.start
4
4
 
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-masscan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Postmodern
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-10 00:00:00.000000000 Z
11
+ date: 2023-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rprogram
14
+ name: command_mapper
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.3'
19
+ version: '0.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.3'
26
+ version: '0.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -62,6 +62,7 @@ files:
62
62
  - gemspec.yml
63
63
  - lib/masscan.rb
64
64
  - lib/masscan/banner.rb
65
+ - lib/masscan/command.rb
65
66
  - lib/masscan/output_file.rb
66
67
  - lib/masscan/parsers.rb
67
68
  - lib/masscan/parsers/binary.rb
@@ -70,9 +71,9 @@ files:
70
71
  - lib/masscan/parsers/plain_text.rb
71
72
  - lib/masscan/program.rb
72
73
  - lib/masscan/status.rb
73
- - lib/masscan/task.rb
74
74
  - lib/masscan/version.rb
75
75
  - ruby-masscan.gemspec
76
+ - spec/command_spec.rb
76
77
  - spec/fixtures/masscan.bin
77
78
  - spec/fixtures/masscan.json
78
79
  - spec/fixtures/masscan.list
@@ -85,7 +86,6 @@ files:
85
86
  - spec/parsers/parser_examples.rb
86
87
  - spec/parsers/plain_text_spec.rb
87
88
  - spec/spec_helper.rb
88
- - spec/task_spec.rb
89
89
  homepage: https://github.com/postmodern/ruby-masscan#readme
90
90
  licenses:
91
91
  - MIT
@@ -94,6 +94,7 @@ metadata:
94
94
  source_code_uri: https://github.com/postmodern/ruby-masscan
95
95
  bug_tracker_uri: https://github.com/postmodern/ruby-masscan/issues
96
96
  changelog_uri: https://github.com/postmodern/ruby-masscan/blob/master/ChangeLog.md
97
+ rubygems_mfa_required: 'true'
97
98
  post_install_message:
98
99
  rdoc_options: []
99
100
  require_paths:
@@ -110,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
111
  version: '0'
111
112
  requirements:
112
113
  - masscan >= 1.0.0
113
- rubygems_version: 3.2.22
114
+ rubygems_version: 3.3.26
114
115
  signing_key:
115
116
  specification_version: 4
116
117
  summary: A Ruby interface to masscan.
data/lib/masscan/task.rb DELETED
@@ -1,179 +0,0 @@
1
- require 'rprogram/task'
2
-
3
- module Masscan
4
- #
5
- # ## `masscan` options:
6
- #
7
- # * `--range` - `masscan.range`
8
- # * `-p` - `masscan.ports`
9
- # * `--banners` - `masscan.banners`
10
- # * `--rate` - `masscan.rate`
11
- # * `--conf` - `masscan.config_file`
12
- # * `--resume` - `masscan.resume`
13
- # * `--echo` - `masscan.echo`
14
- # * `--adapter` - `masscan.adapter`
15
- # * `--adapter-ip` - `masscan.adapter_ip`
16
- # * `--adapter-port` - `masscan.adapter_port`
17
- # * `--adapter-mac` - `masscan.adapter_mac`
18
- # * `--adapter-vlan` - `masscan.adapter_vlan`
19
- # * `--router-mac` - `masscan.router_mac`
20
- # * `--ping` - `masscan.ping`
21
- # * `--exclude` - `masscan.exclude`
22
- # * `--excludefile` - `masscan.exclude_file`
23
- # * `--includefile` - `masscan.include_file`
24
- # * `--append-output` - `masscan.append_output`
25
- # * `--iflist` - `masscan.list_interfaces`
26
- # * `--retries` - `masscan.retries`
27
- # * `--nmap` - `masscan.nmap_help`
28
- # * `--pcap-payloads` - `masscan.pcap_payloads`
29
- # * `--nmap-payloads` - `masscan.nmap_payloads`
30
- # * `--http-method` - `masscan.http_method`
31
- # * `--http-url` - `masscan.http_url`
32
- # * `--http-version` - `masscan.http_version`
33
- # * `--http-host` - `masscan.http_host`
34
- # * `--http-user-agent` - `masscan.http_user_agent`
35
- # * `--http-field` - `masscan.http_field`
36
- # * `--http-field-remove` - `masscan.http_field_remove`
37
- # * `--http-cookie` - `masscan.http_cookie`
38
- # * `--http-payload` - `masscan.http_payload`
39
- # * `--show` - `masscan.show`
40
- # * `--noshow` - `masscan.hide`
41
- # * `--pcap` - `masscan.pcap`
42
- # * `--packet-trace` - `masscan.packet_trace`
43
- # * `--pfring` - `masscan.pfring`
44
- # * `--resume-index` - `masscan.resume_index`
45
- # * `--resume-count` - `masscan.resume_count`
46
- # * `--shards` - `masscan.shards`
47
- # * `--rotate` - `masscan.rotate`
48
- # * `--rotate-offset` - `masscan.rotate_offset`
49
- # * `--rotate-size` - `masscan.rotate_size`
50
- # * `--rotate-dir` - `masscan.rotate_dir`
51
- # * `--seed` - `masscan.seed`
52
- # * `--regress` - `masscan.regress`
53
- # * `--ttl` - `masscan.ttl`
54
- # * `--wait` - `masscan.wait`
55
- # * `--offline` - `masscan.offline`
56
- # * `-sL` - `masscan.print_list`
57
- # * `--interactive` - `masscan.interactive`
58
- # * `--output-format` - `masscan.output_format`
59
- # * `--output-filename` - `masscan.output_file`
60
- # * `-oB` - `masscan.output_binary`
61
- # * `-oX` - `masscan.output_xml`
62
- # * `-oG` - `masscan.output_grepable`
63
- # * ` -oJ` - `masscan.output_json`
64
- # * `-oL` - `masscan.output_list`
65
- # * `--readscan` - `masscan.read_scan`
66
- # * `-V` - `masscan.version`
67
- # * `-h` - `masscan.help`
68
- #
69
- # @see https://github.com/robertdavidgraham/masscan/blob/master/doc/masscan.8.markdown
70
- #
71
- class Task < RProgram::Task
72
-
73
- long_option flag: '--range', name: :range, separator: ','
74
- short_option flag: '-p', name: :ports do |opt,value|
75
- unless value.empty?
76
- [opt.flag, format_port_list(value)]
77
- end
78
- end
79
- long_option flag: '--banners', name: :banners
80
- long_option flag: '--rate', name: :rate
81
- long_option flag: '--conf', name: :config_file
82
- long_option flag: '--resume', name: :resume
83
- long_option flag: '--echo', name: :echo
84
- long_option flag: '--adapter', name: :adapter
85
- long_option flag: '--adapter-ip', name: :adapter_ip
86
- long_option flag: '--adapter-port', name: :adapter_port
87
- long_option flag: '--adapter-mac', name: :adapter_mac
88
- long_option flag: '--adapter-vlan', name: :adapter_vlan
89
- long_option flag: '--router-mac', name: :router_mac
90
- long_option flag: '--ping', name: :ping
91
- long_option flag: '--exclude', name: :exclude, separator: ','
92
- long_option flag: '--excludefile', name: :exclude_file
93
- long_option flag: '--includefile', name: :include_file
94
- long_option flag: '--append-output', name: :append_output
95
- long_option flag: '--iflist', name: :list_interfaces
96
- long_option flag: '--retries', name: :retries
97
- long_option flag: '--nmap', name: :nmap_help
98
- long_option flag: '--pcap-payloads', name: :pcap_payloads
99
- long_option flag: '--nmap-payloads', name: :nmap_payloads
100
-
101
- long_option flag: '--http-method', name: :http_method
102
- long_option flag: '--http-url', name: :http_url
103
- long_option flag: '--http-version', name: :http_version
104
- long_option flag: '--http-host', name: :http_host
105
- long_option flag: '--http-user-agent', name: :http_user_agent
106
-
107
- long_option flag: '--http-field', multiple: true do |opt,value|
108
- name, value = value.first
109
-
110
- [opt.flag, "#{name}:#{value}"]
111
- end
112
-
113
- long_option flag: '--http-field-remove', name: :http_field_remove
114
- long_option flag: '--http-cookie', name: :http_cookie
115
- long_option flag: '--http-payload', name: :http_payload
116
-
117
- long_option flag: '--show', name: :show
118
- long_option flag: '--noshow', name: :hide
119
- long_option flag: '--pcap', name: :pcap
120
- long_option flag: '--packet-trace', name: :packet_trace
121
- long_option flag: '--pfring', name: :pfring
122
- long_option flag: '--resume-index', name: :resume_index
123
- long_option flag: '--resume-count', name: :resume_count
124
- long_option flag: '--shards', name: :shards do |opt,value|
125
- case value.length
126
- when 2 then [opt.flag, "#{value[0]}/#{value[1]}"]
127
- when 1 then [opt.flag, "#{value[0]}"]
128
- else
129
- raise(ArgumentError,"#{self}#shards= does not accept more than two values")
130
- end
131
- end
132
- long_option flag: '--rotate', name: :rotate
133
- long_option flag: '--rotate-offset', name: :rotate_offset
134
- long_option flag: '--rotate-size', name: :rotate_size
135
- long_option flag: '--rotate-dir', name: :rotate_dir
136
- long_option flag: '--seed', name: :seed
137
- long_option flag: '--regress', name: :regress
138
- long_option flag: '--ttl', name: :ttl
139
- long_option flag: '--wait', name: :wait
140
- long_option flag: '--offline', name: :offline
141
- short_option flag: '-sL', name: :print_list
142
- long_option flag: '--interactive', name: :interactive
143
- long_option flag: '--output-format', name: :output_format
144
- long_option flag: '--output-filename', name: :output_file
145
- short_option flag: '-oB', name: :output_binary
146
- short_option flag: '-oX', name: :output_xml
147
- short_option flag: '-oG', name: :output_grepable
148
- short_option flag:' -oJ', name: :output_json
149
- short_option flag: '-oL', name: :output_list
150
- long_option flag: '--readscan', name: :read_scan
151
- short_option :flag => '-V', :name => :version
152
- short_option :flag => '-h', :name => :help
153
-
154
- non_option :tailing => true, :name => :ips
155
-
156
- private
157
-
158
- #
159
- # Formats a port list.
160
- #
161
- # @param [Array<Integer,Range>] ports
162
- # The port ranges.
163
- #
164
- # @return [String]
165
- # Comma separated string.
166
- #
167
- def self.format_port_list(ports)
168
- ports.map { |port|
169
- case port
170
- when Range
171
- "#{port.first}-#{port.last}"
172
- else
173
- port.to_s
174
- end
175
- }.join(',')
176
- end
177
-
178
- end
179
- end
data/spec/task_spec.rb DELETED
@@ -1,121 +0,0 @@
1
- require 'spec_helper'
2
- require 'masscan/task'
3
-
4
- describe Masscan::Task do
5
- describe "#ports=" do
6
- context "when given an empty Array" do
7
- before { subject.ports = [] }
8
-
9
- it "should ignore empty port Arrays" do
10
- subject.ports = []
11
-
12
- expect(subject.arguments).to eq([])
13
- end
14
- end
15
-
16
- context "when given a String" do
17
- let(:ports) { '80,21,25' }
18
-
19
- before { subject.ports = ports }
20
-
21
- it "should emit the String as is" do
22
- expect(subject.arguments).to eq(['-p', ports])
23
- end
24
- end
25
-
26
- context "when given an Array of Strings" do
27
- let(:ports) { %w[80 21 25] }
28
-
29
- before { subject.ports = ports }
30
-
31
- it "should format an Array of String ports" do
32
- expect(subject.arguments).to eq(['-p', ports.join(',')])
33
- end
34
- end
35
-
36
- context "when given an Array of Integers" do
37
- let(:ports) { [80, 21, 25] }
38
-
39
- before { subject.ports = ports }
40
-
41
- it "should format an Array of Integer ports" do
42
- expect(subject.arguments).to eq(['-p', ports.join(',')])
43
- end
44
- end
45
-
46
- context "when given an Array containing a Range" do
47
- let(:ports) { [80, 21..25] }
48
-
49
- before { subject.ports = ports }
50
-
51
- it "should format the Range" do
52
- expect(subject.arguments).to eq([
53
- '-p', "#{ports[0]},#{ports[1].begin}-#{ports[1].end}"
54
- ])
55
- end
56
- end
57
- end
58
-
59
- describe "#shards=" do
60
- context "when given a Rational value" do
61
- let(:rational) { (1/2r) }
62
-
63
- before { subject.shards = rational }
64
-
65
- it "must format it into \#{numerator}/\#{denominator}" do
66
- expect(subject.arguments).to eq([
67
- "--shards", "#{rational.numerator}/#{rational.denominator}"
68
- ])
69
- end
70
- end
71
-
72
- context "when given an Array value" do
73
- let(:array) { [1, 2] }
74
-
75
- before { subject.shards = array }
76
-
77
- it "must format it into \#{array[0]}/\#{array[1]}" do
78
- expect(subject.arguments).to eq([
79
- "--shards", "#{array[0]}/#{array[1]}"
80
- ])
81
- end
82
-
83
- context "but the Array length is > 2" do
84
- let(:array) { [1,2,3] }
85
-
86
- before { subject.shards = array }
87
-
88
- it do
89
- expect {
90
- subject.arguments
91
- }.to raise_error(ArgumentError,"#{described_class}#shards= does not accept more than two values")
92
- end
93
- end
94
- end
95
-
96
- context "otherwise" do
97
- let(:object) { :"1/2" }
98
-
99
- before { subject.shards = object }
100
-
101
- it "must convert the value to a String" do
102
- expect(subject.arguments).to eq([
103
- "--shards", object.to_s
104
- ])
105
- end
106
- end
107
- end
108
-
109
- describe "#http_field=" do
110
- let(:name) { 'X-Foo' }
111
- let(:value) { 'bar' }
112
-
113
- before { subject.http_field = [ [name, value] ] }
114
-
115
- it "must join two values together with a ':'" do
116
- expect(subject.arguments).to eq([
117
- "--http-field", "#{name}:#{value}"
118
- ])
119
- end
120
- end
121
- end