This is a bad lock on a back-office closet, not an open front door
CVE-2025-0198 is an SQL injection in code-projects Point of Sales and Inventory Management System 1.0, specifically in /user/search_result.php via the id parameter. The vulnerable code path sits under the product's user area, and the CNA/NVD scoring already reflects PR:L, meaning the attacker needs at least low-privileged authenticated access before they can send the injected request.
The vendor's MEDIUM 6.3 is already a bit generous for enterprise prioritization, and reality pushes it lower. Yes, there is a public PoC, but the friction stack is substantial: authenticated access, a niche and lightly maintained educational PHP app, unclear internet-facing population, no KEV listing, and a very low EPSS. That makes this a real defect with local blast radius inside the application database, not a broad internet-scale emergency.
4 steps from start to impact.
Get a valid low-privilege session
- Application is deployed and reachable
- Attacker has valid low-privileged credentials or a reusable session cookie
- Login flow is functioning
- This is authenticated remote, not unauthenticated internet RCE
- Many real deployments of this project are lab, demo, or small-business instances rather than enterprise-standard internet services
- If the app is internal-only, the attacker is already post-compromise or on VPN
Hit the injectable search endpoint
/user/search_result.php?id=... and injects SQL metacharacters into id. The published PoC shows a simple boolean-based payload, and common tooling like sqlmap or Burp Repeater can automate parameter probing quickly./user/search_result.phpis present in the deployed build- The authenticated user can reach that endpoint
- Input reaches a SQL query unsafely
- A WAF, reverse proxy rules, or ModSecurity CRS can block obvious quote/boolean payloads
- Route-level authorization may limit which users can reach the page
- Customized forks may have renamed or removed the endpoint
search_result.php with quotes, tautologies, comment markers, or time-based payloads. Authenticated DAST has good coverage; unauthenticated scanning does not.Enumerate and dump application data
sqlmap can enumerate schema objects and extract rows the application's database account can read. Impact is usually disclosure or tampering inside the POS database: users, products, customers, transactions, or supplier records.- Database errors, boolean changes, or time-based responses are observable
- App database account has useful read/write privileges
- The CNA marked technical impact as partial, not full system compromise
- No evidence in the published material shows OS command execution or direct RCE
- Least-privilege DB accounts can cap damage to only the app schema
mysqld/httpd query volume, not as a clean exploit signature.Abuse data for fraud or follow-on access
- Stolen data is valuable enough to the attacker
- The instance contains real production records rather than demo data
- Blast radius is mostly confined to the POS app and its database
- Many instances are likely throwaway labs or low-value edge deployments
- There is no current evidence of broad campaign use
The supporting signals.
| In-the-wild status | No confirmed active exploitation in CISA KEV. CISA ADP enrichment in OpenCVE marks exploitation status as poc, not observed campaign activity. |
|---|---|
| Public PoC | Yes. Researcher *masamune* published a GitHub Gist showing a crafted GET request against /POS_inventory/user/search_result.php?id=.... |
| EPSS | 0.00077 from the user-supplied intel, which is extremely low. Third-party tracking pages also place it in a low percentile band, reinforcing weak near-term exploit demand. |
| KEV status | Not listed in the CISA Known Exploited Vulnerabilities Catalog as of this assessment. |
| CVSS meaning | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L means network-reachable but requires low privileges and only scores low CIA impact. That PR:L is the decisive real-world brake. |
| Affected versions | Point of Sales and Inventory Management System 1.0 is the only version explicitly listed by the CNA/NVD/OSV record. |
| Fixed version | No confirmed vendor-fixed release found. I found no authoritative patched version, advisory update, or distro backport. |
| Exposure reality | This is a niche code-projects PHP sample app with poor fingerprintability and no reliable Shodan/Censys population data located during this review. That usually means exposure exists, but not at the scale of mainstream enterprise software. |
| Disclosure timeline | Disclosed 2025-01-03. VulDB/OpenCVE timeline shows advisory disclosure and public record creation on the same date. |
| Reporter | masamune (VulDB User) is credited in the CNA/OpenCVE record. |
noisgate verdict.
The decisive factor is authenticated access: this bug starts after the attacker already has a valid session, which makes it post-initial-access in many real environments. Add the niche deployment footprint, no KEV, and tiny EPSS, and this lands as low-priority patch debt rather than a top-of-queue incident driver.
Why this verdict
- Start at 6.3, then subtract for attacker position:
PR:Lmeans the exploit chain begins with authenticated remote access, not cold-start internet compromise. - Subtract again for exposure population: this is a niche, educational PHP POS app with unclear enterprise footprint, so the reachable population is far narrower than a mainstream edge product.
- Subtract for threat reality: public PoC exists, but KEV is negative and EPSS is extremely low, which says attackers are not broadly prioritizing this bug.
- Keep it above IGNORE because it is still SQLi: once logged in, an attacker can likely read or alter app data with low friction.
Why not higher?
There is no evidence of unauthenticated exploitation, no evidence of RCE, and no evidence of active campaigns. Requiring a valid session means modern controls like MFA, VPN gating, SSO, and basic network segmentation can remove most of the practical risk before the vulnerable code is ever reached.
Why not lower?
This is still a genuine SQL injection with a published PoC against a live endpoint. If you do expose this app externally or if an attacker already has credentials, data theft and record tampering inside the POS database are plausible and cheap.
What to do — in priority order.
- Pull it off the internet — If any instance is externally reachable, put it behind VPN, an internal reverse proxy, or an allowlist. For a LOW verdict there is no noisgate mitigation SLA; treat this as backlog hygiene, but do it promptly on any internet-facing instance because exposure matters more than the raw CVSS here.
- Gate access with strong auth — Enforce SSO, MFA where possible, and remove shared/default accounts so attackers cannot satisfy the
PR:Lprerequisite cheaply. For LOW, there is no formal mitigation SLA, but this is the highest-value control because authenticated access is the whole exploit hinge. - Add a targeted WAF rule — Create parameter-specific blocking or normalization for
/user/search_result.phpand theidparameter, including quotes, comment markers, boolean tautologies, and time-delay payloads. This is a good stopgap when no confirmed vendor patch exists. - Audit app database privileges — Reduce the web app's database account to the minimum read/write scope needed by the POS workflow. That will not fix the injection, but it can turn a full schema dump into a smaller disclosure event.
- Retire or replace the app — This project shows signs of being old, educational, and lightly maintained. Since no authoritative fixed version was found, the durable answer is replacement or internal-only containment rather than waiting for a mature patch stream.
- A generic perimeter AV product does not stop authenticated SQL injection over normal HTTP requests.
- Patch prioritization based only on the word critical in the description will overreact; the vendor/CNA score and the real attack preconditions matter more here.
- Credential rotation alone is insufficient if the app remains externally reachable with weak role separation and no request filtering.
Crowdsourced verification payload.
Run this on the web server or a source checkout of the application, not from an auditor workstation. Invoke it with the application root path, for example python3 verify_cve_2025_0198.py /var/www/html/PISP or py verify_cve_2025_0198.py C:\xampp\htdocs\PISP; it only needs read access to the application files.
#!/usr/bin/env python3
# verify_cve_2025_0198.py
# Detect likely vulnerable code-projects Point of Sales and Inventory Management System 1.0 SQLi
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
import os
import re
import sys
TARGET_REL = os.path.join('user', 'search_result.php')
SQL_PATTERNS = [
re.compile(r"select\s+.*from", re.I | re.S),
re.compile(r"mysql_query\s*\(", re.I),
re.compile(r"mysqli_query\s*\(", re.I),
re.compile(r"->query\s*\(", re.I),
re.compile(r"prepare\s*\(", re.I),
]
ID_PATTERNS = [
re.compile(r"\$_GET\s*\[\s*['\"]id['\"]\s*\]", re.I),
re.compile(r"\$_REQUEST\s*\[\s*['\"]id['\"]\s*\]", re.I),
]
SAFE_PATTERNS = [
re.compile(r"prepare\s*\(", re.I),
re.compile(r"bind_param\s*\(", re.I),
re.compile(r"intval\s*\(\s*\$_(?:GET|REQUEST)\s*\[\s*['\"]id['\"]\s*\]\s*\)", re.I),
re.compile(r"\(int\)\s*\$_(?:GET|REQUEST)\s*\[\s*['\"]id['\"]\s*\]", re.I),
re.compile(r"filter_input\s*\(\s*INPUT_GET\s*,\s*['\"]id['\"]\s*,\s*FILTER_VALIDATE_INT", re.I),
]
def print_result(state, msg, code):
print(f"{state}: {msg}")
sys.exit(code)
def main():
if len(sys.argv) != 2:
print("Usage: python3 verify_cve_2025_0198.py <app_root>")
sys.exit(2)
root = sys.argv[1]
target = os.path.join(root, TARGET_REL)
if not os.path.exists(root):
print_result("UNKNOWN", f"Application root not found: {root}", 2)
if not os.path.isfile(target):
print_result("UNKNOWN", f"Target file not found: {target}", 2)
try:
with open(target, 'r', encoding='utf-8', errors='ignore') as fh:
data = fh.read()
except Exception as e:
print_result("UNKNOWN", f"Could not read {target}: {e}", 2)
has_id_source = any(p.search(data) for p in ID_PATTERNS)
has_sql = any(p.search(data) for p in SQL_PATTERNS)
has_safe = any(p.search(data) for p in SAFE_PATTERNS)
concatenates_id = False
if has_id_source:
concatenates_id = bool(re.search(r"(id\s*=\s*\$_(?:GET|REQUEST)\s*\[\s*['\"]id['\"]\s*\])", data, re.I)) or \
bool(re.search(r"\.(\s*\$id\s*)", data)) or \
bool(re.search(r"where\s+.*id.*(\$id|\$_GET\s*\[\s*['\"]id['\"]\s*\])", data, re.I | re.S))
if has_id_source and has_sql and not has_safe and concatenates_id:
print_result("VULNERABLE", f"{target} appears to use request parameter 'id' in SQL without clear parameterization or integer validation", 1)
if has_id_source and has_safe:
print_result("PATCHED", f"{target} contains signs of integer validation or prepared statements for 'id'", 0)
print_result("UNKNOWN", f"{target} exists but the script could not conclusively prove vulnerable or patched logic", 2)
if __name__ == '__main__':
main()
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.