SOCKS¶
Serve SOCKS5, SOCKS4 and SOCKS4a clients, on a listener of their own or next to HTTP clients on the
regular one; their connections go through the same filters, routing and interception as a
CONNECT. Use this page for clients that speak SOCKS (curl, browsers, SSH); chaining to an
upstream SOCKS proxy is in Chained proxies.
SOCKS5 mode¶
--mode socks5 (withSocksServer(true), mode=socks5) makes the listener a SOCKS server, as
mitmproxy's socks5 mode does: SOCKS5 (RFC 1928), and
SOCKS4 and 4a as well. --socks (withSocksDetection(true), socks=true) serves SOCKS clients on
the regular listener besides HTTP ones, told apart by their first byte: 0x05 for SOCKS5, 0x04
for SOCKS4 and 4a, which no HTTP request, TLS record or HTTP/2 preface starts with.
java -jar microproxy.jar --port 1080 --mode socks5 --mitm
curl --socks5-hostname 127.0.0.1:1080 http://example.com/ # the proxy resolves the name
curl --proxy socks5h://127.0.0.1:1080 --cacert microproxy-ca.pem https://example.com/ # intercepted
curl --socks5 127.0.0.1:1080 http://example.com/ # curl resolves, sends an address
curl --socks4a 127.0.0.1:1080 http://example.com/
java -jar microproxy.jar --port 8080 --socks --mitm # HTTP and SOCKS clients on one port
curl --proxy socks5h://127.0.0.1:8080 https://example.com/ --cacert microproxy-ca.pem
curl --proxy http://127.0.0.1:8080 https://example.com/ --cacert microproxy-ca.pem
With proxy authentication, SOCKS5 clients give a username and password:
MicroProxy.bootstrap()
.withPort(1080)
.withSocksServer(true)
.withProxyAuthenticator((user, password) -> "alice".equals(user) && "secret".equals(password))
.start();
curl --proxy socks5h://alice:secret@127.0.0.1:1080 http://example.com/
| Flag | Property | Bootstrap | |
|---|---|---|---|
--mode socks5 |
mode=socks5 |
withSocksServer(true) |
the listener serves SOCKS clients only; others are closed |
--socks |
socks=true |
withSocksDetection(true) |
the regular listener serves SOCKS clients too |
--tls-handshake-timeout |
tls_handshake_timeout |
withTlsHandshakeTimeout |
also bounds the SOCKS handshake, up to the request |
One request, one CONNECT¶
Only the CONNECT command is served; BIND and UDP ASSOCIATE
are answered "command not supported". The destination (an IPv4 or IPv6 address, or a host name,
which the proxy resolves) becomes a CONNECT the proxy makes up, which then goes exactly where an
HTTP client's CONNECT goes:
- the filters see it (
clientToProxyRequest,proxyToServerRequest,proxyToServerAllowMitm, ...), and so doActivityTrackers, the access and request logs,FlowContext.timings()and scripts; its answer is the200the client would have got, or the failure; - routing: the
ChainedProxyManager, chained HTTP and SOCKS proxies,NO_PROXYrules, backoff; - then the client's first bytes decide, as after a
CONNECT(Deciding what to intercept): a ClientHello is intercepted with the MITM manager unless the host rules, its SNI and ALPN, the manager or a filter decline it; plain HTTP is served as HTTP, its requests going to the destination through the filters; anything else is tunnelled; - the TCP hooks see its tunnels as they see a
CONNECT's, and an intercepted TLS session that does not carry HTTP (or one with a TCP host) is relayed decrypted through them.
A SOCKS client has no other way to send plain HTTP through the proxy, so its first bytes are read
even without --mitm: its HTTP requests are filtered and logged, and its TLS is tunnelled.
FlowContext.socksVersion() (5, or 4 for SOCKS4 and 4a; empty for HTTP clients) and Starlark's
ctx.socks_version tell SOCKS clients apart.
Connected, then answered¶
As mitmproxy does, the proxy connects to the server (or through its
chained proxy) before it answers the request, so the client learns why a connection failed. The
ClientHello is read only after the answer, since a client sends it only then; this is the order of
an HTTP CONNECT, whose 200 is written once the server is connected and whose server handshake
waits for the ClientHello (ALPN mirroring included).
| Failure | SOCKS5 reply |
|---|---|
The host name does not resolve (UnresolvedHost) |
0x04 host unreachable |
| The connection is refused | 0x05 connection refused |
| No route to the host / the network | 0x04 host unreachable / 0x03 network unreachable |
| The connect timeout passed | 0x06 TTL expired |
The authenticator (checking every request), a filter or the ChainedProxyManager refused it |
0x02 not allowed by ruleset |
Anything else (a chained proxy failed, the pool is exhausted, a filter answered with a 5xx) |
0x01 general failure |
SOCKS4 replies have only "granted" (90) and "rejected" (91).
Authentication¶
Without a proxy authenticator, SOCKS5 clients must
offer "no authentication". With one, they authenticate with a username and password
(RFC 1929), which the authenticator sees as
Proxy-Authorization: Basic on a made-up CONNECT whose target is not known yet (0.0.0.0:0),
so authenticate(user, password), request-aware authenticators and a script's authenticate hook
all work, and the accepted user is ClientDetails.getUserName(). A client that offers no
credentials, SOCKS4's included (its user id is not a credential), is let in only if the
authenticator accepts that request without them; otherwise SOCKS5 clients get "no acceptable
methods" (0xFF) and SOCKS4 clients a rejection. With authenticateEveryRequest(), the request's
own CONNECT, with its destination and the same credentials, is authenticated again.
Hardening¶
The handshake, from the first byte to the request, must finish within the TLS
handshake timeout (--tls-handshake-timeout, 10 seconds by default; with 0, each read within the
idle timeout). SOCKS5 fields are bounded by their one-byte lengths, SOCKS4's user id and host name
by 255 bytes; host names may hold only letters, digits, ., - and _ (or an IP literal).
Not supported: BIND, UDP ASSOCIATE (and so DNS or QUIC through SOCKS), GSSAPI authentication,
SOCKS on a TLS listener or together with a reverse proxy, and mitmproxy's @port listen suffix
(use --port).