CVE-2023-43809 (Go, published 2023-10-04T21:15Z)
Description: value: Soft Serve is a self-hostable Git server for the command line. Prior to version 0.6.2, a security vulnerability in Soft Serve could allow an unauthenticated, remote attacker to bypass public key authentication when keyboard-interactive SSH authentication is active, through the `allow-keyless` setting, and the public key requires additional client-side verification for example using FIDO2 or GPG. This is due to insufficient validation procedures of the public key step during SSH request handshake, granting unauthorized access if the keyboard-interaction mode is utilized. An attacker could exploit this vulnerability by presenting manipulated SSH requests using keyboard-interactive authentication mode. This could potentially result in unauthorized access to the Soft Serve. Users should upgrade to the latest Soft Serve version `v0.6.2` to receive the patch for this issue. To workaround this vulnerability without upgrading, users can temporarily disable Keyboard-Interactive SSH Authentication using the `allow-keyless` setting.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-287 \u2014 Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
Repository: https://github.com/charmbracelet/soft-serve
Files changed:
server/ssh/middleware.go
server/ssh/ssh.go
Commit message: fix(ssh): add authentication middleware

We need to verify that the key used to establish the connection is the
same key used for authentication, otherwise, refuse connection.


Vulnerable code:

Fixed code:
	"github.com/charmbracelet/wish"
	gossh "golang.org/x/crypto/ssh"
// ErrPermissionDenied is returned when a user is not allowed connect.
var ErrPermissionDenied = fmt.Errorf("permission denied")

// AuthenticationMiddleware handles authentication.
func AuthenticationMiddleware(sh ssh.Handler) ssh.Handler {
	return func(s ssh.Session) {
		// XXX: The authentication key is set in the context but gossh doesn't
		// validate the authentication. We need to verify that the _last_ key
		// that was approved is the one that's being used.

		pk := s.PublicKey()
		if pk != nil {
			// There is no public key stored in the context, public-key auth
			// was never requested, skip
			perms := s.Permissions().Permissions
			if perms == nil {
				wish.Fatalln(s, ErrPermissionDenied)
				return
			}

			// Check if the key is the same as the one we have in context
			fp := perms.Extensions["pubkey-fp"]
			if fp != gossh.FingerprintSHA256(pk) {
				wish.Fatalln(s, ErrPermissionDenied)
				return
			}
		}

		sh(s)
	}
}

			// Authentication middleware.
			// gossh.PublicKeyHandler doesn't guarantee that the public key
			// is in fact the one used for authentication, so we need to
			// check it again here.
			AuthenticationMiddleware,
	if config.IsDebug() {
		s.srv.ServerConfigCallback = func(ctx ssh.Context) *gossh.ServerConfig {
			return &gossh.ServerConfig{
				AuthLogCallback: func(conn gossh.ConnMetadata, method string, err error) {
					logger.Debug("authentication", "user", conn.User(), "method", method, "err", err)
				},
			}
		}
	}

func initializePermissions(ctx ssh.Context) {
	perms := ctx.Permissions()
	if perms == nil || perms.Permissions == nil {
		perms = &ssh.Permissions{Permissions: &gossh.Permissions{}}
	}
	if perms.Extensions == nil {
		perms.Extensions = make(map[string]string)
	}
	if perms.Permissions.Extensions == nil {
		perms.Permissions.Extensions = make(map[string]string)
	}
}


		// XXX: store the first "approved" public-key fingerprint in the
		// permissions block to use for authentication later.
		initializePermissions(ctx)
		perms := ctx.Permissions()

		// Set the public key fingerprint to be used for authentication.
		perms.Extensions["pubkey-fp"] = gossh.FingerprintSHA256(pk)
		ctx.SetValue(ssh.ContextKeyPermissions, perms)

	// If we're allowing keyless access, reset the public key fingerprint
	if ac {
		initializePermissions(ctx)
		perms := ctx.Permissions()

		// XXX: reset the public-key fingerprint. This is used to validate the
		// public key being used to authenticate.
		perms.Extensions["pubkey-fp"] = ""
		ctx.SetValue(ssh.ContextKeyPermissions, perms)
	}