Go ile bir web scraper veya otomasyon istemcisi yazdınız; kodunuz doğru, header'lar gerçekçi, rotasyon mantığı sağlam — ama yine de Cloudflare 403 döndürüyor. Sorun uygulama katmanında değil, TLS el sıkışmasında. Go net/http TLS parmak izi düzeltme, tam da bu noktada devreye girer: Go'nun crypto/tls paketi statik ve Chrome'a hiç benzemeyen bir ClientHello üretir, bu yüzden modern WAF'ler isteğinizi daha ilk pakette işaretler. Bu yazıda, parmak izini yakalamayı, Chrome ile karşılaştırmayı, refraction-networking/utls ile nasıl düzeltileceğini ve ProxyHat residential proxy'leri üzerinden nasıl temiz bir ağ kimliğiyle birleştirileceğini anlatacağız.
Go net/http TLS Parmak İzi Neden Engellenir?
Go'nun standart net/http kütüphanesi, crypto/tls üzerinden TLS el sıkışması yapar. Bu el sıkışma statiktir: şifre sıralaması derlenmiş sabitlerden gelir, GREASE değerleri içermez ve Chrome'un gönderdiği birçok TLS extension'ı eksiktir. Sonuç, JA3/JA4 algoritmalarıyla özetlendiğinde çok belirgin bir parmak izidir.
Bu parmak izi neden sorun yaratır? Çünkü Cloudflare, Akamai, Imperva ve DataDome gibi WAF sağlayıcıları, gelen ClientHello paketini inceleyip JA3/JA4 özetini bilinen bot parmak izleriyle karşılaştırır. Go'nun ürettiği özet, gerçek bir tarayıcıdan o kadar farklıdır ki, tek paket bile bot tespiti için yeterlidir. Salesforce JA3 projesi, bu özetlerin nasıl hesaplandığını açıkça belgeler; TLS sürümü, cipher suite'leri, extension'lar ve eliptik eğri grupları virgülle birleştirilip MD5 ile özetlenir.
Pratikte, Go ile yapılan bir istek şu sinyalleri taşır:
- Sabit cipher sıralaması: Chrome, ECDHE-ECDSA-AES128-GCM-SHA256'i öne koyar; Go farklı bir sıra kullanır.
- GREASE yok: Chrome, cipher listesine ve extension listesine rastgele
0x0a0abenzeri yer tutucu değerler ekler; Go hiç eklemez. - Eksik extension'lar:
application_settings,encrypted_client_hello,delegated_credentialsgibi Chrome'un gönderdiği alanlar Go'da yok. - supported_groups ve key_share farkları: Chrome X25519MLKEM768 ile post-quantum anahtar paylaşımı yapar; Go bu eğriyi göndermez.
Tek paket, tek karar. WAF'ler için bu, IP itibarını dahi kontrol etmeden bot sinyali yeter.
Kendi JA3/JA4 Parmak İzinizi Yakalayın
Teoriyi kanıtlamak için kendi parmak izinizi görmelisiniz. tls.peet.ws/api/all endpoint'i, gelen ClientHello'yu ayrıştırıp JA3, JA4 ve JA4H özetlerini döndürür. Go ile basit bir istek atın:
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://tls.peet.ws/api/all")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Çıktıya bakın. ja3 alanında Go'ya özgü bir özet, ja4 alanında ise t13d1516h2_ önekli bir dize göreceksiniz — ama Chrome'un t13d1515h2_… deseninden belirgin biçimde farklı. İşte bu fark, WAF'lerin sizi yakaladığı yerdir.
Karşılaştırma için aynı URL'yi gerçek bir Chrome sekmesinden açın. tls.peet.ws size Chrome'un JA3/JA4 özetini verecektir. Go özetini Chrome özetinin yanına koyun: cipher listesi, extension listesi ve supported_groups dizilimi tamamen farklıdır.
ClientHello Sinyalleri: Go vs Chrome Karşılaştırması
Şimdi sinyalleri somut cipher değerleriyle inceleyelim. Aşağıdaki tablo, Go 1.22'nin standart crypto/tls ClientHello'su ile Chrome 120+ arasındaki temel farkları özetler.
| Sinyal | Go net/http | Chrome 120+ |
|---|---|---|
| Cipher suite #1 | TLS_AES_128_GCM_SHA256 (0x1301) | GREASE (0x0a0a) → TLS_AES_128_GCM_SHA256 |
| GREASE değerleri | Yok | Cipher, extension, supported_groups ve ALPN listelerinde |
| supported_groups | x25519, secp256r1, secp384r1 | GREASE, x25519MLKEM768, x25519, secp256r1, secp384r1 |
| signature_algorithms | PKCS1WithSHA256, ECDSAWithP256AndSHA256, … | Chrome'un sıralaması: ecdsa_secp256r1_sha256, rsa_pss_rsae_sha256, … |
| ALPN | h2, http/1.1 | GREASE, h2, http/1.1 |
| key_share | x25519, secp256r1 | GREASE, x25519MLKEM768, x25519 |
| application_settings | Yok | Var (ALPS üzerinden) |
| encrypted_client_hello | Yok | Var (ECH destekli) |
Her satır, WAF için ayrı bir tespit sinyalidir. Örneğin GREASE değerlerinin olmaması tek başına bot göstergesidir; çünkü 2018'den beri tüm büyük tarayıcılar GREASE gönderir. RFC 8701, GREASE mekanizmasını resmi olarak tanımlar ve tarayıcıların bu değerleri göndermesi beklendiğini belirtir.
Pratik ipucu: WAF'ler yalnızca tek bir sinyale bakmaz; bir skor hesaplar. GREASE eksikliği 3 puan, supported_groups farkı 4 puan, application_settings yokluğu 2 puan ekleyebilir. Eşik aşıldığında istek engellenir.
Çözüm: refraction-networking/utls ile Chrome Parmak İzi Taklidi
refraction-networking/utls, Go'nun TLS el sıkışmasını değiştirmenin standart yoludur. Bu kütüphane, crypto/tls'in Conn tipini genişletir ve önceden tanımlanmış tarayıcı parmak izleriyle ClientHello üretir. uTLS HelloChrome_Auto, en yaygın kullanılan preset'tir ve Chrome'un güncel parmak izini taklit eder.
Kurulum:
go get github.com/refraction-networking/utls
Temel kullanım — bir utls.UClient oluşturup HelloChrome_Auto parmak izini uygulayın:
package main
import (
"context"
"net"
"net/http"
"time"
utls "github.com/refraction-networking/utls"
)
func newUTLSTransport() *http.Transport {
return &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, _ := net.SplitHostPort(addr)
rawConn, err := (&net.Dialer{}).DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
cfg := &utls.Config{
ServerName: host,
NextProtos: []string{"h2", "http/1.1"},
}
uConn := utls.UClient(rawConn, cfg, utls.HelloChrome_Auto)
if err := uConn.HandshakeContext(ctx); err != nil {
rawConn.Close()
return nil, err
}
_ = port
return uConn, nil
},
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
func main() {
client := &http.Client{Transport: newUTLSTransport(), Timeout: 30 * time.Second}
resp, err := client.Get("https://tls.peet.ws/api/all")
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Artık JA3/JA4 Chrome ile eşleşiyor
body := make([]byte, 4096)
n, _ := resp.Body.Read(body)
println(string(body[:n]))
}
Bu örnekte DialTLSContext, standart TCP bağlantısını açar ve üzerine utls.UClient ile Chrome parmak izini uygular. HelloChrome_Auto, en güncel Chrome parmak izini otomatik seçer; Safari gerekiyorsa HelloSafari_16_0 kullanın. NextProtos alanında ALPN değerlerini açıkça belirtmek, HTTP/2 müzakeresinin doğru yapılmasını sağlar.
Dikkat:HelloChrome_Autozaman içinde güncellenir. Chrome yeni bir TLS extension eklediğinde, uTLS yeni bir preset yayınlar. Bu yüzdengo get -u github.com/refraction-networking/utlsile periyodik güncelleyin.
HTTP/2 Üzerinde Ekstra Uyum
uTLS yalnızca TLS katmanını düzeltir. HTTP/2 frame sıralaması ve SETTINGS parametreleri ayrı bir parmak izi alanıdır (JA4H bunu yakalar). Go'nun golang.org/x/net/http2 paketi, Chrome'dan farklı SETTINGS değerleri gönderir: HEADER_TABLE_SIZE 4096 yerine 65536, INITIAL_WINDOW_SIZE farklı. Bu farkları da hizalamak için github.com/refraction-networking/utls/dicttls veya özel HTTP/2 transport katmanı kullanmanız gerekebilir. Tam uyum için CycleTLS veya azuretls-client gibi üst seviye kütüphaneler bu hizalamayı dahili yapar.
Üst Seviye Alternatifler: CycleTLS ve azuretls-client
Her seferinde DialTLSContext yazmak istemiyorsanız, iki olgun alternatif vardır.
CycleTLS
CycleTLS, Go ve Node.js için TLS parmak izi taklidi yapan bir kütüphanedir. uTLS'i sarmalar ve Ja3 string'i ile User-Agent'ı birlikte ayarlamanıza izin verir. Basit API:
import "github.com/Danny-Dasilva/CycleTLS/cycletls"
cycle := cycletls.Init()
resp, err := cycle.Do("https://tls.peet.ws/api/all", cycletls.Options{
Ja3: "771,4865-4866-4867-49195-49199-...,0-23-65257-...,29-23-24,0",
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
}, "GET")
CycleTLS, JA3 string'ini manuel vermenizi ister; bu, esnektir ama güncel Chrome parmak izini kendiniz takip etmeniz demektir.
azuretls-client
azuretls-client, daha yüksek seviyeli bir HTTP istemcisidir. uTLS'i dahili kullanır, HTTP/2 frame sıralamasını Chrome ile hizalar ve proxy desteği yerleşiktir:
import "github.com/Noooste/azuretls-client"
session := azuretls.NewSession()
session.Browser = azuretls.Chrome
session.SetProxy("http://user-country-US-session-abc123:pass@gate.proxyhat.com:8080")
resp, err := session.Get("https://tls.peet.ws/api/all")
azuretls-client, TLS + HTTP/2 + header sıralamasını tek seferde hizalar. Proxy entegrasyonu da tek satır.
| Özellik | Manuel uTLS | CycleTLS | azuretls-client |
|---|---|---|---|
| TLS parmak izi kontrolü | Tam | Manuel JA3 string | Otomatik (Chrome/Safari/Firefox) |
| HTTP/2 frame uyumu | Yok (ekstra gerekir) | Kısmi | Tam |
| Proxy desteği | Manuel | Var | Yerleşik |
| Bakım sıklığı | Yüksek | Orta | Düşük |
| Öğrenme eğrisi | Dik | Orta | Alçak |
Parmak İzi Taklidi Tek Başına Yetmez: ProxyHat Residential Proxy ile Temiz Ağ Kimliği
uTLS ile JA3'ünüz Chrome ile eşleşti — ama istek hâlâ engelleniyorsa, sorun IP katmanındadır. WAF'ler JA3/JA4'ü IP itibarıyla birlikte değerlendirir. Bir veri merkezi IP'sinden gelen Chrome benzeri ClientHello, yine de şüpheli görünür; çünkü gerçek Chrome kullanıcıları veri merkezi bloklarından gezinmez.
Çözüm: uTLS istemcisini ProxyHat residential proxy'leri üzerinden yönlendirmek. Böylece JA3 ve IP itibarı aynı anda insan benzeri görünür.
Go ile ProxyHat Üzerinden uTLS İsteği
Aşağıdaki örnek, uTLS el sıkışmasını ProxyHat residential proxy'si üzerinden yapar. Proxy HTTP CONNECT tüneli kullanır; TLS el sıkışması proxy üzerinden gerçek hedefe yapılır, bu yüzden JA3 hedef sunucu tarafından doğru biçimde görülür.
package main
import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/url"
"time"
utls "github.com/refraction-networking/utls"
)
func newProxiedUTLSTransport(proxyURL string) *http.Transport {
proxy, _ := url.Parse(proxyURL)
return &http.Transport{
Proxy: http.ProxyURL(proxy),
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// 1. Proxy'ye bağlan
proxyAddr := proxy.Host
rawConn, err := (&net.Dialer{Timeout: 15 * time.Second}).DialContext(ctx, "tcp", proxyAddr)
if err != nil {
return nil, err
}
// 2. CONNECT tüneli aç
host, _, _ := net.SplitHostPort(addr)
connectReq := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\nProxy-Authorization: %s\r\n\r\n",
addr, addr, basicAuth(proxy.Username, "pass"))
_, err = rawConn.Write([]byte(connectReq))
if err != nil {
rawConn.Close()
return nil, err
}
// CONNECT yanıtını oku (200 OK beklenir)
buf := make([]byte, 1024)
n, _ := rawConn.Read(buf)
if !bytes.Contains(buf[:n], []byte("200")) {
rawConn.Close()
return nil, fmt.Errorf("CONNECT failed: %s", buf[:n])
}
// 3. Tünel üzerinden uTLS el sıkışması
cfg := &utls.Config{ServerName: host, NextProtos: []string{"h2", "http/1.1"}}
uConn := utls.UClient(rawConn, cfg, utls.HelloChrome_Auto)
if err := uConn.HandshakeContext(ctx); err != nil {
rawConn.Close()
return nil, err
}
return uConn, nil
},
ForceAttemptHTTP2: true,
}
}
func basicAuth(user, pass string) string {
return "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+pass))
}
func main() {
// ProxyHat residential proxy — US çıkışlı, sticky session
proxyURL := "http://user-country-US-session-abc123:pass@gate.proxyhat.com:8080"
client := &http.Client{
Transport: newProxiedUTLSTransport(proxyURL),
Timeout: 30 * time.Second,
}
resp, err := client.Get("https://tls.peet.ws/api/all")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Bu örnekte dikkat edin: user-country-US-session-abc123 kullanıcı adı formatı, ProxyHat'ın geo-hedefleme ve sticky session özelliklerini kullanır. gate.proxyhat.com:8080 HTTP proxy gateway'idir. CONNECT tüneli sayesinde TLS el sıkışması proxy tarafından değil, hedef sunucu tarafından görülür; yani JA3/JA4 doğru biçimde iletilir.
SOCKS5 gerekiyorsa socks5://user-country-US-session-abc123:pass@gate.proxyhat.com:1080 formatını kullanın. SOCKS5, CONNECT'ten daha az ek yük getirir ve bazı senaryolarda 10–15 ms daha düşük gecikme sağlar.
Önemli: uTLS parmak izini değiştirseniz bile, veri merkezi IP'si kullanıyorsanız WAF'ler yine de bot skorunuzu yükseltir. Residential proxy + uTLS kombinasyonu, en düşük engellenme oranını verir. ProxyHat'ın lokasyon sayfasında 190+ ülke seçeneği bulunur; hedef kitlenizin bulunduğu ülkeyi seçerek coğrafi tutarlılığı da sağlayın.
JA4, JA3'ün Yerini Alıyor: Parrot'ları Güncel Tutun
JA4, FoxIO JA4 projesi tarafından geliştirilen daha keskin bir parmak izi standardıdır. JA3'ten farkı: cipher listesini sıralamaz (alfabetik sıralar), bu yüzden tarayıcıların küçük cipher sıralaması değişiklikleri JA4'ü bozmaz. Ayrıca TLS sürümü, SNI varlığı, ALPN ve extension sayısını ayrı segmentler olarak kodlar. Sonuç: t13d1516h2_… formatında bir özet. Go'nun JA4'ü Chrome'dan farklıdır çünkü extension sayısı ve supported_groups farklıdır.
uTLS'in HelloChrome_Auto preset'i, JA3'ü iyi taklit eder ama JA4'ü her zaman %100 eşlemez; özellikle Chrome yeni extension eklediğinde. Bu yüzden:
- Periyodik doğrulama: Her ay
tls.peet.ws/api/allüzerinden hem JA3 hem JA4 çıktınızı gerçek Chrome ile karşılaştırın. - uTLS güncelleme:
go get -u github.com/refraction-networking/utls@latestile yeni preset'leri alın. - Parrot sürümü takibi: Chrome major sürüm değiştirdiğinde JA4 değişir. uTLS genelde 2–4 hafta içinde yeni parrot yayınlar.
- HTTP/2 frame parmak izi: JA4H, HTTP/2 SETTINGS frame'ini de özetler. Go'nun standart http2 transport'u Chrome'dan farklı SETTINGS gönderir; azuretls-client bu farkı kapatır.
Bu adımları takip eden ekipler, genelde %95+ başarı oranına ulaşır. uTLS + residential proxy kombinasyonu ile SERP scraping, fiyat izleme ve sosyal medya araştırması senaryolarında engellenme oranı belirgin biçimde düşer. Daha fazla scraping senaryosu için web scraping kullanım sayfamızı inceleyin; SERP takibi için SERP tracking rehberimize bakın. Fiyatlandırma detayları için ProxyHat fiyatlandırma sayfasını ziyaret edin; resmi proxy bağlantı parametreleri için ProxyHat dokümantasyonuna başvurun.
Yasal ve Etik Çerçeve
TLS parmak izi düzeltme, yalnızca yetkisiz erişim için bir teknik değildir. Meşru kullanım alanları şunlardır:
- Güvenlik araştırması: WAF davranışını anlamak, kendi siteniz üzerinde sızma testi yapmak.
- Meşru otomasyon: Hedef sitenin ToS'una ve
robots.txtkurallarına uyarak veri toplama. - Fiyat izleme: Rakip fiyatlarını toplamak için公开发 API'lerine erişim.
- QA testi: Kendi uygulamanızın farklı TLS istemcilerinden gelen istekleri nasıl işlediğini doğrulama.
Her durumda, hedef sitenin kullanım koşullarına ve robots.txt dosyasına saygı gösterin. GDPR ve CCPA kapsamında kişisel veri toplamıyorsanız bu teknikler sorun yaratmaz; kişisel veri içeriyorsa açık rıza gerekir.
Key Takeaways
- Go'nun
crypto/tlsClientHello'su statiktir; GREASE eksiktir, cipher sıralaması Chrome'dan farklıdır ve eksik extension'lar içerir — bu yüzden JA3/JA4 belirgin biçimde farklıdır. refraction-networking/utlsileHelloChrome_Autokullanarak TLS el sıkışmasını Chrome ile hizalayın; Safari içinHelloSafari_16_0kullanın.- uTLS tek başına yetmez; HTTP/2 frame sıralaması (JA4H) ayrı bir parmak izi alanıdır. Tam uyum için azuretls-client veya CycleTLS kullanın.
- Parmak izi + IP itibarı birlikte değerlendirilir; uTLS istemcisini ProxyHat residential proxy üzerinden
gate.proxyhat.com:8080ile yönlendirin. - JA4, JA3'ün yerini alıyor; parrot'ları her ay doğrulayın ve uTLS'i güncel tutun.
- Meşru otomasyon ve güvenlik araştırması için kullanın; hedef sitenin ToS ve
robots.txtkurallarına uyun.
Go ile inşa ettiğiniz scraper'lar TLS parmak izi yüzünden engelleniyorsa, uTLS + ProxyHat residential proxy kombinasyonu en düşük engellenme oranını sunar. ProxyHat fiyatlandırmasını inceleyin ve ilk isteğinizi bugün gönderin.






