sitecore_scan 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: