sitecore_scan 0.0.2

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 (4) hide show
  1. checksums.yaml +15 -0
  2. data/bin/sitecore-scan +133 -0
  3. data/lib/sitecore_scan.rb +183 -0
  4. metadata +61 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OGExOWRjNzc1NjRiNWYzZmQ5NjYyMGZjOTk4MmQ4ZDRjY2YyNWRkMw==
5
+ data.tar.gz: !binary |-
6
+ NDU2NWU1ZTc0MWNhZDEyNmY3NzMwNDdkNmFlZWM4YjU3NDZlNjE2MQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ Y2Q2NGU2MjMzNjJlODlhNmM5OGQxM2ZiMWJiOWE3ZGUzOTlmZDgyNGE3OTc0
10
+ Zjk2N2YyODJlN2Y1YzhjYjBkYTVhMmQ4MzhhNGYxYWZiZDliODUwNzVmMDQ5
11
+ MmMyYWE3OTE0Y2ZmYWRkNTUxZGNkNmRlNzYwMzBhMGRkM2EyNzA=
12
+ data.tar.gz: !binary |-
13
+ MDFiM2QyYmZmZGY0NWNiNjNhNzJjMWE3NGY1ODRiNmE4MDk1ZGQxMTlhN2E4
14
+ MDJkNGRmMzZmMDJhYzJlMDI5NDg3OTk0ZmRjODYwYmE3M2QzZDkyMmY0NTM3
15
+ MzQ3MjQxYWI3ZmVmZTVkOWJlZjEyNjYyNGVhZmYyZTE2NDFhNjM=
data/bin/sitecore-scan ADDED
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file is part of SitecoreScan
4
+ # https://github.com/bcoles/sitecore_scan
5
+ #
6
+
7
+ require 'sitecore_scan'
8
+ require 'optparse'
9
+ require 'resolv'
10
+
11
+ def banner
12
+ puts "
13
+ _____ _ _ _____
14
+ / ____(_) | / ____|
15
+ | (___ _| |_ ___ ___ ___ _ __ ___| (___ ___ __ _ _ __
16
+ \\___ \\| | __/ _ \\/ __/ _ \\| '__/ _ \\\\___ \\ / __/ _` | '_ \\
17
+ ____) | | || __/ (_| (_) | | | __/____) | (_| (_| | | | |
18
+ |_____/|_|\\__\\___|\\___\\___/|_| \\___|_____/ \\___\\__,_|_| |_|
19
+ version 0.0.2"
20
+ puts
21
+ puts '-' * 60
22
+ end
23
+
24
+ banner
25
+ options = {}
26
+ opts = OptionParser.new do |o|
27
+ o.banner = "Usage: sitecore-scan [options]"
28
+
29
+ o.on('-u URL', '--url URL', 'Sitecore URL to scan') do |v|
30
+ unless v.match(%r{\Ahttps?://})
31
+ puts "- Invalid URL: #{v}"
32
+ exit(1)
33
+ end
34
+ options[:url] = v
35
+ end
36
+
37
+ o.on('-s', '--skip', 'Skip check for Sitecore') do
38
+ options[:skip] = true
39
+ end
40
+
41
+ o.on('-i', '--insecure', 'Skip SSL/TLS validation') do
42
+ options[:insecure] = true
43
+ end
44
+
45
+ o.on('-v', '--verbose', 'Enable verbose output') do
46
+ options[:verbose] = true
47
+ end
48
+
49
+ o.on('-h', '--help', 'Show this help') do
50
+ puts opts
51
+ exit
52
+ end
53
+ end
54
+
55
+ opts.parse!
56
+
57
+ if options[:url].nil?
58
+ puts opts
59
+ exit(1)
60
+ end
61
+
62
+ def scan(url, check: true, insecure: false, verbose: false)
63
+ SitecoreScan.logger = ::Logger.new($stdout).tap do |log|
64
+ log.progname = 'sitecore-scan'
65
+ log.level = verbose ? ::Logger::INFO : ::Logger::WARN
66
+ log.datetime_format = '%Y-%m-%d %H:%M:%S '
67
+ end
68
+
69
+ SitecoreScan.insecure = insecure
70
+
71
+ puts "Scan started at #{Time.now.getutc}"
72
+ puts "URL: #{url}"
73
+
74
+ # parse URL
75
+ target = nil
76
+ begin
77
+ target = URI::parse(url.split('?').first)
78
+ rescue
79
+ puts "- Could not parse target URL: #{url}"
80
+ end
81
+ exit(1) if target.nil?
82
+
83
+ # resolve IP address
84
+ begin
85
+ ip = Resolv.getaddress(target.host).to_s
86
+ puts "IP: #{ip}" unless ip.nil?
87
+ rescue
88
+ puts "- Could not resolve hostname #{target.host}"
89
+ end
90
+
91
+ puts "Port: #{target.port}"
92
+ puts '-' * 60
93
+
94
+ # Check if the URL is Sitecore
95
+ if check
96
+ is_sitecore = SitecoreScan::detectSitecore(url)
97
+ unless is_sitecore
98
+ puts '- Sitecore not found'
99
+ exit(1)
100
+ end
101
+ puts '+ Found Sitecore'
102
+ end
103
+
104
+ # Retrieve Sitecore version from Login page
105
+ version = SitecoreScan::getVersionFromLogin(url)
106
+ puts "+ Version: #{version}" if version
107
+
108
+ # Check if Glimpse debugging is enabled
109
+ glimpse = SitecoreScan::glimpseDebugging(url)
110
+ puts "+ Glimpse debugging is enabled" if glimpse
111
+
112
+ # Check if SOAP API is accessible
113
+ soap_api = SitecoreScan::soapApi(url)
114
+ puts "+ SOAP API is available" if soap_api
115
+
116
+ # Check if Executive Insight Dashboard reporting is accessible
117
+ reporting = SitecoreScan::dashboardReporting(url)
118
+ puts "+ Executive Insight Dashboard reporting is available" if reporting
119
+
120
+ # Check if Telerik Web UI is accessible
121
+ telerik = SitecoreScan::telerikWebUi(url)
122
+ puts "+ Telerik Web Ui is available" if telerik
123
+
124
+ puts "Scan finished at #{Time.now.getutc}"
125
+ puts '-' * 60
126
+ end
127
+
128
+ scan(
129
+ options[:url],
130
+ insecure: options[:insecure],
131
+ check: !options[:skip],
132
+ verbose: options[:verbose]
133
+ )
@@ -0,0 +1,183 @@
1
+ #
2
+ # This file is part of SitecoreScan
3
+ # https://github.com/bcoles/sitecore_scan
4
+ #
5
+
6
+ require 'uri'
7
+ require 'cgi'
8
+ require 'logger'
9
+ require 'net/http'
10
+ require 'openssl'
11
+
12
+ class SitecoreScan
13
+ VERSION = '0.0.2'.freeze
14
+
15
+ def self.logger
16
+ @logger
17
+ end
18
+
19
+ def self.logger=(logger)
20
+ @logger = logger
21
+ end
22
+
23
+ def self.insecure
24
+ @insecure ||= false
25
+ end
26
+
27
+ def self.insecure=(insecure)
28
+ @insecure = insecure
29
+ end
30
+
31
+ #
32
+ # Check if URL is running Sitecore using edit mode
33
+ #
34
+ # @param [String] URL
35
+ #
36
+ # @return [Boolean]
37
+ #
38
+ def self.detectSitecore(url)
39
+ url += '/' unless url.to_s.end_with? '/'
40
+ res = sendHttpRequest("#{url}?sc_mode=edit")
41
+
42
+ return false unless res
43
+
44
+ return true if res['sitecore-item']
45
+ return true if res['set-cookies'].to_s.include?('sc_mode=edit')
46
+ return true if res.code.to_i == 302 && (res['location'].to_s.downcase.include?('sitecore/login') || res['location'].to_s.downcase.include?('user=sitecore'))
47
+
48
+ false
49
+ end
50
+
51
+ #
52
+ # Retrieve Sitecore version from Login page
53
+ #
54
+ # @param [String] URL
55
+ #
56
+ # @return [String] Sitecore version
57
+ #
58
+ def self.getVersionFromLogin(url)
59
+ url += '/' unless url.to_s.end_with? '/'
60
+ res = sendHttpRequest("#{url}sitecore/login")
61
+
62
+ return unless res
63
+
64
+ version = res.body.to_s.scan(%r{(Sitecore\.NET [\d\.]+ \(rev\. \d+\))}).flatten.first
65
+
66
+ return version if version
67
+
68
+ res.body.to_s.scan(%r{<iframe src="https://sdn.sitecore.net/startpage.aspx\?[^"]+v=([\d\.]+)"}).flatten.first
69
+ end
70
+
71
+ #
72
+ # Check if Glimpse debugging is enabled
73
+ #
74
+ # @param [String] URL
75
+ #
76
+ # @return [Boolean]
77
+ #
78
+ def self.glimpseDebugging(url)
79
+ url += '/' unless url.to_s.end_with? '/'
80
+ res = sendHttpRequest("#{url}Glimpse.axd")
81
+
82
+ return false unless res
83
+ return false unless res.code.to_i == 200
84
+ return false unless (res.body.to_s.include?('Glimpse - Configuration Page') || res.body.to_s.include?('Glimpse.AspNet'))
85
+
86
+ true
87
+ end
88
+
89
+ #
90
+ # Check if SOAP API is accessible
91
+ #
92
+ # @param [String] URL
93
+ #
94
+ # @return [Boolean]
95
+ #
96
+ def self.soapApi(url)
97
+ url += '/' unless url.to_s.end_with? '/'
98
+ res = sendHttpRequest("#{url}sitecore/shell/WebService/Service.asmx")
99
+
100
+ return false unless res
101
+ return false unless res.code.to_i == 200
102
+ return false unless res.body.to_s.include? 'Visual Sitecore Service Web Service'
103
+
104
+ true
105
+ end
106
+
107
+ #
108
+ # Check if Executive Insight Dashboard reporting is accessible (CVE-2021-42237)
109
+ # https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB1000776
110
+ #
111
+ # @param [String] URL
112
+ #
113
+ # @return [Boolean]
114
+ #
115
+ def self.dashboardReporting(url)
116
+ url += '/' unless url.to_s.end_with? '/'
117
+ res = sendHttpRequest("#{url}sitecore/shell/ClientBin/Reporting/Report.ashx")
118
+
119
+ return false unless res
120
+ return false unless res.code.to_i == 200
121
+ return false unless res.body.to_s.include? 'Sitecore.Analytics.Reporting'
122
+
123
+ true
124
+ end
125
+
126
+ #
127
+ # Check if Telerik Web UI is accessible (CVE-2017-9248)
128
+ # https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB0978654
129
+ #
130
+ # @param [String] URL
131
+ #
132
+ # @return [Boolean]
133
+ #
134
+ def self.telerikWebUi(url)
135
+ url += '/' unless url.to_s.end_with? '/'
136
+ res = sendHttpRequest("#{url}/Telerik.Web.UI.WebResource.axd")
137
+
138
+ return false unless res
139
+ return false unless res.code.to_i == 200
140
+
141
+ true
142
+ end
143
+
144
+ #
145
+ # Fetch URL
146
+ #
147
+ # @param [String] URL
148
+ #
149
+ # @return [Net::HTTPResponse] HTTP response
150
+ #
151
+ def self.sendHttpRequest(url)
152
+ target = URI.parse(url)
153
+ @logger.info("Fetching #{target}")
154
+
155
+ http = Net::HTTP.new(target.host, target.port)
156
+ if target.scheme.to_s.eql?('https')
157
+ http.use_ssl = true
158
+ http.verify_mode = @insecure ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
159
+ end
160
+ http.open_timeout = 20
161
+ http.read_timeout = 20
162
+ headers = {}
163
+ headers['User-Agent'] = "SitecoreScan/#{VERSION}"
164
+ headers['Accept-Encoding'] = 'gzip,deflate'
165
+
166
+ begin
167
+ res = http.request(Net::HTTP::Get.new(target, headers.to_hash))
168
+ if res.body && res['Content-Encoding'].eql?('gzip')
169
+ sio = StringIO.new(res.body)
170
+ gz = Zlib::GzipReader.new(sio)
171
+ res.body = gz.read
172
+ end
173
+ rescue Timeout::Error, Errno::ETIMEDOUT
174
+ @logger.error("Could not retrieve URL #{target}: Timeout")
175
+ return nil
176
+ rescue => e
177
+ @logger.error("Could not retrieve URL #{target}: #{e}")
178
+ return nil
179
+ end
180
+ @logger.info("Received reply (#{res.body.length} bytes)")
181
+ res
182
+ end
183
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sitecore_scan
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Brendan Coles
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-11-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logger
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ description: A simple remote scanner for Sitecore CMS
28
+ email: bcoles@gmail.com
29
+ executables:
30
+ - sitecore-scan
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - bin/sitecore-scan
35
+ - lib/sitecore_scan.rb
36
+ homepage: https://github.com/bcoles/sitecore_scan
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: 2.0.0
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 2.2.2
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Sitecore scanner
60
+ test_files: []
61
+ has_rdoc: