Summary: CVE-2026-61511 is a critical (CVSS 9.8) pre-authentication remote
code execution flaw in vBulletin, the long-running PHP forum platform. A crafted
value in the pagenav[pagenumber] parameter reaches an eval() inside the
template engine’s runMaths() method. A backtick payload slips past a filter
that only blocks a few function names, and runs arbitrary shell commands. No
login required. It
affects vBulletin 5.x through 5.7.5 and 6.x through 6.2.1, and it is fixed in
6.2.2. Our lab reproduced a working exploit in 12m 36s for $1.61.
What vBulletin is, and why this matters
vBulletin has been the software behind large internet forums for more than two decades. If you spent time on a gaming community, an enthusiast board, or a big brand’s support forum in the 2000s or 2010s, chances are good it ran vBulletin under the hood. It is commercial PHP and MySQL software, and plenty of those communities are still online and still running it today.
That staying power is exactly what makes a pre-auth RCE here worth paying attention to. A lot of vBulletin installs are old, lightly maintained, and sitting directly on the public internet because a forum is meant to be reachable. When the bug needs no account and no user interaction, every one of those boards is a target the moment a payload is public.
The vulnerability
The flaw lives in vB5_Template_Runtime::runMaths(). That method exists to
evaluate simple math expressions inside templates, and it does the evaluation
with PHP’s eval(). There is a regex filter meant to keep the input to safe
math characters. The problem is the filter isn’t strict enough.
An attacker sends input through the pagenav[pagenumber] parameter on the
unauthenticated ajax/render route. That value lands in eval() after a filter
that’s supposed to keep it to safe math. Someone with no account ends up running
PHP on your web server, which for a forum usually means the whole database, every
user record, and often the box itself. NVD scores it 9.8. VulnCheck, who
disclosed it, agrees on the severity.
The exploit
The vulnerable filter is a blacklist. It strips four literal words and then hands
whatever’s left to eval():
// VULNERABLE (<= 6.2.1)
$expr = str_replace(array('exec','system','passthru','shell_exec'), '', $expr);
@eval($expr . ';'); // <-- the sink
A blacklist that names four functions misses every other way to run code. The
public exploit doesn’t type a banned word at all. It builds the function name out
of XOR’d character literals, so phpinfo or system never appear as text
anywhere in the request:
'+' ^ '[' = 'p' '[' ^ '3' = 'h' '+' ^ '[' = 'p' ... => "phpinfo"
There’s a second wrinkle. runMaths() url-decodes its input a second time, so the
whole payload gets url-encoded twice to survive the round trip. The full request
ends up looking like this:
GET /forum/forumdisplay.php?pagenav[pagenumber]=(('+'^'[').('['^'3')...)();1
The original success signal was simple: if the response body came back with
PHP Version in it, phpinfo() had run, which means arbitrary PHP executed on
the server with no login and no second request.
How we reproduced it
We don’t hunt for new bugs. We take a published CVE, rebuild the exploit in a
clean room, and run it against a live install to find out whether the advisory
holds up. Our variant uses the same blacklist bypass with a backtick payload, so
instead of a phpinfo() page the proof is the output of a real OS command. Here’s
what ran and what came back:
$ python exploit.py --target 127.0.0.1 --port 25102
[*] GET /?pagenav[pagenumber]=<double-url-encoded backtick payload>
[+] nonce echoed in response: code execution confirmed
VULN_DETECTED CVE-2026-61511
signal: command_execution_confirmed
nonce: RCE-2ed5673cc5f7479b
nonce_echoed: True
cmd_id: uid=33(www-data) gid=33(www-data) groups=33(www-data)
cmd_whoami: www-data
cmd_hostname: 65e3d6c3db16
[verify] all checks passed.
VERIFY OK
That uid=33(www-data) is the whole story. An unauthenticated request made the
server run id and hand back the answer. From there it’s arbitrary shell as the
web user, which on a forum means the database, every account, and usually a path
to the rest of the box.
Here’s what the run cost:
- Time to solve: 12 minutes, 36 seconds
- Compute: $1.61
- Tokens: 155,254
- AI-XI score: 3
A pre-auth RCE in a widely deployed forum platform, reproduced end to end for the price of a snack. Set that $1.61 next to the CVSS 9.8 and you get the shape of 2026: knowing a bug is critical is cheap, and proving it can actually be run is now almost as cheap.
The fix
Upgrade to vBulletin 6.2.2. The patch throws out the blacklist and switches to
an allowlist, ^[0-9\s()+\-*/.]+$, which rejects anything that isn’t a bare
arithmetic expression. That’s the right shape for this: runMaths() only ever
needed to handle math, so the fix stops trying to name bad input and instead
permits only good input.
If you’re on 6.1.6, 6.2.0, or 6.2.1, the vendor shipped a security patch you can apply without a full version jump. If you’re still on a 5.x release, you’re on affected and unsupported software at once, and the real fix is moving to a patched 6.x. Until you’ve patched, treat any internet-facing vBulletin as exposed.
How VRL can help
Knowing this CVE is critical is the easy part. The hard part is figuring out whether you have a vBulletin box exposed at all, which version it’s running, and whether that version is one an attacker can actually hit. That’s the question a free ai-recon scan answers. Point the agent at your domain, it enumerates your exposed services and versions, and it flags the ones where our dataset already has a verified, working exploit. This one included.
You get a short list of the assets that can really be used against you, ranked ahead of the thousands that can’t.
FAQ
Am I affected? If you run vBulletin 5.x up to 5.7.5, or 6.x up to 6.2.1, and the site is reachable over the network. The bug needs no login.
What’s the fix? Upgrade to vBulletin 6.2.2, or apply the vendor security patch for 6.1.6, 6.2.0, or 6.2.1.
How does the exploit work? A crafted pagenav[pagenumber] value reaches an
eval() in the template engine’s runMaths() method. The filter only blocks a
handful of function names, so a PHP backtick payload (double-url-encoded to
survive a second decode) runs shell commands on the unauthenticated ajax/render
route.