Skip to content

Instantly share code, notes, and snippets.

@Integralist
Created May 15, 2024 09:10
Show Gist options
  • Save Integralist/9302d54e6f1ecb5e8245204315dd9436 to your computer and use it in GitHub Desktop.
Save Integralist/9302d54e6f1ecb5e8245204315dd9436 to your computer and use it in GitHub Desktop.
[Proxy HTTP requests through a SSH connection via SOCKS5 proxy] #ssh #proxy #tunnel

https://www.linkedin.com/pulse/proxying-web-traffic-via-ssh-mark-el-khoury

Essentially your local machines creates a SOCK5 proxy which connects to a remote server via SSH (using either username/password or SSH PKI Cert/Key combination).

The SOCK5 proxy handles sending the HTTP request via the SSH tunnel.

The remote server will automatically process the HTTP request and handle sending it to its intended destination.

The upstream (i.e. the endpoint being requested) will see the request coming from the SSH server and presume that's where it originated.

Go Example

package main

import (
	"golang.org/x/crypto/ssh"
	"golang.org/x/net/proxy"
	"net/http"
	"net/url"
	"io/ioutil"
	"log"
	"time"
)

func main() {
	http.HandleFunc("/", handleRequestAndRedirect)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func handleRequestAndRedirect(w http.ResponseWriter, r *http.Request) {
	// SSH connection details
	sshHost := "ssh.example.com:22"
	sshUser := "your_ssh_user"
	sshPassword := "your_ssh_password"

	// Upstream service URL
	upstreamURL := "http://upstream-service.example.com"

	// Create the SSH client configuration
	sshConfig := &ssh.ClientConfig{
		User: sshUser,
		Auth: []ssh.AuthMethod{
			ssh.Password(sshPassword),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Timeout:         5 * time.Second,
	}

	// Establish the SSH connection
	sshConn, err := ssh.Dial("tcp", sshHost, sshConfig)
	if err != nil {
		http.Error(w, "Failed to dial SSH: "+err.Error(), http.StatusInternalServerError)
		return
	}
	defer sshConn.Close()

	// Create a SOCKS5 proxy client over the SSH connection
	socks5Client, err := proxy.SOCKS5("tcp", "localhost:0", nil, &sshDialer{sshConn})
	if err != nil {
		http.Error(w, "Failed to create SOCKS5 proxy: "+err.Error(), http.StatusInternalServerError)
		return
	}

	// Create an HTTP client that uses the SOCKS5 proxy
	httpClient := &http.Client{
		Transport: &http.Transport{
			Dial: socks5Client.Dial,
		},
	}

	// Create the request to the upstream service
	req, err := http.NewRequest(r.Method, upstreamURL, r.Body)
	if err != nil {
		http.Error(w, "Failed to create request: "+err.Error(), http.StatusInternalServerError)
		return
	}

	// Copy the headers from the original request to the new request
	for key, values := range r.Header {
		for _, value := range values {
			req.Header.Add(key, value)
		}
	}

	// Perform the request
	resp, err := httpClient.Do(req)
	if err != nil {
		http.Error(w, "Failed to perform request: "+err.Error(), http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()

	// Copy the response from the upstream service to the original response
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		http.Error(w, "Failed to read response: "+err.Error(), http.StatusInternalServerError)
		return
	}

	for key, values := range resp.Header {
		for _, value := range values {
			w.Header().Add(key, value)
		}
	}
	w.WriteHeader(resp.StatusCode)
	w.Write(body)
}

// sshDialer is a custom Dialer implementation that uses an SSH connection
type sshDialer struct {
	client *ssh.Client
}

// Dial connects to the address on the named network using the SSH client.
func (d *sshDialer) Dial(network, addr string) (net.Conn, error) {
	return d.client.Dial(network, addr)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment