From 273482e3fab18dbf1851235e2c2541b7c7be9c2f Mon Sep 17 00:00:00 2001 From: Neur0toxine Date: Sat, 18 Nov 2023 22:29:18 +0300 Subject: [PATCH] ssh: fake remote host support --- internal/server/driver/ssh/driver.go | 2 +- internal/server/driver/ssh/params.go | 20 +++--- internal/server/driver/ssh/sshtun/ssh.go | 12 ++-- patch/ssh_fakehost.patch | 79 +++++++++++++++++++++++- pkg/proto/ssh/tcpip.go | 35 ++++++++++- 5 files changed, 127 insertions(+), 21 deletions(-) diff --git a/internal/server/driver/ssh/driver.go b/internal/server/driver/ssh/driver.go index 852210f..7520f70 100644 --- a/internal/server/driver/ssh/driver.go +++ b/internal/server/driver/ssh/driver.go @@ -45,7 +45,7 @@ func New(ctx context.Context, name string, params config.DriverParams) (base.Dri } func (d *SSH) forward(val sshtun.Forward) conn { - tun := sshtun.New(d.params.Address, d.params.Auth.User, d.params.DisableRemoteHostResolve, val, d.auth, d.Log()) + tun := sshtun.New(d.params.Address, d.params.Auth.User, d.params.FakeRemoteHost, val, d.auth, d.Log()) ctx, cancel := context.WithCancel(d.Context()) go tun.Connect(ctx, sshtun.StdoutPrinterSessionCallback(d.Log().With("ssh-output", val.Remote.String()))) return conn{ctx: ctx, cancel: cancel, tun: tun} diff --git a/internal/server/driver/ssh/params.go b/internal/server/driver/ssh/params.go index ae695bf..452d346 100644 --- a/internal/server/driver/ssh/params.go +++ b/internal/server/driver/ssh/params.go @@ -6,16 +6,16 @@ import ( ) type Params struct { - Address string `mapstructure:"address" validate:"required"` - DefaultHost *string `mapstructure:"default_host,omitempty"` - ForwardPort uint16 `mapstructure:"forward_port"` - Auth types.Auth `mapstructure:"auth"` - KeepAlive types.KeepAlive `mapstructure:"keepalive"` - Domain string `mapstructure:"domain"` - DomainProto string `mapstructure:"domain_proto"` - DomainExtractRegex string `mapstructure:"domain_extract_regex" validate:"validregexp"` - Mode types.DomainMode `mapstructure:"mode" validate:"required,oneof=single multi"` - DisableRemoteHostResolve bool `mapstructure:"disable_remote_host_resolve"` + Address string `mapstructure:"address" validate:"required"` + DefaultHost *string `mapstructure:"default_host,omitempty"` + ForwardPort uint16 `mapstructure:"forward_port"` + Auth types.Auth `mapstructure:"auth"` + KeepAlive types.KeepAlive `mapstructure:"keepalive"` + Domain string `mapstructure:"domain"` + DomainProto string `mapstructure:"domain_proto"` + DomainExtractRegex string `mapstructure:"domain_extract_regex" validate:"validregexp"` + Mode types.DomainMode `mapstructure:"mode" validate:"required,oneof=single multi"` + FakeRemoteHost bool `mapstructure:"fake_remote_host"` } func (p *Params) Validate() error { diff --git a/internal/server/driver/ssh/sshtun/ssh.go b/internal/server/driver/ssh/sshtun/ssh.go index 791eada..d1b86f8 100644 --- a/internal/server/driver/ssh/sshtun/ssh.go +++ b/internal/server/driver/ssh/sshtun/ssh.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "strings" "sync" "sync/atomic" "time" @@ -173,15 +174,14 @@ func (c *Tunnel) ipFromAddr(addr net.Addr) net.IP { func handleReverseForwardConn(client net.Conn, forward Forward, log *zap.SugaredLogger) { defer client.Close() - log.Debugf("%s connected", client.RemoteAddr()) - defer log.Debug("closed") - remote, err := net.Dial("tcp", forward.Local.String()) if err != nil { - log.Errorf("dial INTO local service error: %s", err.Error()) + log.Errorf("cannot dial local service: %s", err.Error()) return } + log.Debugf("proxying %s <-> %s", forward.Local.String(), client.RemoteAddr()) + // pipe data in both directions: // - client => remote // - remote => client @@ -194,6 +194,10 @@ func handleReverseForwardConn(client net.Conn, forward Forward, log *zap.Sugared bidipipe.WithName("client", client), bidipipe.WithName("remote", remote), ); err != nil { + // we can safely ignore those errors. + if strings.Contains(err.Error(), "use of closed network connection") { + return + } log.Error(err) } } diff --git a/patch/ssh_fakehost.patch b/patch/ssh_fakehost.patch index cd62087..8d9b7e3 100644 --- a/patch/ssh_fakehost.patch +++ b/patch/ssh_fakehost.patch @@ -1,5 +1,5 @@ ---- pkg/proto/ssh/tcpip.go 2023-11-18 21:39:15.394837005 +0300 -+++ pkg/proto/ssh/tcpip.go 2023-11-18 21:38:25.706173351 +0300 +--- cryptolib/ssh/tcpip.go 2023-11-18 22:20:50.609146922 +0300 ++++ pkg/proto/ssh/tcpip.go 2023-11-18 22:19:08.891684669 +0300 @@ -101,14 +101,18 @@ // ListenTCP requests the remote peer open a listening socket // on laddr. Incoming connections will be available by calling @@ -10,7 +10,7 @@ if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) { return c.autoPortListenWorkaround(laddr) } - + + host := laddr.IP.String() + if len(fakeHost) > 0 { + host = fakeHost[0] @@ -21,3 +21,76 @@ uint32(laddr.Port), } // send message +@@ -133,7 +137,12 @@ + } + + // Register this forward, using the port number we obtained. +- ch := c.forwards.add(laddr) ++ var ch chan forward ++ if len(fakeHost) > 0 { ++ ch = c.forwards.add(laddr, fakeHost[0]) ++ } else { ++ ch = c.forwards.add(laddr) ++ } + + return &tcpListener{laddr, c, ch}, nil + } +@@ -142,7 +151,9 @@ + // forward requests and the tcpListeners. + type forwardList struct { + sync.Mutex +- entries []forwardEntry ++ fdm sync.RWMutex ++ 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() + defer l.Unlock() + f := forwardEntry{ + laddr: addr, + c: make(chan forward, 1), + } ++ if len(fakeHost) > 0 { ++ if l.fakeDomainsMap == nil { ++ l.fakeDomainsMap = make(map[string]string) ++ } ++ defer l.fdm.Unlock() ++ l.fdm.Lock() ++ l.fakeDomainsMap[fakeHost[0]] = l.ipFromAddr(addr) ++ } + l.entries = append(l.entries, f) + return f.c + } + ++func (l *forwardList) ipFromAddr(addr net.Addr) string { ++ host, _, _ := net.SplitHostPort(addr.String()) ++ return host ++} ++ + // See RFC 4254, section 7.2 + type forwardedTCPPayload struct { + Addr string +@@ -206,6 +230,15 @@ + continue + } + ++ l.fdm.RLock() ++ if addr, ok := l.fakeDomainsMap[payload.Addr]; ok { ++ payload.Addr = addr ++ } ++ if addr, ok := l.fakeDomainsMap[payload.OriginAddr]; ok { ++ payload.OriginAddr = addr ++ } ++ l.fdm.RUnlock() ++ + // RFC 4254 section 7.2 specifies that incoming + // addresses should list the address, in string + // format. It is implied that this should be an IP diff --git a/pkg/proto/ssh/tcpip.go b/pkg/proto/ssh/tcpip.go index f12cf97..2054e23 100644 --- a/pkg/proto/ssh/tcpip.go +++ b/pkg/proto/ssh/tcpip.go @@ -137,7 +137,12 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr, fakeHost ...string) (net.Listener } // Register this forward, using the port number we obtained. - ch := c.forwards.add(laddr) + var ch chan forward + if len(fakeHost) > 0 { + ch = c.forwards.add(laddr, fakeHost[0]) + } else { + ch = c.forwards.add(laddr) + } return &tcpListener{laddr, c, ch}, nil } @@ -146,7 +151,9 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr, fakeHost ...string) (net.Listener // forward requests and the tcpListeners. type forwardList struct { sync.Mutex - entries []forwardEntry + fdm sync.RWMutex + fakeDomainsMap map[string]string + entries []forwardEntry } // forwardEntry represents an established mapping of a laddr on a @@ -164,17 +171,30 @@ type forward struct { 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() defer l.Unlock() f := forwardEntry{ laddr: addr, c: make(chan forward, 1), } + if len(fakeHost) > 0 { + if l.fakeDomainsMap == nil { + l.fakeDomainsMap = make(map[string]string) + } + defer l.fdm.Unlock() + l.fdm.Lock() + l.fakeDomainsMap[fakeHost[0]] = l.ipFromAddr(addr) + } l.entries = append(l.entries, f) return f.c } +func (l *forwardList) ipFromAddr(addr net.Addr) string { + host, _, _ := net.SplitHostPort(addr.String()) + return host +} + // See RFC 4254, section 7.2 type forwardedTCPPayload struct { Addr string @@ -210,6 +230,15 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) { continue } + l.fdm.RLock() + if addr, ok := l.fakeDomainsMap[payload.Addr]; ok { + payload.Addr = addr + } + if addr, ok := l.fakeDomainsMap[payload.OriginAddr]; ok { + payload.OriginAddr = addr + } + l.fdm.RUnlock() + // RFC 4254 section 7.2 specifies that incoming // addresses should list the address, in string // format. It is implied that this should be an IP