ruby-ncrack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/ncrack/xml.rb ADDED
@@ -0,0 +1,234 @@
1
+ require 'ncrack/xml/service'
2
+
3
+ require 'nokogiri'
4
+
5
+ module Ncrack
6
+ #
7
+ # Represents an ncrack XML file or XML data.
8
+ #
9
+ # ## Examples
10
+ #
11
+ # require 'ncrack/xml'
12
+ #
13
+ # Ncrack::XML.open('ncrack.xml') do |xml|
14
+ # xml.each_service do |service|
15
+ # puts "#{service.address} #{service.port.number}/#{service.port.name}:"
16
+ #
17
+ # service.each_credentials.each do |credentials|
18
+ # puts " #{credentials}"
19
+ # end
20
+ # end
21
+ # end
22
+ #
23
+ class XML
24
+
25
+ # The parsed XML document.
26
+ #
27
+ # @return [Nokogiri::XML::Node]
28
+ #
29
+ # @api private
30
+ attr_reader :doc
31
+
32
+ # The path to the XML file.
33
+ #
34
+ # @return [String, nil]
35
+ attr_reader :path
36
+
37
+ #
38
+ # Creates a new XML object.
39
+ #
40
+ # @param [Nokogiri::XML] doc
41
+ # The parsed XML document.
42
+ #
43
+ # @param [String, nil] path
44
+ # The path to the XML file.
45
+ #
46
+ # @yield [xml]
47
+ # If a block is given, it will be passed the newly created XML
48
+ # parser.
49
+ #
50
+ # @yieldparam [XML] xml
51
+ # The newly created XML parser.
52
+ #
53
+ # @api private
54
+ #
55
+ def initialize(doc, path: nil)
56
+ @doc = doc
57
+ @path = File.expand_path(path) if path
58
+
59
+ yield self if block_given?
60
+ end
61
+
62
+ #
63
+ # Parses the given XML String.
64
+ #
65
+ # @param [String] xml
66
+ # The XML String.
67
+ #
68
+ # @yield [xml]
69
+ # If a block is given, it will be passed the newly created XML
70
+ # parser.
71
+ #
72
+ # @yieldparam [XML] xml
73
+ # The newly created XML parser.
74
+ #
75
+ # @return [XML]
76
+ # The parsed XML.
77
+ #
78
+ # @api public
79
+ #
80
+ def self.parse(xml,&block)
81
+ new(Nokogiri::XML(xml),&block)
82
+ end
83
+
84
+ #
85
+ # Opens an parses an XML file.
86
+ #
87
+ # @param [String] path
88
+ # The path to the XML file.
89
+ #
90
+ # @yield [xml]
91
+ # If a block is given, it will be passed the newly created XML
92
+ # parser.
93
+ #
94
+ # @yieldparam [XML] xml
95
+ # The newly created XML parser.
96
+ #
97
+ # @return [XML]
98
+ # The parsed XML.
99
+ #
100
+ # @api public
101
+ #
102
+ def self.open(path,&block)
103
+ path = File.expand_path(path)
104
+
105
+ new(Nokogiri::XML(File.open(path)), path: path, &block)
106
+ end
107
+
108
+ #
109
+ # The scanner that produced the XML (aka `ncrack`).
110
+ #
111
+ # @return [String]
112
+ # The value of the `scanner` attribute.
113
+ #
114
+ def scanner
115
+ @scanner ||= @doc.root['scanner']
116
+ end
117
+
118
+ #
119
+ # Additional command-line arguments passed to `ncrack`.
120
+ #
121
+ # @return [String]
122
+ # The value of the `args` attribute.
123
+ #
124
+ def args
125
+ @args ||= @doc.root['args']
126
+ end
127
+
128
+ #
129
+ # The start time.
130
+ #
131
+ # @return [Time]
132
+ # The parsed value of the `start` attribute.
133
+ #
134
+ def start
135
+ @start ||= Time.at(@doc.root['start'].to_i)
136
+ end
137
+
138
+ #
139
+ # The version of `ncrack`.
140
+ #
141
+ # @return [String]
142
+ # The value of the `version` attribute.
143
+ #
144
+ def version
145
+ @version ||= @doc.root['version']
146
+ end
147
+
148
+ #
149
+ # The version of the `ncrack` XML schema.
150
+ #
151
+ # @return [String]
152
+ # The value of the `xmloutputversion` attribute.
153
+ #
154
+ def xml_output_version
155
+ @xml_output_version ||= @doc.root['xmloutputversion']
156
+ end
157
+
158
+ #
159
+ # The verbosity level.
160
+ #
161
+ # @return [Integer]
162
+ # The parsed value of the `level` attribute of the `verbose` child
163
+ # element.
164
+ #
165
+ def verbose
166
+ @verbose ||= @doc.at_xpath('/ncrackrun/verbose')['level'].to_i
167
+ end
168
+
169
+ #
170
+ # The debugging level.
171
+ #
172
+ # @return [Integer]
173
+ # The parsed value of the `level` attribute of the `debugging` child
174
+ # element.
175
+ #
176
+ def debugging
177
+ @debugging ||= @doc.at_xpath('/ncrackrun/debugging')['level'].to_i
178
+ end
179
+
180
+ #
181
+ # Enumerates over every service.
182
+ #
183
+ # @yield [service]
184
+ # If a block is given, it will be passed every service object.
185
+ #
186
+ # @yieldparam [Service] service
187
+ # A service object.
188
+ #
189
+ # @return [Enumerator]
190
+ # If no block is given, an Enumerator object will be returned.
191
+ #
192
+ def each_service
193
+ return enum_for(__method__) unless block_given?
194
+
195
+ @doc.root.xpath('/ncrackrun/service').each do |node|
196
+ yield Service.new(node)
197
+ end
198
+ end
199
+
200
+ #
201
+ # All service object.
202
+ #
203
+ # @return [Array<Service>]
204
+ #
205
+ def services
206
+ each_service.to_a
207
+ end
208
+
209
+ #
210
+ # The first service object.
211
+ #
212
+ # @return [Service, nik]
213
+ #
214
+ def service
215
+ each_service.first
216
+ end
217
+
218
+ #
219
+ # Converts the XML to a String.
220
+ #
221
+ # @return [String]
222
+ # The path to the XML if {#path} is set, or the XML if the XML was parsed
223
+ # from a String.
224
+ #
225
+ def to_s
226
+ if @path
227
+ @path
228
+ else
229
+ @doc.to_s
230
+ end
231
+ end
232
+
233
+ end
234
+ end
data/lib/ncrack.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'ncrack/command'
2
+ require 'ncrack/version'
@@ -0,0 +1,58 @@
1
+ require 'yaml'
2
+
3
+ Gem::Specification.new do |gem|
4
+ gemspec = YAML.load_file('gemspec.yml')
5
+
6
+ gem.name = gemspec.fetch('name')
7
+ gem.version = gemspec.fetch('version') do
8
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
9
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
10
+
11
+ require File.join('ncrack','version')
12
+ Ncrack::VERSION
13
+ end
14
+
15
+ gem.summary = gemspec['summary']
16
+ gem.description = gemspec['description']
17
+ gem.licenses = Array(gemspec['license'])
18
+ gem.authors = Array(gemspec['authors'])
19
+ gem.email = gemspec['email']
20
+ gem.homepage = gemspec['homepage']
21
+ gem.metadata = gemspec['metadata'] if gemspec['metadata']
22
+
23
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
24
+
25
+ gem.files = if gemspec['files'] then glob[gemspec['files']]
26
+ else `git ls-files`.split($/)
27
+ end
28
+
29
+ gem.executables = gemspec.fetch('executables') do
30
+ glob['bin/*'].map { |path| File.basename(path) }
31
+ end
32
+
33
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
34
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
35
+
36
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
37
+ %w[ext lib].select { |dir| File.directory?(dir) }
38
+ })
39
+
40
+ gem.requirements = gemspec['requirements']
41
+ gem.required_ruby_version = gemspec['required_ruby_version']
42
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
43
+ gem.post_install_message = gemspec['post_install_message']
44
+
45
+ split = lambda { |string| string.split(/,\s*/) }
46
+
47
+ if gemspec['dependencies']
48
+ gemspec['dependencies'].each do |name,versions|
49
+ gem.add_dependency(name,split[versions])
50
+ end
51
+ end
52
+
53
+ if gemspec['development_dependencies']
54
+ gemspec['development_dependencies'].each do |name,versions|
55
+ gem.add_development_dependency(name,split[versions])
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE ncrackrun>
3
+ <!-- Ncrack 0.7 scan initiated Mon Nov 29 10:27:35 2021 as: ncrack -v -m HTTP -&#45;user alice,eve,bob,root,admin -&#45;pass foo,bar,test,password,swordfish,god,hunter -g path=/protected_content -oX ncrack.xml http://localhost:4567 -->
4
+ <ncrackrun scanner="ncrack" args="ncrack -v -m HTTP -&#45;user alice,eve,bob,root,admin -&#45;pass foo,bar,test,password,swordfish,god,hunter -g path=/protected_content -oX ncrack.xml http://localhost:4567" start="1638210455" startstr="Mon Nov 29 10:27:35 2021" version="0.7" xmloutputversion="1.00">
5
+ <verbose level="1"/>
6
+ <debugging level="0"/>
7
+ <service starttime="1638210455" endtime="1638210455">
8
+ <address addr="127.0.0.1" addrtype="ipv4"/>
9
+ <port protocol="tcp" portid="4567" name="http"></port>
10
+ <credentials username="admin" password="swordfish"></credentials>
11
+ <credentials username="bob" password="hunter"></credentials>
12
+ </service>
13
+ </ncrackrun>
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'ncrack'
3
+
4
+ describe Ncrack do
5
+ it "should have a VERSION constant" do
6
+ expect(subject.const_get('VERSION')).to_not be_empty
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ require 'rspec'
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+
5
+ require 'ncrack/version'
6
+ include Ncrack
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+ require 'ncrack/xml/address'
3
+ require 'nokogiri'
4
+
5
+ describe Ncrack::XML::Address do
6
+ let(:fixtures_dir) { File.expand_path(File.join(__dir__,'..','fixtures')) }
7
+ let(:path) { File.join(fixtures_dir,'ncrack.xml') }
8
+ let(:xml) { File.read(path) }
9
+ let(:doc) { Nokogiri::XML(File.open(path)) }
10
+ let(:node) { doc.at_xpath('/ncrackrun/service/address') }
11
+
12
+ subject { described_class.new(node) }
13
+
14
+ describe "#addr" do
15
+ subject { super().addr }
16
+
17
+ it "must return a String" do
18
+ expect(subject).to be_kind_of(String)
19
+ end
20
+
21
+ it "must return the 'addr' attribute" do
22
+ expect(subject).to eq(node['addr'])
23
+ end
24
+ end
25
+
26
+ describe "#type" do
27
+ it "must return the 'addrtype' attribute as a Symbol" do
28
+ expect(subject.type).to eq(node['addrtype'].to_sym)
29
+ end
30
+ end
31
+
32
+ describe "#ipv4?" do
33
+ context "when #type returns :ipv4" do
34
+ before { allow(subject).to receive(:type).and_return(:ipv4) }
35
+
36
+ it "must return true" do
37
+ expect(subject.ipv4?).to be(true)
38
+ end
39
+ end
40
+
41
+ context "when #type does not return :ipv4" do
42
+ before { allow(subject).to receive(:type).and_return(:ipv6) }
43
+
44
+ it "must return false" do
45
+ expect(subject.ipv4?).to be(false)
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "#ipv6?" do
51
+ context "when #type returns :ipv6" do
52
+ before { allow(subject).to receive(:type).and_return(:ipv6) }
53
+
54
+ it "must return true" do
55
+ expect(subject.ipv6?).to be(true)
56
+ end
57
+ end
58
+
59
+ context "when #type does not return :ipv6" do
60
+ before { allow(subject).to receive(:type).and_return(:ipv4) }
61
+
62
+ it "must return false" do
63
+ expect(subject.ipv6?).to be(false)
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "#to_s" do
69
+ it "must return #addr" do
70
+ expect(subject.to_s).to eq(subject.addr)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'ncrack/xml/credentials'
3
+ require 'nokogiri'
4
+
5
+ describe Ncrack::XML::Credentials do
6
+ let(:fixtures_dir) { File.expand_path(File.join(__dir__,'..','fixtures')) }
7
+ let(:path) { File.join(fixtures_dir,'ncrack.xml') }
8
+ let(:xml) { File.read(path) }
9
+ let(:doc) { Nokogiri::XML(File.open(path)) }
10
+ let(:node) { doc.at_xpath('/ncrackrun/service/credentials') }
11
+
12
+ subject { described_class.new(node) }
13
+
14
+ describe "#username" do
15
+ subject { super().username }
16
+
17
+ it "must return a String" do
18
+ expect(subject).to be_kind_of(String)
19
+ end
20
+
21
+ it "must return the 'username' attribute" do
22
+ expect(subject).to eq(node['username'])
23
+ end
24
+ end
25
+
26
+ describe "#password" do
27
+ subject { super().password }
28
+
29
+ it "must return a String" do
30
+ expect(subject).to be_kind_of(String)
31
+ end
32
+
33
+ it "must return the 'password' attribute" do
34
+ expect(subject).to eq(node['password'])
35
+ end
36
+ end
37
+
38
+ describe "#to_s" do
39
+ it "must return the 'username:password'" do
40
+ expect(subject.to_s).to eq("#{subject.username}:#{subject.password}")
41
+ end
42
+ end
43
+
44
+ describe "#to_a" do
45
+ it "must return [username, password] tuple" do
46
+ expect(subject.to_a).to eq([subject.username, subject.password])
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'ncrack/xml/port'
3
+ require 'nokogiri'
4
+
5
+ describe Ncrack::XML::Port do
6
+ let(:fixtures_dir) { File.expand_path(File.join(__dir__,'..','fixtures')) }
7
+ let(:path) { File.join(fixtures_dir,'ncrack.xml') }
8
+ let(:xml) { File.read(path) }
9
+ let(:doc) { Nokogiri::XML(File.open(path)) }
10
+ let(:node) { doc.at_xpath('/ncrackrun/service/port') }
11
+
12
+ subject { described_class.new(node) }
13
+
14
+ describe "#protocol" do
15
+ subject { super().protocol }
16
+
17
+ it "must return the 'protocol' attribute as a Symbol" do
18
+ expect(subject).to eq(node['protocol'].to_sym)
19
+ end
20
+ end
21
+
22
+ describe "#number" do
23
+ subject { super().number }
24
+
25
+ it "must return the 'portid' attribute as an Integer" do
26
+ expect(subject).to eq(node['portid'].to_i)
27
+ end
28
+
29
+ it "must be > 0" do
30
+ expect(subject).to be > 0
31
+ end
32
+ end
33
+
34
+ describe "#name" do
35
+ subject { super().name }
36
+
37
+ it "must return a String" do
38
+ expect(subject).to be_kind_of(String)
39
+ end
40
+
41
+ it "must return the 'name' attribute" do
42
+ expect(subject).to eq(node['name'])
43
+ end
44
+ end
45
+
46
+ describe "#to_i" do
47
+ it "must return #number" do
48
+ expect(subject.to_i).to eq(subject.number)
49
+ end
50
+ end
51
+
52
+ describe "#to_s" do
53
+ it "must return the #name" do
54
+ expect(subject.to_s).to eq(subject.name)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+ require 'ncrack/xml/service'
3
+ require 'nokogiri'
4
+
5
+ describe Ncrack::XML::Service do
6
+ let(:fixtures_dir) { File.expand_path(File.join(__dir__,'..','fixtures')) }
7
+ let(:path) { File.join(fixtures_dir,'ncrack.xml') }
8
+ let(:xml) { File.read(path) }
9
+ let(:doc) { Nokogiri::XML(File.open(path)) }
10
+ let(:node) { doc.at_xpath('/ncrackrun/service') }
11
+
12
+ subject { described_class.new(node) }
13
+
14
+ describe "#start_time" do
15
+ subject { super().start_time }
16
+
17
+ it "must return the 'starttime' attribute as a Time object" do
18
+ expect(subject).to eq(Time.at(node['starttime'].to_i))
19
+ end
20
+ end
21
+
22
+ describe "#end_time" do
23
+ subject { super().end_time }
24
+
25
+ it "must return the 'endtime' attribute as a Time object" do
26
+ expect(subject).to eq(Time.at(node['endtime'].to_i))
27
+ end
28
+ end
29
+
30
+ describe "#address" do
31
+ subject { super().address }
32
+
33
+ it "must return an Ncrack::XML::Address object" do
34
+ expect(subject).to be_kind_of(Ncrack::XML::Address)
35
+ end
36
+ end
37
+
38
+ describe "#port" do
39
+ subject { super().port }
40
+
41
+ it "must return an Ncrack::XML::Port object" do
42
+ expect(subject).to be_kind_of(Ncrack::XML::Port)
43
+ end
44
+ end
45
+
46
+ let(:credentials_count) { node.xpath('credentials').count }
47
+
48
+ describe "#each_credentials" do
49
+ context "when given a block" do
50
+ it "must yield each Nikto::XML::Credentials object" do
51
+ expect { |b|
52
+ subject.each_credentials(&b)
53
+ }.to yield_successive_args(*Array.new(credentials_count,Ncrack::XML::Credentials))
54
+ end
55
+ end
56
+
57
+ context "when no block is given" do
58
+ subject { super().each_credentials.to_a }
59
+
60
+ it "must return an Enumerator of Ncrack::XML::Credentials objects" do
61
+ expect(subject.length).to be(credentials_count)
62
+ expect(subject).to all(be_kind_of(Ncrack::XML::Credentials))
63
+ end
64
+ end
65
+ end
66
+
67
+ describe "#credentials" do
68
+ subject { super().credentials }
69
+
70
+ it "must return an Array of #{described_class}::Service" do
71
+ expect(subject).to be_kind_of(Array)
72
+ expect(subject.length).to be(credentials_count)
73
+ expect(subject).to all(be_kind_of(Ncrack::XML::Credentials))
74
+ end
75
+ end
76
+
77
+ describe "#credential" do
78
+ let(:first_credentials) { subject.credentials.first }
79
+ let(:credential) { subject.credential }
80
+
81
+ it "must return the first #credentials" do
82
+ expect(credential.username).to eq(first_credentials.username)
83
+ expect(credential.password).to eq(first_credentials.password)
84
+ end
85
+ end
86
+ end