openvas-cli 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,10 @@
1
+ require 'active_model'
2
+
3
+ # Validates an OpenVAS OID
4
+ # Used by: VasNVT
5
+ class OIDValidator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ record.errors[attribute] << (options[:message] || "is not a valid UUID") unless
8
+ value =~ /\A[0-9\.]{1,80}\z/
9
+ end
10
+ end
@@ -0,0 +1,273 @@
1
+ require 'rubygems'
2
+ require 'socket'
3
+ require 'timeout'
4
+ require 'openssl'
5
+ require 'base64'
6
+ require 'nokogiri'
7
+ require 'time'
8
+
9
+ require 'vas_exceptions'
10
+
11
+
12
+ # = OpenvasCli
13
+ #
14
+ # Provides connectivity to OpenVAS Management service.
15
+ #
16
+ # Author:: Reed Swenson (mailto:fleureed@gmail.com)
17
+ # Copyright:: Copyright (c) 2010 eBankSystems, Inc.
18
+ # License:: GPL v2.0
19
+ class OpenvasCli
20
+
21
+ # OpenVAS username.
22
+ #
23
+ # Will be used for all requests.
24
+ #
25
+ # Defaults to: +admin+
26
+ def self.user
27
+ @@user ||= "admin"
28
+ end
29
+
30
+ # See: user
31
+ def self.user=(val)
32
+ @@user = val
33
+ end
34
+
35
+ # OpenVAS passowrd
36
+ #
37
+ # Defaults to: ""
38
+ def self.password
39
+ @@password ||= ""
40
+ end
41
+
42
+ # See password
43
+ def self.password=(val)
44
+ @@password = val
45
+ end
46
+
47
+ # Hostname or IP that hosts the OpenVAS Management service.
48
+ #
49
+ # Defaults to: +localhost+
50
+ def self.host
51
+ @@host ||= "localhost"
52
+ end
53
+
54
+ # See: host
55
+ def self.host=(val)
56
+ @@host = val
57
+ end
58
+
59
+ # Port on which the OpenVAS Management Service is listening.
60
+ #
61
+ # Defaults to: 9390
62
+ def self.port
63
+ @@port ||= 9390
64
+ end
65
+
66
+ # See: port
67
+ def self.port=(val)
68
+ @@port = val
69
+ end
70
+
71
+ # Communications timeout in seconds.
72
+ #
73
+ # Defaults to: 5
74
+ def self.time_out
75
+ @@time_out ||= 5
76
+ end
77
+
78
+ # See: time_out
79
+ def self.time_out=(val)
80
+ @@time_out = val
81
+ end
82
+
83
+ # Communications buffer size in bytes.
84
+ #
85
+ # Defaults to: 512
86
+ def self.buffer_size
87
+ @@buffer_size ||= 512
88
+ end
89
+
90
+ # See: buffer_size
91
+ def self.buffer_size=(val)
92
+ @@buffer_size = val
93
+ end
94
+
95
+ # By default, new will attempt to log into the OpenVAS management service.
96
+ # If this is set to +false+, the client will bypas the login when initialized.
97
+ # Before the client can send or receive any messages, login must be called.
98
+ #
99
+ # Defaults to: +true+
100
+ def self.auto_login
101
+ @@auto_login ||= true
102
+ end
103
+
104
+ # See: auto_login
105
+ def self.auto_login=(val)
106
+ @@auto_login = val
107
+ end
108
+
109
+ # Log4r style Logger used by the client.
110
+ def self.logger
111
+ @@logger
112
+ end
113
+
114
+ # See: logger
115
+ def self.logger=(val)
116
+ @@logger = val
117
+ end
118
+
119
+ # Initializes the client, connectes to the OpenVas Managment service specified
120
+ # by host & port, and unless auto_login is set to +false+, loggs in using
121
+ # username and password.
122
+ def initialize()
123
+ connect
124
+
125
+ if OpenvasCli.auto_login == true
126
+ login
127
+ end
128
+ end
129
+
130
+ # Closes the active connection and sets it up for re-connection.
131
+ def close
132
+ @socket.close if @socket
133
+ @socket = nil
134
+ end
135
+
136
+ # Logs into the OpenVAS Management service using the specified username and
137
+ # passoword. By default, this method is called by new unless auto_login is
138
+ # set to +false+.
139
+ def login
140
+ log_message("Logging in: :user => #{OpenvasCli.user}", :info)
141
+ areq = Nokogiri::XML::Builder.new { |xml|
142
+ xml.authenticate {
143
+ xml.credentials {
144
+ xml.username { xml.text(OpenvasCli.user) }
145
+ xml.password { xml.text(OpenvasCli.password) }
146
+ }
147
+ }
148
+ }
149
+
150
+ send_receive(areq.doc)
151
+ end
152
+
153
+ # Sends a message to the OpenVAS Management service and receives a response.
154
+ # If, for some reaon, the connection has been severed, it will re-establish
155
+ # the connection and attempts to login again.
156
+ # ---
157
+ # Parameters:
158
+ # [request] an Nokogiri::XML::Document or String to send to the management service.
159
+ # ---
160
+ # Returns:
161
+ # * A Nokogiri::XML::Document that contains the response from the management service.
162
+ # ---
163
+ # Exceptions:
164
+ # [VasExceptions::CommunicationException] When the transmission times out or encounters an unexpected end of file.
165
+ # [VasExceptions::CommandException] When the management service does not send back a 20* response status.
166
+ # ---
167
+ # Usage:
168
+ # cli = OpenvasCli.new
169
+ # req = Nokogiri::XML::Builder.new{ |xml|
170
+ # xml.do_something
171
+ # }
172
+ # response = cli.send_receive(req.doc)
173
+ # #parse the response and do something meaningful
174
+ def send_receive (request)
175
+ if request.kind_of? String
176
+ tosend = request
177
+ else
178
+ tosend = request.to_xml
179
+ end
180
+
181
+ unless @socket && @socket.state !~ /closed/i
182
+ log_message("Socket closed, Reconnecting", :info)
183
+ connect
184
+ login
185
+ end
186
+ log_message("Sending: #{tosend}", :debug)
187
+
188
+ @socket.puts(tosend)
189
+
190
+ rbuf=''
191
+ size=0
192
+ begin
193
+ begin
194
+ timeout(OpenvasCli.time_out) {
195
+ a = @socket.sysread(OpenvasCli.buffer_size)
196
+ size=a.length
197
+ rbuf << a
198
+ }
199
+ rescue Timeout::Error
200
+ size=0
201
+ msg = "Command Timed Out (#{$!})\nCommand: #{tosend}"
202
+ log.message msg, :error
203
+ raise VasExceptions::CommunicationException.new(msg)
204
+ rescue EOFError
205
+ msg = "EOFError(#{$!})\nReceived: #{rbuf}\nCommand: #{tosend}"
206
+ log_message msg, :error
207
+ raise VasExceptions::CommunicationException.new(msg)
208
+ end
209
+ end while size>=OpenvasCli.buffer_size
210
+ response= Nokogiri::XML(rbuf)
211
+
212
+ log_message "RECEIVED: #{response.to_xml}", :debug
213
+
214
+ unless extract_value_from("//@status", response) =~ /20\d/
215
+ msg = "Command Failed: #{extract_value_from("//@status_text", response)}\n" +
216
+ "Command: #{tosend}"
217
+ log_message msg, :error
218
+ raise VasExceptions::CommandException.new(msg)
219
+ end
220
+
221
+ response
222
+ end
223
+
224
+ private
225
+
226
+ def log_message(msg, level)
227
+ if OpenvasCli.logger
228
+ case level
229
+ when :debug
230
+ OpenvasCli.logger.debug msg
231
+ when :info
232
+ OpenvasCli.logger.info msg
233
+ when :warn
234
+ OpenvasCli.logger.warn msg
235
+ when :error
236
+ OpenvasCli.logger.error msg
237
+ when :fatal
238
+ OpenvasCli.logger.fatal msg
239
+ end
240
+ end
241
+ end
242
+
243
+ def extract_value_from(x_str, n)
244
+ ret = ""
245
+ if x_str =~ /@/
246
+ ret = n.at_xpath(x_str).value if n.at_xpath(x_str)
247
+ else
248
+ tn = n.at_xpath(x_str)
249
+ if tn
250
+ if tn.children.count > 0
251
+ tn.children.each { |tnc|
252
+ if tnc.text?
253
+ ret = tnc.text
254
+ end
255
+ }
256
+ else
257
+ ret = tn.text
258
+ end
259
+ end
260
+ end
261
+
262
+ ret
263
+ end
264
+
265
+ def connect
266
+ log_message("Connecting: :host => #{OpenvasCli.host}, :port => #{OpenvasCli.port}", :info)
267
+ plain_socket = TCPSocket.open(OpenvasCli.host, OpenvasCli.port)
268
+ ssl_context = OpenSSL::SSL::SSLContext.new
269
+ @socket = OpenSSL::SSL::SSLSocket.new(plain_socket, ssl_context)
270
+ @socket.sync_close = true
271
+ @socket.connect
272
+ end
273
+ end
@@ -0,0 +1,9 @@
1
+ require 'active_model'
2
+
3
+ # Validates a OpenVAS UUID
4
+ class UUIDValidator < ActiveModel::EachValidator
5
+ def validate_each(record, attribute, value)
6
+ record.errors[attribute] << (options[:message] || "is not a valid UUID") unless
7
+ value =~ /\A[0-9abcdef\-]{1,40}\z/i
8
+ end
9
+ end
@@ -0,0 +1,40 @@
1
+ require 'active_model'
2
+ Dir["#{File.dirname(__FILE__)}/*_validator.rb"].each {|f| require f}
3
+
4
+ # Provides a base from which to build objects that utilize the OpenvasCli object.
5
+ # Includes support for ActiveModel validations and a helper method (extract_value_from)
6
+ # to assist in OpenVAS object implementations.
7
+ class VasBase
8
+ include ActiveModel::Validations
9
+
10
+ # OpenvasCli object to be used for communications. Initializes a new instance
11
+ # if one does not already exist.
12
+ def self.client
13
+ @@client ||= OpenvasCli.new
14
+ end
15
+
16
+ # Helper method to extract a value from a Nokogiri::XML::Node object. If the
17
+ # xpath provided contains an @, then the method assumes that the value resides
18
+ # in an attribute, otherwise it pulls the text of the last +text+ node.
19
+ def self.extract_value_from(x_str, n)
20
+ ret = ""
21
+ if x_str =~ /@/
22
+ ret = n.at_xpath(x_str).value if n.at_xpath(x_str)
23
+ else
24
+ tn = n.at_xpath(x_str)
25
+ if tn
26
+ if tn.children.count > 0
27
+ tn.children.each { |tnc|
28
+ if tnc.text?
29
+ ret = tnc.text
30
+ end
31
+ }
32
+ else
33
+ ret = tn.text
34
+ end
35
+ end
36
+ end
37
+
38
+ ret
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+ # Continer for all cusom exceptions used in openvas-cli.
2
+ module VasExceptions
3
+ # Thrown when the OpenVAS management service rejects a command for some reason.
4
+ class CommandException < StandardError; end
5
+ # Thrown when some sort of communications error (like a timeout or unexpected
6
+ # end-of-file) is encountered.
7
+ class CommunicationException < StandardError; end
8
+ end
@@ -0,0 +1,70 @@
1
+ require 'vas_base'
2
+
3
+ # Describes a NSIS/OpenVAS scan rule.
4
+ #--
5
+ # TODO: Filter by other useful fields
6
+ # TODO: Find external links for more information
7
+ # TODO: Redcloth and Autolink description
8
+ # TODO: Implement sorting options[:sort_by]
9
+ #++
10
+ class VasNVT < VasBase
11
+ # Unique identifier for rule.
12
+ attr_accessor :oid
13
+ # Human readable name of the rule.
14
+ attr_accessor :name
15
+
16
+ attr_accessor :category
17
+ attr_accessor :copyright
18
+ attr_accessor :description
19
+ attr_accessor :summary
20
+ attr_accessor :family
21
+ attr_accessor :version
22
+ attr_accessor :cvss_base
23
+ attr_accessor :risk_factor
24
+ attr_accessor :cve_id
25
+ attr_accessor :bugtraq_id
26
+ attr_accessor :xrefs
27
+ attr_accessor :tags
28
+ attr_accessor :preferences
29
+
30
+ validates :oid, :presence => true, :OID => true
31
+
32
+ # Pulls VasNVT rules that match the provided options.
33
+ #
34
+ # === Options:
35
+ # [:nvt_oid => [VasNVT.oid]] will pull only the NVT rule that matches the given OID
36
+ # [:family => [VasFamily.name]] will pull all NVT rules for the specified family
37
+ def self.get_all(options = {})
38
+ params = {:details => '1'}
39
+ params[:nvt_oid] = options[:oid] if options[:oid]
40
+ params[:family] = options[:family] if options[:family]
41
+
42
+ req = Nokogiri::XML::Builder.new { |xml|
43
+ xml.get_nvts(params)
44
+ }
45
+
46
+ rules = client.send_receive(req.doc)
47
+
48
+ ret = []
49
+ rules.xpath("//nvt").each { |nvt|
50
+ rule = VasNVT.new
51
+ rule.oid = extract_value_from("@oid", nvt)
52
+ rule.name = extract_value_from("name", nvt)
53
+ rule.category = extract_value_from("category", nvt)
54
+ rule.copyright = extract_value_from("copyright", nvt)
55
+ rule.description = extract_value_from("description", nvt).strip
56
+ rule.summary = extract_value_from("summary", nvt)
57
+ rule.family = extract_value_from("family", nvt)
58
+ rule.version = extract_value_from("version", nvt)
59
+ rule.cvss_base = extract_value_from("cvss_base", nvt)
60
+ rule.risk_factor = extract_value_from("risk_factor", nvt)
61
+ rule.cve_id = extract_value_from("cve_id", nvt)
62
+ rule.bugtraq_id = extract_value_from("bugtraq_id", nvt)
63
+ rule.xrefs = extract_value_from("xrefs", nvt).split(/, ?/)
64
+ rule.tags = extract_value_from("tags", nvt).split(/, ?/)
65
+ ret << rule
66
+ }
67
+
68
+ ret
69
+ end
70
+ end
@@ -0,0 +1,37 @@
1
+ require 'vas_base'
2
+
3
+ # Category for NVT rules
4
+ class VasNVTFamily < VasBase
5
+ # Category Name
6
+ attr_accessor :name
7
+ # Number of rules in the family
8
+ attr_accessor :nvt_count
9
+
10
+ validates :name, :presence => true, :length => {:minimum => 1}
11
+
12
+ # Pulls all NVT Families defined on the server.
13
+ #
14
+ # === Options:
15
+ # None.
16
+ #--
17
+ # TODO: Implement options[:sort_by]
18
+ #++
19
+ def self.get_all(options = {})
20
+
21
+ req = Nokogiri::XML::Builder.new { |xml|
22
+ xml.get_nvt_families
23
+ }
24
+
25
+ fams = client.send_receive(req.doc)
26
+
27
+ ret = []
28
+ fams.xpath('//family').each { |f|
29
+ family = VasNVTFamily.new
30
+ family.name = extract_value_from('name', f)
31
+ family.nvt_count = extract_value_from('max_nvt_count', f).to_i
32
+ ret << family
33
+ }
34
+
35
+ ret
36
+ end
37
+ end