Search code examples
rubyoptparse

Optparser defaulting to usage_page no matter what flag is given


I'm playing around with OptParse and trying to get it to run correctly, however, as of right now all of my options and defaulting to the usage_page method. I'm not sure why this would be happening, I've attempted to move the CHOICES constant to right above the begin/rescue clause, I've also attempted to use start = OptParser.new;options = start.parse_argv(ARGV) neither of these work. I've also attempted (as you can see from my code) to split the OptParser.new and CHOICES = OptParse.parse_argv(ARGV) up, I'm a little bit confused and could use some help, why isn't anything I try working? I don't understand, it either tells me that it's not a variable, not a method, or throws the usage page?

(This is an open source project that I'm attempting to help with)

My source:

PATH = Dir.pwd
VERSION = Whitewidow.version
SEARCH = File.readlines("#{PATH}/lib/search_query.txt").sample
info = YAML.load_file("#{PATH}/lib/rand-agents.yaml")
@user_agent = info['user_agents'][info.keys.sample]
OPTIONS = Struct.new(:default, :file, :example)

def usage_page
  Format.usage("You can run me with the following flags: #{File.basename(__FILE__)} -[d|e|h] -[f] <path/to/file/if/any>")
  exit
end

def examples_page
  Format.usage('This is my examples page, I\'ll show you a few examples of how to get me to do what you want.')
  Format.usage('Running me with a file: whitewidow.rb -f <path/to/file> keep the file inside of one of my directories.')
  Format.usage('Running me default, if you don\'t want to use a file, because you don\'t think I can handle it, or for whatever reason, you can run me default by passing the Default flag: whitewidow.rb -d this will allow me to scrape Google for some SQL vuln sites, no guarentees though!')
  Format.usage('Running me with my Usage flag will show you the boring usage page.. Yeah it\'s not very exciting..')
end

class OptParser

  def self.parse_argv(options)
    args = OPTIONS.new
    opt_parser = OptionParser.new do |opts|
      opts.banner = usage_page
      opts.on('-d', '--default', 'Run me in default mode, I\'ll scrape Google for SQL vulns.') do |d|
        args[:default] = d
      end
      opts.on('-fFILE', '--file=FILE', 'Pass me the name of the file. Leave out the beginning forward slash. (/lib/ <= incorrect lib/ <=correct)') do |f|
        args.fileFILE = f
      end
      opts.on('-e', '--example', 'Shows my example page. It\'s really not that hard to figure out, but I\'m a nice albino widow.') do |e|
      args[:example] = e
      end
      opts.on('-h', '--help', 'Shows a complete list of all my usages, with what they do, and their secondary flag.') do
        puts opts
        exit
      end
      opts.on('-u', '--usage', 'Shows my usage page, a short list of possible flags, use the help flag (-h) for a more complete list.') do
        usage_page
        exit
      end
    end
    opt_parser.parse!(options)
    return args
  end
end

def pull_proxy
  info = parse("http://www.nntime.com/",'.odd', 1)
  @ip = info[/\D\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\D/].gsub(">", "").gsub("<", "")
  @port = info[/8080/] || info[/3128/] || info[/80/] || info[/3129/] || info[/6129/]
  proxy = "#{@ip}:#{@port}"
  Format.info("Proxy discovered: #{proxy}")
end

def page(site)
  Nokogiri::HTML(RestClient.get(site))
end

def parse(site, tag, i)
  parsing = page(site)
  parsing.css(tag)[i].to_s
end

def get_urls
  if OPTIONS[:default]
    Format.info('I\'ll run in default mode then!')
    Format.info("I'm searching for possible SQL vulnerable sites, using search query #{SEARCH}")
    agent = Mechanize.new
    agent.user_agent = @user_agent
    page = agent.get('http://www.google.com/')
    google_form = page.form('f')
    google_form.q = "#{SEARCH}"
    url = agent.submit(google_form, google_form.buttons.first)
      url.links.each do |link|
        if link.href.to_s =~ /url.q/
          str = link.href.to_s
          str_list = str.split(%r{=|&})
          urls = str_list[1]
          next if urls.split('/')[2].start_with? 'stackoverflow.com', 'github.com', 'www.sa-k.net', 'yoursearch.me', 'search1.speedbit.com', 'duckfm.net', 'search.clearch.org', 'webcache.googleusercontent.com'
          next if urls.split('/')[1].start_with? 'ads/preferences?hl=en'
          urls_to_log = URI.decode(urls)
          Format.success("Site found: #{urls_to_log}")
          sleep(1)
          File.open("#{PATH}/tmp/SQL_sites_to_check.txt", 'a+') { |s| s.puts("#{urls_to_log}'") }
        end
      end
    Format.info("I've dumped possible vulnerable sites into #{PATH}/tmp/SQL_sites_to_check.txt")
  else
    begin_vulnerability_check
  end
end

def begin_vulnerability_check
  (OPTIONS.file) ? file = IO.read(ARGV[1]) : file = IO.read("#{PATH}/tmp/SQL_sites_to_check.txt")
  #if options.file
  file.each_line do |vuln|
    Format.info("Let's check this file out..")
    #IO.read("#{PATH}/#{ARGV[1]}").each_line do |vuln|
      begin
        Format.info("Parsing page for SQL syntax error: #{vuln.chomp}")
        Timeout::timeout(10) do
          begin
            if parse("#{vuln.chomp}'", 'html', 0)[/You have an error in your SQL syntax/]
              Format.success("URL: #{vuln.chomp} returned SQL syntax error, temporarily dumped to SQL_VULN.txt")
              File.open("#{PATH}/tmp/SQL_VULN.txt", "a+") { |s| s.puts(vuln) }
              sleep(1)
            else
              Format.warning("URL: #{vuln.chomp} is not vulnerable, dumped to non_exploitable.txt")
              File.open("#{PATH}/log/non_exploitable.txt", "a+") { |s| s.puts(vuln) }
              sleep(1)
            end
          rescue Timeout::Error, OpenSSL::SSL::SSLError
            Format.info("URL: #{vuln.chomp} failed to load dumped to non_exploitable.txt")
            File.open("#{PATH}/log/non_exploitable.txt", "a+") { |s| s.puts(vuln) }
            next
            sleep(1)
          end
        end
      rescue RestClient::ResourceNotFound, RestClient::InternalServerError, RestClient::RequestTimeout, RestClient::Gone, RestClient::SSLCertificateNotVerified, RestClient::Forbidden, OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, URI::InvalidURIError, Errno::ECONNRESET, Timeout::Error, OpenSSL::SSL::SSLError, ArgumentError, RestClient::MultipleChoices, RestClient::Unauthorized, SocketError, RestClient::BadRequest, RestClient::ServerBrokeConnection => e
        Format.err("URL: #{vuln.chomp} failed due to an error while connecting, URL dumped to non_exploitable.txt")
        File.open("#{PATH}/log/non_exploitable.txt", "a+") { |s| s.puts(vuln) }
        next
      end
  end
  usage_page if OPTIONS.nil?
end

case OPTIONS
  when OPTIONS[:default] || OPTIONS.file
    begin
      Whitewidow.spider
      sleep(1)
      Credits.credits
      sleep(1)
      Legal.legal
      get_urls
      begin_vulnerability_check
      File.truncate("#{PATH}/tmp/SQL_sites_to_check.txt", 0)
      Format.info("I'm truncating SQL_sites_to_check file back to #{File.size("#{PATH}/tmp/SQL_sites_to_check.txt")}")
      Copy.file("#{PATH}/tmp/SQL_VULN.txt", "#{PATH}/log/SQL_VULN.LOG")
      File.truncate("#{PATH}/tmp/SQL_VULN.txt", 0)
      Format.info("I've run all my tests and queries, and logged all important information into #{PATH}/log/SQL_VULN.LOG") unless File.size("#{PATH}/log/SQL_VULN.LOG") == 0
    rescue Mechanize::ResponseCodeError, RestClient::ServiceUnavailable, OpenSSL::SSL::SSLError, RestClient::BadGateway => e
      d = DateTime.now
      Format.fatal("Well this is pretty crappy.. I seem to have encountered a #{e} error. I'm gonna take the safe road and quit scanning before I break something. You can either try again, or manually delete the URL that caused the error.")
      File.open("#{PATH}/log/error_log.LOG", 'a+'){ |error| error.puts("[#{d.month}-#{d.day}-#{d.year} :: #{Time.now.strftime("%T")}]#{e}") }
      Format.info("I'll log the error inside of #{PATH}/log/error_log.LOG for further analysis.")
    end
  when OPTIONS[:example]
    examples_page
  else
    Format.info("You failed to pass me a flag, let's try again and pass a flag this time!")
  end

 OptParser.new
 OPTIONS = OptParser.parse_argv(ARGV)

Solution

  • Using OptionParser is quite easy, but you're going about it a very hard way. Start with this:

    require 'optparse'
    
    options = {}
    OptionParser.new do |opt|
      opt.on('-f', '--foo', 'Foo it') { |o| options[:foo] = o }
      opt.on('-b', '--[no-]bar', 'Maybe bar it') { |o| options[:bar] = o }
      opt.on('-i', '--int INTEGER', Integer, 'I want an integer') { |o| options[:int] = o }
      opt.on('-o', '--float FLOAT', Float, 'I want a Float') { |o| options[:float] = o }
      opt.on('-a', '--array INTEGER', Array, 'I want an Array') { |o| options[:array] = o }
    end.parse!
    
    puts options
    

    and saving it to test.rb, then running it:

    $ ruby test.rb -h
    Usage: test [options]
    -f, --foo                        Foo it
    -b, --[no-]bar                   Maybe bar it
    -i, --int INTEGER                I want an integer
    -o, --float FLOAT                I want a Float
    -a, --array INTEGER              I want an Array
    

    So the built-in help works.

    $ ruby test.rb -f
    {:foo=>true}
    

    It sees the -f flag.

    $ ruby test.rb --bar
    {:bar=>true}
    

    It sees the --bar flag.

    $ ruby test.rb --no-bar
    {:bar=>false}
    

    It understands that --bar should be toggled off.

    $ ruby test.rb --foo --no-bar
    {:foo=>true, :bar=>false}
    

    It understands both flags.

    $ ruby test.rb -i 1
    {:int=>1}
    

    Yep, it coerced "1" to an Integer.

    $ ruby test.rb -i 1.1
    test.rb:10:in `<main>': invalid argument: -i 1.1 (OptionParser::InvalidArgument)
    

    Yep, it expected an Integer and got a float and complained.

    $ ruby test.rb -o 1.1
    {:float=>1.1}
    
    $ ruby test.rb -o 1.0
    {:float=>1.0}
    

    It's happy with a float.

    $ ruby test.rb -o 1
    {:float=>1.0}
    

    And coerces a "1" to a Float. That's good.

    $ ruby test.rb -a 1
    {:array=>["1"]}
    
    $ ruby test.rb -a 1,2
    {:array=>["1", "2"]}
    
    $ ruby test.rb -a 1,2,zed
    {:array=>["1", "2", "zed"]}
    

    It's handling arrays right.

    options is a nice container of the flags that were set and their parameters. Using it allows many different ways of branching logic. I tend to go with something like this:

    require 'optparse'
    
    options = {}
    OptionParser.new do |opt|
      opt.on('-f', '--foo', 'Foo it') { |o| options[:foo] = o }
      opt.on('-b', '--[no-]bar', 'Maybe bar it') { |o| options[:bar] = o }
      opt.on('-i', '--int INTEGER', Integer, 'I want an integer') { |o| options[:int] = o }
      opt.on('-o', '--float FLOAT', Float, 'I want a Float') { |o| options[:float] = o }
      opt.on('-a', '--array INTEGER', Array, 'I want an Array') { |o| options[:array] = o }
    end.parse!
    
    str = case
          when options.values_at(:foo, :bar).all?
            'foo and bar'
          when options.values_at(:int, :float, :array).one?
            'one of int, float or array'
          when options[:foo]
            'foo'
          when options[:bar]
            'bar'
          end
    
    puts '%s set' % str
    

    Don't create an OptionParser class. When you call it "OptionParser" you opened the class, then any methods you created were added to it, possibly overwriting core methods, breaking the class. Instead, create an instance of it with a block, then use parse! at the end of the block to give it control and let it figure out what flags were passed to it.

    The OptionParser class is pretty powerful so the documentation can be a little confusing, but the examples help.