diff --git a/lib/rapid7/nexpose.rb b/lib/rapid7/nexpose.rb index c844fe3877..f095731f04 100644 --- a/lib/rapid7/nexpose.rb +++ b/lib/rapid7/nexpose.rb @@ -144,6 +144,7 @@ class APIRequest begin prepare_http_client + @raw_response = @http.post(@uri.path, @req, @headers) @raw_response_data = @raw_response.body @res = parse_xml(@raw_response_data) @@ -155,19 +156,32 @@ class APIRequest @sid = attributes['session-id'] - if(attributes['success'] and attributes['success'].to_i == 1) - @success = true - else + @success = true + + if(attributes['success'] and attributes['success'].to_i == 0) @success = false - @res.elements.each('//Failure/Exception') do |s| - s.elements.each('message') do |m| - @error = m.text - end - s.elements.each('stacktrace') do |m| - @trace = m.text - end + end + + # Look for a stack trace + @res.elements.each('//Failure/Exception') do |s| + + # 1.1 returns lower case elements + s.elements.each('message') do |m| + @error = m.text + end + s.elements.each('stacktrace') do |m| + @trace = m.text + end + + # 1.2 returns capitalized elements + s.elements.each('Message') do |m| + @error = m.text + end + s.elements.each('Stacktrace') do |m| + @trace = m.text end end + # This is a hack to handle corner cases where a heavily loaded Nexpose instance # drops our HTTP connection before processing. We try 5 times to establish a # connection in these situations. The actual exception occurs in the Ruby @@ -194,9 +208,7 @@ class APIRequest @error = "Nexpose has not been properly licensed" end - if ! (@success or @error) - @error = "Nexpose service returned an unrecognized response: #{@raw_response_data.inspect}" - end + @success = false if @error @sid end @@ -234,6 +246,17 @@ module NexposeAPI xml end + def make_xml_plain(name, opts={}, data='') + xml = REXML::Element.new(name) + + opts.keys.each do |k| + xml.attributes[k] = "#{opts[k]}" + end + + xml.text = data + + xml + end def scan_stop(param) r = execute(make_xml('ScanStopRequest', { 'scan-id' => param })) r.success @@ -342,6 +365,28 @@ module NexposeAPI r.success end + def vuln_exception_create(vuln_id, reason, scope, comment='', attrs={}) + attrs = attrs.merge({ 'vuln-id' => vuln_id, 'reason' => reason, 'scope' => scope }) + req = make_xml('VulnerabilityExceptionCreateRequest', attrs) + com = make_xml_plain('comment', {}, comment.to_s) + req << com + r = execute(req, '1.2') + end + + def vuln_exception_approve(exception_id, comment='', attrs={}) + attrs = attrs.merge({ 'exception-id' => exception_id }) + req = make_xml('VulnerabilityExceptionApproveRequest', attrs) + com = make_xml_plain('comment', {}, comment.to_s) + req << com + r = execute(req, '1.2') + end + + def vuln_exception_update_expiration(exception_id, expiration_date, attrs={}) + attrs = attrs.merge({ 'exception-id' => exception_id, 'expiration-date' => expiration_date }) + req = make_xml('VulnerabilityExceptionUpdateExpirationDateRequest', attrs) + r = execute(req, '1.2') + end + def asset_group_delete(connection, id, debug = false) r = execute(make_xml('AssetGroupDeleteRequest', { 'group-id' => param })) r.success @@ -349,10 +394,10 @@ module NexposeAPI def asset_group_create(name, description, devices) req = make_xml('AssetGroupSaveRequest') - req_ag = make_xml('AssetGroup', { 'id' => "-1", 'name' => name, 'description' => description }) - req_devices = make_xml('Devices') + req_ag = make_xml_plain('AssetGroup', { 'id' => "-1", 'name' => name, 'description' => description }) + req_devices = make_xml_plain('Devices') devices.each do |did| - req_devices << make_xml('device', { 'id' => did }) + req_devices << make_xml_plain('device', { 'id' => did }) end req_ag << req_devices req << req_ag @@ -639,6 +684,7 @@ class Connection @session_id = nil @error = false @url = "https://#{@host}:#{@port}/api/1.1/xml" + @url_base = "https://#{@host}:#{@port}/api/" end # Establish a new connection and Session ID @@ -650,10 +696,15 @@ class Connection @session_id = r.sid return true end + + false end # Logout of the current connection def logout + # Bypass logout unless we have an actual session ID + return true unless @session_id + r = execute(make_xml('LogoutRequest', {'sync-id' => 0})) if(r.success) return true @@ -662,8 +713,8 @@ class Connection end # Execute an API request - def execute(xml) - APIRequest.execute(url,xml.to_s) + def execute(xml, version='1.1') + APIRequest.execute("#{@url_base}#{version}/xml", xml.to_s) end # Download a specific URL