ruby-nikto 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.
@@ -0,0 +1,64 @@
1
+ require 'time'
2
+
3
+ module Nikto
4
+ class XML
5
+ #
6
+ # Represents a `statistics` XML element.
7
+ #
8
+ class Statistics
9
+
10
+ #
11
+ # Initializes the statistics object.
12
+ #
13
+ # @param [Nokogiri::XML::Node] node
14
+ # The XML node for the `statistics` XML element.
15
+ #
16
+ # @api private
17
+ #
18
+ def initialize(node)
19
+ @node = node
20
+ end
21
+
22
+ #
23
+ # The number of seconds elapsed.
24
+ #
25
+ # @return [Intger]
26
+ # The parsed value of the `elapsed` attribute.
27
+ #
28
+ def elapsed
29
+ @elapsed ||= @node['elapsed'].to_i
30
+ end
31
+
32
+ #
33
+ # The number of items found.
34
+ #
35
+ # @return [Intger]
36
+ # The parsed value of the `itemsfound` attribute.
37
+ #
38
+ def items_found
39
+ @items_found ||= @node['itemsfound'].to_i
40
+ end
41
+
42
+ #
43
+ # The number of items tested.
44
+ #
45
+ # @return [Intger]
46
+ # The parsed value of the `itemstested` attribute.
47
+ #
48
+ def items_tested
49
+ @items_tested ||= @node['itemstested'].to_i
50
+ end
51
+
52
+ #
53
+ # The end-time of the scan.
54
+ #
55
+ # @return [Time]
56
+ # The parsed value of the `endtime` attribute.
57
+ #
58
+ def end_time
59
+ @end_time ||= Time.parse(@node['endtime'])
60
+ end
61
+
62
+ end
63
+ end
64
+ end
data/lib/nikto/xml.rb ADDED
@@ -0,0 +1,230 @@
1
+ require 'nikto/xml/scan_details'
2
+
3
+ require 'nokogiri'
4
+
5
+ module Nikto
6
+ #
7
+ # Represents an nikto XML file or XML data.
8
+ #
9
+ # ## Example
10
+ #
11
+ # require 'nikto/xml'
12
+ #
13
+ # Nikto::XML.open('nikto.xml') do |xml|
14
+ # xml.each_scan_details do |scan_details|
15
+ # puts "#{scan_details.site_name}"
16
+ #
17
+ # scan_details.each_item do |item|
18
+ # puts " #{item.uri}"
19
+ # puts
20
+ # puts " #{item.description}"
21
+ # puts
22
+ # end
23
+ # end
24
+ # end
25
+ #
26
+ class XML
27
+
28
+ # The parsed XML document.
29
+ #
30
+ # @return [Nokogiri::XML]
31
+ #
32
+ # @api private
33
+ attr_reader :doc
34
+
35
+ # The path to the XML file.
36
+ #
37
+ # @return [String, nil]
38
+ attr_reader :path
39
+
40
+ #
41
+ # Creates a new XML object.
42
+ #
43
+ # @param [Nokogiri::XML] doc
44
+ # The parsed XML document.
45
+ #
46
+ # @param [String, nil] path
47
+ # The path to the XML file.
48
+ #
49
+ # @yield [xml]
50
+ # If a block is given, it will be passed the newly created XML
51
+ # parser.
52
+ #
53
+ # @yieldparam [XML] xml
54
+ # The newly created XML parser.
55
+ #
56
+ # @api private
57
+ #
58
+ def initialize(doc, path: nil)
59
+ @doc = doc
60
+ @path = File.expand_path(path) if path
61
+
62
+ yield self if block_given?
63
+ end
64
+
65
+ #
66
+ # Parses the given XML String.
67
+ #
68
+ # @param [String] xml
69
+ # The XML String.
70
+ #
71
+ # @yield [xml]
72
+ # If a block is given, it will be passed the newly created XML
73
+ # parser.
74
+ #
75
+ # @yieldparam [XML] xml
76
+ # The newly created XML parser.
77
+ #
78
+ # @return [XML]
79
+ # The parsed XML.
80
+ #
81
+ # @api public
82
+ #
83
+ def self.parse(xml,&block)
84
+ new(Nokogiri::XML(xml),&block)
85
+ end
86
+
87
+ #
88
+ # Opens an parses an XML file.
89
+ #
90
+ # @param [String] path
91
+ # The path to the XML file.
92
+ #
93
+ # @yield [xml]
94
+ # If a block is given, it will be passed the newly created XML
95
+ # parser.
96
+ #
97
+ # @yieldparam [XML] xml
98
+ # The newly created XML parser.
99
+ #
100
+ # @return [XML]
101
+ # The parsed XML.
102
+ #
103
+ # @api public
104
+ #
105
+ def self.open(path,&block)
106
+ path = File.expand_path(path)
107
+ doc = Nokogiri::XML(File.open(path))
108
+
109
+ new(doc, path: path, &block)
110
+ end
111
+
112
+ #
113
+ # The `hoststest` value.
114
+ #
115
+ # @return [Integer]
116
+ # The parsed value of the `hoststest` attribute.
117
+ #
118
+ def hosts_test
119
+ @hosts_test ||= @doc.root['@hoststest'].to_i
120
+ end
121
+
122
+ #
123
+ # Additional command-line options passed to `nikto`.
124
+ #
125
+ # @return [String]
126
+ # The value of the `options` attribute.
127
+ #
128
+ def options
129
+ @doc.root['options']
130
+ end
131
+
132
+ #
133
+ # When the scan started.
134
+ #
135
+ # @return [Time]
136
+ # The parsed value of the `scanstart` attribute.
137
+ #
138
+ def scan_start
139
+ @scan_start ||= Time.parse(@doc.root['scanstart'])
140
+ end
141
+
142
+ #
143
+ # When the scan completed.
144
+ #
145
+ # @return [Time]
146
+ # The parsed value `scanned` attribute.
147
+ #
148
+ def scan_end
149
+ @scan_end ||= Time.parse(@doc.root['scanend'])
150
+ end
151
+
152
+ #
153
+ # The duration of the scan.
154
+ #
155
+ # @return [String]
156
+ # The value of the `scanelapsed` attribute.
157
+ #
158
+ def scan_elapsed
159
+ @doc.root['scanelapsed']
160
+ end
161
+
162
+ #
163
+ # The Nikto XML schema version.
164
+ #
165
+ # @return [String]
166
+ # The value of the `nxmlversion` attribute.
167
+ #
168
+ def nikto_xml_version
169
+ @doc.root['nxmlversion']
170
+ end
171
+
172
+ #
173
+ # Parses each `scandetails` child element.
174
+ #
175
+ # @yield [scan_details]
176
+ # If a block is given, it will be yielded each scan details object.
177
+ #
178
+ # @yieldparam [ScanDetails] scan_details
179
+ # A scan details object.
180
+ #
181
+ # @return [Enumerator]
182
+ # If no block is given, an Enumerator will be returned.
183
+ #
184
+ def each_scan_details
185
+ return enum_for(__method__) unless block_given?
186
+
187
+ @doc.xpath('/niktoscan/scandetails').each do |node|
188
+ yield ScanDetails.new(node)
189
+ end
190
+ end
191
+
192
+ #
193
+ # The scan details.
194
+ #
195
+ # @return [Array<ScanDetails>]
196
+ #
197
+ def scan_details
198
+ each_scan_details.to_a
199
+ end
200
+
201
+ alias each_target each_scan_details
202
+
203
+ alias targets scan_details
204
+
205
+ #
206
+ # The first scan details object.
207
+ #
208
+ # @return [ScanDetails, nil]
209
+ #
210
+ def target
211
+ each_target.first
212
+ end
213
+
214
+ #
215
+ # Converts the XML object to a String.
216
+ #
217
+ # @return [String]
218
+ # The path to the XML if {#path} is set, or the XML if the XML was parsed
219
+ # from a String.
220
+ #
221
+ def to_s
222
+ if @path
223
+ @path
224
+ else
225
+ @doc.to_s
226
+ end
227
+ end
228
+
229
+ end
230
+ end
data/lib/nikto.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'nikto/command'
2
+ require 'nikto/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('nikto','version')
12
+ Nikto::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,97 @@
1
+ require 'spec_helper'
2
+ require 'nikto/command'
3
+
4
+ describe Nikto::Command do
5
+ describe described_class::OptionString do
6
+ let(:map) do
7
+ {
8
+ :a => '1',
9
+ :b => '2',
10
+ :c => '3'
11
+ }
12
+ end
13
+
14
+ subject { described_class.new(map) }
15
+
16
+ describe "#initialize" do
17
+ it "must initialize #type to be a Map of the options" do
18
+ expect(subject.type).to be_kind_of(CommandMapper::Types::Map)
19
+ expect(subject.type.map).to eq(map)
20
+ end
21
+
22
+ it "must set #separator to ''" do
23
+ expect(subject.separator).to eq('')
24
+ end
25
+ end
26
+ end
27
+
28
+ describe described_class::PortList do
29
+ describe "#validate" do
30
+ context "when given a single port number" do
31
+ let(:value) { 443 }
32
+
33
+ it "must return true" do
34
+ expect(subject.validate(value)).to be(true)
35
+ end
36
+ end
37
+
38
+ context "when given a Range of port numbers" do
39
+ let(:value) { (1..1024) }
40
+
41
+ it "must return true" do
42
+ expect(subject.validate(value)).to be(true)
43
+ end
44
+ end
45
+
46
+ context "when given an Array of port numbers" do
47
+ let(:value) { [80, 443] }
48
+
49
+ it "must return true" do
50
+ expect(subject.validate(value)).to be(true)
51
+ end
52
+
53
+ context "and the Array contains Ranges" do
54
+ let(:value) { [80, (1..42), 443] }
55
+
56
+ it "must return true" do
57
+ expect(subject.validate(value)).to be(true)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "#format" do
64
+ context "when given a single port number" do
65
+ let(:value) { 443 }
66
+
67
+ it "must return the formatted port number" do
68
+ expect(subject.format(value)).to eq(value.to_s)
69
+ end
70
+ end
71
+
72
+ context "when given a Range of port numbers" do
73
+ let(:value) { (1..1024) }
74
+
75
+ it "must return the formatted port number range (ex: 1-102)" do
76
+ expect(subject.format(value)).to eq("#{value.begin}-#{value.end}")
77
+ end
78
+ end
79
+
80
+ context "when given an Array of port numbers" do
81
+ let(:value) { [80, 443] }
82
+
83
+ it "must return the formatted list of port numbers" do
84
+ expect(subject.format(value)).to eq(value.join(','))
85
+ end
86
+
87
+ context "and the Array contains Ranges" do
88
+ let(:value) { [80, (1..42), 443] }
89
+
90
+ it "must return the formatted list of port numbers and port ranges" do
91
+ expect(subject.format(value)).to eq("#{value[0]},#{value[1].begin}-#{value[1].end},#{value[2]}")
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,47 @@
1
+ <?xml version="1.0" ?>
2
+ <!DOCTYPE niktoscan SYSTEM "/usr/share/doc/nikto/nikto.dtd">
3
+ <niktoscan hoststest="0" options="-host example.com -output nikto.xml" version="2.1.6" scanstart="Mon Nov 29 07:22:56 2021" scanend="Wed Dec 31 16:00:00 1969" scanelapsed=" seconds" nxmlversion="1.2">
4
+
5
+ <scandetails targetip="93.184.216.34" targethostname="example.com" targetport="80" targetbanner="ECS (sec/974D)" starttime="2021-11-29 07:22:57" sitename="http://example.com:80/" siteip="http://93.184.216.34:80/" hostheader="example.com" errors="0" checks="4587">
6
+
7
+
8
+ <item id="#ID#" osvdbid="#TEMPL_OSVDB#" osvdblink="#TEMPL_OSVDB_LINK#" method="#TEMPL_HTTP_METHOD#">
9
+ <description><![CDATA[#TEMPL_MSG#]]></description>
10
+ <uri><![CDATA[#TEMPL_URI#]]></uri>
11
+ <namelink><![CDATA[#TEMPL_ITEM_NAME_LINK#]]></namelink>
12
+ <iplink><![CDATA[#TEMPL_ITEM_IP_LINK#]]></iplink>
13
+ </item>
14
+
15
+ <item id="#ID#" osvdbid="#TEMPL_OSVDB#" osvdblink="#TEMPL_OSVDB_LINK#" method="#TEMPL_HTTP_METHOD#">
16
+ <description><![CDATA[#TEMPL_MSG#]]></description>
17
+ <uri><![CDATA[#TEMPL_URI#]]></uri>
18
+ <namelink><![CDATA[#TEMPL_ITEM_NAME_LINK#]]></namelink>
19
+ <iplink><![CDATA[#TEMPL_ITEM_IP_LINK#]]></iplink>
20
+ </item>
21
+
22
+ <item id="#ID#" osvdbid="#TEMPL_OSVDB#" osvdblink="#TEMPL_OSVDB_LINK#" method="#TEMPL_HTTP_METHOD#">
23
+ <description><![CDATA[#TEMPL_MSG#]]></description>
24
+ <uri><![CDATA[#TEMPL_URI#]]></uri>
25
+ <namelink><![CDATA[#TEMPL_ITEM_NAME_LINK#]]></namelink>
26
+ <iplink><![CDATA[#TEMPL_ITEM_IP_LINK#]]></iplink>
27
+ </item>
28
+
29
+ <item id="#ID#" osvdbid="#TEMPL_OSVDB#" osvdblink="#TEMPL_OSVDB_LINK#" method="#TEMPL_HTTP_METHOD#">
30
+ <description><![CDATA[#TEMPL_MSG#]]></description>
31
+ <uri><![CDATA[#TEMPL_URI#]]></uri>
32
+ <namelink><![CDATA[#TEMPL_ITEM_NAME_LINK#]]></namelink>
33
+ <iplink><![CDATA[#TEMPL_ITEM_IP_LINK#]]></iplink>
34
+ </item>
35
+
36
+ <item id="#ID#" osvdbid="#TEMPL_OSVDB#" osvdblink="#TEMPL_OSVDB_LINK#" method="#TEMPL_HTTP_METHOD#">
37
+ <description><![CDATA[#TEMPL_MSG#]]></description>
38
+ <uri><![CDATA[#TEMPL_URI#]]></uri>
39
+ <namelink><![CDATA[#TEMPL_ITEM_NAME_LINK#]]></namelink>
40
+ <iplink><![CDATA[#TEMPL_ITEM_IP_LINK#]]></iplink>
41
+ </item>
42
+
43
+ <statistics elapsed="178" itemsfound="5" itemstested="4587" endtime="2021-11-29 07:25:55" />
44
+ </scandetails>
45
+
46
+
47
+ </niktoscan>
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'nikto/version'
3
+
4
+ describe Nikto 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 'nikto/version'
6
+ include Nikto
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+ require 'nikto/xml/item'
3
+ require 'nokogiri'
4
+
5
+ describe Nikto::XML::Item do
6
+ let(:fixtures_dir) { File.expand_path(File.join(__dir__,'..','fixtures')) }
7
+ let(:path) { File.join(fixtures_dir,'nikto.xml') }
8
+ let(:xml) { File.read(path) }
9
+ let(:doc) { Nokogiri::XML(File.open(path)) }
10
+ let(:node) { doc.at_xpath('/niktoscan/scandetails/item') }
11
+
12
+ subject { described_class.new(node) }
13
+
14
+ describe "#description" do
15
+ subject { super().description }
16
+
17
+ it "must return a String" do
18
+ expect(subject).to be_kind_of(String)
19
+ end
20
+
21
+ it "must return the inner text of the 'description' child element" do
22
+ expect(subject).to eq(node.at_xpath('description').inner_text)
23
+ end
24
+ end
25
+
26
+ describe "#uri" do
27
+ subject { super().uri }
28
+
29
+ it "must return a String" do
30
+ expect(subject).to be_kind_of(String)
31
+ end
32
+
33
+ it "must return the inner text of the 'uri' child element" do
34
+ expect(subject).to eq(node.at_xpath('uri').inner_text)
35
+ end
36
+ end
37
+
38
+ describe "#name_link" do
39
+ subject { super().name_link }
40
+
41
+ it "must return a String" do
42
+ expect(subject).to be_kind_of(String)
43
+ end
44
+
45
+ it "must return the inner text of the 'namelink' child element" do
46
+ expect(subject).to eq(node.at_xpath('namelink').inner_text)
47
+ end
48
+ end
49
+
50
+ describe "#ip_link" do
51
+ subject { super().ip_link }
52
+
53
+ it "must return a String" do
54
+ expect(subject).to be_kind_of(String)
55
+ end
56
+
57
+ it "must return the inner text of the 'iplink' child element" do
58
+ expect(subject).to eq(node.at_xpath('iplink').inner_text)
59
+ end
60
+ end
61
+ end