lib/ronin/vulns/cli/web_vuln_command.rb in ronin-vulns-0.1.5 vs lib/ronin/vulns/cli/web_vuln_command.rb in ronin-vulns-0.2.0.rc1

- old
+ new

@@ -1,10 +1,10 @@ # frozen_string_literal: true # # ronin-vulns - A Ruby library for blind vulnerability testing. # -# Copyright (c) 2022-2023 Hal Brodigan (postmodern.mod3 at gmail.com) +# Copyright (c) 2022-2024 Hal Brodigan (postmodern.mod3 at gmail.com) # # ronin-vulns is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. @@ -17,13 +17,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with ronin-vulns. If not, see <https://www.gnu.org/licenses/>. # require 'ronin/vulns/cli/command' -require 'ronin/vulns/cli/logging' +require 'ronin/vulns/cli/importable' +require 'ronin/vulns/cli/printing' require 'ronin/support/network/http/cookie' +require 'ronin/support/network/http/user_agents' require 'set' module Ronin module Vulns @@ -31,22 +33,52 @@ # # Base class for all web vulnerability commands. # class WebVulnCommand < Command - include Logging + include Printing + include Importable + option :import, desc: 'Imports discovered vulnerabilities into the database' option :first, short: '-F', desc: 'Only find the first vulnerability for each URL' do @scan_mode = :first end option :all, short: '-A', desc: 'Find all vulnerabilities for each URL' do @scan_mode = :all end + option :print_curl, desc: 'Also prints an example curl command for each vulnerability' + + option :print_http, desc: 'Also prints an example HTTP request for each vulnerability' + + option :request_method, short: '-M', + value: { + type: { + 'COPY' => :copy, + 'DELETE' => :delete, + 'GET' => :get, + 'HEAD' => :head, + 'LOCK' => :lock, + 'MKCOL' => :mkcol, + 'MOVE' => :move, + 'OPTIONS' => :options, + 'PATCH' => :patch, + 'POST' => :post, + 'PROPFIND' => :propfind, + 'PROPPATCH' => :proppatch, + 'PUT' => :put, + 'TRACE' => :trace, + 'UNLOCK' => :unlock + } + }, + desc: 'The HTTP request method to use' do |verb| + self.request_method = verb + end + option :header, short: '-H', value: { type: /[A-Za-z0-9-]+:\s*\w+/, usage: '"Name: value"' }, @@ -54,10 +86,29 @@ name, value = header.split(/:\s*/,2) self.headers[name] = value end + option :user_agent_string, short: '-U', + value: { + type: String, + usage: 'STRING' + }, + desc: 'Sets the User-Agent header' do |ua| + self.user_agent = ua + end + + option :user_agent, short: '-u', + value: { + type: Support::Network::HTTP::UserAgents::ALIASES.transform_keys { |key| + key.to_s.tr('_','-') + } + }, + desc: 'Sets the User-Agent to use' do |name| + self.user_agent = name + end + option :cookie, short: '-C', value: { type: String, usage: 'COOKIE' }, @@ -146,10 +197,14 @@ }, desc: 'Tests the form param name' do |name| self.test_form_params << name end + option :test_all_form_params, desc: 'Tests all form param names' do + self.test_form_params = true + end + option :input, short: '-i', value: { type: String, usage: 'FILE' }, @@ -195,73 +250,202 @@ unless (options[:input] || !urls.empty?) print_error "must specify URL(s) or --input" exit(-1) end - vulns_discovered = false + db_connect if options[:import] + vulns = [] + if options[:input] File.open(options[:input]) do |file| file.each_line(chomp: true) do |url| - vulns_discovered ||= process_url(url) + process_url(url) do |vuln| + vulns << vuln + end end end elsif !urls.empty? urls.each do |url| - vulns_discovered ||= process_url(url) + process_url(url) do |vuln| + vulns << vuln + end end end - unless vulns_discovered - puts colors.green("No vulnerabilities found") - end + puts unless vulns.empty? + print_vulns(vulns) end # + # Print a summary of all web vulnerabilities found. + # + # @param [Array<WebVuln>] vulns + # The discovered web vulnerabilities. + # + # @param [Boolean] print_curl + # Prints an example `curl` command to trigger the web vulnerability. + # + # @param [Boolean] print_http + # Prints an example HTTP request to trigger the web vulnerability. + # + # @since 0.2.0 + # + def print_vulns(vulns, print_curl: options[:print_curl], + print_http: options[:print_http]) + super(vulns, print_curl: print_curl, + print_http: print_http) + end + + # + # Prints detailed information about a discovered web vulnerability. + # + # @param [WebVuln] vuln + # The web vulnerability to log. + # + # @param [Boolean] print_curl + # Prints an example `curl` command to trigger the web vulnerability. + # + # @param [Boolean] print_http + # Prints an example HTTP request to trigger the web vulnerability. + # + # @since 0.2.0 + # + def print_vuln(vuln, print_curl: options[:print_curl], + print_http: options[:print_http]) + super(vuln, print_curl: print_curl, + print_http: print_http) + end + + # # Processes a URL. # # @param [String] url # A URL to scan. # - # @return [Boolean] - # Indicates whether a vulnerability was discovered in the URL. + # @yield [vuln] + # The given block will be passed each newly discovered web + # vulnerability. # + # @yieldparam [WebVuln] vuln + # A newly discovered web vulnerability. + # def process_url(url) unless url.start_with?('http://') || url.start_with?('https://') print_error("URL must start with http:// or https://: #{url.inspect}") exit(-1) end - vuln_discovered = false - if @scan_mode == :first if (first_vuln = test_url(url)) - log_vuln(first_vuln) - - vuln_discovered = true + process_vuln(first_vuln) + yield first_vuln end else scan_url(url) do |vuln| - log_vuln(vuln) - - vuln_discovered = true + process_vuln(vuln) + yield vuln end end + end - return vuln_discovered + # + # Logs and optioanlly imports a new discovered web vulnerability. + # + # @param [WebVuln] vuln + # The discovered web vulnerability. + # + # @since 0.2.0 + # + def process_vuln(vuln) + log_vuln(vuln) + import_vuln(vuln) if options[:import] end # + # The HTTP request method to use. + # + # @return [:copy, :delete, :get, :head, :lock, :mkcol, :move, + # :options, :patch, :post, :propfind, :proppatch, :put, + # :trace, :unlock] + # + # @since 0.2.0 + # + def request_method + @scan_kwargs[:request_method] + end + + # + # Sets the HTTP request method to use. + # + # @param [:copy, :delete, :get, :head, :lock, :mkcol, :move, + # :options, :patch, :post, :propfind, :proppatch, :put, + # :trace, :unlock] new_request_method + # + # @return [:copy, :delete, :get, :head, :lock, :mkcol, :move, + # :options, :patch, :post, :propfind, :proppatch, :put, + # :trace, :unlock] + # + # @since 0.2.0 + # + def request_method=(new_request_method) + @scan_kwargs[:request_method] = new_request_method + end + + # # Additional headers. # # @return [Hash{String => String}] # def headers @scan_kwargs[:headers] ||= {} end # + # The optional HTTP `User-Agent` header to send. + # + # @return [String, :random, :chrome, :chrome_linux, :chrome_macos, + # :chrome_windows, :chrome_iphone, :chrome_ipad, + # :chrome_android, :firefox, :firefox_linux, :firefox_macos, + # :firefox_windows, :firefox_iphone, :firefox_ipad, + # :firefox_android, :safari, :safari_macos, :safari_iphone, + # :safari_ipad, :edge, :linux, :macos, :windows, :iphone, + # :ipad, :android, nil] + # + # @since 0.2.0 + # + def user_agent + @scan_kwargs[:user_agent] + end + + # + # Sets the HTTP `User-Agent` header. + # + # @param [String, :random, :chrome, :chrome_linux, :chrome_macos, + # :chrome_windows, :chrome_iphone, :chrome_ipad, + # :chrome_android, :firefox, :firefox_linux, :firefox_macos, + # :firefox_windows, :firefox_iphone, :firefox_ipad, + # :firefox_android, :safari, :safari_macos, :safari_iphone, + # :safari_ipad, :edge, :linux, :macos, :windows, :iphone, + # :ipad, :android] new_user_agent + # The new `User-Agent` value to send. + # + # @return [String, :random, :chrome, :chrome_linux, :chrome_macos, + # :chrome_windows, :chrome_iphone, :chrome_ipad, + # :chrome_android, :firefox, :firefox_linux, :firefox_macos, + # :firefox_windows, :firefox_iphone, :firefox_ipad, + # :firefox_android, :safari, :safari_macos, :safari_iphone, + # :safari_ipad, :edge, :linux, :macos, :windows, :iphone, + # :ipad, :android] + # + # @since 0.2.0 + # + def user_agent=(new_user_agent) + @scan_kwargs[:user_agent] = new_user_agent + end + + # # The optional `Cookie` header to send. # # @return [Ronin::Support::Network::HTTP::Cookie] # def cookie @@ -354,9 +538,21 @@ # # @return [Set<String>, nil] # def test_form_params @scan_kwargs[:form_params] ||= Set.new + end + + # + # Sets the form params to test. + # + # @param [Set<String>, true] new_form_params + # The new form param names to test. + # + # @return [Set<String>, true] + # + def test_form_params=(new_form_params) + @scan_kwargs[:form_params] = new_form_params end # # Scans a URL for web vulnerabilities. #