« April 2009 | Main | June 2009 »

10 posts from May 2009

05/24/2009

Ruby: IIS WebDAV Checker

Sometimes, the frustrating thing about being a systems administrator is that often one's attention gets distracted easily by current events. The IIS WebDAV vulnerability and exploit making the rounds recently made me wonder just how many sites that I am responsible for supporting are utilizing WebDAV. So, the latest batch of Tomcat Management articles will have to wait a little bit longer.

Administering a large number of sites, it actually isn't all that unusual to come across a site built by someone else that might not have the required controls in place that Disables WebDAV on an IIS server, so a discovery tool like this I thought might be useful to more folks than just myself.

So, during this long, rainy, first unofficial start of Summer weekend, I've written the following script that will scan a site and report back whether or not WebDAV is enabled on it. I've tested it on all platforms except for SharePoint Portal, and I'd appreciate it if anyone out there with access to a SharePoint portal server could run it and report back whether the script reports correctly. I'd also appreciate any feedback from readers who are more experienced with Ruby as to whether this script could be fixed or made better.

The script uses the logic from a recent Technet blog article regarding the most recent WebDAV vulnerability. It doesn't work with SSL encrypted sites yet.

#!/usr/bin/env ruby

# Original Written by Chris @ Blogging Techstacks
# Updates by $Your_Name_Could_Go_Here!

# This script tests for the existence of an IIS server running WebDAV. It
# is based upon the following article:
# http://blogs.technet.com/srd/archive/2009/05/20/answers-to-the-iis-webdav-authentication-bypass-questions.aspx
#
# This script DOES NOT attempt any kind of exploit against WebDAV servers.
# All it does is test for the existence of WebDAV.
#
# Usage: ./iisWebDAVTest.rb <site> where <site> must use
# the following syntax: http://fully-qualifed-hostname/
# Trailing slash is required.
# Works with https as well!

require 'net/http'

# Text Colorization Class--not needed but looks fancy
class String
 def red; colorize(self, "\e[1m\e[31m"); end
 def green; colorize(self, "\e[1m\e[32m"); end
 def dark_green; colorize(self, "\e[32m"); end
 def yellow; colorize(self, "\e[1m\e[33m"); end
 def blue; colorize(self, "\e[1m\e[34m"); end
 def dark_blue; colorize(self, "\e[34m"); end
 def purple; colorize(self, "\e[1m\e[35m"); end
 def colorize(text, color_code) "#{color_code}#{text}\e[0m" end
end

puts "\nEnter the URL for the site (example: http://www.some-site.com/)"
name = gets 

url = URI.parse(name)
begin
req = Net::HTTP::Options.new(url.path)
resp = Net::HTTP.start(url.host, url.port) {|http|
 http.request(req) 
}
rescue Errno::ECONNRESET, Errno::ECONNABORTED
 puts "Connection Dropped! - Site could be dropping OPTIONS requests."
 exit
end
# Case statement - delivers response based on status code returned from site
statuscode = resp.code
result = case statuscode
 when "200" 
 then
 if resp.header['DAV'] == '1, 2' && resp.header['MS-Author-Via'] == 'DAV'
 then
  puts "Site has WebDAV enabled. Could be at risk. Get current on patches.".yellow
  puts "Allowable Methods: " + resp.header['Allow']
 elsif resp.header['DAV'] == '1,2' && resp.header['MS-Author-Via'] == 'DAV' && resp.header['X-MSDAVEXT'].exists
 then
  puts "Site uses Sharepoint's DAV".dark_green
  puts "Allowable Methods: " + resp.header['Allow']
 else
 puts "Server is not running WebDAV"
 puts "Allowable Methods: " + resp.header['Allow']
 end
 when "301" 
 then 
 puts "\nSite responded with a 301 - Permanent Redirect".yellow
 puts "\nAn OPTIONS request to redirect target: " 
 puts resp.header['Location'].dark_green 
 puts "may not work and RFC 2616 states that an OPTIONS"
 puts "request MUST NOT automatically follow a redirect."
 puts
 puts "Retry the request to: " + resp.header['Location'].dark_green
 puts "but you might get no response or an error."
 puts
 when "302" 
 then
 puts "\nSite responded with a 302 - Found".yellow
 puts "\nAn OPTIONS request to redirect target: " 
 puts resp.header['Location'].dark_green 
 puts "may not work and RFC 2616 states that an OPTIONS"
 puts "request MUST NOT automatically follow a redirect."
 puts
 puts "Retry the request to: " + resp.header['Location'].dark_green
 puts "but you might get no response or an error."
 puts
 when "403" 
 then 
 puts "OPTIONS is disabled. 403 Forbidden".red
 when "404" 
 then 
 puts "OPTIONS is probably disabled. 404 Not Found".red
 when "405" 
 then 
 puts "OPTIONS is disabled. 405 Method Not Allowed Response".red
 when "501" 
 then 
 puts "OPTIONS is disabled. 501 Not Implemented Response.".red
 else 
 puts "Unexpected Response."
end

05/13/2009

Groovy 1.6.3 Released

Taking a short break from my Tomcat Management articles to let everyone know that Groovy 1.6.3 has been released. Downloads are at the usual site. Wait a few days and additional download types (Installers for Windows, Universal Installers) should become available.

05/12/2009

Tomcat Management: Using the JMXProxy

One hidden gem bundled with the Tomcat Manager is the JMXProxy, which allows you to do a whole bunch of cool and frightening things with your tomcat container and running applications. It is enabled by default once the Tomcat Manager is enabled. You'll need more than a browser to interact with it, however, because simply loading http://localhost:8080/manager/jmxproxy dumps everything in regular text, which is useful for getting MBean names.

Below are some groovy scripts that will highlight some of the interesting things you can get from the JMXProxy.

Get Tomcat Thread Pool

#!/usr/bin/env groovy 

package groovyx.net.http
import static groovyx.net.http.Method.GET
import static groovyx.net.http.ContentType.TEXT

def http = new HTTPBuilder( 'http://localhost:8080/manager/jmxproxy/?qry=*:type=ThreadPool,name=http-8080' )
http.request( GET,TEXT ) { req ->
http.auth.basic( 'tomcat', 'tomcat' )
headers.'User-Agent' = 'GroovyHTTPBuilderTest/0.4'

response.success = { resp, reader ->
println "-----Response-----"
println System.out << reader
println "\n------------------"
}
response.failure = { resp ->
println "Something bad happened. ${resp.statusLine}"
}
}
The script above will print out some useful stats regarding Thread Pool statistics on the HTTP connector. Output looks something like the following:
-----Response-----
OK - Number of results: 1

Name: Catalina:type=ThreadPool,name=http-8080
modelerType: org.apache.tomcat.util.modeler.BaseModelMBean
maxThreads: 40
backlog: 100
currentThreadsBusy: 1
acceptorThreadCount: 1
threadPriority: 5
paused: false
port: 8080
tcpNoDelay: true
soLinger: -1
soTimeout: 20000
daemon: true
running: true
currentThreadCount: 6
name: http-8080

org.codehaus.groovy.runtime.FlushingStreamWriter@2580b3

------------------
Are you interested in connector-related statistics? Well, the following script will provide all the useful statistics exposed by the HTTP Connector.

Get Tomcat Connector Statistics

#!/usr/bin/env groovy 

package groovyx.net.http
import static groovyx.net.http.Method.GET
import static groovyx.net.http.ContentType.TEXT

def http = new HTTPBuilder( 'http://localhost:8080/manager/jmxproxy/?qry=*:type=Connector,port=8080' )
http.request( GET,TEXT ) { req ->
http.auth.basic( 'tomcat', 'tomcat' )
headers.'User-Agent' = 'GroovyHTTPBuilderTest/0.4'

response.success = { resp, reader ->
println "-----Response-----"
println System.out << reader
println "\n------------------"
}
response.failure = { resp ->
println "Something bad happened. ${resp.statusLine}"
}
}
Output will look similar to the following:
-----Response-----
OK - Number of results: 1

Name: Catalina:type=Connector,port=8080
modelerType: org.apache.catalina.mbeans.ConnectorMBean
connectionUploadTimeout: 300000
keepAliveTimeout: -1
protocol: HTTP/1.1
maxThreads: 40
acceptCount: 100
connectionTimeout: 20000
connectionLinger: -1
scheme: http
port: 8080
tcpNoDelay: true
xpoweredBy: false
compression: off
maxHttpHeaderSize: 8192
proxyPort: 0
secure: false
maxPostSize: 2097152
redirectPort: 8443
bufferSize: 2048
emptySessionPath: false
threadPriority: 5
useBodyEncodingForURI: false
protocolHandlerClassName: org.apache.coyote.http11.Http11Protocol
enableLookups: false
allowTrace: false
maxKeepAliveRequests: 100
disableUploadTimeout: true

org.codehaus.groovy.runtime.FlushingStreamWriter@2580b3

------------------

List Running Servlets

#!/usr/bin/env groovy 


package groovyx.net.http
import static groovyx.net.http.Method.GET
import static groovyx.net.http.ContentType.TEXT

def http = new HTTPBuilder( 'http://localhost:8080/manager/jmxproxy/?qry=*:j2eeType=Servlet,*' )
http.request( GET,TEXT ) { req ->
http.auth.basic( 'tomcat', 'tomcat' )
headers.'User-Agent' = 'GroovyHTTPBuilderTest/0.4'

response.success = { resp, reader ->
println "-----Response-----"
println System.out << reader
println "\n------------------"
}
response.failure = { resp ->
println "Something bad happened. ${resp.statusLine}"
}
}
There is an awful lot of output for this one so I will not be including the output for this particular script in this article.

Don't assume that interaction with the jmxproxy is all 'getting' and no 'setting'. This article dealt with the cool things you can do with the jmxproxy—the next one will deal with the frightening things.