Repository navigation
DefaultTLSConfig sends no ALPN and offers TLS 1.0 + RC4 + 3DES #587
Description
Summary
DefaultTLSConfig produces a ClientHello that no mainstream client has sent since about 2012: TLS
1.0 and 1.1 offered, 25 cipher suites including RC4 and 3DES, and no ALPN extension at all. CDN bot
scoring treats that handshake as a non-browser client and returns a block page before any HTTP
header is read.
This affects every consumer. httpx, nuclei and katana send a byte-identical hello.
Measured
Loopback TLS server logging tls.ClientHelloInfo, same machine, back to back:
| fastdialer consumers | Go net/http stdlib |
|
|---|---|---|
| TLS versions | 1.3, 1.2, 1.1, 1.0 | 1.3, 1.2 |
| ALPN | none | h2, http/1.1 |
| ciphers | 25, incl. 0x0005 RC4, 0x000a 3DES |
13 |
Identical output from httpx v1.9.0 and v1.12.0, nuclei v3.9.0, katana v1.6.1 (fastdialer v0.5.4,
v0.5.10, v0.5.6 respectively). DefaultTLSConfig is unchanged from v0.5.4 through v0.5.25 and main.
Confirmed on the wire too, via a public JA4 echo: t13d2511_… (no ALPN, 25 ciphers) versus
t13d1312h1_… after the change below.
Source
fastdialer/default_config.go:27
var DefaultTLSConfig = &tls.Config{
Renegotiation: tls.RenegotiateOnceAsClient,
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS10,
CipherSuites: getUnsafeCipherSuites(), // every insecure + every secure suite
} // NextProtos never setThe missing NextProtos also means HTTP/2 can never be negotiated by any consumer, since ALPN is
how h2 is agreed.
Impact
On one Cloudflare-fronted estate, 11 of 11 hosts returned Cloudflare block pages to stock httpx.
With only these three fields changed, 0 of 11 did, and every response matched a stock Go client
byte for byte, including three genuine 403s that were not block pages. Three hosts serving HTTP 200
were being recorded as blocked.
This is likely the real cause behind httpx#2224, #2384, #2044 and #624, all closed with
"use -tls-impersonate". In #2224 the reporter stated that -tls-impersonate did not help. The
problem is not that the client fails to impersonate a browser. It is that the default handshake
looks a decade old.
Suggested change
MinVersion: tls.VersionTLS12,
CipherSuites: nil, // Go's secure defaults
NextProtos: []string{"h2", "http/1.1"}, // or {"http/1.1"} to avoid changing protocol behaviourPreferably as options rather than new hardcoded values, so scanning genuinely legacy infrastructure
stays possible. Keeping the current values as the default would still solve this for anyone who can
set the option.
On the compatibility cost: the permissive default exists to reach old servers, which is fair. In our
data it buys nothing. Across ~8,600 HTTPS listeners from eight separate estates, zero supported
nothing at or above TLS 1.2. Pre-1.2 does appear (TLS 1.1 on 889 listeners, TLS 1.0 on 615) but
every one of those also offered 1.2 or 1.3. That is one sample, not a universal claim, which is why
configurable beats changing the default outright.
Repro
cfg := &tls.Config{
Certificates: []tls.Certificate{selfSigned()},
GetConfigForClient: func(h *tls.ClientHelloInfo) (*tls.Config, error) {
fmt.Printf("alpn=%v versions=%v ciphers=%d\n", h.SupportedProtos, h.SupportedVersions, len(h.CipherSuites))
return nil, nil
},
}Point any fastdialer consumer at it and compare with a stock net/http client.
Activity