Skip to content
Back to blog CVE

When an Empty Answer Is the Right Answer (CVE-2026-78726)_

· 3 min read

TL;DR

Pluck CMS 4.7.20 lets you pass the settings-page CAPTCHA by submitting nothing at all (CVE-2026-78726). The check compares the stored CAPTCHA code against your answer with strtolower() on both sides, but never confirms the stored code actually exists. On a fresh admin session the code is NULL, and in PHP 8 strtolower(NULL) is an empty string, which matches an empty submission perfectly. An admin who hits the settings endpoint before the CAPTCHA is ever generated can change the site title and admin email with no valid code. Block8.ai's Security Research Team rates it CVSS 3.1 of 4.3 (Medium), CWE-287 with CWE-476 contributing.

What the CAPTCHA is there to do

The settings page in Pluck controls things worth protecting: the site title, the administrator email address. To make automated or drive-by changes harder, the form asks for a CAPTCHA code. The idea is the familiar one. The server generates a code, stashes it in the session, shows a distorted version to the human, and only accepts the form if the human types back the same code.

The whole mechanism rests on one assumption: that there is a code to compare against. The server put one in the session, so of course it is there.

Except when it isn't.

Where the trust breaks

The comparison in data/inc/settings.php looks like this:

if (strtolower($_SESSION['captcha']['code']) == strtolower($captcha))

Read it carefully. It lowercases the stored code, lowercases your answer, and checks they are equal. What it never does is ask whether $_SESSION['captcha']['code'] was ever set.

Here is the sequence that bites. The CAPTCHA code is only generated when you actually load the settings page, because that is where the image is rendered. If an administrator logs in with a brand-new session and goes straight to the settings endpoint by POST, without first loading the page, the session variable has never been populated. It is NULL.

Under the bonnet

Now the PHP 8 behaviour does the rest of the work for the attacker.

  • strtolower(NULL) returns '' (an empty string).
  • strtolower(''), from an empty captcha= field, also returns ''.
  • '' == '' is true.

The check passes. The settings save goes through. No valid code was ever involved.

The proof of concept is a single request:

curl -b admin_session_cookie \
  -H 'Referer: http://target/admin.php' \
  -X POST 'http://target/admin.php?action=settings' \
  -d 'cont1=BYPASS_PROOF&cont2=attacker@example.com&captcha=&save=Save'

The server answers "Saving settings..." with no CAPTCHA error, and data/settings/options.php now reads $sitetitle = 'BYPASS_PROOF'. The one catch is timing: the admin session must be fresh, with no prior visit to admin.php?action=settings, so that the code slot is still NULL.

This is why the write-up lists two CWEs. The surface failure is CWE-287, Improper Authentication, because a security check is satisfied without the secret it was meant to verify. The contributing cause is CWE-476, a NULL dereference flowing through strtolower(), where the language's lenient handling of NULL converts a missing value into a matchable empty string instead of an error.

Why it matters, and the nastier version

On its own, this is configuration tampering. Changing the site title is noisy and cosmetic; changing the admin email is more interesting, because that is often the lever for account recovery. The base CVSS of 4.3 reflects that limited, admin-only direct impact.

The sting is in the combination. Pluck 4.7.20 also carries a CSRF weakness in requestedByTheSameDomain(), which returns true when the Referer header is absent. Pair the two and the CAPTCHA is no longer even a speed bump against an outsider: an attacker's page can fire a cross-site POST at the settings endpoint of a freshly logged-in admin, with an empty captcha= field and no Referer, and the change lands. The CAPTCHA that was supposed to demand a human in the loop silently steps aside.

How to fix it

  • Confirm the session value exists before comparing it. A guard such as if (!empty($_SESSION['captcha']['code']) && strtolower(...) == strtolower(...)) turns a missing code into a failed check instead of a passed one.
  • Reject empty or missing CAPTCHA submissions outright, before any comparison.
  • Use a strict comparison (===) and avoid feeding NULL into string functions that silently coerce it. In PHP 8, passing NULL to strtolower() is a deprecation, not a safeguard.
  • Fix the related CSRF weakness too. The two are far more dangerous together than apart.

Until a patched release ships, operators should be aware that the settings CAPTCHA offers little protection on a new session.

Closing

A verification is only as good as the thing it verifies against. Here the code compared two values and declared them equal, which was technically true and completely useless, because one of them was a placeholder the system had never filled in. The Block8.ai Security Research Team found this during authorised research into Pluck CMS. The takeaway is older than any CAPTCHA: before you check whether the answer is correct, check that there was ever a question.

Keep reading

AI-Powered Penetration Testing

Guard rails start before the first scan

The most important safety decisions in an AI penetration test happen before testing starts. Here’s how Block8.ai builds scope into the platform.

3 min read

Faster. Smarter. Simpler_

Block8.ai delivers AI-powered penetration testing, validated by CREST-certified experts, from $5K.

Start testing

AI-Powered Penetration Testing for Everyone_