Skip to content

Commit

Permalink
--wip-- [skip ci]
Browse files Browse the repository at this point in the history
  • Loading branch information
kaanescape committed Feb 24, 2025
1 parent 91480fc commit 8431567
Show file tree
Hide file tree
Showing 9 changed files with 779 additions and 114 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ require (
github.com/oapi-codegen/runtime v1.1.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
golang.org/x/crypto v0.17.0
golang.org/x/crypto v0.23.0
golang.org/x/net v0.25.0
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
Expand Down Expand Up @@ -153,8 +153,8 @@ golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
Expand Down
117 changes: 8 additions & 109 deletions pkg/privatelocation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import (
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"net"
"strconv"

"github.com/Escape-Technologies/cli/pkg/log"
"github.com/Escape-Technologies/cli/pkg/socks5"
"golang.org/x/crypto/ssh"
)

Expand All @@ -30,7 +29,7 @@ func StartLocation(ctx context.Context, locationId string, sshPrivateKey ed25519
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}

client, err := ssh.Dial("tcp", "a6afba49141654b028ed54a37057b33b-ce4ca0de8f229555.elb.us-east-1.amazonaws.com:2222", config)
client, err := ssh.Dial("tcp", "a014172aab3dc49b58e1e50d3a8af9eb-fa031b140357b851.elb.us-east-1.amazonaws.com:2222", config)
if err != nil {
return fmt.Errorf("failed to dial: %w", err)
}
Expand All @@ -48,127 +47,27 @@ func StartLocation(ctx context.Context, locationId string, sshPrivateKey ed25519
case <-ctx.Done():
return nil
default:
log.Info("Waiting for connection")
conn, err := listener.Accept()
if err != nil {
log.Error("Failed to accept connection: %v", err)
continue
}
log.Info("Accepted connection, creating socks5 server")
go handleSocks5Connection(conn)
}
}
}
//this shit works, TODO: make this readable
func handleSocks5Connection(conn net.Conn) {
defer conn.Close()
socks5Server, err := socks5.New(&socks5.Config{})

// Read SOCKS5 version and number of authentication methods
buf := make([]byte, 2)
if _, err := io.ReadFull(conn, buf); err != nil {
log.Error("Failed to read SOCKS5 header: %v", err)
return
}

if buf[0] != 5 { // SOCKS5 version
log.Error("Unsupported SOCKS version: %d", buf[0])
return
}

// Read authentication methods
methods := make([]byte, buf[1])
if _, err := io.ReadFull(conn, methods); err != nil {
log.Error("Failed to read auth methods: %v", err)
return
}

// Respond with no authentication required
if _, err := conn.Write([]byte{5, 0}); err != nil {
log.Error("Failed to write auth response: %v", err)
return
}

// Read connection request
buf = make([]byte, 4)
if _, err := io.ReadFull(conn, buf); err != nil {
log.Error("Failed to read connection request: %v", err)
return
}

if buf[0] != 5 || buf[1] != 1 { // Only support CONNECT command
log.Error("Unsupported SOCKS5 command: %d", buf[1])
return
}

// Read address type
var host string
switch buf[3] {
case 1: // IPv4
addr := make([]byte, 4)
if _, err := io.ReadFull(conn, addr); err != nil {
log.Error("Failed to read IPv4 address: %v", err)
return
}
host = net.IPv4(addr[0], addr[1], addr[2], addr[3]).String()
case 3: // Domain name
lenBuf := make([]byte, 1)
if _, err := io.ReadFull(conn, lenBuf); err != nil {
log.Error("Failed to read domain length: %v", err)
return
}
domainBuf := make([]byte, lenBuf[0])
if _, err := io.ReadFull(conn, domainBuf); err != nil {
log.Error("Failed to read domain: %v", err)
return
}
host = string(domainBuf)
case 4: // IPv6
addr := make([]byte, 16)
if _, err := io.ReadFull(conn, addr); err != nil {
log.Error("Failed to read IPv6 address: %v", err)
return
}
host = net.IP(addr).String()
default:
log.Error("Unsupported address type: %d", buf[3])
return
}

// Read port
portBuf := make([]byte, 2)
if _, err := io.ReadFull(conn, portBuf); err != nil {
log.Error("Failed to read port: %v", err)
return
}
port := int(portBuf[0])<<8 | int(portBuf[1])

// Connect to target
target, err := net.Dial("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
log.Error("Failed to connect to target: %v", err)
conn.Write([]byte{5, 1, 0, 1, 0, 0, 0, 0, 0, 0}) // Connection refused
return
}
defer target.Close()

// Send success response
localAddr := target.LocalAddr().(*net.TCPAddr)
response := make([]byte, 10)
response[0] = 5 // SOCKS5
response[1] = 0 // Success
response[2] = 0 // Reserved
response[3] = 1 // IPv4
copy(response[4:8], localAddr.IP.To4())
response[8] = byte(localAddr.Port >> 8)
response[9] = byte(localAddr.Port)
if _, err := conn.Write(response); err != nil {
log.Error("Failed to write success response: %v", err)
log.Error("Failed to create socks5 server: %v", err)
return
}
log.Info("Socks5 server created")

// Start proxying data
go func() {
io.Copy(target, conn)
}()
io.Copy(conn, target)
socks5Server.ServeConn(conn)
}

func GenSSHKeys(name string) (string, ed25519.PrivateKey) {
Expand Down
151 changes: 151 additions & 0 deletions pkg/socks5/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package socks5

import (
"fmt"
"io"
)

const (
NoAuth = uint8(0)
noAcceptable = uint8(255)
UserPassAuth = uint8(2)
userAuthVersion = uint8(1)
authSuccess = uint8(0)
authFailure = uint8(1)
)

var (
UserAuthFailed = fmt.Errorf("User authentication failed")
NoSupportedAuth = fmt.Errorf("No supported authentication mechanism")
)

// A Request encapsulates authentication state provided
// during negotiation
type AuthContext struct {
// Provided auth method
Method uint8
// Payload provided during negotiation.
// Keys depend on the used auth method.
// For UserPassauth contains Username
Payload map[string]string
}

type Authenticator interface {
Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error)
GetCode() uint8
}

// NoAuthAuthenticator is used to handle the "No Authentication" mode
type NoAuthAuthenticator struct{}

func (a NoAuthAuthenticator) GetCode() uint8 {
return NoAuth
}

func (a NoAuthAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) {
_, err := writer.Write([]byte{socks5Version, NoAuth})
return &AuthContext{NoAuth, nil}, err
}

// UserPassAuthenticator is used to handle username/password based
// authentication
type UserPassAuthenticator struct {
Credentials CredentialStore
}

func (a UserPassAuthenticator) GetCode() uint8 {
return UserPassAuth
}

func (a UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) {
// Tell the client to use user/pass auth
if _, err := writer.Write([]byte{socks5Version, UserPassAuth}); err != nil {
return nil, err
}

// Get the version and username length
header := []byte{0, 0}
if _, err := io.ReadAtLeast(reader, header, 2); err != nil {
return nil, err
}

// Ensure we are compatible
if header[0] != userAuthVersion {
return nil, fmt.Errorf("Unsupported auth version: %v", header[0])
}

// Get the user name
userLen := int(header[1])
user := make([]byte, userLen)
if _, err := io.ReadAtLeast(reader, user, userLen); err != nil {
return nil, err
}

// Get the password length
if _, err := reader.Read(header[:1]); err != nil {
return nil, err
}

// Get the password
passLen := int(header[0])
pass := make([]byte, passLen)
if _, err := io.ReadAtLeast(reader, pass, passLen); err != nil {
return nil, err
}

// Verify the password
if a.Credentials.Valid(string(user), string(pass)) {
if _, err := writer.Write([]byte{userAuthVersion, authSuccess}); err != nil {
return nil, err
}
} else {
if _, err := writer.Write([]byte{userAuthVersion, authFailure}); err != nil {
return nil, err
}
return nil, UserAuthFailed
}

// Done
return &AuthContext{UserPassAuth, map[string]string{"Username": string(user)}}, nil
}

// authenticate is used to handle connection authentication
func (s *Server) authenticate(conn io.Writer, bufConn io.Reader) (*AuthContext, error) {
// Get the methods
methods, err := readMethods(bufConn)
if err != nil {
return nil, fmt.Errorf("Failed to get auth methods: %v", err)
}

// Select a usable method
for _, method := range methods {
cator, found := s.authMethods[method]
if found {
return cator.Authenticate(bufConn, conn)
}
}

// No usable method found
return nil, noAcceptableAuth(conn)
}

// noAcceptableAuth is used to handle when we have no eligible
// authentication mechanism
func noAcceptableAuth(conn io.Writer) error {
conn.Write([]byte{socks5Version, noAcceptable})
return NoSupportedAuth
}

// readMethods is used to read the number of methods
// and proceeding auth methods
func readMethods(r io.Reader) ([]byte, error) {
header := []byte{0}
if _, err := r.Read(header); err != nil {
return nil, err
}

numMethods := int(header[0])
methods := make([]byte, numMethods)
_, err := io.ReadAtLeast(r, methods, numMethods)
return methods, err
}
17 changes: 17 additions & 0 deletions pkg/socks5/credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package socks5

// CredentialStore is used to support user/pass authentication
type CredentialStore interface {
Valid(user, password string) bool
}

// StaticCredentials enables using a map directly as a credential store
type StaticCredentials map[string]string

func (s StaticCredentials) Valid(user, password string) bool {
pass, ok := s[user]
if !ok {
return false
}
return password == pass
}
Loading

0 comments on commit 8431567

Please sign in to comment.