NotrinosERP: Authenticated arbitrary file upload leads to remote code execution via HRM employee "Documents" (doc_file)
#### Summary 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: ```php // 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
- Products
- notrinos/notrinos-erp
- Affected versions
- 0.10, 0.6, 0.7, 0.8, 0.9, 1.0.0
- Fixed versions
- 1.1.0
- CVEs
- CVE-2026-76913
- Confirmed fixed CVEs
- Not specified
- Updated in this feed
- 1 Nov 2026, 18:43 UTC · Revision 1
Source evidence
NotrinosERP: Authenticated arbitrary file upload leads to remote code execution via HRM employee "Documents" (doc_file)
core-osv · Original sourcePublished 10 Jul 2026, 19:34 UTC
#### Summary 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: ```php // 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
osv:GHSA-qv4m-m73m-8hj7
notrinos/notrinos-erp
Fixed versions: 1.1.0
Complete source record
{
"id": "7a44ef2896b865c7e63331aa7fa326091dd467eb8e3c627cb986cfd7917d8e0c",
"url": "https://osv.dev/vulnerability/GHSA-qv4m-m73m-8hj7",
"method": "structured",
"payload": {
"kind": "advisory",
"title": "NotrinosERP: Authenticated arbitrary file upload leads to remote code execution via HRM employee \"Documents\" (doc_file)",
"summary": "#### Summary 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: ```php // 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",
"evidence": "osv:GHSA-qv4m-m73m-8hj7",
"products": [
{
"name": {
"value": "notrinos/notrinos-erp",
"evidence": "/affected/0/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "1.1.0",
"evidence": "/affected/0/ranges"
}
],
"affected_versions": [
{
"value": "0.10",
"evidence": "/affected/0/versions"
},
{
"value": "0.6",
"evidence": "/affected/0/versions"
},
{
"value": "0.7",
"evidence": "/affected/0/versions"
},
{
"value": "0.8",
"evidence": "/affected/0/versions"
},
{
"value": "0.9",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.0",
"evidence": "/affected/0/versions"
}
]
}
],
"severity": "high",
"structured": {
"id": "GHSA-qv4m-m73m-8hj7",
"aliases": [
"CVE-2026-76913",
"GHSA-qv4m-m73m-8hj7"
],
"metrics": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
}
],
"affected": [
{
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "0"
},
{
"fixed": "1.1.0"
}
]
}
],
"package": {
"name": "notrinos/notrinos-erp",
"purl": "pkg:composer/notrinos/notrinos-erp",
"ecosystem": "Packagist"
},
"versions": [
"0.10",
"0.6",
"0.7",
"0.8",
"0.9",
"1.0.0"
],
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-qv4m-m73m-8hj7/GHSA-qv4m-m73m-8hj7.json",
"last_known_affected_version_range": "<= 1.0.0"
}
}
],
"modified": "2026-10-27T15:15:07.978557694Z",
"provider": "osv",
"references": [
"https://github.com/notrinos/NotrinosERP/security/advisories/GHSA-qv4m-m73m-8hj7",
"https://github.com/notrinos/NotrinosERP/commit/71cac7cd199e40cff7f2b18551620133e29f8967",
"https://github.com/notrinos/NotrinosERP",
"https://github.com/notrinos/NotrinosERP/releases/tag/1.1.0"
],
"provider_metadata": {
"cwe_ids": [
"CWE-22",
"CWE-434"
],
"severity": "HIGH",
"github_reviewed": true,
"nvd_published_at": null,
"github_reviewed_at": "2026-07-10T19:34:03Z"
}
},
"related_cves": [
{
"value": "CVE-2026-76913",
"evidence": "/aliases"
}
],
"security_relevant": true,
"severity_evidence": "/database_specific/severity"
},
"source_id": "core-osv",
"document_id": 462823,
"published_at": "2026-07-10T19:34:03+00:00"
}