From 4c3166bf9f025ab0a08f14b5a6aa42c0fb68b01b Mon Sep 17 00:00:00 2001 From: fhgffy <102001626+fhgffy@users.noreply.github.com> Date: Wed, 14 Oct 2026 00:29:10 +0800 Subject: [PATCH] fix(socks5): select only offered authentication methods --- proxy/socks5/server.go | 14 +++++++++- proxy/socks5/server_test.go | 56 +++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 proxy/socks5/server_test.go diff --git a/proxy/socks5/server.go b/proxy/socks5/server.go index 41465a0..498e8c5 100644 --- a/proxy/socks5/server.go +++ b/proxy/socks5/server.go @@ -4,6 +4,7 @@ import ( "errors" "io" "net" + "slices" "strings" "sync" "time" @@ -208,8 +209,19 @@ func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) { return nil, err } - // write VER METHOD + method := byte(socks.AuthNone) if s.user != "" && s.password != "" { + method = socks.AuthPassword + } + if !slices.Contains(buf[:nmethods], method) { + if _, err := c.Write([]byte{Version, 0xff}); err != nil { + return nil, err + } + return nil, errors.New("no acceptable authentication method") + } + + // write VER METHOD + if method == socks.AuthPassword { _, err := c.Write([]byte{Version, socks.AuthPassword}) if err != nil { return nil, err diff --git a/proxy/socks5/server_test.go b/proxy/socks5/server_test.go new file mode 100644 index 0000000..a5414fd --- /dev/null +++ b/proxy/socks5/server_test.go @@ -0,0 +1,56 @@ +package socks5 + +import ( + "io" + "net" + "testing" + "time" + + "github.com/nadoo/glider/pkg/socks" +) + +func TestHandshakeSelectsOfferedMethod(t *testing.T) { + for _, tt := range []struct { + name string + user string + password string + methods []byte + want byte + }{ + {name: "no authentication", methods: []byte{socks.AuthNone}, want: socks.AuthNone}, + {name: "password authentication", user: "user", password: "pass", methods: []byte{socks.AuthPassword}, want: socks.AuthPassword}, + {name: "password offered second", user: "user", password: "pass", methods: []byte{socks.AuthNone, socks.AuthPassword}, want: socks.AuthPassword}, + {name: "no supported method", methods: []byte{0x80}, want: 0xff}, + {name: "no methods", want: 0xff}, + {name: "password not offered", user: "user", password: "pass", methods: []byte{socks.AuthNone}, want: 0xff}, + {name: "no-auth not offered", methods: []byte{socks.AuthPassword}, want: 0xff}, + } { + t.Run(tt.name, func(t *testing.T) { + client, server := net.Pipe() + t.Cleanup(func() { client.Close(); server.Close() }) + client.SetDeadline(time.Now().Add(3 * time.Second)) + done := make(chan error, 1) + go func() { + s := &Socks5{user: tt.user, password: tt.password} + _, err := s.handshake(server) + server.Close() + done <- err + }() + greeting := append([]byte{Version, byte(len(tt.methods))}, tt.methods...) + if _, err := client.Write(greeting); err != nil { + t.Fatal(err) + } + var reply [2]byte + if _, err := io.ReadFull(client, reply[:]); err != nil { + t.Fatal(err) + } + if reply != [2]byte{Version, tt.want} { + t.Errorf("method reply = %v, want [%d %d]", reply, Version, tt.want) + } + client.Close() + if err := <-done; err == nil { + t.Error("incomplete handshake unexpectedly succeeded") + } + }) + } +}