ruby-ncrack 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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