GitHub Security Advisory: GHSA-hg7g-56h5-5pqr
Package: wwbn/avideo (COMPOSER)
Severity: MODERATE (CVSS 5.3)
CWE: CWE-804: Guessable CAPTCHA
Vulnerable versions: <= 29.0
Patched in: unpatched

Summary: CAPTCHA Bypass in WWBN/AVideo via Attacker-Controlled Length Parameter and Missing Token Invalidation on Failure

Details:
## Summary

`objects/getCaptcha.php` accepts the CAPTCHA length (`ql`) directly from the query string with no clamping or sanitization, letting any unauthenticated client force the server to generate a 1-character CAPTCHA word. Combined with a case-insensitive `strcasecmp` comparison over a ~33-character alphabet and the fact that failed validations do NOT consume the stored session token, an attacker can trivially brute-force the CAPTCHA on any endpoint that relies on `Captcha::validation()` (user registration, password recovery, contact form, etc.) in at most ~33 requests per session.

## Details

Three cooperating flaws in `objects/getCaptcha.php` and `objects/captcha.php` reduce CAPTCHA protection to a deterministic bypass.

### 1. External control of CAPTCHA strength (`objects/getCaptcha.php:7`)

```php
$largura        = empty($_GET['l'])  ? 120 : $_GET['l'];
$altura         = empty($_GET['a'])  ? 40  : $_GET['a'];
$tamanho_fonte  = empty($_GET['tf']) ? 18  : $_GET['tf'];
$quantidade_letras = empty($_GET['ql']) ? 5 : $_GET['ql']; // attacker-controlled

$capcha = new Captcha($largura, $altura, $tamanho_fonte, $quantidade_letras);
$capcha->getCaptchaImage();
```

There is no minimum, no type-check, and no clamping. Requesting `/objects/getCaptcha.php?ql=1` causes the server to generate a single-character word and save it to the attacker's own PHP session.

### 2. Small alphabet stored in the session (`objects/captcha.php:33-39`)

```php
$letters = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnPpQqRrSsTtUuVvYyXxWwZz23456789';
$palavra = substr(str_shuffle($letters), 0, ($this->quantidade_letras));
if (User::isAdmin() && empty($_REQUEST['forceCaptcha'])) {
    $palavra = "admin";
}
_session_start();
$_SESSION["palavra"] = $palavra;
```

After case-folding the alphabet is 25 letters (A–Z minus `O`) plus digits `2-9`, i.e. 33 unique values. For an unauthenticated attacker the admin branch at line 35 is unreachable, so the value is purely random over that 33-symbol set.

### 3. Weak comparison and token NOT invalidated on failure (`objects/captcha.php:58-75`)

```php
public static function validation($word)
{
    if (User::isAdmin() && $_SESSION["palavra"] === 'admin') {
        return true;
    }
    _session_start();
    if (empty($_SESSION["palavra"])) {
        _error_log("Captcha validation Error: you type ({$word}) and session is empty ...");
        return false;
    }
    $validation = (strcasecmp($word, $_SESSION["palavra"]) == 0);
    if (!$validation) {
        _error_log("Captcha validation Error: you type ({$word}) and session is ({$_SESSION["palavra"]}) ...");
    } else {
        unset($_SESSION["palavra"]); // Consume the captcha token to prevent reuse
    }
    return $validation;
}
```

Two problems here:

* `strcasecmp` is case-insensitive, collapsing the alphabet to ~33 distinct values.
* `unset($_SESSION["palavra"])` only runs in the **success** branch. Every failed guess leaves the stored word intact, so the same session can be retried agains