# frozen_string_literal: true
#
# ronin-vulns - A Ruby library for blind vulnerability testing.
#
# Copyright (c) 2022-2023 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.
#
# ronin-vulns is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ronin-vulns. If not, see .
#
require 'ronin/vulns/web_vuln'
require 'chars'
require 'cgi'
module Ronin
module Vulns
#
# Represents an Open Redirect vulnerability.
#
# ## Features
#
# * Checks 301, 302, 303, 307, and 308 HTTP redirects.
# * Checks `meta` refresh redirects.
# * Includes random alpha-numeric data in the test values.
#
class OpenRedirect < WebVuln
# The desired redirect URL to use in the test.
#
# @return [String]
attr_reader :test_url
#
# Initializes the Open Redirect vulnerability.
#
# @param [String, URI::HTTP] url
# The URL to exploit.
#
# @param [String] test_url
# The desired redirect URL to test the URL with.
#
def initialize(url, test_url: self.class.random_test_url, **kwargs)
super(url,**kwargs)
@test_url = test_url
end
#
# Generates a random redirect URL to use in tests.
#
# @return [String]
# A random URL to https://ronin-rb.dev/vulns/open_redirect.html.
#
# @api private
#
def self.random_test_url
"https://ronin-rb.dev/vulns/open_redirect.html?id=#{Chars::ALPHA_NUMERIC.random_string(5)}"
end
#
# Tests whether the URL has a vulnerable Open Redirect.
#
# @return [Boolean]
#
def vulnerable?
response = exploit(@test_url)
case response.code
when '301', '302', '303', '307', '308'
if (locations = response.get_fields('Location'))
escaped_test_url = Regexp.escape(@test_url)
regexp = /\A#{escaped_test_url}.*\z/
locations.last =~ regexp
end
else
content_type = response.content_type
if content_type && content_type.include?('text/html')
escaped_test_url = Regexp.escape(CGI.escapeHTML(@test_url))
regexp = %r{
]*
)
)
\s*
# /> or / >
(?:/\s*)?>
}xi
response.body =~ regexp
end
end
end
#
# Returns the type or kind of vulnerability.
#
# @return [Symbol]
#
# @note
# This is used internally to map an vulnerability class to a printable
# type.
#
# @api private
#
# @abstract
#
def self.vuln_type
:open_redirect
end
end
end
end