[MEDIUM] Admin key accepted via ?key= query parameter leaks the secret into proxy access logs #137

Closed
opened 2026-09-10 03:48:27 +00:00 by fen · 2 comments
Collaborator

adminKeyOK (internal/api/admin.go) falls back to r.URL.Query().Get("key") when the X-Admin-Key header is absent. Any admin API call made this way puts the full admin key into Traefik access logs, browser history, and Referer headers. Accepting the key only via the header would eliminate the leak.

Repro

1. KEY=<real admin key>
2. curl -sk --resolve palette-dev.archfox.org:443:10.0.1.47 "https://palette-dev.archfox.org/admin/api/settings?key=$KEY"
Expected: 401 (or only header-based auth).
Actual: 200 with full settings JSON.

Found during exploratory security/bug pass on palette-dev (2026-09-09). Reporting only, no fix attached.

adminKeyOK (internal/api/admin.go) falls back to r.URL.Query().Get("key") when the X-Admin-Key header is absent. Any admin API call made this way puts the full admin key into Traefik access logs, browser history, and Referer headers. Accepting the key only via the header would eliminate the leak. ### Repro ``` 1. KEY=<real admin key> 2. curl -sk --resolve palette-dev.archfox.org:443:10.0.1.47 "https://palette-dev.archfox.org/admin/api/settings?key=$KEY" Expected: 401 (or only header-based auth). Actual: 200 with full settings JSON. ``` Found during exploratory security/bug pass on palette-dev (2026-09-09). Reporting only, no fix attached.
fen added spent time 5 seconds 2026-09-10 03:49:08 +00:00
fen added the Medium label 2026-09-10 03:49:49 +00:00
Author
Collaborator

Pentest pass summary (palette-dev, 2026-09-09)

Unauthenticated + normal-user exploratory pass across rate limiting, admin authz, paste/attachment abuse, injection, info leakage, DOS, and functional UX. Issues filed from this pass: #137 #138 #139 #141 #142 #143 #144.

Verified resilient (no issue filed):

  • Rate limit anti-spoofing: rightmost-XFF keying; spoofed XFF/X-Real-IP/True-Client-IP all stay 429; burst 5 + 1rps confirmed; 429 carries retry-after.
  • Admin authz: all /admin/api/* return 401 with no/empty/wrong key; constant-time compare + admin rate limiting present in code; case variations ineffective.
  • Paste access control: unlisted+password not readable via API/raw/HTML without password; wrong-password uniform 404; unlock brute force 429s after 5 attempts incl. GET ?password=; burn-after-reads budget enforced with per-viewer dedupe + atomic claim; expired pastes 404 on api/raw/html; expired custom slug released.
  • Slug validation: traversal/unicode/long/quotes rejected; reserved routes (admin/static/api/new/history) rejected; collisions 400; case-sensitive slugs served correctly.
  • XSS: stored <script>/onerror in titles escaped in page and /api/public; history/table client render escapes; raw content_type hardened to text/plain + nosniff for html/svg/lying content types.
  • Info leakage: generic error bodies, no stack traces/db errors; static path traversal 404s; no open redirects.
  • DOS: 100k-line paste (7MB page) renders in 45ms; deep nesting fine; 31MB body 413 (5MB limit).
  • UX: copy button present, burn flow consumes budget then 404s correctly, invalid ?theme= falls back safely.

Severity counts: 0 critical, 0 high, 2 medium (#137 #141), 5 low (#138 #139 #142 #143 #144). E2EE out of scope per owner.

## Pentest pass summary (palette-dev, 2026-09-09) Unauthenticated + normal-user exploratory pass across rate limiting, admin authz, paste/attachment abuse, injection, info leakage, DOS, and functional UX. Issues filed from this pass: #137 #138 #139 #141 #142 #143 #144. **Verified resilient (no issue filed):** - Rate limit anti-spoofing: rightmost-XFF keying; spoofed XFF/X-Real-IP/True-Client-IP all stay 429; burst 5 + 1rps confirmed; 429 carries retry-after. - Admin authz: all /admin/api/* return 401 with no/empty/wrong key; constant-time compare + admin rate limiting present in code; case variations ineffective. - Paste access control: unlisted+password not readable via API/raw/HTML without password; wrong-password uniform 404; unlock brute force 429s after 5 attempts incl. GET ?password=; burn-after-reads budget enforced with per-viewer dedupe + atomic claim; expired pastes 404 on api/raw/html; expired custom slug released. - Slug validation: traversal/unicode/long/quotes rejected; reserved routes (admin/static/api/new/history) rejected; collisions 400; case-sensitive slugs served correctly. - XSS: stored <script>/onerror in titles escaped in page and /api/public; history/table client render escapes; raw content_type hardened to text/plain + nosniff for html/svg/lying content types. - Info leakage: generic error bodies, no stack traces/db errors; static path traversal 404s; no open redirects. - DOS: 100k-line paste (7MB page) renders in 45ms; deep nesting fine; 31MB body 413 (5MB limit). - UX: copy button present, burn flow consumes budget then 404s correctly, invalid ?theme= falls back safely. Severity counts: 0 critical, 0 high, 2 medium (#137 #141), 5 low (#138 #139 #142 #143 #144). E2EE out of scope per owner.
fen added spent time 20 seconds 2026-09-10 04:18:37 +00:00
fen added In Progress and removed Medium labels 2026-09-10 04:18:37 +00:00
fen closed this issue 2026-09-10 04:23:27 +00:00
Author
Collaborator

Fixed in PR #150: admin key is now accepted only via the X-Admin-Key header; the ?key= fallback is removed (repro now returns 401). Header path re-verified 200. Log: 20m.

Fixed in PR #150: admin key is now accepted only via the X-Admin-Key header; the ?key= fallback is removed (repro now returns 401). Header path re-verified 200. Log: 20m.
fen added QA and removed In Progress labels 2026-09-10 04:23:28 +00:00
fen added spent time 24 minutes 2026-09-10 13:57:36 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Total Time Spent: 25 minutes
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: poslop/palette#137