# 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 'ronin/vulns/ssti/test_expression'
module Ronin
module Vulns
#
# Represents a Server Side Template Injection (SSTI) vulnerability.
#
class SSTI < WebVuln
# List of common Server Side Template Injection (SSTI) escapes.
#
# @api private
ESCAPES = [
nil, # does not escape the expression
->(expression) { "{{#{expression}}}" },
->(expression) { "${#{expression}}" },
->(expression) { "${{#{expression}}}" },
->(expression) { "\#{#{expression}}" },
->(expression) { "<%= #{expression} %>" }
]
# How to escape the payload so that it's executed.
#
# @return [Proc, nil]
# The proc that will accept a String and return a String, or `nil` to
# indicate that the payload will not be escaped.
attr_reader :escape
# The test expression to use when testing the URL for SSTI.
#
# @return [TestExpression]
attr_reader :test_expr
#
# Initializes the Server Side Template Injection (SSTI) vulnerability.
#
# @param [String, URI::HTTP] url
# The URL to exploit.
#
# @param [Proc, nil] escape
# How to escape a given payload. Either a proc that will accept a String
# and return a String, or `nil` to indicate that the payload will not
# be escaped.
#
# @param [TestExpression] test_expr
# The test payload and expected result to check for when testing the URL
# for SSTI.
#
def initialize(url, escape: nil,
test_expr: self.class.random_test,
**kwargs)
super(url,**kwargs)
@escape = escape
@test_expr = test_expr
unless @test_expr
raise(ArgumentError,"must specify both a test expression")
end
end
#
# Generates a random `N*M` SSTI test.
#
# @return [TestExpression]
# A random test expression.
#
def self.random_test
int1 = rand(1_000..1_999)
int2 = rand(1_000..1_999)
string = "#{int1}*#{int2}"
result = (int1 * int2).to_s
return TestExpression.new(string,result)
end
#
# Scans the URL for Server Side Template Injection (SSTI) vulnerabilities.
#
# @param [URI::HTTP, String] url
# The URL to scan.
#
# @param [Array, Proc, nil] escape
# The escape method to use. If `escape:` is not given, then all escapes
# in {ESCAPES} will be tested..
#
# @param [Hash{Symbol => Object}] kwargs
# Additional keyword arguments for {#initialize}.
#
# @option kwargs [Array, Symbol, String, true, nil] :query_params
# The query param name(s) to test.
#
# @option kwargs [Array, Symbol, String, nil] :header_names
# The header name(s) to test.
#
# @option kwargs [Array, Symbol, String, true, nil] :cookie_params
# The cookie param name(s) to test.
#
# @option kwargs [Array, Symbol, String, nil] :form_params
# The form param name(s) to test.
#
# @option kwargs [Ronin::Support::Network::HTTP, nil] :http
# An HTTP session to use for testing the LFI.
#
# @option kwargs [Hash{String => String}, nil] :headers
# Additional headers to send with requests.
#
# @option kwargs [String, Ronin::Support::Network::HTTP::Cookie, nil] :cookie
# Additional cookie params to send with requests.
#
# @option kwargs [String, nil] :referer
# Optional `Referer` header to send with requests.
#
# @option kwargs [Hash{String => String}, nil] :form_data
# Additional form data to send with requests.
#
# @yield [vuln]
# If a block is given it will be yielded each discovered vulnerability.
#
# @yieldparam [SSTI] vuln
# A discovered SSTI vulnerability in the URL.
#
# @return [Array]
# All discovered SSTI vulnerabilities.
#
def self.scan(url, escape: ESCAPES, **kwargs,&block)
vulns = []
Array(escape).each do |escape_char|
vulns.concat(super(url, escape: escape_char, **kwargs, &block))
end
return vulns
end
#
# Escapes the payload using {#escape}.
#
# @param [String] payload
#
# @return [String]
#
def encode_payload(payload)
if @escape then @escape.call(payload)
else payload
end
end
#
# Determine whether the URL is vulnerable to Server Side Template
# Injection (SSTI).
#
# @return [Boolean]
#
def vulnerable?
response = exploit(@test_expr.string)
body = response.body
return body.include?(@test_expr.result)
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
:ssti
end
end
end
end