nexpose 0.0.98 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,17 @@
1
- # Nexpose
2
-
3
- This is the official gem package for the Ruby Nexpose API included in the Metasploit Framework. This version is based on SVN revision 12430.
4
- The upstream for this gem can be found at https://metasploit.com/svn/framework3/trunk/lib/rapid7
5
-
6
- # Credits
7
- Rapid7 LLC
1
+ # Nexpose
2
+
3
+ This is the official gem package for the Ruby Nexpose API.
4
+
5
+ For assistance with using the gem, to share your scripts, or to discuss different approaches, please visit the Rapid7 forums for Nexpose: https://community.rapid7.com/community/nexpose
6
+
7
+ ## Contributions
8
+
9
+ This package is currently a work in progress. Most of the Nexpose API is exposed, but there are some operations missing. Also, there are stylistic differences between different calls that we hope to eliminate over time. We welcome contributions to this package. We ask only that pull requests and patches adhere to our coding standards.
10
+
11
+ * Favor returning classes over key-value maps. Classes tend to be easier for users to manipulate and use.
12
+ * Unless otherwise noted, code should adhere to the Ruby Style Guide: https://github.com/bbatsov/ruby-style-guide
13
+ * Use YARDoc comment style to improve the API documentation of the gem.
14
+
15
+ ## Credits
16
+
17
+ Rapid7 LLC
data/Rakefile CHANGED
@@ -1,22 +1,17 @@
1
- # encoding: utf-8
2
-
3
- task :build => :update do
4
- Rake::Task['clean'].execute
5
- puts "[*] Building nexpose.gemspec"
6
- system "gem build nexpose.gemspec &> /dev/null"
7
- end
8
-
9
- task :release => :build do
10
- puts "[*] Pushing nexpose to rubygems.org"
11
- system "gem push nexpose-*.gem &> /dev/null"
12
- Rake::Task['clean'].execute
13
- end
14
-
15
- task :clean do
16
- system "rm *.gem &> /dev/null"
17
- end
18
-
19
- task :update do
20
- system "rm -f lib/nexpose.rb"
21
- system "svn export https://metasploit.com/svn/framework3/trunk/lib/rapid7/nexpose.rb lib/nexpose.rb"
22
- end
1
+ # encoding: utf-8
2
+
3
+ task :build do
4
+ Rake::Task['clean'].execute
5
+ puts "[*] Building nexpose.gemspec"
6
+ system "gem build nexpose.gemspec &> /dev/null"
7
+ end
8
+
9
+ task :release => :build do
10
+ puts "[*] Pushing nexpose to rubygems.org"
11
+ system "gem push nexpose-*.gem &> /dev/null"
12
+ Rake::Task['clean'].execute
13
+ end
14
+
15
+ task :clean do
16
+ system "rm *.gem &> /dev/null"
17
+ end
@@ -0,0 +1,5 @@
1
+ # Nexpose Client
2
+
3
+ The nexpose.rb file should act simply as a means of collecing all the sub-elements of the client into a single module.
4
+
5
+ If adding or adjusting code, please note that all calls directly against the Connection object are currently implemented within the NexposeAPI module. This style of call should mostly be for listing and simple query calls, and not for configuration requests that will return an editable class.
@@ -1,130 +1,104 @@
1
- #
2
- # The NeXpose API
3
- #
4
- =begin
5
-
6
- Copyright (C) 2009-2011, Rapid7 LLC
7
- All rights reserved.
8
-
9
- Redistribution and use in source and binary forms, with or without modification,
10
- are permitted provided that the following conditions are met:
11
-
12
- * Redistributions of source code must retain the above copyright notice,
13
- this list of conditions and the following disclaimer.
14
-
15
- * Redistributions in binary form must reproduce the above copyright notice,
16
- this list of conditions and the following disclaimer in the documentation
17
- and/or other materials provided with the distribution.
18
-
19
- * Neither the name of Rapid7 LLC nor the names of its contributors
20
- may be used to endorse or promote products derived from this software
21
- without specific prior written permission.
22
-
23
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
27
- ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
30
- ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
-
34
- =end
35
-
36
- #
37
- # WARNING! This code makes an SSL connection to the NeXpose server, but does NOT
38
- # verify the certificate at this time. This can be a security issue if
39
- # an attacker is able to man-in-the-middle the connection between the
40
- # Metasploit console and the NeXpose server. In the common case of
41
- # running NeXpose and Metasploit on the same host, this is a low risk.
42
- #
43
-
44
- #
45
- # WARNING! This code is still rough and going through substantive changes. While
46
- # you can build tools using this library today, keep in mind that method
47
- # names and parameters may change in the future.
48
- #
49
-
50
- require 'date'
51
- require 'rexml/document'
52
- require 'net/https'
53
- require 'net/http'
54
- require 'uri'
55
- require 'rex/mime'
56
- require 'nexpose/error'
57
- require 'nexpose/util'
58
- require 'nexpose/user'
59
- require 'nexpose/api_request'
60
- require 'nexpose/misc'
61
- require 'nexpose/report'
62
- require 'nexpose/scan'
63
- require 'nexpose/scan_engine'
64
- require 'nexpose/silo'
65
- require 'nexpose/site'
66
- require 'nexpose/ticket'
67
- require 'nexpose/vuln'
68
- require 'nexpose/creds'
69
- require 'nexpose/connection'
70
-
71
- module Nexpose
72
-
73
- # TODO add
74
- def self.site_device_scan(connection, site_id, device_array, host_array, debug = false)
75
-
76
- request_xml = '<SiteDevicesScanRequest session-id="' + connection.session_id.to_s + '" site-id="' + site_id.to_s + '">'
77
- request_xml += '<Devices>'
78
- device_array.each do |d|
79
- request_xml += '<device id="' + d.to_s + '"/>'
80
- end
81
- request_xml += '</Devices>'
82
- request_xml += '<Hosts>'
83
- # The host array can only by single IP addresses for now. TODO: Expand to full API Spec.
84
- host_array.each do |h|
85
- request_xml += '<range from="' + h.to_s + '"/>'
86
- end
87
- request_xml += '</Hosts>'
88
- request_xml += '</SiteDevicesScanRequest>'
89
-
90
- r = connection.execute(request_xml)
91
- r.success ? {:engine_id => r.attributes['engine_id'], :scan_id => r.attributes['scan-id']} : nil
92
- end
93
-
94
- # === Description
95
- # TODO
96
- def self.getAttribute(attribute, xml)
97
- value = ''
98
- #@value = substr(substr(strstr(strstr(@xml,@attribute),'"'),1),0,strpos(substr(strstr(strstr(@xml,@attribute),'"'),1),'"'))
99
- value
100
- end
101
-
102
- # === Description
103
- # Returns an ISO 8601 formatted date/time stamp. All dates in NeXpose must use this format.
104
- def self.get_iso_8601_date(int_date)
105
- #@date_mod = date('Ymd\THis000', @int_date)
106
- date_mod = ''
107
- return date_mod
108
- end
109
-
110
- # ==== Description
111
- # Echos the last XML API request and response for the specified object. (Useful for debugging)
112
- def self.printXML(object)
113
- puts "request" + object.request_xml.to_s
114
- puts "response is " + object.response_xml.to_s
115
- end
116
-
117
-
118
- def self.testa(ip, port, user, passwd)
119
- nsc = Connection.new(ip, user, passwd, port)
120
-
121
- nsc.login
122
- site_listing = SiteListing.new(nsc)
123
-
124
- site_listing.sites.each do |site|
125
- puts "name is #{site.site_name}"
126
- puts "id is #{site.id}"
127
- end
128
- end
129
-
130
- end
1
+ #
2
+ # The Nexpose API
3
+ #
4
+ =begin
5
+
6
+ Copyright (C) 2009-2012, Rapid7 LLC
7
+ All rights reserved.
8
+
9
+ Redistribution and use in source and binary forms, with or without modification,
10
+ are permitted provided that the following conditions are met:
11
+
12
+ * Redistributions of source code must retain the above copyright notice,
13
+ this list of conditions and the following disclaimer.
14
+
15
+ * Redistributions in binary form must reproduce the above copyright notice,
16
+ this list of conditions and the following disclaimer in the documentation
17
+ and/or other materials provided with the distribution.
18
+
19
+ * Neither the name of Rapid7 LLC nor the names of its contributors
20
+ may be used to endorse or promote products derived from this software
21
+ without specific prior written permission.
22
+
23
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
27
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
30
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
+
34
+ =end
35
+
36
+ #
37
+ # WARNING! This code makes an SSL connection to the Nexpose server, but does NOT
38
+ # verify the certificate at this time. This can be a security issue if
39
+ # an attacker is able to man-in-the-middle the connection between the
40
+ # Metasploit console and the Nexpose server. In the common case of
41
+ # running Nexpose and Metasploit on the same host, this is a low risk.
42
+ #
43
+
44
+ #
45
+ # WARNING! This code is still rough and going through substantive changes. While
46
+ # you can build tools using this library today, keep in mind that
47
+ # method names and parameters may change in the future.
48
+ #
49
+
50
+ require 'date'
51
+ require 'rexml/document'
52
+ require 'net/https'
53
+ require 'net/http'
54
+ require 'uri'
55
+ require 'rex/mime'
56
+ require 'ipaddr'
57
+ require 'nexpose/error'
58
+ require 'nexpose/util'
59
+ require 'nexpose/user'
60
+ require 'nexpose/api_request'
61
+ require 'nexpose/manage'
62
+ require 'nexpose/misc'
63
+ require 'nexpose/report'
64
+ require 'nexpose/scan'
65
+ require 'nexpose/scan_engine'
66
+ require 'nexpose/silo'
67
+ require 'nexpose/site'
68
+ require 'nexpose/ticket'
69
+ require 'nexpose/vuln'
70
+ require 'nexpose/creds'
71
+ require 'nexpose/connection'
72
+ require 'nexpose/role'
73
+ require 'nexpose/common'
74
+
75
+ module Nexpose
76
+
77
+ # TODO add
78
+ def self.site_device_scan(connection, site_id, device_array, host_array, debug = false)
79
+
80
+ request_xml = '<SiteDevicesScanRequest session-id="' + connection.session_id.to_s + '" site-id="' + site_id.to_s + '">'
81
+ request_xml += '<Devices>'
82
+ device_array.each do |d|
83
+ request_xml += '<device id="' + d.to_s + '"/>'
84
+ end
85
+ request_xml += '</Devices>'
86
+ request_xml += '<Hosts>'
87
+ # The host array can only by single IP addresses for now. TODO: Expand to full API Spec.
88
+ host_array.each do |h|
89
+ request_xml += '<range from="' + h.to_s + '"/>'
90
+ end
91
+ request_xml += '</Hosts>'
92
+ request_xml += '</SiteDevicesScanRequest>'
93
+
94
+ r = connection.execute(request_xml)
95
+ r.success ? {:engine_id => r.attributes['engine_id'], :scan_id => r.attributes['scan-id']} : nil
96
+ end
97
+
98
+ # ==== Description
99
+ # Echos the last XML API request and response for the specified object. (Useful for debugging)
100
+ def self.printXML(object)
101
+ puts "request" + object.request_xml.to_s
102
+ puts "response is " + object.response_xml.to_s
103
+ end
104
+ end
@@ -1,144 +1,133 @@
1
- module Nexpose
2
- class APIRequest
3
- include XMLUtils
4
-
5
- attr_reader :http
6
- attr_reader :uri
7
- attr_reader :headers
8
- attr_reader :retry_count
9
- attr_reader :time_out
10
- attr_reader :pause
11
-
12
- attr_reader :req
13
- attr_reader :res
14
- attr_reader :sid
15
- attr_reader :success
16
-
17
- attr_reader :error
18
- attr_reader :trace
19
-
20
- attr_reader :raw_response
21
- attr_reader :raw_response_data
22
-
23
- #
24
- #
25
- #
26
- def initialize(req, url, api_version='1.1')
27
- @url = url
28
- @req = req
29
- @api_version = api_version
30
- @url = @url.sub('API_VERSION', @api_version)
31
- prepare_http_client
32
- end
33
-
34
- #
35
- #
36
- #
37
- def prepare_http_client
38
- @retry_count = 0
39
- @retry_count_max = 10
40
- @time_out = 30
41
- @pause = 2
42
- @uri = URI.parse(@url)
43
- @http = Net::HTTP.new(@uri.host, @uri.port)
44
- @http.use_ssl = true
45
- #
46
- # XXX: This is obviously a security issue, however, we handle this at the client level by forcing
47
- # a confirmation when the nexpose host is not localhost. In a perfect world, we would present
48
- # the server signature before accepting it, but this requires either a direct callback inside
49
- # of this module back to whatever UI, or opens a race condition between accept and attempt.
50
- #
51
- @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
52
- @headers = {'Content-Type' => 'text/xml'}
53
- @success = false
54
- end
55
-
56
- #
57
- #
58
- #
59
- def execute
60
- @conn_tries = 0
61
-
62
- begin
63
- prepare_http_client
64
- @raw_response = @http.post(@uri.path, @req, @headers)
65
- @raw_response_data = @raw_response.read_body
66
- @res = parse_xml(@raw_response_data)
67
-
68
- if (not @res.root)
69
- @error = "NeXpose service returned invalid XML"
70
- return @sid
71
- end
72
-
73
- @sid = attributes['session-id']
74
-
75
- if (attributes['success'] and attributes['success'].to_i == 1)
76
- @success = true
77
- elsif @api_version =~ /1.2/ and @res and (@res.get_elements '//Exception').count < 1
78
- @success = true
79
- else
80
- @success = false
81
- @res.elements.each('//Failure/Exception') do |s|
82
- s.elements.each('message') do |m|
83
- @error = m.text
84
- end
85
- s.elements.each('stacktrace') do |m|
86
- @trace = m.text
87
- end
88
- end
89
- end
90
- # This is a hack to handle corner cases where a heavily loaded NeXpose instance
91
- # drops our HTTP connection before processing. We try 5 times to establish a
92
- # connection in these situations. The actual exception occurs in the Ruby
93
- # http library, which is why we use such generic error classes.
94
- rescue ::ArgumentError, ::NoMethodError
95
- if @conn_tries < 5
96
- @conn_tries += 1
97
- retry
98
- end
99
- rescue ::Timeout::Error
100
- if @conn_tries < 5
101
- @conn_tries += 1
102
- retry
103
- end
104
- @error = "NeXpose host did not respond"
105
- rescue ::Errno::EHOSTUNREACH, ::Errno::ENETDOWN, ::Errno::ENETUNREACH, ::Errno::ENETRESET, ::Errno::EHOSTDOWN, ::Errno::EACCES, ::Errno::EINVAL, ::Errno::EADDRNOTAVAIL
106
- @error = "NeXpose host is unreachable"
107
- # Handle console-level interrupts
108
- rescue ::Interrupt
109
- @error = "received a user interrupt"
110
- rescue ::Errno::ECONNRESET, ::Errno::ECONNREFUSED, ::Errno::ENOTCONN, ::Errno::ECONNABORTED
111
- @error = "NeXpose service is not available"
112
- rescue ::REXML::ParseException
113
- @error = "NeXpose has not been properly licensed"
114
- end
115
-
116
- if !(@success or @error)
117
- @error = "NeXpose service returned an unrecognized response: #{@raw_response_data.inspect}"
118
- end
119
-
120
- @sid
121
- end
122
-
123
- #
124
- #
125
- #
126
- def attributes(*args)
127
- return if not @res.root
128
- @res.root.attributes(*args)
129
- end
130
-
131
- #
132
- #
133
- #
134
- def self.execute(url, req, api_version='1.1')
135
- obj = self.new(req, url, api_version)
136
- obj.execute
137
- if (not obj.success)
138
- raise APIError.new(obj, "Action failed: #{obj.error}")
139
- end
140
- obj
141
- end
142
-
143
- end
144
- end
1
+ module Nexpose
2
+ class APIRequest
3
+ include XMLUtils
4
+
5
+ attr_reader :http
6
+ attr_reader :uri
7
+ attr_reader :headers
8
+ attr_reader :retry_count
9
+ attr_reader :time_out
10
+ attr_reader :pause
11
+
12
+ attr_reader :req
13
+ attr_reader :res
14
+ attr_reader :sid
15
+ attr_reader :success
16
+
17
+ attr_reader :error
18
+ attr_reader :trace
19
+
20
+ attr_reader :raw_response
21
+ attr_reader :raw_response_data
22
+
23
+ def initialize(req, url, api_version='1.1')
24
+ @url = url
25
+ @req = req
26
+ @api_version = api_version
27
+ @url = @url.sub('API_VERSION', @api_version)
28
+ prepare_http_client
29
+ end
30
+
31
+ def prepare_http_client
32
+ @retry_count = 0
33
+ @retry_count_max = 10
34
+ @time_out = 30
35
+ @pause = 2
36
+ @uri = URI.parse(@url)
37
+ @http = Net::HTTP.new(@uri.host, @uri.port)
38
+ @http.use_ssl = true
39
+ #
40
+ # XXX: This is obviously a security issue, however, we handle this at the client level by forcing
41
+ # a confirmation when the nexpose host is not localhost. In a perfect world, we would present
42
+ # the server signature before accepting it, but this requires either a direct callback inside
43
+ # of this module back to whatever UI, or opens a race condition between accept and attempt.
44
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
45
+ @headers = {'Content-Type' => 'text/xml'}
46
+ @success = false
47
+ end
48
+
49
+ def execute
50
+ @conn_tries = 0
51
+
52
+ begin
53
+ prepare_http_client
54
+ @raw_response = @http.post(@uri.path, @req, @headers)
55
+ @raw_response_data = @raw_response.read_body
56
+ @res = parse_xml(@raw_response_data)
57
+
58
+ if (not @res.root)
59
+ @error = 'Nexpose service returned invalid XML.'
60
+ return @sid
61
+ end
62
+
63
+ @sid = attributes['session-id']
64
+
65
+ if (attributes['success'] and attributes['success'].to_i == 1)
66
+ @success = true
67
+ elsif @api_version =~ /1.2/ and @res and (@res.get_elements '//Exception').count < 1
68
+ @success = true
69
+ else
70
+ @success = false
71
+ @res.elements.each('//Failure/Exception') do |s|
72
+ s.elements.each('message') do |m|
73
+ @error = m.text
74
+ end
75
+ s.elements.each('stacktrace') do |m|
76
+ @trace = m.text
77
+ end
78
+ end
79
+ end
80
+ # This is a hack to handle corner cases where a heavily loaded Nexpose instance
81
+ # drops our HTTP connection before processing. We try 5 times to establish a
82
+ # connection in these situations. The actual exception occurs in the Ruby
83
+ # http library, which is why we use such generic error classes.
84
+ rescue OpenSSL::SSL::SSLError
85
+ if @conn_tries < 5
86
+ @conn_tries += 1
87
+ retry
88
+ end
89
+ rescue ::ArgumentError, ::NoMethodError
90
+ if @conn_tries < 5
91
+ @conn_tries += 1
92
+ retry
93
+ end
94
+ rescue ::Timeout::Error
95
+ if @conn_tries < 5
96
+ @conn_tries += 1
97
+ retry
98
+ end
99
+ @error = 'Nexpose host did not respond.'
100
+ rescue ::Errno::EHOSTUNREACH, ::Errno::ENETDOWN, ::Errno::ENETUNREACH, ::Errno::ENETRESET, ::Errno::EHOSTDOWN, ::Errno::EACCES, ::Errno::EINVAL, ::Errno::EADDRNOTAVAIL
101
+ @error = 'Nexpose host is unreachable.'
102
+ # Handle console-level interrupts
103
+ rescue ::Interrupt
104
+ @error = 'Received a user interrupt.'
105
+ rescue ::Errno::ECONNRESET, ::Errno::ECONNREFUSED, ::Errno::ENOTCONN, ::Errno::ECONNABORTED
106
+ @error = 'Nexpose service is not available.'
107
+ rescue ::REXML::ParseException
108
+ @error = 'Nexpose has not been properly licensed.'
109
+ end
110
+
111
+ if !(@success or @error)
112
+ @error = "Nexpose service returned an unrecognized response: #{@raw_response_data.inspect}"
113
+ end
114
+
115
+ @sid
116
+ end
117
+
118
+ def attributes(*args)
119
+ return if not @res.root
120
+ @res.root.attributes(*args)
121
+ end
122
+
123
+ def self.execute(url, req, api_version='1.1')
124
+ obj = self.new(req, url, api_version)
125
+ obj.execute
126
+ if (not obj.success)
127
+ raise APIError.new(obj, "Action failed: #{obj.error}")
128
+ end
129
+ obj
130
+ end
131
+
132
+ end
133
+ end