qualys 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -2
  3. data/.rubocop.yml +7 -0
  4. data/.rubocop_todo.yml +71 -0
  5. data/.travis.yml +13 -1
  6. data/Gemfile +3 -1
  7. data/Gemfile.lock +91 -0
  8. data/README.md +50 -10
  9. data/Rakefile +3 -4
  10. data/lib/qualys.rb +10 -11
  11. data/lib/qualys/api.rb +38 -47
  12. data/lib/qualys/auth.rb +11 -20
  13. data/lib/qualys/compliance.rb +3 -7
  14. data/lib/qualys/config.rb +3 -5
  15. data/lib/qualys/host.rb +17 -0
  16. data/lib/qualys/report.rb +90 -0
  17. data/lib/qualys/scans.rb +10 -22
  18. data/lib/qualys/version.rb +1 -1
  19. data/lib/qualys/vulnerability.rb +74 -0
  20. data/qualys.gemspec +18 -19
  21. data/spec/fixtures/vcr_cassettes/api_get.yml +52 -0
  22. data/spec/fixtures/vcr_cassettes/create_global_report.yml +68 -0
  23. data/spec/fixtures/vcr_cassettes/emptyscans.yml +35 -0
  24. data/spec/fixtures/vcr_cassettes/get.yml +107 -0
  25. data/spec/fixtures/vcr_cassettes/global_report.yml +17800 -0
  26. data/spec/fixtures/vcr_cassettes/load_global_report.yml +625 -0
  27. data/spec/fixtures/vcr_cassettes/login.yml +50 -0
  28. data/spec/fixtures/vcr_cassettes/logout.yml +50 -0
  29. data/spec/fixtures/vcr_cassettes/scan.yml +73 -0
  30. data/spec/fixtures/vcr_cassettes/scans.yml +89 -0
  31. data/spec/fixtures/vcr_cassettes/templates.yml +121 -0
  32. data/spec/fixtures/vcr_cassettes/try_load_not_existing_report.yml +63 -0
  33. data/spec/fixtures/vcr_cassettes/unlogged.yml +45 -0
  34. data/spec/fixtures/vcr_cassettes/wrong.yml +101 -0
  35. data/spec/qualys/api_spec.rb +27 -0
  36. data/spec/qualys/report_spec.rb +65 -0
  37. data/spec/qualys/scans_spec.rb +75 -0
  38. data/spec/qualys/version_spec.rb +11 -0
  39. data/spec/qualys/vulnerability_spec.rb +53 -0
  40. data/spec/qualys_spec.rb +20 -0
  41. data/spec/spec_helper.rb +37 -0
  42. metadata +61 -15
  43. data/.rock.yml +0 -17
  44. data/lib/qualys/reports.rb +0 -47
@@ -6,37 +6,28 @@ module Qualys
6
6
 
7
7
  # Do Login
8
8
  def self.login
9
-
10
9
  # Request a login
11
- response = self.api_post('session/', {
12
- :body => {
13
- :action => 'login',
14
- :username => Qualys::Config.username,
15
- :password => Qualys::Config.password
16
- }
17
- })
18
-
10
+ response = api_post('/session/', body: {
11
+ action: 'login',
12
+ username: Qualys::Config.username,
13
+ password: Qualys::Config.password
14
+ })
15
+
19
16
  # set the session key
20
17
  Qualys::Config.session_key = response.header['Set-Cookie']
21
18
  true
22
-
23
19
  end
24
20
 
25
21
  # Set Logout
26
22
  def self.logout
27
-
28
23
  # Request a login
29
- response = self.api_post('session/', {
30
- :body => {
31
- :action => 'logout',
32
- }
33
- })
34
-
24
+ api_post('/session/', body: {
25
+ action: 'logout'
26
+ })
27
+
35
28
  # set the session key
36
29
  Qualys::Config.session_key = nil
37
30
  true
38
-
39
31
  end
40
-
41
32
  end
42
- end
33
+ end
@@ -1,21 +1,17 @@
1
1
  module Qualys
2
2
  class Compliance < Api
3
-
4
3
  def self.all
5
- response = api_get("compliance/control/", { :query => { :action => 'list' }} )
4
+ response = api_get('/compliance/control/', query: { action: 'list' })
6
5
 
7
- unless response.parsed_response['<COMPLIANCE_SCAN_RESULT_OUTPUT']['RESPONSE'].has_key? 'COMPLIANCE_SCAN'
6
+ unless response.parsed_response['<COMPLIANCE_SCAN_RESULT_OUTPUT']['RESPONSE'].key? 'COMPLIANCE_SCAN'
8
7
  return []
9
8
  end
10
9
 
11
10
  response.parsed_response['COMPLIANCE_SCAN_RESULT_OUTPUT']['RESPONSE']['COMPLIANCE_SCAN']
12
-
13
11
  end
14
12
 
15
13
  def self.each
16
- self.all
14
+ all
17
15
  end
18
-
19
16
  end
20
-
21
17
  end
@@ -26,10 +26,8 @@ module Qualys
26
26
  #
27
27
  # @param [ String ] path The path to the file.
28
28
  def load!(path)
29
- settings = YAML.load(ERB.new(File.new(path).read).result)['api']
30
- if settings.is_a? Hash
31
- from_hash(settings)
32
- end
29
+ settings = YAML.safe_load(ERB.new(File.new(path).read).result)['api']
30
+ from_hash(settings) if settings.is_a? Hash
33
31
  end
34
32
  end
35
- end
33
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qualys
4
+ # a scanned target
5
+ class Host
6
+ attr_accessor :ip, :tracking_method, :dns, :operating_system, :vuln_info_list, :vulnerabilities
7
+
8
+ def initialize(host, vulnerabilities = nil)
9
+ @ip = host['IP']
10
+ @tracking_method = host['TRACKING_METHOD']
11
+ @dns = host['DNS']
12
+ @operating_system = host['OPERATING_SYSTEM']
13
+ @vuln_info_list = host['VULN_INFO_LIST']
14
+ @vulnerabilities = vulnerabilities
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qualys
4
+ # Qualys reports
5
+ class Report < Api
6
+ attr_accessor :header, :host_list, :glossary, :appendices
7
+
8
+ # accepted timeout in seconds
9
+ TIMEOUT = 60.0
10
+
11
+ class << self
12
+ def find_by_id(id)
13
+ response = api_get('/report/', query: {
14
+ action: 'fetch',
15
+ id: id
16
+ })
17
+
18
+ # check if report exist
19
+ return unless response.parsed_response.keys.include?('ASSET_DATA_REPORT')
20
+
21
+ Report.new(response.parsed_response)
22
+ end
23
+
24
+ # returns the list of the templates
25
+ def templates
26
+ auth = { username: Qualys::Config.username, password: Qualys::Config.password }
27
+ response = HTTParty.get('https://qualysapi.qualys.eu/msp/report_template_list.php',
28
+ basic_auth: auth)
29
+ response.parsed_response['REPORT_TEMPLATE_LIST']['REPORT_TEMPLATE']
30
+ end
31
+
32
+ def delete(id)
33
+ api_post('/report/', query: {
34
+ action: 'delete',
35
+ id: id
36
+ })
37
+ end
38
+
39
+ # returns the id of the report
40
+ def create_global_report
41
+ scan_template = templates.detect { |template| template['TITLE'] == 'Technical Report' }
42
+ response = api_post('/report/', query: {
43
+ action: 'launch',
44
+ report_title: 'Generated_by_Ruby_Qualys_gem',
45
+ report_type: 'Scan',
46
+ output_format: 'xml',
47
+ template_id: scan_template['ID']
48
+ })
49
+
50
+ response.parsed_response['SIMPLE_RETURN']['RESPONSE']['ITEM_LIST']['ITEM']['VALUE']
51
+ end
52
+
53
+ # returns a report global report object.
54
+ # This method can be time consuming and times out after 64 s
55
+ def global_report
56
+ report_id = create_global_report
57
+ report = find_by_id(report_id)
58
+
59
+ 10.times do
60
+ sleep(TIMEOUT / 10)
61
+ report = find_by_id(report_id)
62
+ break unless report.nil?
63
+ end
64
+
65
+ raise Qualys::Report::Exception, 'Report generation timed out' if report.nil?
66
+
67
+ delete(report_id)
68
+ report
69
+ end
70
+ end
71
+
72
+ def initialize(report)
73
+ @header = report['ASSET_DATA_REPORT']['HEADER']
74
+ @host_list = report['ASSET_DATA_REPORT']['HOST_LIST']['HOST']
75
+ @glossary = report['ASSET_DATA_REPORT']['GLOSSARY']['VULN_DETAILS_LIST']['VULN_DETAILS']
76
+ @appendices = report['ASSET_DATA_REPORT']['APPENDICES']
77
+ end
78
+
79
+ def hosts
80
+ hosts ||= host_list.map do |xml_host|
81
+ vulnerabilities = xml_host['VULN_INFO_LIST']['VULN_INFO'].map do |vuln|
82
+ Qualys::Vulnerability.new(vuln, @glossary)
83
+ end
84
+ Qualys::Host.new(xml_host, vulnerabilities)
85
+ end
86
+
87
+ hosts
88
+ end
89
+ end
90
+ end
@@ -1,37 +1,28 @@
1
1
  module Qualys
2
2
  class Scans < Api
3
-
4
3
  def self.all
5
- response = api_get("scan/", { :query => { :action => 'list' }} )
6
- unless response.parsed_response['SCAN_LIST_OUTPUT']['RESPONSE'].has_key? 'SCAN_LIST'
4
+ response = api_get('/scan/', query: { action: 'list' })
5
+ unless response.parsed_response['SCAN_LIST_OUTPUT']['RESPONSE'].key? 'SCAN_LIST'
7
6
  return []
8
7
  end
9
8
 
10
9
  scanlist = response.parsed_response['SCAN_LIST_OUTPUT']['RESPONSE']['SCAN_LIST']['SCAN']
11
- scanlist.map!{|scan| Scan.new(scan)}
12
-
13
- end
14
-
15
- def self.each
16
- self.all
10
+ scanlist.map! { |scan| Scan.new(scan) }
17
11
  end
18
12
 
19
13
  def self.get(ref)
20
- response = api_get("scan/", {
21
- :query => {
22
- :action => 'fetch',
23
- :scan_ref => ref,
24
- :mode => 'extended',
25
- :output_format => 'json'
26
- }} )
14
+ response = api_get('/scan/', query: {
15
+ action: 'fetch',
16
+ scan_ref: ref,
17
+ mode: 'extended',
18
+ output_format: 'json'
19
+ })
27
20
 
28
21
  JSON.parse(response.parsed_response)
29
22
  end
30
-
31
23
  end
32
24
 
33
25
  class Scan
34
-
35
26
  attr_accessor :ref, :title, :type, :date, :duration, :status, :target, :user
36
27
 
37
28
  def initialize(scan)
@@ -50,15 +41,12 @@ module Qualys
50
41
  end
51
42
 
52
43
  def finished?
53
- if @status.eql? 'Finished'
54
- return true
55
- end
44
+ return true if @status.eql? 'Finished'
56
45
 
57
46
  false
58
47
  end
59
48
  end
60
49
 
61
50
  class Details
62
-
63
51
  end
64
52
  end
@@ -1,3 +1,3 @@
1
1
  module Qualys
2
- VERSION = '0.1.4'
2
+ VERSION = '0.1.5'.freeze
3
3
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qualys
4
+ # Qualys vulnerabilities from a report xml
5
+ class Vulnerability
6
+ attr_accessor :qid, :type, :port, :service, :protocol, :ssl, :result,
7
+ :first_found, :last_found, :times_found, :status, :details,
8
+ :cve_code_list, :title, :severity, :category, :threat, :impact,
9
+ :solution, :pci_flag, :correlation, :vendor_reference_list, :last_update,
10
+ :url
11
+
12
+ def initialize(vuln, glossary)
13
+ parse_vuln vuln
14
+
15
+ match_details glossary
16
+
17
+ parse_cve if @details['CVE_ID_LIST']
18
+ parse_details
19
+ # gives the url to the qualys knowledge base for this vulnerabitlty
20
+ parse_url
21
+ end
22
+
23
+ def to_s
24
+ "#{qid}, #{title}, severity : #{severity}, cves: #{cve_code_list&.join(', ') || 'no cve'}"
25
+ end
26
+
27
+ private
28
+
29
+ def parse_cve
30
+ cve_xlm_array = if @details['CVE_ID_LIST']['CVE_ID'].is_a?(Array)
31
+ @details['CVE_ID_LIST']['CVE_ID']
32
+ else
33
+ [@details['CVE_ID_LIST']['CVE_ID']]
34
+ end
35
+ @cve_code_list = cve_xlm_array.map { |cve| cve['ID'] }
36
+ end
37
+
38
+ # this methods finds the details for this qid in the report's glossary
39
+ def match_details(glossary)
40
+ @details = glossary.select { |detail| detail['id'] == @qid }[0]
41
+ end
42
+
43
+ def parse_details
44
+ @title = @details['TITLE']
45
+ @severity = @details['SEVERITY']
46
+ @category = @details['CATEGORY']
47
+ @threat = @details['THREAT']
48
+ @impact = @details['IMPACT']
49
+ @solution = @details['SOLUTION']
50
+ @pci_flag = @details['PCI_FLAG']
51
+ @correlation = @details['CORRELATION']
52
+ @vendor_reference_list = @details['VENDOR_REFERENCE_LIST']
53
+ @last_update = @details['BUGTRAQ_ID_LIST']
54
+ end
55
+
56
+ def parse_url
57
+ @url = 'https://qualysguard.qualys.eu/fo/common/vuln_info.php?id=' + @qid[4..-1]
58
+ end
59
+
60
+ def parse_vuln(vuln)
61
+ @qid = vuln['QID']['id']
62
+ @type = vuln['TYPE']
63
+ @port = vuln['PORT']
64
+ @service = vuln['SERVICE']
65
+ @protocol = vuln['PROTOCOL']
66
+ @ssl = vuln['SSL']
67
+ @result = vuln['RESULT']
68
+ @first_found = vuln['FIRST_FOUND']
69
+ @last_found = vuln['LAST_FOUND']
70
+ @times_found = vuln['TIMES_FOUND']
71
+ @status = vuln['VULN_STATUS']
72
+ end
73
+ end
74
+ end
@@ -1,37 +1,36 @@
1
1
  # Created by hand, like a real man
2
2
  # coding: utf-8
3
+
3
4
  lib = File.expand_path('../lib', __FILE__)
4
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
6
  require 'qualys/version'
6
7
 
7
8
  Gem::Specification.new do |s|
8
-
9
9
  s.name = 'qualys'
10
10
  s.version = Qualys::VERSION
11
- s.date = '2015-02-25'
12
- s.summary = "qualys API Client"
13
- s.description = "Easily interface with the qualys for consuming events"
14
- s.authors = ["Mike Mackintosh"]
11
+ s.date = '2017-12-17'
12
+ s.summary = 'qualys API Client'
13
+ s.description = 'Easily interface with the qualys for consuming events'
14
+ s.authors = ['Mike Mackintosh']
15
15
  s.email = 'm@zyp.io'
16
16
  s.homepage =
17
17
  'http://github.com/mikemackintosh/ruby-qualys'
18
18
 
19
19
  s.license = 'MIT'
20
-
21
- s.require_paths = ["lib"]
20
+
21
+ s.require_paths = ['lib']
22
22
  s.files = `git ls-files -z`.split("\x0")
23
- #s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
- #s.test_files = s.files.grep(%r{^(test|spec|features)/})
23
+ # s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
25
25
 
26
- s.add_dependency 'json'
27
26
  s.add_dependency 'erubis'
28
- s.add_dependency 'httparty'
29
-
30
- s.add_development_dependency "bundler"
31
- s.add_development_dependency "rake"
32
- s.add_development_dependency "rspec"
33
- s.add_development_dependency "vcr"
34
- s.add_development_dependency "webmock"
35
- s.add_development_dependency "rubocop"
27
+ s.add_dependency 'httparty', '~> 0.15'
28
+ s.add_dependency 'json'
36
29
 
37
- end
30
+ s.add_development_dependency 'bundler'
31
+ s.add_development_dependency 'rake'
32
+ s.add_development_dependency 'rspec'
33
+ s.add_development_dependency 'rubocop'
34
+ s.add_development_dependency 'vcr'
35
+ s.add_development_dependency 'webmock'
36
+ end
@@ -0,0 +1,52 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://qualysapi.qualys.eu/api/2.0/fo/scan/?action=list
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ response:
10
+ status:
11
+ code: 200
12
+ message: OK
13
+ body:
14
+ encoding: UTF-8
15
+ string: "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE SCAN_LIST_OUTPUT
16
+ SYSTEM \"https://qualysapi.qualys.eu/api/2.0/fo/scan/scan_list_output.dtd\">\n<!--
17
+ This report was generated with an evaluation version of Qualys //--> \n<SCAN_LIST_OUTPUT>\n
18
+ \ <RESPONSE>\n <DATETIME>2017-12-11T12:49:00Z</DATETIME>\n <SCAN_LIST>\n
19
+ \ <SCAN>\n <REF>scan/XXXX633905.79357</REF>\n <TYPE>On-Demand</TYPE>\n
20
+ \ <TITLE><![CDATA[shellshock 20171207]]></TITLE>\n <USER_LOGIN>Thomas</USER_LOGIN>\n
21
+ \ <LAUNCH_DATETIME>2017-12-07T08:05:05Z</LAUNCH_DATETIME>\n <DURATION>00:01:43</DURATION>\n
22
+ \ <PROCESSING_PRIORITY>0 - No Priority</PROCESSING_PRIORITY>\n <PROCESSED>1</PROCESSED>\n
23
+ \ <STATUS>\n <STATE>Error</STATE>\n </STATUS>\n <TARGET><![CDATA[47.69.112.62]]></TARGET>\n
24
+ \ </SCAN>\n <SCAN>\n <REF>scan/1512633883.79354</REF>\n <TYPE>On-Demand</TYPE>\n
25
+ \ <TITLE><![CDATA[shellshock 20171207]]></TITLE>\n <USER_LOGIN>Thomas</USER_LOGIN>\n
26
+ \ <LAUNCH_DATETIME>2017-12-07T08:04:43Z</LAUNCH_DATETIME>\n <DURATION>00:07:02</DURATION>\n
27
+ \ <PROCESSING_PRIORITY>0 - No Priority</PROCESSING_PRIORITY>\n <PROCESSED>1</PROCESSED>\n
28
+ \ <STATUS>\n <STATE>Finished</STATE>\n </STATUS>\n <TARGET><![CDATA[47.69.112.62]]></TARGET>\n
29
+ \ </SCAN>\n <SCAN>\n <REF>scan/1512633720.79248</REF>\n <TYPE>On-Demand</TYPE>\n
30
+ \ <TITLE><![CDATA[shellshock]]></TITLE>\n <USER_LOGIN>Thomas</USER_LOGIN>\n
31
+ \ <LAUNCH_DATETIME>2017-12-07T08:01:59Z</LAUNCH_DATETIME>\n <DURATION>00:00:06</DURATION>\n
32
+ \ <PROCESSING_PRIORITY>0 - No Priority</PROCESSING_PRIORITY>\n <PROCESSED>1</PROCESSED>\n
33
+ \ <STATUS>\n <STATE>Error</STATE>\n </STATUS>\n <TARGET><![CDATA[47.69.112.62]]></TARGET>\n
34
+ \ </SCAN>\n <SCAN>\n <REF>scan/1512469561.67379</REF>\n <TYPE>On-Demand</TYPE>\n
35
+ \ <TITLE><![CDATA[test_vuln]]></TITLE>\n <USER_LOGIN>Thomas</USER_LOGIN>\n
36
+ \ <LAUNCH_DATETIME>2017-12-05T10:26:01Z</LAUNCH_DATETIME>\n <DURATION>00:04:48</DURATION>\n
37
+ \ <PROCESSING_PRIORITY>1 - Emergency</PROCESSING_PRIORITY>\n <PROCESSED>1</PROCESSED>\n
38
+ \ <STATUS>\n <STATE>Finished</STATE>\n </STATUS>\n <TARGET><![CDATA[192.168.1.100]]></TARGET>\n
39
+ \ </SCAN>\n <SCAN>\n <REF>scan/1512466786.67046</REF>\n <TYPE>On-Demand</TYPE>\n
40
+ \ <TITLE><![CDATA[test]]></TITLE>\n <USER_LOGIN>Thomas</USER_LOGIN>\n
41
+ \ <LAUNCH_DATETIME>2017-12-05T09:39:46Z</LAUNCH_DATETIME>\n <DURATION>00:07:56</DURATION>\n
42
+ \ <PROCESSING_PRIORITY>0 - No Priority</PROCESSING_PRIORITY>\n <PROCESSED>1</PROCESSED>\n
43
+ \ <STATUS>\n <STATE>Finished</STATE>\n </STATUS>\n <TARGET><![CDATA[88.78.187.177]]></TARGET>\n
44
+ \ </SCAN>\n </SCAN_LIST>\n </RESPONSE>\n</SCAN_LIST_OUTPUT>\n<!--
45
+ This report was generated with an evaluation version of Qualys //--> \n<!--
46
+ CONFIDENTIAL AND PROPRIETARY INFORMATION. Qualys provides the QualysGuard
47
+ Service \"As Is,\" without any warranty of any kind. Qualys makes no warranty
48
+ that the information contained in this report is complete or error-free. Copyright
49
+ 2017, Qualys, Inc. //--> \n"
50
+ http_version:
51
+ recorded_at: Mon, 11 Dec 2017 12:49:00 GMT
52
+ recorded_with: VCR 4.0.0