ronin-rb/ronin-code-sql

View on GitHub
lib/ronin/code/sql/injection.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true
#
# ronin-code-sql - A Ruby DSL for crafting SQL Injections.
#
# Copyright (c) 2007-2024 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# ronin-code-sql 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-code-sql 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-code-sql.  If not, see <https://www.gnu.org/licenses/>.
#

require 'ronin/code/sql/literals'
require 'ronin/code/sql/clauses'
require 'ronin/code/sql/injection_expr'
require 'ronin/code/sql/statement_list'

module Ronin
  module Code
    module SQL
      #
      # Represents a SQL injection (SQLi).
      #
      # @api public
      #
      # @see http://en.wikipedia.org/wiki/SQL_injection
      #
      class Injection < StatementList

        include Literals
        include Clauses

        # Default place holder values.
        PLACE_HOLDERS = {
          integer: 1,
          decimal: 1.0,
          string:  '1',
          list:    [nil],
          column:  :id
        }

        # The type of element to escape out of
        #
        # @return [:integer, :decimal, :string, :column]
        attr_reader :escape

        # The expression that will be injected
        #
        # @return [InjectionExpr]
        attr_reader :expression

        #
        # Initializes a new SQL injection.
        #
        # @param [:integer, :decimal, :string, :column] escape
        #   The type of element to escape out of.
        #
        # @param [String, Symbol, Integer] place_holder
        #   Place-holder data.
        #
        # @yield [(injection)]
        #   If a block is given, it will be evaluated within the injection.
        #   If the block accepts an argument, the block will be called with the
        #   new injection.
        #
        # @yieldparam [Injection] injection
        #   The new injection.
        #
        def initialize(escape:       :integer,
                       place_holder: PLACE_HOLDERS.fetch(escape),
                       &block)
          @escape     = escape
          @expression = InjectionExpr.new(place_holder)

          super(&block)
        end

        #
        # Appends an `AND` expression to the injection.
        #
        # @yield [(expr)]
        #   The return value of the block will be used as the right-hand side
        #   operand.  If the block accepts an argument, it will be called with
        #   the injection.
        #
        # @yieldparam [InjectionExpr] expr
        #
        # @return [self]
        #
        def and(&block)
          @expression.and(&block)
          return self
        end

        #
        # Appends an `OR` expression to the injection.
        #
        # @yield [(expr)]
        #   The return value of the block will be used as the right-hand side
        #   operand. If the block accepts an argument, it will be called with
        #   the injection expression.
        #
        # @yieldparam [InjectionExpr] expr
        #
        # @return [self]
        #
        def or(&block)
          @expression.or(&block)
          return self
        end

        #
        # Converts the SQL injection to SQL.
        #
        # @param [Boolean] terminate
        #   Specifies whether to terminate the injection with `;--`.
        #
        # @param [Hash{Symbol => Object}] kwargs
        #   Additional keyword arguments for {Emitter#initialize}.
        #
        # @return [String]
        #   The raw SQL.
        #
        def to_sql(terminate: false, **kwargs)
          emitter = emitter(**kwargs)
          sql     = @expression.to_sql(**kwargs)

          unless clauses.empty?
            sql << emitter.space << emitter.emit_clauses(clauses)
          end

          unless statements.empty?
            sql << ';' << emitter.space << emitter.emit_statement_list(self)
          end

          case @escape
          when :string, :list
            if (terminate || (sql[0,1] != sql[-1,1]))
              # terminate the expression
              sql << ';' << emitter.emit_comment
            else
              sql = sql[0..-2]
            end

            # balance the quotes
            sql = sql[1..]
          else
            if terminate
              # terminate the expression
              sql << ';' << emitter.emit_comment
            end
          end

          return sql
        end

      end
    end
  end
end