nexpose 0.0.9 → 0.0.91

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,144 @@
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
@@ -0,0 +1,106 @@
1
+ module Nexpose
2
+
3
+ # === Description
4
+ # Object that represents a connection to a NeXpose Security Console.
5
+ #
6
+ # === Examples
7
+ # # Create a new Nexpose Connection on the default port
8
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
9
+ #
10
+ # # Login to NSC and Establish a Session ID
11
+ # nsc.login()
12
+ #
13
+ # # Check Session ID
14
+ # if (nsc.session_id)
15
+ # puts "Login Successful"
16
+ # else
17
+ # puts "Login Failure"
18
+ # end
19
+ #
20
+ # # //Logout
21
+ # logout_success = nsc.logout()
22
+ # if (! logout_success)
23
+ # puts "Logout Failure" + "<p>" + nsc.error_msg.to_s
24
+ # end
25
+ #
26
+ class Connection
27
+ include XMLUtils
28
+ include NexposeAPI
29
+
30
+ # true if an error condition exists; false otherwise
31
+ attr_reader :error
32
+ # Error message string
33
+ attr_reader :error_msg
34
+ # The last XML request sent by this object
35
+ attr_reader :request_xml
36
+ # The last XML response received by this object
37
+ attr_reader :response_xml
38
+ # Session ID of this connection
39
+ attr_reader :session_id
40
+ # The hostname or IP Address of the NSC
41
+ attr_reader :host
42
+ # The port of the NSC (default is 3780)
43
+ attr_reader :port
44
+ # The username used to login to the NSC
45
+ attr_reader :username
46
+ # The password used to login to the NSC
47
+ attr_reader :password
48
+ # The URL for communication
49
+ attr_reader :url
50
+
51
+ # Constructor for Connection
52
+ def initialize(ip, user, pass, port = 3780, silo_id = nil)
53
+ @host = ip
54
+ @port = port
55
+ @username = user
56
+ @password = pass
57
+ @silo_id = silo_id
58
+ @session_id = nil
59
+ @error = false
60
+ @url = "https://#{@host}:#{@port}/api/API_VERSION/xml"
61
+ end
62
+
63
+ # Establish a new connection and Session ID
64
+ def login
65
+ begin
66
+ login_hash = {'sync-id' => 0, 'password' => @password, 'user-id' => @username}
67
+ unless @silo_id.nil?
68
+ login_hash['silo-id'] = @silo_id
69
+ end
70
+ r = execute(make_xml('LoginRequest', login_hash))
71
+ rescue APIError
72
+ raise AuthenticationFailed.new(r)
73
+ end
74
+ if (r.success)
75
+ @session_id = r.sid
76
+ true
77
+ end
78
+ end
79
+
80
+ # Logout of the current connection
81
+ def logout
82
+ r = execute(make_xml('LogoutRequest', {'sync-id' => 0}))
83
+ if (r.success)
84
+ return true
85
+ end
86
+ raise APIError.new(r, 'Logout failed')
87
+ end
88
+
89
+ # Execute an API request
90
+ def execute(xml, version = '1.1')
91
+ @api_version = version
92
+ APIRequest.execute(@url, xml.to_s, @api_version)
93
+ end
94
+
95
+ # Download a specific URL
96
+ def download(url)
97
+ uri = URI.parse(url)
98
+ http = Net::HTTP.new(@host, @port)
99
+ http.use_ssl = true
100
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX: security issue
101
+ headers = {'Cookie' => "nexposeCCSessionID=#{@session_id}"}
102
+ resp, data = http.get(uri.path, headers)
103
+ data
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,189 @@
1
+ module Nexpose
2
+ include Sanitize
3
+
4
+ # === Description
5
+ # Object that represents administrative credentials to be used during a scan. When retrived from an existing site configuration the credentials will be returned as a security blob and can only be passed back as is during a Site Save operation. This object can only be used to create a new set of credentials.
6
+ #
7
+ class AdminCredentials
8
+ # Security blob for an existing set of credentials
9
+ attr_reader :securityblob
10
+ # Designates if this object contains user defined credentials or a security blob
11
+ attr_reader :isblob
12
+ # The service for these credentials. Can be All.
13
+ attr_reader :service
14
+ # The host for these credentials. Can be Any.
15
+ attr_reader :host
16
+ # The port on which to use these credentials.
17
+ attr_reader :port
18
+ # The user id or username
19
+ attr_reader :userid
20
+ # The password
21
+ attr_reader :password
22
+ # The realm for these credentials
23
+ attr_reader :realm
24
+ # When using httpheaders, this represents the set of headers to pass
25
+ # with the authentication request.
26
+ attr_reader :headers
27
+
28
+ def initialize(isblob = false)
29
+ @isblob = isblob
30
+ end
31
+
32
+ # Sets the credentials information for this object.
33
+ def setCredentials(service, host, port, userid, password, realm)
34
+ @isblob = false
35
+ @securityblob = nil
36
+ @service = service
37
+ @host = host
38
+ @port = port
39
+ @userid = userid
40
+ @password = password
41
+ @realm = realm
42
+ end
43
+
44
+ # TODO: add description
45
+ def setService(service)
46
+ @service = service
47
+ end
48
+
49
+ def setHost(host)
50
+ @host = host
51
+ end
52
+
53
+ # TODO: add description
54
+ def setBlob(securityblob)
55
+ @isblob = true
56
+ @securityblob = securityblob
57
+ end
58
+
59
+ # Add Headers to credentials for httpheaders.
60
+ def setHeaders(headers)
61
+ @headers = headers
62
+ end
63
+
64
+ def to_xml
65
+ xml = ''
66
+ xml << '<adminCredentials'
67
+ xml << %Q{ service="#{replace_entities(service)}"} if (service)
68
+ xml << %Q{ userid="#{replace_entities(userid)}"} if (userid)
69
+ xml << %Q{ password="#{replace_entities(password)}"} if (password)
70
+ xml << %Q{ realm="#{replace_entities(realm)}"} if (realm)
71
+ xml << %Q{ host="#{replace_entities(host)}"} if (host)
72
+ xml << %Q{ port="#{replace_entities(port)}"} if (port)
73
+ xml << '>'
74
+ xml << replace_entities(securityblob) if (isblob)
75
+ xml << @headers.to_xml()
76
+ xml << '</adminCredentials>'
77
+
78
+ xml
79
+ end
80
+ end
81
+
82
+ # Object that represents Header name-value pairs, associated with Web Session Authentication.
83
+ class Header
84
+ # Name, one per Header
85
+ attr_reader :name
86
+ # Value, one per Header
87
+ attr_reader :value
88
+
89
+ # Construct with name value pair
90
+ def initialize(name, value)
91
+ @name = name
92
+ @value = value
93
+ end
94
+
95
+ def to_xml
96
+ xml = ''
97
+ xml << '<Header'
98
+ xml << %Q{ name="#{replace_entities(name)}"} if (name)
99
+ xml << %Q{ value="#{replace_entities(value)}"} if (value)
100
+ xml << '/>'
101
+ xml
102
+ end
103
+ end
104
+
105
+ # Object that represents Headers, associated with Web Session Authentication.
106
+ class Headers
107
+ # A regular expression used to match against the response to identify authentication failures.
108
+ attr_reader :soft403
109
+ # Base URL of the application for which the form authentication applies.
110
+ attr_reader :webapproot
111
+ # When using httpheaders, this represents the set of headers to pass with the authentication request.
112
+ attr_reader :headers
113
+
114
+ def initialize(webapproot, soft403)
115
+ @headers = []
116
+ @webapproot = webapproot
117
+ @soft403 = soft403
118
+ end
119
+
120
+ def addHeader(header)
121
+ @headers.push(header)
122
+ end
123
+
124
+
125
+ def to_xml
126
+ xml = ''
127
+ xml << '<Headers'
128
+ xml << %Q{ soft403="#{replace_entities(soft403)}"} if (soft403)
129
+ xml << %Q{ webapproot="#{replace_entities(webapproot)}"} if (webapproot)
130
+ xml << '>'
131
+ @headers.each do |header|
132
+ xml << header.to_xml
133
+ end
134
+ xml << '</Headers>'
135
+ xml
136
+ end
137
+ end
138
+
139
+ # When using htmlform, this represents the login form information.
140
+ class Field
141
+ # The name of the HTML field (form parameter).
142
+ attr_reader :name
143
+ # The value of the HTML field (form parameter).
144
+ attr_reader :value
145
+ # The type of the HTML field (form parameter).
146
+ attr_reader :type
147
+ # Is the HTML field (form parameter) dynamically generated? If so,
148
+ # the login page is requested and the value of the field is extracted
149
+ # from the response.
150
+ attr_reader :dynamic
151
+ # If the HTML field (form parameter) is a radio button, checkbox or select
152
+ # field, this flag determines if the field should be checked (selected).
153
+ attr_reader :checked
154
+
155
+ # TODO
156
+ end
157
+
158
+ # When using htmlform, this represents the login form information.
159
+ class HTMLForm
160
+ # The name of the form being submitted.
161
+ attr_reader :name
162
+ # The HTTP action (URL) through which to submit the login form.
163
+ attr_reader :action
164
+ # The HTTP request method with which to submit the form.
165
+ attr_reader :method
166
+ # The HTTP encoding type with which to submit the form.
167
+ attr_reader :enctype
168
+
169
+ # TODO
170
+ end
171
+
172
+ # When using htmlform, this represents the login form information.
173
+ class HTMLForms
174
+ # The URL of the login page containing the login form.
175
+ attr_reader :parentpage
176
+ # A regular expression used to match against the response to identify
177
+ # authentication failures.
178
+ attr_reader :soft403
179
+ # Base URL of the application for which the form authentication applies.
180
+ attr_reader :webapproot
181
+
182
+ # TODO
183
+ end
184
+
185
+ # When using ssh-key, this represents the PEM-format keypair information.
186
+ class PEMKey
187
+ # TODO
188
+ end
189
+ end
@@ -0,0 +1,21 @@
1
+ module Nexpose
2
+ class APIError < ::RuntimeError
3
+ attr_accessor :req, :reason
4
+
5
+ def initialize(req, reason = '')
6
+ @req = req
7
+ @reason = reason
8
+ end
9
+
10
+ def to_s
11
+ "NexposeAPI: #{@reason}"
12
+ end
13
+ end
14
+
15
+ class AuthenticationFailed < APIError
16
+ def initialize(req)
17
+ @req = req
18
+ @reason = "Login Failed"
19
+ end
20
+ end
21
+ end