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