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.
- checksums.yaml +15 -0
- data/bin/sitecore-scan +133 -0
- data/lib/sitecore_scan.rb +183 -0
- 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:
|