ruby-masscan 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,106 @@
1
+ require 'masscan/parsers/plain_text'
2
+ require 'masscan/status'
3
+ require 'masscan/banner'
4
+
5
+ require 'json'
6
+
7
+ module Masscan
8
+ module Parsers
9
+ #
10
+ # Parses the `masscan -oJ` and `masscan --output-format ndjson` output
11
+ # formats.
12
+ #
13
+ # @api semipublic
14
+ #
15
+ module JSON
16
+ extend PlainText
17
+
18
+ #
19
+ # Opens a JSON file for parsing.
20
+ #
21
+ # @param [String] path
22
+ # The path to the file.
23
+ #
24
+ # @yield [file]
25
+ # If a block is given, it will be passed the opened file.
26
+ # Once the block returns, the file will be closed.
27
+ #
28
+ # @yieldparam [File] file
29
+ # The opened file.
30
+ #
31
+ # @return [File]
32
+ # If no block was given, the opened file will be returned.
33
+ #
34
+ def self.open(path,&block)
35
+ File.open(path,&block)
36
+ end
37
+
38
+ #
39
+ # Parses the masscan JSON or ndjson data.
40
+ #
41
+ # @param [#each_line] io
42
+ # The IO object to read from.
43
+ #
44
+ # @yield [record]
45
+ # If a block is given, it will be passed each parsed record.
46
+ #
47
+ # @yieldparam [Status, Banner] record
48
+ # A parsed record, either a {Status} or a {Banner} object.
49
+ #
50
+ # @return [Enumerator]
51
+ # If no block is given, it will return an Enumerator.
52
+ #
53
+ def self.parse(io)
54
+ return enum_for(__method__,io) unless block_given?
55
+
56
+ io.each_line do |line|
57
+ line.chomp!
58
+
59
+ if line == "," || line == "[" || line == "]"
60
+ # skip
61
+ else
62
+ json = ::JSON.parse(line)
63
+
64
+ ip = parse_ip(json['ip'])
65
+ timestamp = parse_timestamp(json['timestamp'])
66
+
67
+ if (ports_json = json['ports'])
68
+ if (port_json = ports_json.first)
69
+ proto = parse_ip_protocol(port_json['proto'])
70
+ port = port_json['port']
71
+
72
+ if (service_json = port_json['service'])
73
+ service_name = parse_app_protocol(service_json['name'])
74
+ service_banner = service_json['banner']
75
+
76
+ yield Banner.new(
77
+ proto,
78
+ port,
79
+ ip,
80
+ timestamp,
81
+ service_name,
82
+ service_banner
83
+ )
84
+ else
85
+ status = parse_status(port_json['status'])
86
+ ttl = port_json['ttl']
87
+ reason = parse_reason(port_json['reason'])
88
+
89
+ yield Status.new(
90
+ status,
91
+ proto,
92
+ port,
93
+ reason,
94
+ ttl,
95
+ ip,
96
+ timestamp
97
+ )
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,84 @@
1
+ require 'masscan/parsers/plain_text'
2
+ require 'masscan/status'
3
+ require 'masscan/banner'
4
+
5
+ module Masscan
6
+ module Parsers
7
+ #
8
+ # Parses the `masscan -oL` output format.
9
+ #
10
+ # @api semipublic
11
+ #
12
+ module List
13
+ extend PlainText
14
+
15
+ #
16
+ # Opens a list file for parsing.
17
+ #
18
+ # @param [String] path
19
+ # The path to the file.
20
+ #
21
+ # @yield [file]
22
+ # If a block is given, it will be passed the opened file.
23
+ # Once the block returns, the file will be closed.
24
+ #
25
+ # @yieldparam [File] file
26
+ # The opened file.
27
+ #
28
+ # @return [File]
29
+ # If no block was given, the opened file will be returned.
30
+ #
31
+ def self.open(path,&block)
32
+ File.open(path,&block)
33
+ end
34
+
35
+ #
36
+ # Parses the masscan simple list data.
37
+ #
38
+ # @param [#each_line] io
39
+ # The IO object to read from.
40
+ #
41
+ # @yield [record]
42
+ # If a block is given, it will be passed each parsed record.
43
+ #
44
+ # @yieldparam [Status, Banner] record
45
+ # A parsed record, either a {Status} or a {Banner} object.
46
+ #
47
+ # @return [Enumerator]
48
+ # If no block is given, it will return an Enumerator.
49
+ #
50
+ def self.parse(io)
51
+ return enum_for(__method__,io) unless block_given?
52
+
53
+ io.each_line do |line|
54
+ line.chomp!
55
+
56
+ if line.start_with?('open ') || line.start_with?('closed ')
57
+ type, ip_proto, port, ip, timestamp = line.split(' ',5)
58
+
59
+ yield Status.new(
60
+ parse_status(type),
61
+ parse_ip_protocol(ip_proto),
62
+ port.to_i,
63
+ nil,
64
+ nil,
65
+ parse_ip(ip),
66
+ parse_timestamp(timestamp)
67
+ )
68
+ elsif line.start_with?('banner ')
69
+ type, ip_proto, port, ip, timestamp, app_proto, banner = line.split(' ',7)
70
+
71
+ yield Banner.new(
72
+ parse_ip_protocol(ip_proto),
73
+ port.to_i,
74
+ parse_ip(ip),
75
+ parse_timestamp(timestamp),
76
+ parse_app_protocol(app_proto),
77
+ banner
78
+ )
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,151 @@
1
+ module Masscan
2
+ module Parsers
3
+ #
4
+ # Common methods for parsing plain-text data.
5
+ #
6
+ # @api private
7
+ #
8
+ module PlainText
9
+ # Mapping of status strings to their keywords.
10
+ STATUSES = {
11
+ 'open' => :open,
12
+ 'closed' => :closed
13
+ }
14
+
15
+ #
16
+ # Parses a status string.
17
+ #
18
+ # @param [String] status
19
+ # The status string to parse.
20
+ #
21
+ # @return [:open, :closed, String]
22
+ # The status keyword or a String if the status wasn't in {STATUSES}.
23
+ #
24
+ def parse_status(status)
25
+ STATUSES[status] || status
26
+ end
27
+
28
+ REASONS = {
29
+ 'fin' => :fin,
30
+ 'syn' => :syn,
31
+ 'rst' => :rst,
32
+ 'psh' => :psh,
33
+ 'ack' => :ack,
34
+ 'urg' => :urg,
35
+ 'ece' => :ece,
36
+ 'cwr' => :cwr
37
+ }
38
+
39
+ #
40
+ # Parses a reason string.
41
+ #
42
+ # @param [String] reason
43
+ # The reason string to parse.
44
+ #
45
+ # @return [Array<:fin, :syn, :rst, :psh, :ack, :urg, :ece, :cwr>]
46
+ # The reason keywords or a String if the flag wasn't in {REASONS}.
47
+ #
48
+ def parse_reason(reason)
49
+ flags = reason.split('-')
50
+ flags.map! { |flag| REASONS[flag] || flag }
51
+ flags
52
+ end
53
+
54
+ # Mapping of IP protocol names to their keywords.
55
+ IP_PROTOCOLS = {
56
+ 'tcp' => :tcp,
57
+ 'udp' => :udp,
58
+ 'icmp' => :icmp,
59
+ 'sctp' => :sctp
60
+ }
61
+
62
+ #
63
+ # Parses an IP protocol name.
64
+ #
65
+ # @param [String] proto
66
+ # The IP protocol name.
67
+ #
68
+ # @return [:tcp, :udp, :icmp, :sctp, String]
69
+ # The IP protocol keyword or a String if the IP protocol wasn't in
70
+ # {IP_PROTOCOLS}.
71
+ #
72
+ def parse_ip_protocol(proto)
73
+ IP_PROTOCOLS[proto] || proto
74
+ end
75
+
76
+ # Mapping of application protocol names to their keywords.
77
+ APP_PROTOCOLS = {
78
+ "ssh1" => :ssh1,
79
+ "ssh2" => :ssh2,
80
+ "ssh" => :ssh,
81
+ "http" => :http,
82
+ "ftp" => :ftp,
83
+ "dns-ver" => :dns_ver,
84
+ "snmp" => :smtp,
85
+ "nbtstat" => :nbtstat,
86
+ "ssl" => :ssl3,
87
+ "smtp" => :smtp,
88
+ "smb" => :smb,
89
+ "pop" => :pop,
90
+ "imap" => :imap,
91
+ "X509" => :x509,
92
+ "zeroaccess" => :zeroaccess,
93
+ "title" => :html_title,
94
+ "html" => :html,
95
+ "ntp" => :ntp,
96
+ "vuln" => :vuln,
97
+ "heartbleed" => :heartbleed,
98
+ "ticketbleed" => :ticketbleed,
99
+ "vnc" => :vnc,
100
+ "safe" => :safe,
101
+ "memcached" => :memcached,
102
+ "scripting" => :scripting,
103
+ "versioning" => :versioning,
104
+ "coap" => :coap,
105
+ "telnet" => :telnet,
106
+ "rdp" => :rdp,
107
+ "http.server" => :http_server
108
+ }
109
+
110
+ #
111
+ # Parses an application protocol name.
112
+ #
113
+ # @param [String] proto
114
+ # The application protocol name.
115
+ #
116
+ # @return [Symbol, String]
117
+ # The IP protocol keyword or a String if the application protocol wasn't
118
+ # in {APP_PROTOCOLS}.
119
+ #
120
+ def parse_app_protocol(proto)
121
+ APP_PROTOCOLS[proto] || proto
122
+ end
123
+
124
+ #
125
+ # Parses a timestamp.
126
+ #
127
+ # @param [String] timestamp
128
+ # The numeric timestamp value.
129
+ #
130
+ # @return [Time]
131
+ # The parsed timestamp value.
132
+ #
133
+ def parse_timestamp(timestamp)
134
+ Time.at(timestamp.to_i)
135
+ end
136
+
137
+ #
138
+ # Parses an IP address.
139
+ #
140
+ # @param [String] ip
141
+ # The string representation of the IP address.
142
+ #
143
+ # @return [IPAddr]
144
+ # The parsed IP address.
145
+ #
146
+ def parse_ip(ip)
147
+ IPAddr.new(ip)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,3 @@
1
+ require 'masscan/parsers/binary'
2
+ require 'masscan/parsers/list'
3
+ require 'masscan/parsers/json'
@@ -0,0 +1,100 @@
1
+ require 'masscan/task'
2
+
3
+ require 'rprogram/program'
4
+
5
+ module Masscan
6
+ #
7
+ # Represents the `masscan` program.
8
+ #
9
+ class Program < RProgram::Program
10
+
11
+ name_program 'masscan'
12
+
13
+ #
14
+ # Finds the `masscan` program and performs a scan.
15
+ #
16
+ # @param [Hash{Symbol => Object}] options
17
+ # Additional options for masscan.
18
+ #
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
24
+ # used to specify options for masscan.
25
+ #
26
+ # @yieldparam [Task] task
27
+ # The masscan task object.
28
+ #
29
+ # @return [Boolean]
30
+ # Specifies whether the command exited normally.
31
+ #
32
+ # @example Specifying `masscan` options via a Hash.
33
+ # Masscan::Program.scan(
34
+ # ips: '192.168.1.1/24',
35
+ # ports: [22, 80, 443],
36
+ # )
37
+ #
38
+ # @example Specifying `masscan` options via a {Task} object.
39
+ # Masscan::Program.scan do |masscan|
40
+ # masscan.ips = '192.168.1.1/24'
41
+ # masscan.ports = [22, 80, 443]
42
+ # end
43
+ #
44
+ # @see #scan
45
+ #
46
+ def self.scan(options={},exec_options={},&block)
47
+ find.scan(options,exec_options,&block)
48
+ end
49
+
50
+ #
51
+ # Finds the `masscan` program and performs a scan, but runs `masscan` under
52
+ # `sudo`.
53
+ #
54
+ # @see scan
55
+ #
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)
97
+ end
98
+
99
+ end
100
+ end