new_cms_scanner 0.13.7

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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +26 -0
  4. data/app/app.rb +24 -0
  5. data/app/controllers/core/cli_options.rb +117 -0
  6. data/app/controllers/core.rb +82 -0
  7. data/app/controllers/interesting_findings.rb +25 -0
  8. data/app/finders/interesting_findings/fantastico_fileslist.rb +21 -0
  9. data/app/finders/interesting_findings/headers.rb +17 -0
  10. data/app/finders/interesting_findings/robots_txt.rb +20 -0
  11. data/app/finders/interesting_findings/search_replace_db_2.rb +19 -0
  12. data/app/finders/interesting_findings/xml_rpc.rb +61 -0
  13. data/app/finders/interesting_findings.rb +25 -0
  14. data/app/formatters/cli.rb +65 -0
  15. data/app/formatters/cli_no_color.rb +9 -0
  16. data/app/formatters/cli_no_colour.rb +17 -0
  17. data/app/formatters/json.rb +14 -0
  18. data/app/models/fantastico_fileslist.rb +34 -0
  19. data/app/models/headers.rb +44 -0
  20. data/app/models/interesting_finding.rb +48 -0
  21. data/app/models/robots_txt.rb +31 -0
  22. data/app/models/search_replace_db_2.rb +17 -0
  23. data/app/models/user.rb +35 -0
  24. data/app/models/version.rb +49 -0
  25. data/app/models/xml_rpc.rb +78 -0
  26. data/app/user_agents.txt +46 -0
  27. data/app/views/cli/core/banner.erb +1 -0
  28. data/app/views/cli/core/finished.erb +8 -0
  29. data/app/views/cli/core/help.erb +4 -0
  30. data/app/views/cli/core/started.erb +6 -0
  31. data/app/views/cli/core/version.erb +1 -0
  32. data/app/views/cli/interesting_findings/_array.erb +10 -0
  33. data/app/views/cli/interesting_findings/findings.erb +23 -0
  34. data/app/views/cli/scan_aborted.erb +5 -0
  35. data/app/views/cli/usage.erb +3 -0
  36. data/app/views/json/core/banner.erb +1 -0
  37. data/app/views/json/core/finished.erb +10 -0
  38. data/app/views/json/core/help.erb +4 -0
  39. data/app/views/json/core/started.erb +5 -0
  40. data/app/views/json/core/version.erb +1 -0
  41. data/app/views/json/interesting_findings/findings.erb +24 -0
  42. data/app/views/json/scan_aborted.erb +5 -0
  43. data/lib/cms_scanner/browser/actions.rb +48 -0
  44. data/lib/cms_scanner/browser/options.rb +90 -0
  45. data/lib/cms_scanner/browser.rb +96 -0
  46. data/lib/cms_scanner/cache/file_store.rb +77 -0
  47. data/lib/cms_scanner/cache/typhoeus.rb +25 -0
  48. data/lib/cms_scanner/controller.rb +105 -0
  49. data/lib/cms_scanner/controllers.rb +67 -0
  50. data/lib/cms_scanner/errors/http.rb +72 -0
  51. data/lib/cms_scanner/errors/scan.rb +14 -0
  52. data/lib/cms_scanner/errors.rb +11 -0
  53. data/lib/cms_scanner/exit_code.rb +25 -0
  54. data/lib/cms_scanner/finders/base_finders.rb +45 -0
  55. data/lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb +121 -0
  56. data/lib/cms_scanner/finders/finder/enumerator.rb +77 -0
  57. data/lib/cms_scanner/finders/finder/fingerprinter.rb +48 -0
  58. data/lib/cms_scanner/finders/finder/smart_url_checker/findings.rb +33 -0
  59. data/lib/cms_scanner/finders/finder/smart_url_checker.rb +60 -0
  60. data/lib/cms_scanner/finders/finder.rb +75 -0
  61. data/lib/cms_scanner/finders/finding.rb +54 -0
  62. data/lib/cms_scanner/finders/findings.rb +26 -0
  63. data/lib/cms_scanner/finders/independent_finder.rb +30 -0
  64. data/lib/cms_scanner/finders/independent_finders.rb +26 -0
  65. data/lib/cms_scanner/finders/same_type_finder.rb +19 -0
  66. data/lib/cms_scanner/finders/same_type_finders.rb +26 -0
  67. data/lib/cms_scanner/finders/unique_finder.rb +19 -0
  68. data/lib/cms_scanner/finders/unique_finders.rb +47 -0
  69. data/lib/cms_scanner/finders.rb +12 -0
  70. data/lib/cms_scanner/formatter/buffer.rb +17 -0
  71. data/lib/cms_scanner/formatter.rb +149 -0
  72. data/lib/cms_scanner/helper.rb +7 -0
  73. data/lib/cms_scanner/numeric.rb +13 -0
  74. data/lib/cms_scanner/parsed_cli.rb +37 -0
  75. data/lib/cms_scanner/progressbar_null_output.rb +23 -0
  76. data/lib/cms_scanner/public_suffix/domain.rb +42 -0
  77. data/lib/cms_scanner/references.rb +132 -0
  78. data/lib/cms_scanner/scan.rb +88 -0
  79. data/lib/cms_scanner/target/hashes.rb +45 -0
  80. data/lib/cms_scanner/target/platform/php.rb +62 -0
  81. data/lib/cms_scanner/target/platform.rb +3 -0
  82. data/lib/cms_scanner/target/scope.rb +103 -0
  83. data/lib/cms_scanner/target/server/apache.rb +27 -0
  84. data/lib/cms_scanner/target/server/generic.rb +72 -0
  85. data/lib/cms_scanner/target/server/iis.rb +29 -0
  86. data/lib/cms_scanner/target/server/nginx.rb +27 -0
  87. data/lib/cms_scanner/target/server.rb +6 -0
  88. data/lib/cms_scanner/target.rb +124 -0
  89. data/lib/cms_scanner/typhoeus/hydra.rb +12 -0
  90. data/lib/cms_scanner/typhoeus/response.rb +27 -0
  91. data/lib/cms_scanner/version.rb +6 -0
  92. data/lib/cms_scanner/vulnerability.rb +46 -0
  93. data/lib/cms_scanner/web_site.rb +145 -0
  94. data/lib/cms_scanner.rb +141 -0
  95. metadata +426 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 56edd064a963293e585d678c18e58db39cbd514a56586ab7f445bc9f28d4d10b
4
+ data.tar.gz: 52a249bb09f8b840e67bce8e51f1f2170a1a4fc9efaf3ab9726417bb4b6ca83c
5
+ SHA512:
6
+ metadata.gz: e1c015727d38e5019eeff523c3951e0b8291968b9c437c986fd483d73d012aece1bc8c92e79d3a048a17389c2d62532d8d0039b0833b1ac30b93f449b26acf09
7
+ data.tar.gz: 237d542e023caf48867ed491e53c51b5a63a3fbffaeb043405644c6346462ebd3b11860fc2d1cec151ae76a5c89d67d012b12c1c1adb540e8ae9078096254674
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2014-2015 - WPScanTeam
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # CMSScanner
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/cms_scanner.svg)](https://badge.fury.io/rb/cms_scanner)
4
+ ![Build](https://github.com/wpscanteam/CMSScanner/workflows/Build/badge.svg)
5
+ [![Coverage Status](https://img.shields.io/coveralls/wpscanteam/CMSScanner.svg)](https://coveralls.io/r/wpscanteam/CMSScanner)
6
+ [![Code Climate](https://api.codeclimate.com/v1/badges/b90b7f9f6982792ef8d6/maintainability)](https://codeclimate.com/github/wpscanteam/CMSScanner/maintainability)
7
+
8
+ The goal of this gem is to provide a quick and easy way to create a CMS/WebSite Scanner by acting like a Framework and providing classes, formatters etc.
9
+
10
+ ## /!\ This gem is currently Experimental /!\
11
+
12
+ ## A basic implementation example is available in the example folder.
13
+
14
+ To start to play with it, copy all its files and folders into a new git repository and run `bundle install && rake install` inside it.
15
+ It will create a `cmsscan` command that you can run against a target, ie `cmsscan --url https://www.google.com`
16
+
17
+
18
+ Install Dependencies: `bundle install`
19
+
20
+ ## Contributing
21
+
22
+ 1. Fork it ( https://github.com/wpscanteam/CMSScanner/fork )
23
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
24
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
25
+ 4. Push to the branch (`git push origin my-new-feature`)
26
+ 5. Create new Pull Request
data/app/app.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Formatters
4
+ require_relative 'formatters/cli'
5
+ require_relative 'formatters/cli_no_colour'
6
+ require_relative 'formatters/cli_no_color'
7
+ require_relative 'formatters/json'
8
+
9
+ # Controllers
10
+ require_relative 'controllers/core'
11
+ require_relative 'controllers/interesting_findings'
12
+
13
+ # Models
14
+ require_relative 'models/interesting_finding'
15
+ require_relative 'models/robots_txt'
16
+ require_relative 'models/fantastico_fileslist'
17
+ require_relative 'models/search_replace_db_2'
18
+ require_relative 'models/headers'
19
+ require_relative 'models/xml_rpc'
20
+ require_relative 'models/version'
21
+ require_relative 'models/user'
22
+
23
+ # Finders
24
+ require_relative 'finders/interesting_findings'
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Controller
5
+ # CLI Options for the Core Controller
6
+ class Core < Base
7
+ def cli_options
8
+ formats = NS::Formatter.availables
9
+
10
+ [
11
+ OptURL.new(['-u', '--url URL', 'The URL to scan'],
12
+ required_unless: %i[help hh version],
13
+ default_protocol: 'http'),
14
+ OptBoolean.new(['--force', 'Do not check if target returns a 403'])
15
+ ] + mixed_cli_options + [
16
+ OptFilePath.new(['-o', '--output FILE', 'Output to FILE'], writable: true, exists: false),
17
+ OptChoice.new(['-f', '--format FORMAT',
18
+ 'Output results in the format supplied'], choices: formats),
19
+ OptChoice.new(['--detection-mode MODE'],
20
+ choices: %w[mixed passive aggressive],
21
+ normalize: :to_sym,
22
+ default: :mixed),
23
+ OptArray.new(['--scope DOMAINS',
24
+ 'Comma separated (sub-)domains to consider in scope. ',
25
+ 'Wildcard(s) allowed in the trd of valid domains, e.g: *.target.tld'], advanced: true)
26
+ ] + cli_browser_options
27
+ end
28
+
29
+ def mixed_cli_options
30
+ [
31
+ OptBoolean.new(['-h', '--help', 'Display the simple help and exit']),
32
+ OptBoolean.new(['--hh', 'Display the full help and exit']),
33
+ OptBoolean.new(['--version', 'Display the version and exit']),
34
+ OptBoolean.new(['--ignore-main-redirect', 'Ignore the main redirect (if any) and scan the target url'],
35
+ advanced: true),
36
+ OptBoolean.new(['-v', '--verbose', 'Verbose mode']),
37
+ OptBoolean.new(['--[no-]banner', 'Whether or not to display the banner'], default: true),
38
+ OptPositiveInteger.new(['--max-scan-duration SECONDS',
39
+ 'Abort the scan if it exceeds the time provided in seconds'],
40
+ advanced: true)
41
+ ]
42
+ end
43
+
44
+ # @return [ Array<OptParseValidator::OptBase> ]
45
+ def cli_browser_options
46
+ cli_browser_headers_options + [
47
+ OptBoolean.new(['--random-user-agent', '--rua',
48
+ 'Use a random user-agent for each scan']),
49
+ OptFilePath.new(['--user-agents-list FILE-PATH',
50
+ 'List of agents to use with --random-user-agent'],
51
+ exists: true,
52
+ advanced: true,
53
+ default: APP_DIR.join('user_agents.txt')),
54
+ OptCredentials.new(['--http-auth login:password']),
55
+ OptPositiveInteger.new(['-t', '--max-threads VALUE', 'The max threads to use'],
56
+ default: 5),
57
+ OptPositiveInteger.new(['--throttle MilliSeconds', 'Milliseconds to wait before doing another web request. ' \
58
+ 'If used, the max threads will be set to 1.']),
59
+ OptPositiveInteger.new(['--request-timeout SECONDS', 'The request timeout in seconds'],
60
+ default: 60),
61
+ OptPositiveInteger.new(['--connect-timeout SECONDS', 'The connection timeout in seconds'],
62
+ default: 30),
63
+ OptBoolean.new(['--disable-tls-checks',
64
+ 'Disables SSL/TLS certificate verification, and downgrade to TLS1.0+ ' \
65
+ '(requires cURL 7.66 for the latter)'])
66
+ ] + cli_browser_proxy_options + cli_browser_cookies_options + cli_browser_cache_options
67
+ end
68
+
69
+ # @return [ Array<OptParseValidator::OptBase> ]
70
+ def cli_browser_headers_options
71
+ [
72
+ OptString.new(['--user-agent VALUE', '--ua']),
73
+ OptHeaders.new(['--headers HEADERS', 'Additional headers to append in requests'], advanced: true),
74
+ OptString.new(['--vhost VALUE', 'The virtual host (Host header) to use in requests'], advanced: true)
75
+ ]
76
+ end
77
+
78
+ # @return [ Array<OptParseValidator::OptBase> ]
79
+ def cli_browser_proxy_options
80
+ [
81
+ OptProxy.new(['--proxy protocol://IP:port',
82
+ 'Supported protocols depend on the cURL installed']),
83
+ OptCredentials.new(['--proxy-auth login:password'])
84
+ ]
85
+ end
86
+
87
+ # @return [ Array<OptParseValidator::OptBase> ]
88
+ def cli_browser_cookies_options
89
+ [
90
+ OptString.new(['--cookie-string COOKIE',
91
+ 'Cookie string to use in requests, ' \
92
+ 'format: cookie1=value1[; cookie2=value2]']),
93
+ OptFilePath.new(['--cookie-jar FILE-PATH', 'File to read and write cookies'],
94
+ writable: true,
95
+ readable: true,
96
+ create: true,
97
+ default: File.join(tmp_directory, 'cookie_jar.txt'))
98
+ ]
99
+ end
100
+
101
+ # @return [ Array<OptParseValidator::OptBase> ]
102
+ def cli_browser_cache_options
103
+ [
104
+ OptInteger.new(['--cache-ttl TIME_TO_LIVE', 'The cache time to live in seconds'],
105
+ default: 600, advanced: true),
106
+ OptBoolean.new(['--clear-cache', 'Clear the cache before the scan'], advanced: true),
107
+ OptDirectoryPath.new(['--cache-dir PATH'],
108
+ readable: true,
109
+ writable: true,
110
+ create: true,
111
+ default: File.join(tmp_directory, 'cache'),
112
+ advanced: true)
113
+ ]
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'core/cli_options'
4
+
5
+ module CMSScanner
6
+ module Controller
7
+ # Core Controller
8
+ class Core < Base
9
+ def setup_cache
10
+ return unless NS::ParsedCli.cache_dir
11
+
12
+ storage_path = File.join(NS::ParsedCli.cache_dir, Digest::MD5.hexdigest(target.url))
13
+
14
+ Typhoeus::Config.cache = Cache::Typhoeus.new(storage_path)
15
+ Typhoeus::Config.cache.clean if NS::ParsedCli.clear_cache
16
+ end
17
+
18
+ def before_scan
19
+ maybe_output_banner_help_and_version
20
+
21
+ setup_cache
22
+ check_target_availability
23
+ end
24
+
25
+ def maybe_output_banner_help_and_version
26
+ output('banner') if NS::ParsedCli.banner
27
+ output('help', help: option_parser.simple_help, simple: true) if NS::ParsedCli.help
28
+ output('help', help: option_parser.full_help, simple: false) if NS::ParsedCli.hh
29
+ output('version') if NS::ParsedCli.version
30
+
31
+ exit(NS::ExitCode::OK) if NS::ParsedCli.help || NS::ParsedCli.hh || NS::ParsedCli.version
32
+ end
33
+
34
+ # Checks that the target is accessible, raises related errors otherwise
35
+ #
36
+ # @return [ Void ]
37
+ def check_target_availability
38
+ res = NS::Browser.get(target.url)
39
+
40
+ case res.code
41
+ when 0
42
+ raise Error::TargetDown, res
43
+ when 401
44
+ raise Error::HTTPAuthRequired
45
+ when 403
46
+ raise Error::AccessForbidden, NS::ParsedCli.random_user_agent unless NS::ParsedCli.force
47
+ when 407
48
+ raise Error::ProxyAuthRequired
49
+ end
50
+
51
+ # Checks for redirects
52
+ # An out of scope redirect will raise an Error::HTTPRedirect
53
+ effective_url = target.homepage_res.effective_url
54
+
55
+ return if target.in_scope?(effective_url)
56
+
57
+ raise Error::HTTPRedirect, effective_url unless NS::ParsedCli.ignore_main_redirect
58
+
59
+ target.homepage_res = res
60
+ end
61
+
62
+ def run
63
+ @start_time = Time.now
64
+ @start_memory = NS.start_memory
65
+
66
+ output('started', url: target.url, ip: target.ip, effective_url: target.homepage_url)
67
+ end
68
+
69
+ def after_scan
70
+ @stop_time = Time.now
71
+ @elapsed = @stop_time - @start_time
72
+ @used_memory = GetProcessMem.new.bytes - @start_memory
73
+
74
+ output('finished',
75
+ cached_requests: NS.cached_requests,
76
+ requests_done: NS.total_requests,
77
+ data_sent: NS.total_data_sent,
78
+ data_received: NS.total_data_received)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Controller
5
+ # InterestingFindings Controller
6
+ class InterestingFindings < Base
7
+ def cli_options
8
+ [
9
+ OptChoice.new(
10
+ ['--interesting-findings-detection MODE',
11
+ 'Use the supplied mode for the interesting findings detection. '],
12
+ choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
13
+ )
14
+ ]
15
+ end
16
+
17
+ def run
18
+ mode = NS::ParsedCli.interesting_findings_detection || NS::ParsedCli.detection_mode
19
+ findings = target.interesting_findings(mode: mode)
20
+
21
+ output('findings', findings: findings) unless findings.empty?
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ module InterestingFindings
6
+ # FantasticoFileslist finder
7
+ class FantasticoFileslist < Finder
8
+ # @return [ InterestingFinding ]
9
+ def aggressive(_opts = {})
10
+ path = 'fantastico_fileslist.txt'
11
+ res = target.head_and_get(path)
12
+
13
+ return if res.body.strip.empty?
14
+ return unless res.headers && res.headers['Content-Type']&.start_with?('text/plain')
15
+
16
+ NS::Model::FantasticoFileslist.new(target.url(path), confidence: 70, found_by: found_by)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ module InterestingFindings
6
+ # Interesting Headers finder
7
+ class Headers < Finder
8
+ # @return [ InterestingFinding ]
9
+ def passive(_opts = {})
10
+ r = NS::Model::Headers.new(target.homepage_url, confidence: 100, found_by: found_by)
11
+
12
+ r.interesting_entries.empty? ? nil : r
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ module InterestingFindings
6
+ # Robots.txt finder
7
+ class RobotsTxt < Finder
8
+ # @return [ InterestingFinding ]
9
+ def aggressive(_opts = {})
10
+ path = 'robots.txt'
11
+ res = target.head_and_get(path)
12
+
13
+ return unless res&.code == 200 && res.body =~ /(?:user-agent|(?:dis)?allow):/i
14
+
15
+ NS::Model::RobotsTxt.new(target.url(path), confidence: 100, found_by: found_by)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ module InterestingFindings
6
+ # SearchReplaceDB2 finder
7
+ class SearchReplaceDB2 < Finder
8
+ # @return [ InterestingFinding ]
9
+ def aggressive(_opts = {})
10
+ path = 'searchreplacedb2.php'
11
+
12
+ return unless /by interconnect/i.match?(target.head_and_get(path).body)
13
+
14
+ NS::Model::SearchReplaceDB2.new(target.url(path), confidence: 100, found_by: found_by)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ module InterestingFindings
6
+ # XML RPC finder
7
+ class XMLRPC < Finder
8
+ # @return [ Array<String> ] The potential urls to the XMl RPC file
9
+ def potential_urls
10
+ @potential_urls ||= []
11
+ end
12
+
13
+ # @return [ Array<XMLRPC> ]
14
+ def passive(opts = {})
15
+ [passive_headers(opts), passive_body(opts)].compact
16
+ end
17
+
18
+ # @return [ XMLRPC ]
19
+ def passive_headers(_opts = {})
20
+ url = target.homepage_res.headers['X-Pingback']
21
+
22
+ return unless target.in_scope?(url)
23
+
24
+ potential_urls << url
25
+
26
+ NS::Model::XMLRPC.new(url, confidence: 30, found_by: 'Headers (Passive Detection)')
27
+ end
28
+
29
+ # @return [ XMLRPC ]
30
+ def passive_body(_opts = {})
31
+ target.homepage_res.html.css('link[rel="pingback"]').each do |tag|
32
+ url = tag.attribute('href').to_s
33
+
34
+ next unless target.in_scope?(url)
35
+
36
+ potential_urls << url
37
+
38
+ return NS::Model::XMLRPC.new(url, confidence: 30, found_by: 'Link Tag (Passive Detection)')
39
+ end
40
+ nil
41
+ end
42
+
43
+ # @return [ XMLRPC ]
44
+ def aggressive(_opts = {})
45
+ potential_urls << target.url('xmlrpc.php')
46
+
47
+ potential_urls.uniq.each do |potential_url|
48
+ next unless target.in_scope?(potential_url)
49
+
50
+ res = NS::Browser.post(potential_url, body: Digest::MD5.hexdigest(rand(999_999).to_s[0..5]))
51
+
52
+ next unless /<methodResponse>/i.match?(res&.body)
53
+
54
+ return NS::Model::XMLRPC.new(potential_url, confidence: 100, found_by: DIRECT_ACCESS)
55
+ end
56
+ nil
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'interesting_findings/headers'
4
+ require_relative 'interesting_findings/robots_txt'
5
+ require_relative 'interesting_findings/fantastico_fileslist'
6
+ require_relative 'interesting_findings/search_replace_db_2'
7
+ require_relative 'interesting_findings/xml_rpc'
8
+
9
+ module CMSScanner
10
+ module Finders
11
+ module InterestingFindings
12
+ # Interesting Files Finder
13
+ class Base
14
+ include IndependentFinder
15
+
16
+ # @param [ CMSScanner::Target ] target
17
+ def initialize(target)
18
+ %w[Headers RobotsTxt FantasticoFileslist SearchReplaceDB2 XMLRPC].each do |f|
19
+ finders << NS::Finders::InterestingFindings.const_get(f).new(target)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Formatter
5
+ # CLI Formatter
6
+ class Cli < Base
7
+ # @return [ String ]
8
+ def info_icon
9
+ green('[+]')
10
+ end
11
+
12
+ # @return [ String ]
13
+ def notice_icon
14
+ blue('[i]')
15
+ end
16
+
17
+ # @return [ String ]
18
+ def warning_icon
19
+ amber('[!]')
20
+ end
21
+
22
+ # @return [ String ]
23
+ def critical_icon
24
+ red('[!]')
25
+ end
26
+
27
+ # @param [ String ] text
28
+ # @return [ String ]
29
+ def bold(text)
30
+ colorize(text, 1)
31
+ end
32
+
33
+ # @param [ String ] text
34
+ # @return [ String ]
35
+ def red(text)
36
+ colorize(text, 31)
37
+ end
38
+
39
+ # @param [ String ] text
40
+ # @return [ String ]
41
+ def green(text)
42
+ colorize(text, 32)
43
+ end
44
+
45
+ # @param [ String ] text
46
+ # @return [ String ]
47
+ def amber(text)
48
+ colorize(text, 33)
49
+ end
50
+
51
+ # @param [ String ] text
52
+ # @return [ String ]
53
+ def blue(text)
54
+ colorize(text, 34)
55
+ end
56
+
57
+ # @param [ String ] text
58
+ # @param [ Integer ] color_code
59
+ # @return [ String ]
60
+ def colorize(text, color_code)
61
+ "\e[#{color_code}m#{text}\e[0m"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Formatter
5
+ # Because Reason https://github.com/wpscanteam/CMSScanner/issues/56
6
+ class CliNoColor < CliNoColour
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Formatter
5
+ # CLI No Colour Formatter
6
+ class CliNoColour < Cli
7
+ # Override to get the cli views
8
+ def format
9
+ 'cli'
10
+ end
11
+
12
+ def colorize(text, _color_code)
13
+ text
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Formatter
5
+ # JSON Formatter
6
+ class Json < Base
7
+ include Buffer
8
+
9
+ def beautify
10
+ puts JSON.pretty_generate(JSON.parse("{#{buffer.chomp.chomp(',')}}"))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Model
5
+ # Fantastico is a commercial script library that automates the installation of web applications to a website.
6
+ # Fantastico scripts are executed from the administration area of a website control panel such as cPanel.
7
+ # It creates a file named fantastico_fileslist.txt that is publicly available and contains a list of all the
8
+ # files from the current directory. The contents of this file may expose sensitive information to an attacker.
9
+ class FantasticoFileslist < InterestingFinding
10
+ # @return [ String ]
11
+ def to_s
12
+ @to_s ||= "Fantastico list found: #{url}"
13
+ end
14
+
15
+ # @return [ Array<String> ] The interesting files/dirs detected
16
+ def interesting_entries
17
+ results = []
18
+
19
+ entries.each do |entry|
20
+ next unless /(?:admin|\.log|\.sql|\.db)/i.match?(entry)
21
+
22
+ results << entry
23
+ end
24
+ results
25
+ end
26
+
27
+ def references
28
+ @references ||= {
29
+ url: ['https://web.archive.org/web/20140518040021/http://www.acunetix.com/vulnerabilities/fantastico-fileslist/']
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Model
5
+ # Interesting Headers
6
+ class Headers < InterestingFinding
7
+ # @return [ Hash ] The headers
8
+ def entries
9
+ res = NS::Browser.get(url)
10
+ return [] unless res&.headers
11
+
12
+ res.headers
13
+ end
14
+
15
+ # @return [ Array<String> ] The interesting headers detected
16
+ def interesting_entries
17
+ results = []
18
+
19
+ entries.each do |header, value|
20
+ next if known_headers.include?(header.downcase)
21
+
22
+ results << "#{header}: #{Array(value).join(', ')}"
23
+ end
24
+ results
25
+ end
26
+
27
+ # @return [ Array<String> ] Downcased known headers
28
+ def known_headers
29
+ %w[
30
+ age accept-ranges cache-control content-encoding content-length content-type connection date
31
+ etag expires keep-alive location last-modified link pragma set-cookie strict-transport-security
32
+ transfer-encoding vary x-cache x-content-security-policy x-content-type-options
33
+ x-frame-options x-language x-permitted-cross-domain-policies x-pingback x-varnish
34
+ x-webkit-csp x-xss-protection
35
+ ]
36
+ end
37
+
38
+ # @return [ String ]
39
+ def to_s
40
+ @to_s ||= 'Headers'
41
+ end
42
+ end
43
+ end
44
+ end