From 64fa306f8d0a20cab5a57c0615f9726e3417b101 Mon Sep 17 00:00:00 2001 From: Neur0toxine Date: Fri, 24 Nov 2023 22:01:53 +0300 Subject: [PATCH] ssh: localhost remote by default, auto username, update proto lib --- internal/server/driver/ssh/driver.go | 17 +++++- internal/server/driver/ssh/sshtun/connect.go | 1 + internal/server/manager.go | 21 +++++-- patch/ssh_proto_patches.patch | 58 +++++++------------- pkg/proto/ssh/client_auth.go | 20 ++++++- pkg/proto/ssh/common.go | 15 ++++- 6 files changed, 82 insertions(+), 50 deletions(-) diff --git a/internal/server/driver/ssh/driver.go b/internal/server/driver/ssh/driver.go index fc2b5cd..e277961 100644 --- a/internal/server/driver/ssh/driver.go +++ b/internal/server/driver/ssh/driver.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net" + "os/user" "path" "regexp" "strconv" @@ -69,9 +70,21 @@ func New(ctx context.Context, name string, params config.DriverParams) (base.Dri return drv, nil } +func (d *SSH) getUser() string { + if d.params.Auth.User != "" { + return d.params.Auth.User + } + userData, err := user.Current() + if err != nil { + d.Log().Warn("cannot get current user name - using 'root'") + return "root" + } + return userData.Username +} + func (d *SSH) forward(val sshtun.Forward, domainMatcher func(string)) conn { tun := sshtun.New(d.params.Address, - d.params.Auth.User, + d.getUser(), d.auth, sshtun.TunnelConfig{ Forward: val, @@ -307,7 +320,7 @@ func (d *SSH) remoteEndpoint(remoteHost string) sshtun.Endpoint { } if remoteHost == "" && !d.params.FakeRemoteHost { // Listen on all interfaces if no host was provided. - remoteHost = "0.0.0.0" + remoteHost = "127.0.0.1" } return sshtun.Endpoint{ Host: remoteHost, diff --git a/internal/server/driver/ssh/sshtun/connect.go b/internal/server/driver/ssh/sshtun/connect.go index 35e3b72..2b3e7bc 100644 --- a/internal/server/driver/ssh/sshtun/connect.go +++ b/internal/server/driver/ssh/sshtun/connect.go @@ -151,6 +151,7 @@ func (t *Tunnel) connect(ctx context.Context, go func() { defer wg.Done() _ = sess.Wait() + t.log.Debug("main session has been terminated") }() } diff --git a/internal/server/manager.go b/internal/server/manager.go index 367f6b4..9440751 100644 --- a/internal/server/manager.go +++ b/internal/server/manager.go @@ -14,6 +14,7 @@ import ( "github.com/Neur0toxine/sshpoke/internal/server/driver/base" "github.com/Neur0toxine/sshpoke/internal/server/driver/plugin" "github.com/Neur0toxine/sshpoke/pkg/dto" + "go.uber.org/zap" ) type Manager struct { @@ -26,6 +27,7 @@ type Manager struct { statusLock sync.RWMutex ctx context.Context defaultServer string + log *zap.SugaredLogger } type ServerStatus struct { @@ -47,23 +49,24 @@ func NewManager(ctx context.Context, servers []config.Server, defaultServer stri statusMap: make(map[string]ServerStatus), forwards: make(map[string]bool), defaultServer: defaultServer, + log: logger.Sugar.With("component", "manager"), } for _, serverConfig := range servers { server, err := driver.New(ctx, serverConfig.Name, serverConfig.Driver, serverConfig.Params) if err != nil { - logger.Sugar.Errorf("cannot initialize server '%s': %s", serverConfig.Name, err) + m.log.Errorf("cannot initialize server '%s': %s", serverConfig.Name, err) continue } server.SetEventStatusCallback(m.eventStatusCallback(server.Name())) if server.Driver() == config.DriverPlugin { pl := server.(plugin.Plugin) if pl.Token() == "" { - logger.Sugar.Warnf("server '%s' will not work because it doesn't have a token", pl.Name()) + m.log.Warnf("server '%s' will not work because it doesn't have a token", pl.Name()) continue } existing, found := m.plugins[pl.Token()] if found { - logger.Sugar.Fatalw("two plugins cannot have the same token", + m.log.Fatalw("two plugins cannot have the same token", "plugin1", existing.Name(), "plugin2", pl.Name(), "token", pl.Token()) } m.plugins[pl.Token()] = pl @@ -114,7 +117,7 @@ func (m *Manager) eventStatusCallback(serverName string) base.EventStatusCallbac } func (m *Manager) processEventStatus(serverName string, event dto.EventStatus) { - logger.Sugar.Debugw("received EventStatus from server", + m.log.Debugw("received EventStatus from server", "serverName", serverName, "eventStatus", event) item, found := docker.Default.GetContainer(event.ID, true) if !found { @@ -175,10 +178,16 @@ func (m *Manager) markAndSweepForwards() { m.forwards[id] = false // unmark } else { if state { - m.processEventStatus(serverName, dto.EventStatus{ + err := m.ProcessEvent(dto.Event{ Type: dto.EventStop, - ID: containerID, + Container: dto.Container{ + ID: containerID, + Server: serverName, + }, }) + if err != nil { + m.log.Warnf("cannot process mark-and-sweep event: %s", err) + } continue } m.forwards[id] = true // mark diff --git a/patch/ssh_proto_patches.patch b/patch/ssh_proto_patches.patch index eb5b671..64a3719 100644 --- a/patch/ssh_proto_patches.patch +++ b/patch/ssh_proto_patches.patch @@ -1,38 +1,20 @@ diff -Naru cryptolib/ssh/cipher.go pkg/proto/ssh/cipher.go ---- cryptolib/ssh/cipher.go 2023-11-21 18:52:03.117248053 +0300 -+++ pkg/proto/ssh/cipher.go 2023-11-21 17:19:04.688042738 +0300 +--- cryptolib/ssh/cipher.go 2023-11-24 21:59:11.581318943 +0300 ++++ pkg/proto/ssh/cipher.go 2023-11-24 21:39:11.897832847 +0300 @@ -16,8 +16,8 @@ "hash" "io" - + - "golang.org/x/crypto/chacha20" "github.com/Neur0toxine/sshpoke/pkg/proto/ssh/internal/poly1305" + "golang.org/x/crypto/chacha20" ) - + const ( diff -Naru cryptolib/ssh/common.go pkg/proto/ssh/common.go ---- cryptolib/ssh/common.go 2023-11-21 18:52:03.217249582 +0300 -+++ pkg/proto/ssh/common.go 2023-11-21 17:28:23.221203344 +0300 -@@ -7,14 +7,13 @@ - import ( - "crypto" - "crypto/rand" -+ _ "crypto/sha1" -+ _ "crypto/sha256" -+ _ "crypto/sha512" - "fmt" - "io" - "math" - "sync" -- -- _ "crypto/sha1" -- _ "crypto/sha256" -- _ "crypto/sha512" - ) - - // These are string constants in the SSH protocol. -@@ -279,6 +278,9 @@ +--- cryptolib/ssh/common.go 2023-11-24 21:59:11.677320636 +0300 ++++ pkg/proto/ssh/common.go 2023-11-24 21:39:08.401775359 +0300 +@@ -287,6 +287,9 @@ // The allowed MAC algorithms. If unspecified then a sensible default is // used. Unsupported values are silently ignored. MACs []string @@ -40,15 +22,15 @@ diff -Naru cryptolib/ssh/common.go pkg/proto/ssh/common.go + // Called on every incoming handshake packet for client. Only receives data and extended data packets. + HandshakePacketReader func(p []byte) } - + // SetDefaults sets sensible values for unset fields in config. This is diff -Naru cryptolib/ssh/handshake.go pkg/proto/ssh/handshake.go ---- cryptolib/ssh/handshake.go 2023-11-21 18:52:03.209249460 +0300 -+++ pkg/proto/ssh/handshake.go 2023-11-21 18:51:58.681180283 +0300 +--- cryptolib/ssh/handshake.go 2023-11-24 21:59:11.673320566 +0300 ++++ pkg/proto/ssh/handshake.go 2023-11-24 21:59:05.113204819 +0300 @@ -401,6 +401,14 @@ t.printPacket(p, false) } - + + if t.config.HandshakePacketReader != nil && p[0] == msgChannelData { + data, err := decode(p) + packetData, ok := data.(*channelDataMsg) @@ -61,8 +43,8 @@ diff -Naru cryptolib/ssh/handshake.go pkg/proto/ssh/handshake.go return nil, fmt.Errorf("ssh: first packet should be msgKexInit") } diff -Naru cryptolib/ssh/tcpip.go pkg/proto/ssh/tcpip.go ---- cryptolib/ssh/tcpip.go 2023-11-21 18:52:03.129248237 +0300 -+++ pkg/proto/ssh/tcpip.go 2023-11-21 17:19:04.688042738 +0300 +--- cryptolib/ssh/tcpip.go 2023-11-24 21:59:11.593319154 +0300 ++++ pkg/proto/ssh/tcpip.go 2023-11-24 21:39:08.401775359 +0300 @@ -101,14 +101,18 @@ // ListenTCP requests the remote peer open a listening socket // on laddr. Incoming connections will be available by calling @@ -73,7 +55,7 @@ diff -Naru cryptolib/ssh/tcpip.go pkg/proto/ssh/tcpip.go if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) { return c.autoPortListenWorkaround(laddr) } - + + host := laddr.IP.String() + if len(fakeHost) > 0 { + host = fakeHost[0] @@ -86,7 +68,7 @@ diff -Naru cryptolib/ssh/tcpip.go pkg/proto/ssh/tcpip.go // send message @@ -133,7 +137,12 @@ } - + // Register this forward, using the port number we obtained. - ch := c.forwards.add(laddr) + var ch chan forward @@ -95,7 +77,7 @@ diff -Naru cryptolib/ssh/tcpip.go pkg/proto/ssh/tcpip.go + } else { + ch = c.forwards.add(laddr) + } - + return &tcpListener{laddr, c, ch}, nil } @@ -142,7 +151,9 @@ @@ -107,12 +89,12 @@ diff -Naru cryptolib/ssh/tcpip.go pkg/proto/ssh/tcpip.go + fakeDomainsMap map[string]string + entries []forwardEntry } - + // forwardEntry represents an established mapping of a laddr on a @@ -160,17 +171,30 @@ raddr net.Addr // the raddr of the incoming connection } - + -func (l *forwardList) add(addr net.Addr) chan forward { +func (l *forwardList) add(addr net.Addr, fakeHost ...string) chan forward { l.Lock() @@ -132,7 +114,7 @@ diff -Naru cryptolib/ssh/tcpip.go pkg/proto/ssh/tcpip.go l.entries = append(l.entries, f) return f.c } - + +func (l *forwardList) ipFromAddr(addr net.Addr) string { + host, _, _ := net.SplitHostPort(addr.String()) + return host @@ -144,7 +126,7 @@ diff -Naru cryptolib/ssh/tcpip.go pkg/proto/ssh/tcpip.go @@ -206,6 +230,15 @@ continue } - + + l.fdm.RLock() + if addr, ok := l.fakeDomainsMap[payload.Addr]; ok { + payload.Addr = addr diff --git a/pkg/proto/ssh/client_auth.go b/pkg/proto/ssh/client_auth.go index 5c3bc25..34bf089 100644 --- a/pkg/proto/ssh/client_auth.go +++ b/pkg/proto/ssh/client_auth.go @@ -307,7 +307,10 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand } var methods []string var errSigAlgo error - for _, signer := range signers { + + origSignersLen := len(signers) + for idx := 0; idx < len(signers); idx++ { + signer := signers[idx] pub := signer.PublicKey() as, algo, err := pickSignatureAlgorithm(signer, extensions) if err != nil && errSigAlgo == nil { @@ -321,6 +324,21 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand if err != nil { return authFailure, nil, err } + // OpenSSH 7.2-7.7 advertises support for rsa-sha2-256 and rsa-sha2-512 + // in the "server-sig-algs" extension but doesn't support these + // algorithms for certificate authentication, so if the server rejects + // the key try to use the obtained algorithm as if "server-sig-algs" had + // not been implemented if supported from the algorithm signer. + if !ok && idx < origSignersLen && isRSACert(algo) && algo != CertAlgoRSAv01 { + if contains(as.Algorithms(), KeyAlgoRSA) { + // We retry using the compat algorithm after all signers have + // been tried normally. + signers = append(signers, &multiAlgorithmSigner{ + AlgorithmSigner: as, + supportedAlgorithms: []string{KeyAlgoRSA}, + }) + } + } if !ok { continue } diff --git a/pkg/proto/ssh/common.go b/pkg/proto/ssh/common.go index d14bbe4..97ef56a 100644 --- a/pkg/proto/ssh/common.go +++ b/pkg/proto/ssh/common.go @@ -7,13 +7,14 @@ package ssh import ( "crypto" "crypto/rand" - _ "crypto/sha1" - _ "crypto/sha256" - _ "crypto/sha512" "fmt" "io" "math" "sync" + + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" ) // These are string constants in the SSH protocol. @@ -126,6 +127,14 @@ func isRSA(algo string) bool { return contains(algos, underlyingAlgo(algo)) } +func isRSACert(algo string) bool { + _, ok := certKeyAlgoNames[algo] + if !ok { + return false + } + return isRSA(algo) +} + // supportedPubKeyAuthAlgos specifies the supported client public key // authentication algorithms. Note that this doesn't include certificate types // since those use the underlying algorithm. This list is sent to the client if