ruby-nikto 0.1.0

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