Refactor and fix some specs, avoid sleeping in time-based shared examples
This commit is contained in:
parent
854df7e93b
commit
89fef9f9fe
|
@ -5,5 +5,5 @@ RSpec.describe Msf::Exploit::SQLi::MySQLi::Common do
|
|||
sqli_obj = Msf::Exploit::SQLi::MySQLi::Common.new({}, {}, {}) {}
|
||||
expect(sqli_obj.send(:hex_encode_strings, query)).to eql encoded_query
|
||||
end
|
||||
it_should_behave_like "Msf::Exploit::SQLi::Common", described_class
|
||||
it_should_behave_like 'Msf::Exploit::SQLi::Common', described_class
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
RSpec.describe Msf::Exploit::SQLi::MySQLi::TimeBasedBlind do
|
||||
it_should_behave_like 'TimeBasedBlind', described_class
|
||||
end
|
|
@ -4,7 +4,7 @@ require 'msf/core'
|
|||
require 'msf/core/module'
|
||||
require 'msf/core/exploit/sqli'
|
||||
|
||||
RSpec.shared_examples "Msf::Exploit::SQLi::Common" do|sqli_class|
|
||||
RSpec.shared_examples 'Msf::Exploit::SQLi::Common' do |sqli_class|
|
||||
let(:common_class) do
|
||||
sqli_class
|
||||
end
|
||||
|
@ -14,89 +14,136 @@ RSpec.shared_examples "Msf::Exploit::SQLi::Common" do|sqli_class|
|
|||
end
|
||||
|
||||
context 'Without opts' do
|
||||
it 'Should return a mask of (known bits common to all bytes, number of bits to guess)' do
|
||||
range = (0b01011010..0b01011110) # common bits are 01011***
|
||||
sqli_obj = common_class.new({}, {}, {}) {}
|
||||
known_bits, to_guess = sqli_obj.send(:get_bitmask, range)
|
||||
expect([known_bits, to_guess]).to eql [0b01011000, 3] # 3 changing bits, upper fixed bits = 01011
|
||||
let(:sqli_obj) do
|
||||
common_class.new({}, {}, {}) do |payload|
|
||||
payload[/'(.+?)'/, 1] || ''
|
||||
end
|
||||
|
||||
it 'should yield the query to the block, and return its return value' do
|
||||
end
|
||||
context('#run_sql') do
|
||||
queries = ["select concat(username,':',password) from users", "select 'hello'", 'select 1234 from users']
|
||||
sqli_obj = common_class.new({}, {}, {}) do |payload|
|
||||
payload
|
||||
query_results = [ ':', 'hello', '' ]
|
||||
queries.each_with_index do |query, i|
|
||||
it 'Should call vprint_status on run_sql' do
|
||||
expect(sqli_obj).to receive(:vprint_status).once
|
||||
expect(sqli_obj.run_sql(query)).to eql query_results[i]
|
||||
end
|
||||
queries.each do |query|
|
||||
expect(sqli_obj.run_sql(query)).to eql query
|
||||
end
|
||||
end
|
||||
|
||||
it 'Should return true if the block returns what the SQL statement should return' do
|
||||
sqli_obj1 = common_class.new({}, {}, {}) { |payload| payload[/'(.+?)'/, 1] }
|
||||
sqli_obj2 = common_class.new({}, {}, {}) { '<div id="articles"></div>' }
|
||||
expect(sqli_obj1.test_vulnerable).to eql true
|
||||
expect(sqli_obj2.test_vulnerable).to eql false
|
||||
context('#test_vulnerable') do
|
||||
it 'Should detect if the sqli object is expected to perform SQLi successfully' do
|
||||
expect(sqli_obj.test_vulnerable).to eql true
|
||||
allow(sqli_obj).to receive(:run_sql).and_return '<div id="articles"></div>'
|
||||
expect(sqli_obj.test_vulnerable).to eql false
|
||||
end
|
||||
it 'Should call vprint_status when running queries' do
|
||||
sqli_obj = common_class.new({}, {}, {}) { |payload| payload[/'(.+?)'/, 1] }
|
||||
expect(sqli_obj).to receive(:vprint_status).once
|
||||
sqli_obj.run_sql("select '1'")
|
||||
end
|
||||
it 'Query should have a limit on the number of rows returned' do
|
||||
|
||||
context('#dump_table_fields') do
|
||||
let(:query_proc) do
|
||||
sqli_obj.instance_variable_get(:@query_proc)
|
||||
end
|
||||
result_limit = rand(1..26)
|
||||
results = result_limit.times.map { |i| ('a'.ord + i).chr }
|
||||
sqli_obj = common_class.new({}, {}, {}) do |payload|
|
||||
expect(payload).to match /limit #{result_limit}/i
|
||||
results.join(',')
|
||||
common_query = /^select.*password.*from.*maindb\.users\s*;?\s*?(?:#|--)?$/mi
|
||||
condition_query = /^select.*password.*from.*maindb\.users\s+where/mi
|
||||
limit_query = /^select.*password.*from.*maindb\.users\s+limit/mi
|
||||
|
||||
# query without condition and limit
|
||||
it 'Should yield valid queries' do
|
||||
expect(query_proc).to receive(:call).with(common_query).and_call_original
|
||||
sqli_obj.dump_table_fields('maindb.users', %w[password])
|
||||
end
|
||||
# query with condition string
|
||||
it 'Should yield valid queries when the user adds a condition' do
|
||||
expect(query_proc).to receive(:call).with(condition_query).and_call_original
|
||||
sqli_obj.dump_table_fields('maindb.users', %w[password], "username='admin'")
|
||||
end
|
||||
# query with limit
|
||||
it 'Should yield valid queries when the user adds a limit number' do
|
||||
expect(query_proc).to receive(:call).with(limit_query).and_call_original
|
||||
sqli_obj.dump_table_fields('maindb.users', %w[password], '', result_limit)
|
||||
end
|
||||
expect(sqli_obj.dump_table_fields('information_schema.tables', %w[table_name], '', result_limit)).to(eql(results.map { |name| [name] }))
|
||||
end
|
||||
end
|
||||
context 'truncation_length set' do
|
||||
let(:opts) do
|
||||
{ truncation_length: rand(1..20) }
|
||||
end
|
||||
it 'Should call truncated_query if truncation_length is set, and should parse its return value correctly' do
|
||||
sqli_obj = common_class.new({}, {}, {}, opts) { 'a,b,c' }
|
||||
expect(sqli_obj).to receive(:truncated_query).with(/\^OFFSET\^/).and_return('a,b,c')
|
||||
expect(sqli_obj.enum_table_names).to eql %w[a b c]
|
||||
end
|
||||
|
||||
it 'Should concatenate slices of the output to produce output similar to that of run_sql' do
|
||||
dump_data = 'articles,users,purchases,roles,sessions'.each_char.each_slice(opts[:truncation_length]).to_a
|
||||
i = -1
|
||||
sqli_obj = common_class.new({}, {}, {}, opts) { dump_data[i += 1] || '' }
|
||||
expect(sqli_obj.send(:truncated_query, '')).to eql dump_data.join
|
||||
let(:sqli_obj) do
|
||||
common_class.new({}, {}, {}, opts) do |payload|
|
||||
payload[/'(.+?)'/, 1] || ''
|
||||
end
|
||||
end
|
||||
|
||||
context 'builtin encoder set' do
|
||||
let(:query_proc) do
|
||||
sqli_obj.instance_variable_get(:@query_proc)
|
||||
end
|
||||
context '#truncated_query should act like run_sql' do
|
||||
let(:query_result) do
|
||||
'e951a99943ebe29c6fc425c7df2a0544,028cad8c0961163ef8401d3573b41d8e,b090e41c61a321c99bca94bdb26d1788'
|
||||
end
|
||||
let(:sqli_obj) do
|
||||
i = 0
|
||||
common_class.new({}, {}, {}, opts) do |_payload|
|
||||
slice = query_result[i, opts[:truncation_length]]
|
||||
i += opts[:truncation_length]
|
||||
if slice.empty?
|
||||
i = 0
|
||||
''
|
||||
else
|
||||
slice
|
||||
end
|
||||
end
|
||||
end
|
||||
it 'Should concatenate the slices and return output like run_sql' do
|
||||
expect(sqli_obj.send(:truncated_query, 'select substr(username,^OFFSET^,' \
|
||||
"#{opts[:truncation_length]}) from users")).to eql query_result
|
||||
end
|
||||
end
|
||||
end
|
||||
context 'with builtin encoder set' do
|
||||
let(:opts) do
|
||||
{ encoder: common_class::ENCODERS.keys.sample }
|
||||
end
|
||||
it 'Should set the encoder correctly' do
|
||||
let(:sqli_obj) do
|
||||
common_class.new({}, {}, {}, opts) {}
|
||||
end
|
||||
context '#initialize' do
|
||||
it 'should set the encoder correctly' do
|
||||
next if common_class::ENCODERS.empty?
|
||||
|
||||
current_encoder = common_class::ENCODERS[opts[:encoder]]
|
||||
sqli_obj = common_class.new({}, {}, {}, opts) {}
|
||||
object_encoder = sqli_obj.instance_variable_get(:@encoder)
|
||||
expect(object_encoder).to eql current_encoder
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
context 'custom encoder set' do
|
||||
let(:opts) do
|
||||
{ encoder: { encode: 'reverse(^DATA^)', decode: :reverse.to_proc } }
|
||||
end
|
||||
|
||||
it 'Should yield a query that calls reverse(), and call reverse from Ruby on the results' do
|
||||
dump_data = %w[articles users purchases roles sessions]
|
||||
function_call = /reverse\(/i
|
||||
sqli_obj = common_class.new({}, {}, {}, opts) do |payload|
|
||||
expect(payload).to match function_call
|
||||
let(:dump_data) do
|
||||
%w[
|
||||
ALL_PLUGINS APPLICABLE_ROLES CHARACTER_SETS CHECK_CONSTRAINTS COLLATIONS
|
||||
COLLATION_CHARACTER_SET_APPLICABILITY COLUMNS COLUMN_PRIVILEGES ENABLED_ROLES
|
||||
]
|
||||
end
|
||||
let(:sqli_obj) do
|
||||
common_class.new({}, {}, {}, opts) do |_payload|
|
||||
# the server response should be encoded
|
||||
dump_data.map(&:reverse).join(',')
|
||||
end
|
||||
end
|
||||
context '#initialize' do
|
||||
it 'should set the custom encoder' do
|
||||
expect(sqli_obj.instance_variable_get(:@encoder)).to eql opts[:encoder]
|
||||
end
|
||||
end
|
||||
|
||||
context '#enum_table_names' do
|
||||
function_call = /reverse\(/i
|
||||
it 'Should apply the encoder correctly in the query, and use the decoder to retrieve decoded results' do
|
||||
expect(sqli_obj.instance_variable_get(:@query_proc)).to receive(:call).with(function_call).and_call_original
|
||||
expect(sqli_obj.enum_table_names).to eql dump_data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
# -*- coding:binary -*-
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/module'
|
||||
require 'msf/core/exploit/sqli'
|
||||
require 'base64'
|
||||
|
||||
RSpec.shared_examples "TimeBasedBlind" do|sqli_class|
|
||||
RSpec.shared_examples 'TimeBasedBlind' do |sqli_class|
|
||||
let(:common_class) do
|
||||
sqli_class
|
||||
end
|
||||
let(:datastore) do
|
||||
{ 'SqliDelay' => 1.0 }
|
||||
end
|
||||
it "Should return true if the block takes more than datastore['SqliDelay'] to run" do
|
||||
sqli_obj = described_class.new(datastore, {}, {}) do|payload|
|
||||
sleep(payload[/\d+\.?\d*/].to_f)
|
||||
let(:sqli_obj) do
|
||||
creation_time = Time.now
|
||||
common_class.new(datastore, {}, {}) do |payload|
|
||||
delay = payload[/\d+(?:.?\d*)?/].to_f
|
||||
allow(Time).to receive(:now).and_return(creation_time + delay)
|
||||
end
|
||||
end
|
||||
context '#blind_request' do
|
||||
it "Should return true if the block takes more than datastore['SqliDelay'] to run" do
|
||||
expect(sqli_obj.send(:blind_request, 'sleep(1.3)')).to eql true
|
||||
expect(sqli_obj.send(:blind_request, 'sleep(0.5)')).to eql false
|
||||
expect(sqli_obj.send(:blind_request, 'sleep(0)')).to eql false
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Msf::Exploit::SQLi::MySQLi::TimeBasedBlind do
|
||||
it_should_behave_like "TimeBasedBlind", described_class
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue