mediaprogMediaProg Devboard
← Zurück zum Board
offenvon awsexperte· 2026-08-26 00:18:31

Unauthentifizierte SQL-Injection über GPrintDownload (GX4, weiterhin offen)

Schwachstellenmeldung · nachweislich ausgenutzt

Ein öffentlich erreichbarer AJAX-Endpunkt in Gambio GX4 lässt sich ohne Anmeldung für beliebige Datenbankabfragen missbrauchen. Ursache ist eine Escaping-Funktion, die sich mit einem doppelten Backslash umgehen lässt. In GX5 ist die Stelle bereits behoben — im GX4-Zweig steht der Fix bis heute aus.

Vorfall
18.08.2026, 15:25–15:32
Betroffen
GX4 v4.7.2 – v4.9.5
Zugriff nötig
keiner
In GX5
bereits behoben

Das Problem in zwei Sätzen

request_port.php?module=GPrintDownload&key=… reicht den key-Parameter durch gm_prepare_string() und verkettet ihn anschließend direkt in eine SQL-Abfrage. Die Funktion überspringt ihr eigenes Escaping, sobald der Wert bereits maskiert aussieht — was ein Angreifer trivial vortäuschen kann.

Kern der Sache

gm_magic_check() soll erkennen, ob ein String schon escaped ist. Sie prüft dafür, ob vor einem Anführungszeichen ein Backslash steht. Bei der Eingabe 1\\' steht dort einer — also gilt der String als „schon escaped“ und mysqli_real_escape_string() wird nicht aufgerufen. MySQL wertet \\ danach als literalen Backslash, das Anführungszeichen schließt den String, und die Injection steht.

Der verwundbare Code

// system/classes/customizer/GPrintDownloadAjaxHandler.inc.php

function get_permission_status($p_customers_id = NULL)
{
    return true;                      // ← keinerlei Zugriffsprüfung
}

function proceed()
{
    $c_download_key = gm_prepare_string($this->v_data_array['GET']['key']);

    $t_get_file_data = xtc_db_query("SELECT filename, encrypted_filename
                                     FROM " . TABLE_GM_GPRINT_UPLOADS . "
                                     WHERE download_key = '" . $c_download_key . "'");

Die Ursache eine Ebene tiefer

// gm/inc/gm_prepare_string.inc.php

function gm_magic_check($string){
    if(preg_match('/(^"|[^\\\\]{1}")/', $string) == 1) return false;
    if(preg_match('/(^\'|[^\\\\]{1}\')/', $string) == 1) return false;
    else return true;                 // ← „sieht escaped aus“
}

function gm_prepare_string($string, $strip = false){
    if(!$strip){
        if(ini_get('magic_quotes_gpc') == 0 /* … */){
            if(!gm_magic_check($string))   // ← false = escapen, true = überspringen
                $string = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $string);
        }
    }
    return $string;
}

Nachgerechnet mit dem Originalcode:

Eingabegm_magic_checkFolge
1'falsewird escaped — korrekt
1\'trueEscaping übersprungen
1\\'trueEscaping übersprungen — so lief der Angriff

Der Endpunkt ist ohne Anmeldung erreichbar: get_permission_status() gibt unbedingt true zurück.

Tragweite

Zwei Dinge machen den Fund größer als einen einzelnen Endpunkt.

  • Kein Login nötig. Der Angriff läuft anonym über das Shop-Frontend. In der Praxis wurden damit customers_id, customers_email_address und vor allem password_request_key ausgelesen.
  • Die Escaping-Funktion ist nicht auf diesen Handler beschränkt. Im untersuchten Shop wird gm_prepare_string() an 79 Stellen in 26 Dateien aufgerufen. Überall dort, wo das Ergebnis ebenfalls in einen SQL-String verkettet wird, greift derselbe Bypass. Diese Stellen sollten mitgeprüft werden.
Warum password_request_key das eigentliche Ziel war

Der Angreifer forderte im Shop einen Passwort-Reset an, las den frisch erzeugten Token per SQL-Injection direkt aus der Datenbank und konnte damit das Passwort setzen — ohne je Zugriff auf das Postfach zu haben. Dieser Ablauf ist in den Zugriffsprotokollen elfmal nachweisbar. Auf dem betroffenen Shop lagen 738 gültige Reset-Tokens in der Tabelle, weil Gambio sie nach Gebrauch nicht verwirft.

Nachweis der Ausnutzung

Rekonstruiert aus den Apache-Zugriffsprotokollen. Angreifer-IP 176.105.204.211, 211 Anfragen, automatisiert (SQLMap-Signatur 0x71786b7671).

  1. Vorab-Sondierung Test auf vorhandene Webshells: /admin/403WebShell.php, /media/content/gmo.php — beide 404.
  2. Normaler Shop-Besuch Startseite, Session wird aufgebaut.
  3. SQL-Injection beginnt ORDER BY 1…10 zur Spaltenermittlung, dann UNION ALL SELECT, Abfrage der DB-Version.
  4. Reset-Tokens ausgelesen SELECT customers_id, customers_email_address, password_request_key FROM customers WHERE customers_status = 0
  5. Passwort-Reset angefordert POST /password_double_opt.php?action=first_opt_in — erzeugt einen frischen Token in der Datenbank.
  6. Neuen Token abgegriffen Zweite SQLi-Runde, gezielt auf password_request_key. Der Zyklus wiederholt sich bis 15:30.
  7. Webshell abgelegt media/content/gmo.php, 78.729 Bytes.
  8. Webshell aktiv HTTP 200. Danach Dateibrowser (?loknya=…) und zwei POST-Aktionen.
  9. Letzter Zugriff Danach kein weiterer Aufruf der Shell.
  10. Entdeckt und neutralisiert Shell umbenannt und auf chmod 000 gesetzt.

Beteiligte Adressen

IPHerkunftNetzSQLi-AnfragenRolle
176.105.204.211 Ukraine, Poltava RPC HomeNet Ltd. — Privatanschluss 97 Einbruch + Webshell
169.150.0.0 anonymisiert im Log gekürzt 354 nur Sondierung
188.137.245.134 Deutschland Podaon SIA — germany-1.vps.ac 8 nur Sondierung

User-Agent durchgehend Chrome/151.0.0.0 auf Windows. Keine dieser Adressen taucht in Bestellungen oder Kundendaten des Shops auf.

Patch-Stand bei Gambio

Der Fix existiert bereits — er ist nur nie in den GX4-Zweig zurückgeflossen. Das ist der eigentliche Grund, warum diese Meldung noch relevant ist.

Fassunggm_prepare_stringHandlerStatus
GX5 v5.0.2.0 escaped bedingungslos,
gm_magic_check nicht mehr aufgerufen
nutzt xtc_db_input() behoben
GX4 + Security Update 2026-08 unverändert — Bypass wirkt nutzt gm_prepare_string() offen
Das Update behebt dieselbe Fehlerklasse — nur diese Stelle nicht

Security Update 2026-08 v1.0 (freigegeben für v4.7.2.0 bis v4.9.5.0) enthält genau vier Dateien. Alle vier ersetzen unsicheres Escaping durch xtc_db_input() bzw. (int)-Casts:

inc/xtc_display_banner.inc.php       → xtc_db_input($identifier), (int)$identifier
inc/xtc_format_price.inc.php         → xtc_db_input($_SESSION['currency'])
inc/xtc_update_whos_online.inc.php   → xtc_db_input(...)
gm/classes/GMTracker.php             → xtc_db_input($_SERVER['REMOTE_ADDR'])

GPrintDownloadAjaxHandler.inc.php ist nicht dabei, obwohl es dasselbe Muster aufweist, ohne Anmeldung erreichbar ist und in GX5 bereits umgestellt wurde. Ein Shop mit aktuellem Security Update ist an dieser Stelle weiterhin angreifbar.

Der betroffene Shop lief auf einer Version aus dieser Liste. Das erklärt, warum ein gepflegtes System kompromittiert werden konnte.

Sofortmaßnahme für Betreiber

Wirkt ohne Core-Änderung und übersteht ein Update. Sinnvoll überall dort, wo GPrint nicht genutzt wird — im untersuchten Shop existierte die Tabelle gm_gprint_downloads nicht einmal, und in 30 Tagen gab es keinen einzigen legitimen Aufruf.

# .htaccess im Shop-Root, ganz oben
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{QUERY_STRING} (^|&)module=GPrintDownload [NC]
    RewriteRule ^request_port\.php$ - [F,L]
</IfModule>

Zusätzlich sollten alle bestehenden Reset-Tokens verworfen werden — welche davon abgeflossen sind, lässt sich nachträglich nicht feststellen:

UPDATE customers SET password_request_key = NULL
 WHERE password_request_key IS NOT NULL;

Vorschlag für den Patch

Der eigentliche Fix gehört in den Handler, nicht in die Escaping-Funktion: Der Download-Schlüssel ist ein Bezeichner, kein Freitext.

function proceed()
{
    $t_key = (string)($this->v_data_array['GET']['key'] ?? '');

    // Whitelist statt Escaping — der Schluessel ist ein Hash-Bezeichner
    if ($t_key === '' || !preg_match('/^[A-Za-z0-9_-]{1,64}$/', $t_key)) {
        $this->v_output_buffer = 'Error: File does not exist!';
        return true;
    }

    // Gebundener Parameter statt Verkettung
    $t_get_file_data = StaticGXCoreLoader::getDatabaseQueryBuilder()
        ->query('SELECT filename, encrypted_filename FROM '
                . TABLE_GM_GPRINT_UPLOADS . ' WHERE download_key = ?', [$t_key]);
    // …

Unabhängig davon empfiehlt sich, gm_magic_check() zu entfernen. Die Funktion stammt aus der Zeit von magic_quotes_gpc, das seit PHP 5.4 nicht mehr existiert. Ihre Heuristik kann Angreifereingaben von echtem Escaping nicht unterscheiden — sie schwächt jede Stelle, die sie schützt.

Analyse auf Basis der Apache-Zugriffsprotokolle, der Shop-Datenbank und des Dateisystems eines produktiven GX4-Shops. Zeitangaben in MEZ/MESZ (UTC+2). Die Webshell selbst wurde nicht ausgeführt und liegt gesichert vor.

#sicherheit#sql-injection#gx4#gprintdownload