# frozen_string_literal: true # # ronin-vulns - A Ruby library for blind vulnerability testing. # # 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. # # 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 'ronin/vulns/reflected_xss/test_string' require 'ronin/vulns/reflected_xss/context' require 'set' module Ronin module Vulns # # Represents a (Reflected) Cross Site Scripting (XSS) vulnerability. # # ## Features # # * Tests a URL with just one HTTP request (per param). # * Tests which HTML special characters are allowed. # * Identifies the context, tag name, and/or attribute name of the XSS. # * Determines viability of XSS based on the context. # * Includes random data in the test values. # class ReflectedXSS < WebVuln # The characters that are allowed and will not be escaped or filtered. # # @return [Set, nil] attr_reader :allowed_chars # The context the XSS occurred in. # # @return [Context, nil] attr_reader :context # # Tests the test string by sending an HTTP request with the test string # embedded. # # @param [TestString] test_string # # @yield [body, match] # If the response was `text/html` and the test string appears (at least # partially) in the response body, the response body and match data will # be yielded. # # @yieldparam [String] body # The response body. # # @yieldparam [MatchData] match # The matched data for the test string. # # @api private # def test_string(test_string) test_string = test_string.wrap(random_value,random_value) response = exploit("#{original_value}#{test_string}") content_type = response.content_type body = response.body if content_type && content_type.include?('text/html') if (match = test_string.match(body)) yield body, match end end end # # Tests whether characters in the test string will be escaped/filtered or # passed through and updates {#allowed_chars}. # # @param [TestString] test_string # The test string to send. # # @yield [body, match] # If a block is given, it will be passed the response body and the # regular expression match data, if the response contains the test # string. # # @yieldparam [String] body # The response body. # # @yieldparam [MatchData] match # The matched data for the test string. # # @api private # def test_chars(test_string) test_string(test_string) do |body,match| @allowed_chars ||= Set.new @allowed_chars.merge(match.captures.compact) yield body, match if block_given? end end # HTML special characters to test. HTML_TEST_STRING = TestString.build("'\"= /><") # # Tests which HTML characters are accepted or escaped/filtered. # # @yield [body, match] # If a block is given, it will be passed the response body and the # regular expression match data, if the response contains the test # string. # # @yieldparam [String] body # The response body. # # @yieldparam [MatchData] match # The matched data for the test string. # # @api private # def test_html_chars(&block) test_chars(HTML_TEST_STRING,&block) end # # Tests whether the URL is vulnerable to (Reflected) Cross Site Scripting # (XSS). # # @return [Boolean] # Indicates whether the URL is vulnerable to (Reflected) Cross Site # Scripting (XSS). # # @note # If the URL is vulnerable, {#allowed_chars} and {#context} will be set. # def vulnerable? # test HTML special characters test_html_chars do |body,match| xss_index = match.begin(0) # determine the contents which the XSS occurs if (@context = Context.identify(body,xss_index)) # determine whether enough special HTML characters are allowed to # escape the context which the XSS occurs. return @context.viable?(@allowed_chars) end end return false 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 :reflected_xss end end end end