# 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 'time'
module Ronin
module Vulns
#
# Represents a Command Injection vulnerability.
#
# ## Features
#
# * Supports using `;`, `|`, `&`, and `\n` escape characters.
# * Supports escaping single and double-quoted strings.
# * Supports using `;`, `#`, and `\n` terminator characters.
#
# @since 0.2.0
#
class CommandInjection < WebVuln
# The character to use to escape a quoted string.
#
# @return [String, nil]
attr_reader :escape_quote
# The escape character or string to use to escape the command and execute
# another.
#
# @return [String]
attr_reader :escape_operator
# The terminator charactor to terminate the injected command with.
#
# @return [String, nil]
attr_reader :terminator
#
# Initializes the command injection vulnerability.
#
# @param [URI::HTTP, String] url
# The URL to test or exploit.
#
# @param [String, nil] escape_quote
# The optional character to use to escape a quoted string.
#
# @param [String] escape_operator
# The escape character or string to use to escape the command
# and execute another.
#
# @param [String, nil] terminator
# The optional terminator character to terminate the injected command
# with.
#
def initialize(url, escape_quote: nil,
escape_operator: nil,
terminator: nil,
**kwargs)
super(url,**kwargs)
@escape_quote = escape_quote
@escape_operator = escape_operator
@terminator = terminator
@escape_string = build_escape_string
end
private
#
# Builds the command escape String.
#
# @return [String, nil]
#
def build_escape_string
if @escape_quote || @escape_operator
"#{original_value}#{@escape_quote}#{@escape_operator}"
end
end
public
#
# Scans the URL for command injections.
#
# Tests the URL and a specific query param, header name, cookie param, or
# form param for Command Injection by enumerating over various Command
# Injection escape syntaxes.
#
# @param [URI::HTTP] url
# The URL to test.
#
# @param [Array, String, nil] escape_quote
# The optional escape quote character(s) to test.
#
# @param [Array, String, nil] escape_operator
# The optional escape operator character(s) to test.
#
# @param [Array, Stirng, nil] terminator
# The optional command termination character(s) to test.
#
# @param [Ronin::Support::Network::HTTP, nil] http
# An HTTP session to use for testing the URL.
#
# @param [Hash{Symbol => Object}] kwargs
# Additional keyword arguments for {#initialize}.
#
# @option kwargs [Symbol, String, true, nil] :query_param
# The query param name to test.
#
# @option kwargs [Symbol, String, nil] :header_name
# The header name to test.
#
# @option kwargs [Symbol, String, true, nil] :cookie_param
# The cookie param name to test.
#
# @option kwargs [Symbol, String, nil] :form_param
# The form param name to test.
#
# @return [CommandInjection, nil]
# The first discovered Command Injection vulnerability for the specific
# query param, header name, cookie param, or form param.
#
# @api private
#
# @since 0.2.0
#
def self.test_param(url, escape_quote: [nil, "'", '"', '`'],
escape_operator: [';', '|', '&', "\n"],
terminator: [nil, ';', '#', "\n"],
# keyword arguments for initialize
http: , **kwargs)
Array(escape_quote).each do |escape_quote_char|
Array(escape_operator).each do |escape_operator_char|
Array(terminator).each do |terminator_char|
vuln = new(url, escape_quote: escape_quote_char,
escape_operator: escape_operator_char,
terminator: terminator_char,
http: http,
**kwargs)
return vuln if vuln.vulnerable?
end
end
end
return nil
end
#
# Escapes the given SQL and turns it into a SQL injection.
#
# @param [#to_s] command
# The command to escape.
#
# @return [String]
# The escaped SQL expression.
#
def escape(command)
cmdi = "#{@escape_string}#{command}"
if @terminator
cmdi << @terminator
elsif (@escape_quote && cmdi.end_with?(@escape_quote))
cmdi.chop!
end
return cmdi
end
#
# Encodes the command injection payload.
#
# @see #escape
#
def encode_payload(sql)
escape(sql)
end
#
# Tests whether the URL is vulnerable to command injection.
#
# @return [Boolean]
#
def vulnerable?
test_command_output || test_sleep
end
# Regular expression to match the output of the `id` command.
ID_OUTPUT_REGEX = /uid=\d+\([^\)]+\) gid=\d+\([^\)]+\) groups=\d+\([^\)]+\)/
#
# Tests whether the URL is vulnerable to command injection, by executing
# the `id` command and the output is included in the response body.
#
# @return [Boolean]
#
# @api private
#
def test_command_output
response = exploit('id')
if response.body =~ ID_OUTPUT_REGEX
return true
end
end
#
# Tests whether the URL is vulnerable to command injection, by calling the
# sleep command to see if it takes longer for the response to be
# returned.
#
# @return [Boolean]
#
# @api private
#
def test_sleep
start_time = Time.now
exploit("sleep 5")
stop_time = Time.now
delta = (stop_time - start_time)
# if the response took more than 5 seconds, our SQL sleep function
# probably worked.
return delta > 5.0
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
:command_injection
end
end
end
end