> ## Content Index
> Fetch the complete content index at: https://plaintext.block8.ai/llms.txt
> Use this file to discover other available public pages before exploring further.

# When an Empty Answer Is the Right Answer (CVE-2026-78726)
- URL: https://plaintext.block8.ai/blog/when-an-empty-answer-is-the-right-answer-cve-2026-78726/
- Published: 2026-10-29T22:54:00.000Z
- Updated: 2026-10-29T22:53:59.000Z
- Author: Lukasz S
- Tags: CVE

## 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:

```php
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:

```bash
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.

![Block8.ai](https://plaintext.block8.ai/assets/images/logos/B8_Shield-Magenta.svg) 

AI-powered penetration testing

### Block8.ai Team

[Meet the team →](https://plaintext.block8.ai/team/)