yawast 0.2.0.beta1
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 +7 -0
- data/.gitignore +21 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/README.md +454 -0
- data/Rakefile +9 -0
- data/bin/yawast +69 -0
- data/lib/commands/cms.rb +10 -0
- data/lib/commands/head.rb +12 -0
- data/lib/commands/scan.rb +11 -0
- data/lib/commands/ssl.rb +11 -0
- data/lib/commands/utils.rb +36 -0
- data/lib/resources/common.txt +1960 -0
- data/lib/scanner/apache.rb +72 -0
- data/lib/scanner/cms.rb +14 -0
- data/lib/scanner/core.rb +95 -0
- data/lib/scanner/generic.rb +323 -0
- data/lib/scanner/iis.rb +63 -0
- data/lib/scanner/nginx.rb +13 -0
- data/lib/scanner/obj_presence.rb +63 -0
- data/lib/scanner/php.rb +19 -0
- data/lib/scanner/ssl.rb +237 -0
- data/lib/scanner/ssl_labs.rb +491 -0
- data/lib/shared/http.rb +67 -0
- data/lib/string_ext.rb +16 -0
- data/lib/uri_ext.rb +5 -0
- data/lib/util.rb +25 -0
- data/lib/yawast.rb +57 -0
- data/test/base.rb +43 -0
- data/test/data/apache_server_info.txt +486 -0
- data/test/data/apache_server_status.txt +184 -0
- data/test/data/cms_none_body.txt +242 -0
- data/test/data/cms_wordpress_body.txt +467 -0
- data/test/data/iis_server_header.txt +13 -0
- data/test/data/tomcat_release_notes.txt +172 -0
- data/test/data/wordpress_readme_html.txt +86 -0
- data/test/test_cmd_util.rb +35 -0
- data/test/test_helper.rb +5 -0
- data/test/test_object_presence.rb +36 -0
- data/test/test_scan_apache_banner.rb +58 -0
- data/test/test_scan_apache_server_info.rb +22 -0
- data/test/test_scan_apache_server_status.rb +22 -0
- data/test/test_scan_cms.rb +27 -0
- data/test/test_scan_iis_headers.rb +40 -0
- data/test/test_scan_nginx_banner.rb +18 -0
- data/test/test_shared_http.rb +40 -0
- data/test/test_shared_util.rb +44 -0
- data/test/test_string_ext.rb +15 -0
- data/test/test_yawast.rb +17 -0
- data/yawast.gemspec +35 -0
- metadata +283 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
module Yawast
|
2
|
+
module Scanner
|
3
|
+
class Apache
|
4
|
+
def self.check_banner(banner)
|
5
|
+
#don't bother if this doesn't look like Apache
|
6
|
+
return if !banner.include? 'Apache'
|
7
|
+
@apache = true
|
8
|
+
|
9
|
+
modules = banner.split(' ')
|
10
|
+
server = modules[0]
|
11
|
+
|
12
|
+
#hack - fix '(distro)' issue, such as with 'Apache/2.2.22 (Ubuntu)'
|
13
|
+
# if we don't do this, it triggers a false positive on the module check
|
14
|
+
if /\(\w*\)/.match modules[1]
|
15
|
+
server += " #{modules[1]}"
|
16
|
+
modules.delete_at 1
|
17
|
+
end
|
18
|
+
|
19
|
+
#print the server info no matter what we do next
|
20
|
+
Yawast::Utilities.puts_info "Apache Server: #{server}"
|
21
|
+
modules.delete_at 0
|
22
|
+
|
23
|
+
if modules.count > 0
|
24
|
+
Yawast::Utilities.puts_warn 'Apache Server: Module listing enabled'
|
25
|
+
modules.each { |mod| Yawast::Utilities.puts_warn "\t\t#{mod}" }
|
26
|
+
puts ''
|
27
|
+
|
28
|
+
#check for special items
|
29
|
+
modules.each do |mod|
|
30
|
+
if mod.include? 'OpenSSL'
|
31
|
+
Yawast::Utilities.puts_warn "OpenSSL Version Disclosure: #{mod}"
|
32
|
+
puts ''
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.check_all(uri, head)
|
39
|
+
#this check for @apache may yield false negatives.. meh.
|
40
|
+
if @apache
|
41
|
+
#run all the defined checks
|
42
|
+
check_server_status(uri.copy)
|
43
|
+
check_server_info(uri.copy)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.check_server_status(uri)
|
48
|
+
uri.path = '/server-status'
|
49
|
+
uri.query = '' if uri.query != nil
|
50
|
+
|
51
|
+
ret = Yawast::Shared::Http.get(uri)
|
52
|
+
|
53
|
+
if ret.include? 'Apache Server Status'
|
54
|
+
Yawast::Utilities.puts_vuln "Apache Server Status page found: #{uri}"
|
55
|
+
puts ''
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.check_server_info(uri)
|
60
|
+
uri.path = '/server-info'
|
61
|
+
uri.query = '' if uri.query != nil
|
62
|
+
|
63
|
+
ret = Yawast::Shared::Http.get(uri)
|
64
|
+
|
65
|
+
if ret.include? 'Apache Server Information'
|
66
|
+
Yawast::Utilities.puts_vuln "Apache Server Info page found: #{uri}"
|
67
|
+
puts ''
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/scanner/cms.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Yawast
|
2
|
+
module Scanner
|
3
|
+
class Cms
|
4
|
+
def self.get_generator(body)
|
5
|
+
regex = /<meta name="generator[^>]+content\s*=\s*['"]([^'"]+)['"][^>]*>/
|
6
|
+
match = body.match regex
|
7
|
+
|
8
|
+
if match
|
9
|
+
Yawast::Utilities.puts_info "Meta Generator: #{match[1]}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/scanner/core.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
module Yawast
|
2
|
+
module Scanner
|
3
|
+
class Core
|
4
|
+
def self.print_header(uri)
|
5
|
+
Yawast.header
|
6
|
+
|
7
|
+
puts "Scanning: #{uri.to_s}"
|
8
|
+
puts
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.setup(uri, options)
|
12
|
+
unless @setup
|
13
|
+
print_header(uri)
|
14
|
+
Yawast.set_openssl_options
|
15
|
+
|
16
|
+
Yawast::Scanner::Generic.server_info(uri, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
@setup = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.process(uri, options)
|
23
|
+
setup(uri, options)
|
24
|
+
|
25
|
+
begin
|
26
|
+
#setup the proxy
|
27
|
+
Yawast::Shared::Http.setup(options.proxy, options.cookie)
|
28
|
+
|
29
|
+
#cache the HEAD result, so that we can minimize hits
|
30
|
+
head = Yawast::Shared::Http.head(uri)
|
31
|
+
Yawast::Scanner::Generic.head_info(head)
|
32
|
+
|
33
|
+
#perfom SSL checks
|
34
|
+
check_ssl(uri, options, head)
|
35
|
+
|
36
|
+
#process the 'scan' stuff that goes beyond 'head'
|
37
|
+
unless options.head
|
38
|
+
#server specific checks
|
39
|
+
Yawast::Scanner::Apache.check_all(uri, head)
|
40
|
+
Yawast::Scanner::Iis.check_all(uri, head)
|
41
|
+
|
42
|
+
Yawast::Scanner::ObjectPresence.check_source_control(uri)
|
43
|
+
Yawast::Scanner::ObjectPresence.check_sitemap(uri)
|
44
|
+
Yawast::Scanner::ObjectPresence.check_cross_domain(uri)
|
45
|
+
Yawast::Scanner::ObjectPresence.check_wsftp_log(uri)
|
46
|
+
Yawast::Scanner::ObjectPresence.check_trace_axd(uri)
|
47
|
+
Yawast::Scanner::ObjectPresence.check_elmah_axd(uri)
|
48
|
+
Yawast::Scanner::ObjectPresence.check_readme_html(uri)
|
49
|
+
Yawast::Scanner::ObjectPresence.check_release_notes_txt(uri)
|
50
|
+
|
51
|
+
Yawast::Scanner::Generic.check_propfind(uri)
|
52
|
+
Yawast::Scanner::Generic.check_options(uri)
|
53
|
+
Yawast::Scanner::Generic.check_trace(uri)
|
54
|
+
|
55
|
+
#check for common directories
|
56
|
+
if options.dir
|
57
|
+
Yawast::Scanner::Generic.directory_search(uri, options.dirrecursive)
|
58
|
+
end
|
59
|
+
|
60
|
+
get_cms(uri, options)
|
61
|
+
end
|
62
|
+
|
63
|
+
puts 'Scan complete.'
|
64
|
+
rescue => e
|
65
|
+
Yawast::Utilities.puts_error "Fatal Error: Can not continue. (#{e.message})"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.get_cms(uri, options)
|
70
|
+
setup(uri, options)
|
71
|
+
|
72
|
+
body = Yawast::Shared::Http.get(uri)
|
73
|
+
Yawast::Scanner::Cms.get_generator(body)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.check_ssl(uri, options, head)
|
77
|
+
setup(uri, options)
|
78
|
+
|
79
|
+
if uri.scheme == 'https' && !options.nossl
|
80
|
+
head = Yawast::Shared::Http.head(uri) if head == nil
|
81
|
+
|
82
|
+
if options.internalssl
|
83
|
+
Yawast::Scanner::Ssl.info(uri, !options.nociphers, options.sweet32count)
|
84
|
+
else
|
85
|
+
Yawast::Scanner::SslLabs.info(uri, options.sslsessioncount)
|
86
|
+
end
|
87
|
+
|
88
|
+
Yawast::Scanner::Ssl.check_hsts(head)
|
89
|
+
elsif uri.scheme == 'http'
|
90
|
+
puts 'Skipping TLS checks; URL is not HTTPS'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,323 @@
|
|
1
|
+
require 'ipaddr_extensions'
|
2
|
+
|
3
|
+
module Yawast
|
4
|
+
module Scanner
|
5
|
+
class Generic
|
6
|
+
def self.server_info(uri, options)
|
7
|
+
begin
|
8
|
+
puts 'DNS Information:'
|
9
|
+
|
10
|
+
dns = Resolv::DNS.new
|
11
|
+
Resolv::DNS.open do |resv|
|
12
|
+
a = resv.getresources(uri.host, Resolv::DNS::Resource::IN::A)
|
13
|
+
unless a.empty?
|
14
|
+
a.each do |ip|
|
15
|
+
begin
|
16
|
+
host_name = dns.getname(ip.address)
|
17
|
+
rescue
|
18
|
+
host_name = 'N/A'
|
19
|
+
end
|
20
|
+
|
21
|
+
Yawast::Utilities.puts_info "\t\t#{ip.address} (#{host_name})"
|
22
|
+
|
23
|
+
# if address is private, force internal SSL mode, don't show links
|
24
|
+
if IPAddr.new(ip.address.to_s, Socket::AF_INET).private?
|
25
|
+
options.internalssl = true
|
26
|
+
else
|
27
|
+
puts "\t\t\t\thttps://www.shodan.io/host/#{ip.address}"
|
28
|
+
puts "\t\t\t\thttps://censys.io/ipv4/#{ip.address}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
aaaa = resv.getresources(uri.host, Resolv::DNS::Resource::IN::AAAA)
|
34
|
+
unless aaaa.empty?
|
35
|
+
aaaa.each do |ip|
|
36
|
+
begin
|
37
|
+
host_name = dns.getname(ip.address)
|
38
|
+
rescue
|
39
|
+
host_name = 'N/A'
|
40
|
+
end
|
41
|
+
|
42
|
+
Yawast::Utilities.puts_info "\t\t#{ip.address} (#{host_name})"
|
43
|
+
|
44
|
+
# if address is private, force internal SSL mode, don't show links
|
45
|
+
if IPAddr.new(ip.address.to_s, Socket::AF_INET6).private?
|
46
|
+
options.internalssl = true
|
47
|
+
else
|
48
|
+
puts "\t\t\t\thttps://www.shodan.io/host/#{ip.address.to_s.downcase}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
txt = resv.getresources(uri.host, Resolv::DNS::Resource::IN::TXT)
|
54
|
+
unless txt.empty?
|
55
|
+
txt.each do |rec|
|
56
|
+
Yawast::Utilities.puts_info "\t\tTXT: #{rec.data}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
mx = resv.getresources(uri.host, Resolv::DNS::Resource::IN::MX)
|
61
|
+
unless mx.empty?
|
62
|
+
mx.each do |rec|
|
63
|
+
Yawast::Utilities.puts_info "\t\tMX: #{rec.exchange} (#{rec.preference})"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
ns = resv.getresources(uri.host, Resolv::DNS::Resource::IN::NS)
|
68
|
+
unless ns.empty?
|
69
|
+
ns.each do |rec|
|
70
|
+
Yawast::Utilities.puts_info "\t\tNS: #{rec.name}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
puts
|
76
|
+
rescue => e
|
77
|
+
Yawast::Utilities.puts_error "Error getting basic information: #{e.message}"
|
78
|
+
raise
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.head_info(head)
|
83
|
+
begin
|
84
|
+
server = ''
|
85
|
+
powered_by = ''
|
86
|
+
cookies = Array.new
|
87
|
+
pingback = ''
|
88
|
+
frame_options = ''
|
89
|
+
content_options = ''
|
90
|
+
csp = ''
|
91
|
+
backend_server = ''
|
92
|
+
runtime = ''
|
93
|
+
xss_protection = ''
|
94
|
+
via = ''
|
95
|
+
hpkp = ''
|
96
|
+
|
97
|
+
Yawast::Utilities.puts_info 'HEAD:'
|
98
|
+
head.each do |k, v|
|
99
|
+
Yawast::Utilities.puts_info "\t\t#{k}: #{v}"
|
100
|
+
|
101
|
+
server = v if k.downcase == 'server'
|
102
|
+
powered_by = v if k.downcase == 'x-powered-by'
|
103
|
+
pingback = v if k.downcase == 'x-pingback'
|
104
|
+
frame_options = v if k.downcase == 'x-frame-options'
|
105
|
+
content_options = v if k.downcase == 'x-content-type-options'
|
106
|
+
csp = v if k.downcase == 'content-security-policy'
|
107
|
+
backend_server = v if k.downcase == 'x-backend-server'
|
108
|
+
runtime = v if k.downcase == 'x-runtime'
|
109
|
+
xss_protection = v if k.downcase == 'x-xss-protection'
|
110
|
+
via = v if k.downcase == 'via'
|
111
|
+
hpkp = v if k.downcase == 'public-key-pins'
|
112
|
+
|
113
|
+
if k.downcase == 'set-cookie'
|
114
|
+
#this chunk of magic manages to properly split cookies, when multiple are sent together
|
115
|
+
v.gsub(/(,([^;,]*=)|,$)/) { "\r\n#{$2}" }.split(/\r\n/).each { |c| cookies.push(c) }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
puts ''
|
119
|
+
|
120
|
+
if server != ''
|
121
|
+
Yawast::Scanner::Apache.check_banner(server)
|
122
|
+
Yawast::Scanner::Php.check_banner(server)
|
123
|
+
Yawast::Scanner::Iis.check_banner(server)
|
124
|
+
Yawast::Scanner::Nginx.check_banner(server)
|
125
|
+
|
126
|
+
if server == 'cloudflare-nginx'
|
127
|
+
Yawast::Utilities.puts_info 'NOTE: Server appears to be Cloudflare; WAF may be in place.'
|
128
|
+
puts
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
if powered_by != ''
|
133
|
+
Yawast::Utilities.puts_warn "X-Powered-By Header Present: #{powered_by}"
|
134
|
+
end
|
135
|
+
|
136
|
+
if xss_protection == '0'
|
137
|
+
Yawast::Utilities.puts_warn 'X-XSS-Protection Disabled Header Present'
|
138
|
+
end
|
139
|
+
|
140
|
+
unless pingback == ''
|
141
|
+
Yawast::Utilities.puts_info "X-Pingback Header Present: #{pingback}"
|
142
|
+
end
|
143
|
+
|
144
|
+
unless runtime == ''
|
145
|
+
if runtime.is_number?
|
146
|
+
Yawast::Utilities.puts_warn 'X-Runtime Header Present; likely indicates a RoR application'
|
147
|
+
else
|
148
|
+
Yawast::Utilities.puts_warn "X-Runtime Header Present: #{runtime}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
unless backend_server == ''
|
153
|
+
Yawast::Utilities.puts_warn "X-Backend-Server Header Present: #{backend_server}"
|
154
|
+
end
|
155
|
+
|
156
|
+
unless via == ''
|
157
|
+
Yawast::Utilities.puts_warn "Via Header Present: #{via}"
|
158
|
+
end
|
159
|
+
|
160
|
+
if frame_options == ''
|
161
|
+
Yawast::Utilities.puts_warn 'X-Frame-Options Header Not Present'
|
162
|
+
else
|
163
|
+
if frame_options.downcase == 'allow'
|
164
|
+
Yawast::Utilities.puts_vuln "X-Frame-Options Header: #{frame_options}"
|
165
|
+
else
|
166
|
+
Yawast::Utilities.puts_info "X-Frame-Options Header: #{frame_options}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
if content_options == ''
|
171
|
+
Yawast::Utilities.puts_warn 'X-Content-Type-Options Header Not Present'
|
172
|
+
else
|
173
|
+
Yawast::Utilities.puts_info "X-Content-Type-Options Header: #{content_options}"
|
174
|
+
end
|
175
|
+
|
176
|
+
if csp == ''
|
177
|
+
Yawast::Utilities.puts_warn 'Content-Security-Policy Header Not Present'
|
178
|
+
end
|
179
|
+
|
180
|
+
if hpkp == ''
|
181
|
+
Yawast::Utilities.puts_warn 'Public-Key-Pins Header Not Present'
|
182
|
+
end
|
183
|
+
|
184
|
+
puts ''
|
185
|
+
|
186
|
+
unless cookies.empty?
|
187
|
+
Yawast::Utilities.puts_info 'Cookies:'
|
188
|
+
|
189
|
+
cookies.each do |val|
|
190
|
+
Yawast::Utilities.puts_info "\t\t#{val.strip}"
|
191
|
+
|
192
|
+
elements = val.strip.split(';')
|
193
|
+
|
194
|
+
#check for secure cookies
|
195
|
+
unless elements.include? ' Secure'
|
196
|
+
Yawast::Utilities.puts_warn "\t\t\tCookie missing Secure flag"
|
197
|
+
end
|
198
|
+
|
199
|
+
#check for HttpOnly cookies
|
200
|
+
unless elements.include? ' HttpOnly'
|
201
|
+
Yawast::Utilities.puts_warn "\t\t\tCookie missing HttpOnly flag"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
puts ''
|
206
|
+
end
|
207
|
+
|
208
|
+
puts ''
|
209
|
+
rescue => e
|
210
|
+
Yawast::Utilities.puts_error "Error getting head information: #{e.message}"
|
211
|
+
raise
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.directory_search(uri, recursive, banner = true)
|
216
|
+
if banner
|
217
|
+
if recursive
|
218
|
+
puts 'Recursively searching for common directories (this will take a while)...'
|
219
|
+
else
|
220
|
+
puts 'Searching for common directories...'
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
begin
|
225
|
+
req = Yawast::Shared::Http.get_http(uri)
|
226
|
+
req.use_ssl = uri.scheme == 'https'
|
227
|
+
req.keep_alive_timeout = 600
|
228
|
+
headers = Yawast::Shared::Http.get_headers
|
229
|
+
|
230
|
+
req.start do |http|
|
231
|
+
File.open("lib/resources/common.txt", "r") do |f|
|
232
|
+
f.each_line do |line|
|
233
|
+
check = uri.copy
|
234
|
+
check.path = check.path + "#{line.strip}/"
|
235
|
+
|
236
|
+
res = http.head(check, headers)
|
237
|
+
|
238
|
+
if res.code == '200'
|
239
|
+
Yawast::Utilities.puts_info "\tFound: '#{check.to_s}'"
|
240
|
+
directory_search check, recursive, false if recursive
|
241
|
+
elsif res.code == '301'
|
242
|
+
Yawast::Utilities.puts_info "\tFound Redirect: '#{check.to_s} -> '#{res['Location']}'"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
rescue => e
|
248
|
+
Yawast::Utilities.puts_error "Error searching for directories (#{e.message})"
|
249
|
+
end
|
250
|
+
|
251
|
+
puts '' if banner
|
252
|
+
end
|
253
|
+
|
254
|
+
def self.check_options(uri)
|
255
|
+
begin
|
256
|
+
req = Yawast::Shared::Http.get_http(uri)
|
257
|
+
req.use_ssl = uri.scheme == 'https'
|
258
|
+
headers = Yawast::Shared::Http.get_headers
|
259
|
+
res = req.request(Options.new('/', headers))
|
260
|
+
|
261
|
+
if res['Public'] != nil
|
262
|
+
Yawast::Utilities.puts_info "Public HTTP Verbs (OPTIONS): #{res['Public']}"
|
263
|
+
|
264
|
+
puts ''
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def self.check_trace(uri)
|
270
|
+
begin
|
271
|
+
req = Yawast::Shared::Http.get_http(uri)
|
272
|
+
req.use_ssl = uri.scheme == 'https'
|
273
|
+
headers = Yawast::Shared::Http.get_headers
|
274
|
+
res = req.request(Trace.new('/', headers))
|
275
|
+
|
276
|
+
if res.body.include? 'TRACE / HTTP/1.1'
|
277
|
+
Yawast::Utilities.puts_warn 'HTTP TRACE Enabled'
|
278
|
+
puts "\t\t\"curl -X TRACE #{uri}\""
|
279
|
+
|
280
|
+
puts ''
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def self.check_propfind(uri)
|
286
|
+
begin
|
287
|
+
req = Yawast::Shared::Http.get_http(uri)
|
288
|
+
req.use_ssl = uri.scheme == 'https'
|
289
|
+
headers = Yawast::Shared::Http.get_headers
|
290
|
+
res = req.request(Propfind.new('/', headers))
|
291
|
+
|
292
|
+
if res.code.to_i <= 400 && res.body.length > 0 && res['Content-Type'] == 'text/xml'
|
293
|
+
Yawast::Utilities.puts_warn 'Possible Info Disclosure: PROPFIND Enabled'
|
294
|
+
puts "\t\t\"curl -X PROPFIND #{uri}\""
|
295
|
+
|
296
|
+
puts ''
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
#Custom class to allow using the PROPFIND verb
|
303
|
+
class Propfind < Net::HTTPRequest
|
304
|
+
METHOD = "PROPFIND"
|
305
|
+
REQUEST_HAS_BODY = false
|
306
|
+
RESPONSE_HAS_BODY = true
|
307
|
+
end
|
308
|
+
|
309
|
+
#Custom class to allow using the OPTIONS verb
|
310
|
+
class Options < Net::HTTPRequest
|
311
|
+
METHOD = "OPTIONS"
|
312
|
+
REQUEST_HAS_BODY = false
|
313
|
+
RESPONSE_HAS_BODY = true
|
314
|
+
end
|
315
|
+
|
316
|
+
#Custom class to allow using the TRACE verb
|
317
|
+
class Trace < Net::HTTPRequest
|
318
|
+
METHOD = "TRACE"
|
319
|
+
REQUEST_HAS_BODY = false
|
320
|
+
RESPONSE_HAS_BODY = true
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
data/lib/scanner/iis.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Yawast
|
2
|
+
module Scanner
|
3
|
+
class Iis
|
4
|
+
def self.check_banner(banner)
|
5
|
+
#don't bother if this doesn't include IIS
|
6
|
+
return if !banner.include? 'Microsoft-IIS/'
|
7
|
+
@iis = true
|
8
|
+
|
9
|
+
Yawast::Utilities.puts_warn "IIS Version: #{banner}"
|
10
|
+
puts ''
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.check_all(uri, head)
|
14
|
+
return if !@iis
|
15
|
+
|
16
|
+
#run all the defined checks
|
17
|
+
check_asp_banner(head)
|
18
|
+
check_mvc_version(head)
|
19
|
+
check_asp_net_debug(uri)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.check_asp_banner(head)
|
23
|
+
head.each do |k, v|
|
24
|
+
if k.downcase == 'x-aspnet-version'
|
25
|
+
Yawast::Utilities.puts_warn "ASP.NET Version: #{v}"
|
26
|
+
puts ''
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.check_mvc_version(head)
|
32
|
+
head.each do |k, v|
|
33
|
+
if k.downcase == 'x-aspnetmvc-version'
|
34
|
+
Yawast::Utilities.puts_warn "ASP.NET MVC Version: #{v}"
|
35
|
+
puts ''
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.check_asp_net_debug(uri)
|
41
|
+
begin
|
42
|
+
req = Yawast::Shared::Http.get_http(uri)
|
43
|
+
req.use_ssl = uri.scheme == 'https'
|
44
|
+
headers = Yawast::Shared::Http.get_headers
|
45
|
+
headers['Command'] = 'stop-debug'
|
46
|
+
headers['Accept'] = '*/*'
|
47
|
+
res = req.request(Debug.new('/', headers))
|
48
|
+
|
49
|
+
if res.code == 200
|
50
|
+
Yawast::Utilities.puts_vuln 'ASP.NET Debugging Enabled'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#Custom class to allow using the DEBUG verb
|
57
|
+
class Debug < Net::HTTPRequest
|
58
|
+
METHOD = "DEBUG"
|
59
|
+
REQUEST_HAS_BODY = false
|
60
|
+
RESPONSE_HAS_BODY = true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Yawast
|
2
|
+
module Scanner
|
3
|
+
class Nginx
|
4
|
+
def self.check_banner(banner)
|
5
|
+
#don't bother if this doesn't include nginx
|
6
|
+
return if !banner.include? 'nginx/'
|
7
|
+
|
8
|
+
Yawast::Utilities.puts_warn "nginx Version: #{banner}"
|
9
|
+
puts ''
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Yawast
|
2
|
+
module Scanner
|
3
|
+
class ObjectPresence
|
4
|
+
def self.check_source_control(uri)
|
5
|
+
check_path(uri, '/.git/', true)
|
6
|
+
check_path(uri, '/.hg/', true)
|
7
|
+
check_path(uri, '/.svn/', true)
|
8
|
+
check_path(uri, '/.bzr/', true)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.check_cross_domain(uri)
|
12
|
+
check_path(uri, '/crossdomain.xml', false)
|
13
|
+
check_path(uri, '/clientaccesspolicy.xml', false)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.check_sitemap(uri)
|
17
|
+
check_path(uri, '/sitemap.xml', false)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.check_wsftp_log(uri)
|
21
|
+
#check both upper and lower, as they are both seen in the wild
|
22
|
+
check_path(uri, '/WS_FTP.LOG', false)
|
23
|
+
check_path(uri, '/ws_ftp.log', false)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.check_trace_axd(uri)
|
27
|
+
check_path(uri, '/Trace.axd', false)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.check_elmah_axd(uri)
|
31
|
+
check_path(uri, '/elmah.axd', false)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.check_readme_html(uri)
|
35
|
+
check_path(uri, '/readme.html', false)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.check_release_notes_txt(uri)
|
39
|
+
check_path(uri, '/RELEASE-NOTES.txt', false)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.check_path(uri, path, vuln)
|
43
|
+
#note: this only checks directly at the root, I'm not sure if this is what we want
|
44
|
+
# should probably be relative to what's passed in, instead of overriding the path.
|
45
|
+
check = uri.copy
|
46
|
+
check.path = "#{path}"
|
47
|
+
code = Yawast::Shared::Http.get_status_code(check)
|
48
|
+
|
49
|
+
if code == "200"
|
50
|
+
msg = "'#{path}' found: #{check}"
|
51
|
+
|
52
|
+
if vuln
|
53
|
+
Yawast::Utilities.puts_vuln msg
|
54
|
+
else
|
55
|
+
Yawast::Utilities.puts_warn msg
|
56
|
+
end
|
57
|
+
|
58
|
+
puts ''
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/scanner/php.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Yawast
|
2
|
+
module Scanner
|
3
|
+
class Php
|
4
|
+
def self.check_banner(banner)
|
5
|
+
#don't bother if this doesn't include PHP
|
6
|
+
return if !banner.include? 'PHP/'
|
7
|
+
|
8
|
+
modules = banner.split(' ')
|
9
|
+
|
10
|
+
modules.each do |mod|
|
11
|
+
if mod.include? 'PHP/'
|
12
|
+
Yawast::Utilities.puts_warn "PHP Version: #{mod}"
|
13
|
+
puts ''
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|