lib/ronin/sql/injection.rb in ronin-sql-0.2.4 vs lib/ronin/sql/injection.rb in ronin-sql-1.0.0

- old
+ new

@@ -1,11 +1,12 @@ # -# Ronin SQL - A Ronin library providing support for SQL related security -# tasks. +# Ronin SQL - A Ruby DSL for crafting SQL Injections. # -# Copyright (c) 2007-2009 Hal Brodigan (postmodern.mod3 at gmail.com) +# Copyright (c) 2007-2013 Hal Brodigan (postmodern.mod3 at gmail.com) # +# This file is part of Ronin SQL. +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # @@ -17,198 +18,165 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # -require 'ronin/sql/error' -require 'ronin/code/sql/injection' -require 'ronin/sessions/http' -require 'ronin/extensions/uri' -require 'ronin/web/extensions/nokogiri' -require 'ronin/web/spider' +require 'ronin/sql/binary_expr' +require 'ronin/sql/literals' +require 'ronin/sql/clauses' +require 'ronin/sql/statement_list' -require 'parameters' -require 'nokogiri' - module Ronin module SQL - class Injection + # + # Represents a SQL injection (SQLi). + # + class Injection < StatementList - include Parameters - include Sessions::HTTP + include Literals + include Clauses - # The URL to inject upon - attr_reader :url + # Default place holder values. + PLACE_HOLDERS = { + integer: 1, + decimal: 1.0, + string: '1', + list: [nil], + column: :id + } - # The URL query param to inject into - attr_reader :param + # The type of element to escape out of + attr_reader :escape - # Options for crafting SQL injections - attr_reader :sql_options + # The place holder data + attr_reader :place_holder - # HTTP request method (either :get or :post) - parameter :http_method, - :default => :get, - :description => 'HTTP request method to use' + # The expression that will be injected + attr_reader :expression # - # Creates a new Injection object with the specified _url_, _param_ - # to inject upon and the given _options_ which will be used - # for crafting SQL injections. + # Initializes a new SQL injection. # - def initialize(url,param,options={}) - super() - - @url = url - @param = param - @sql_options = options - end - + # @param [Hash] options + # Additional injection options. # - # Spider a site starting at the specified _url_ using the given - # _options_ and return an Array of URLs which are vulnerable to SQL - # Injection. If a _block_ is given, it will be passed vulnerable SQL - # Injection objects as they are found. + # @option options [:integer, :decimal, :string, :column] :escape (:integer) + # The type of element to escape out of. # - # Injection.spider('http://www.target.com/contact/') - # # => [...] + # @option options [Boolean] :terminate + # Specifies whether to terminate the SQLi with a comment. # - # Injection.spider('http://www.target.com/') do |injection| - # ... - # end + # @option options [String, Symbol, Integer] :place_holder + # Place-holder data. # - def Injection.spider(url,options={},&block) - injections = [] - - Web::Spider.site(url,options) do |spider| - spider.every_url_like(/\?[a-zA-Z0-9_]/) do |vuln_url| - found = vuln_url.sql_injections - - found.each(&block) if block - injections += found - end - end - - return injections - end - + # @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. # - # Creates a new Code::SQL::Injection object using the given _options_ - # and _block_. The given _options_ will be merged with the injections - # sql_options, to create a tailored Code::SQL::Injection object. + # @yieldparam [Injection] injection + # The new injection. # - def sql(options={},&block) - Code::SQL::Injection.new(@sql_options.merge(options),&block) - end - - def inject(options={},&block) - injection = (options[:sql] || sql(options,&block)) - - injection_url = URI(@url.to_s) - injection_url.query_params[@param.to_s] = injection - - request_method = (options[:method] || @http_method) - options = options.merge(:url => injection_url) - - if request_method == :post - return http_post_body(options) - else - return http_get_body(options) + def initialize(options={},&block) + @escape = options.fetch(:escape,:integer) + @place_holder = options.fetch(:place_holder) do + PLACE_HOLDERS.fetch(@escape) end - end - def inject_error(options={}) - inject({:sql => "'"}.merge(options)) - end + @expression = @place_holder - def error(options={}) - inject_error(options).sql_error + super(&block) end - def has_error?(options={}) - Error.has_message?(inject_error(options)) - end + # + # Appends an `AND` expression to the injection. + # + # @yield [(injection)] + # 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 [Injection] injection + # + # @return [self] + # + def and(&block) + value = case block.arity + when 0 then instance_eval(&block) + else block.call(self) + end - def vulnerable?(options={}) - body1 = inject(options) { no_rows } - body2 = inject(options) { all_rows } - - if (body1.sql_error? || body2.sql_error?) - return false - end - - body1 = Nokogiri::HTML(body1) - body2 = Nokogiri::HTML(body2) - - return body1.total_children < body2.total_children + @expression = BinaryExpr.new(@expression,:AND,value) + return self end - def has_column?(column,options={}) - body1 = inject(options) - body2 = inject(options.merge(:symbols => {:column => column})) do - has_column?(column) - end + # + # Appends an `OR` expression to the injection. + # + # @yield [(injection)] + # 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 [Injection] injection + # + # @return [self] + # + def or(&block) + value = case block.arity + when 0 then instance_eval(&block) + else block.call(self) + end - if (body1.sql_error? || body2.sql_error?) - return false - end - - body1 = Nokogiri::HTML(body1) - body2 = Nokogiri::HTML(body2) - - return body1.total_children == body2.total_children + @expression = BinaryExpr.new(@expression,:OR,value) + return self end - def has_table?(table,options={}) - body1 = inject(options) - body2 = inject(options.merge(:symbols => {:table => table})) do - has_table?(table) - end + # + # Converts the SQL injection to SQL. + # + # @param [Hash] options + # Additional options for {Emitter#initialize}. + # + # @option options [Boolean] :terminate + # Specifies whether to terminate the injection with `;--`. + # + # @return [String] + # The raw SQL. + # + def to_sql(options={}) + emitter = emitter(options) + sql = '' - if (body1.sql_error? || body2.sql_error?) - return false - end + sql << emitter.emit(@expression) - body1 = Nokogiri::HTML(body1) - body2 = Nokogiri::HTML(body2) - - return body1.total_children == body2.total_children - end - - def uses_column?(column,options={}) - body1 = inject(options) - body2 = inject(options.merge(:symbols => {:column => column})) do - uses_column?(table) + unless clauses.empty? + sql << emitter.space << emitter.emit_clauses(clauses) end - if (body1.sql_error? || body2.sql_error?) - return false + unless statements.empty? + sql << ';' << emitter.space << emitter.emit_statement_list(self) end - body1 = Nokogiri::HTML(body1) - body2 = Nokogiri::HTML(body2) + case @escape + when :string, :list + if (options[:terminate] || (sql[0,1] != sql[-1,1])) + # terminate the expression + sql << ';--' + else + sql = sql[0..-2] + end - return body1.total_children == body2.total_children - end - - def uses_table?(table,options={}) - body1 = inject(options) - body2 = inject(options.merge(:symbols => {:table => table})) do - uses_table?(table) + # balance the quotes + sql = sql[1..-1] + else + if options[:terminate] + # terminate the expression + sql << ';--' + end end - if (body1.sql_error? || body2.sql_error?) - return false - end - - body1 = Nokogiri::HTML(body1) - body2 = Nokogiri::HTML(body2) - - return body1.total_children == body2.total_children - end - - def to_s - @url.to_s + return sql end end end end