إذا كنت مهندس Go تبني عميل scraping أو أداة أتمتة وتجد أن طلباتك تُحظر فورًا بواسطة Cloudflare أو Akamai رغم أنك تستخدم بروكسي نظيف، فالمشكلة على الأرجح ليست في عنوان IP — بل في بصمة TLS في Go net/http. مكتبة crypto/tls القياسية في Go تُنتج ClientHello ثابتًا يسهل تمييزه عن متصفحات Chrome الحقيقية، مما يمنحك JA3/JA4 فريدًا تكتشفه جدرار الحماية (WAF) في أقل من 200ms.
في هذا الدليل، سنحلل بالتفصيل لماذا تختلف بصمة Go عن Chrome، وكيف تُصلحها باستخدام refraction-networking/utls، ثم ندمج النتيجة مع بروكسي سكني عبر ProxyHat ليبدو كل من JA3 وسمعة IP كأنهما من مستخدم حقيقي. كل الأمثلة هنا مُصممة لأغراض البحث الأمني الشرعي والأتمتة المصرّح بها.
بصمة TLS في Go net/http: لماذا يُحظر عميلك القياسي؟
عندما يبدأ Go اتصال TLS، تستدعي طبقة net/http مكتبة crypto/tls التي تُنشئ رسالة ClientHello بترتيب cipher suites ثابت ومجموعة extensions محددة. هذا الترتيب لا يتغير بين الإصدارات إلا نادرًا، ولا يتضمن قيم GREASE (Generate Random Extensions And Sustain Entropy) التي تستخدمها Chrome وFirefox لإضافة عشوائية مدروسة.
النتيجة؟ بصمة JA3 ثابتة لكل عميل Go تقريبًا. يمكن لأي WAF مطابقتها بقاعدة واحدة: ja3 == "649..." ← حظر. هذا يفسر لماذا تُرفض طلباتك حتى مع تغيير User-Agent وإضافة headers واقعية — البصمة على مستوى TLS تكشف الحقيقة قبل حتى أن يقرأ WAF جسم الطلب. حسب RFC 8446 (TLS 1.3)، رسالة ClientHello مصممة للتفاوض على المعلمات الأمنية، لكن الترتيب والامتدادات المرسلة تخلق بصمة سلوكية فريدة.
التقاط بصمتك الحالية
قبل إصلاح أي شيء، قِس المشكلة. استخدم خدمة tls.peet.ws لالتقاط JA3 وJA4 لعميل Go الحالي:
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))
}
ستلاحظ أن JA3 الناتج يختلف جذريًا عن JA3 الخاص بـ Chrome. على سبيل المثال، قد ترى JA3 مثل 649...771,4865-4866-4867... بينما Chrome يُنتج شيئًا مثل 771,4865-4866-4867-49195-49199... مع قيم GREASE موزعة في مواقع محددة. الفرق واضح وفاضح.
تفكيك إشارات ClientHello: Go مقابل Chrome الحقيقي
لِمَ يختلف الأمر؟ لنُفكّك الإشارات واحدة تلو الأخرى ونقارنها بشكل ملموس.
1. Cipher Suites (مجموعات التشفير)
Go يرسل cipher suites بترتيب ثابت يبدأ بـ TLS_AES_128_GCM_SHA256، بينما Chrome يُضيف قيم GREASE عشوائية في البداية وبين كل cipher suite. مثلاً:
// Go net/http (ثابت، بدون GREASE)
TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256...
// Chrome (مع GREASE — القيم العشوائية مميزة بـ *)
*0x0a0a*, TLS_AES_128_GCM_SHA256, *0x1a1a*,
TLS_AES_256_GCM_SHA384, *0x2a2a*, TLS_CHACHA20_POLY1305_SHA256...
قيم GREASE هذه محددة في RFC 8701 وتُضاف في مواقع دقيقة داخل قائمة cipher suites وextensions وsupported groups. Go لا يطبق RFC 8701 على الإطلاق.
2. Supported Groups (مجموعات الدعم)
Go يرسل x25519, secp256r1, secp384r1 بترتيب ثابت. Chrome يُضيف قيم GREASE ويُرتب المجموعات بشكل مختلف، مع إضافة x25519_kyber768 في الإصدارات الحديثة لدعم post-quantum.
3. Signature Algorithms (خوارزميات التوقيع)
قائمة Go محدودة وثابتة (حوالي 8 خوارزميات). Chrome يدعم قائمة أوسع (12+ خوارزمية) تتضمن ed25519 وترتيبًا مختلفًا يعكس أولويات المتصفح.
4. Extensions (الامتدادات)
Go يرسل حوالي 10-12 extension بترتيب ثابت. Chrome يرسل 15-20 extension بما في ذلك encrypted_client_hello (ECH)، application_settings، وgrease extensions. غياب هذه الامتدادات وحدها يكفي لتمييز Go عن أي متصفح حقيقي.
5. Key Share (مشاركة المفاتيح)
Go يرسل key_share لـ x25519 فقط. Chrome يرسل key_share لـ x25519 وsecp256r1 معًا، وأحيانًا x25519_kyber768 في الإصدارات الأحدث. عدد key shares الإضافية يُغير طول ClientHello وبالتالي يُغير JA3.
6. ALPN (تطبيق البروتوكول)
Go يرسل h2, http/1.1 بترتيب ثابت. Chrome يُضيف قيمة GREASE في البداية: *0x0a0a*, h2, http/1.1.
| الإشارة | Go net/http | Chrome الحقيقي |
|---|---|---|
| GREASE في Cipher Suites | غير موجود | موجود (عشوائي) |
| عدد Extensions | 10-12 | 15-20 |
| Key Share Groups | x25519 فقط | x25519 + secp256r1 |
| encrypted_client_hello | غير مدعوم | مدعوم |
| application_settings | غير موجود | موجود |
| ALPN GREASE | غير موجود | موجود |
الخلاصة: حتى لو نجحت في مطابقة User-Agent وHTTP headers، بصمة TLS تكشف أنك لست Chrome. WAFs مثل Cloudflare تستخدم JA3/JA4 كطبقة أولى للفلترة قبل حتى فحص الـ headers.
الحل: استخدام uTLS لتمويه بصمة المصافحة
مكتبة refraction-networking/utls هي fork من crypto/tls تتيح لك تحديد بصمة ClientHello مسبقة الصنع تطابق متصفحًا حقيقيًا. الأكثر شيوعًا هو HelloChrome_Auto الذي يطابق أحدث إصدار Chrome تلقائيًا، وHelloSafari_16_0 للحالات التي تريد فيها مطابقة بصمة Safari.
الإعداد الأساسي مع uTLS
package main
import (
"context"
"fmt"
"io"
"net"
"net/http"
utls "github.com/refraction-networking/utls"
)
func main() {
transport := &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, _, _ := net.SplitHostPort(addr)
// الاتصال TCP العادي
rawConn, err := (&net.Dialer{}).DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
// إنشاء UClient مع بصمة Chrome
uConn := utls.UClient(rawConn, &utls.Config{
ServerName: host,
}, utls.HelloChrome_Auto)
// بدء المصافحة
if err := uConn.HandshakeContext(ctx); err != nil {
rawConn.Close()
return nil, err
}
return uConn, nil
},
}
client := &http.Client{Transport: transport}
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))
}
بعد تشغيل هذا الكود، ستحصل على JA3/JA4 يطابق Chrome. جرّب أيضًا HelloSafari_16_0 إذا كنت تريد مطابقة بصمة Safari بدلاً من Chrome — مفيد لبعض حالات الاستخدام التي تستهدف جمهور Apple.
لماذا HelloChrome_Auto؟
HelloChrome_Auto يختار تلقائيًا أحدث بصمة Chrome متوفرة في uTLS. هذا يعني أنك لست بحاجة لتحديث الكود عند خروج إصدار Chrome جديد — المكتبة تتكفل بذلك. ومع ذلك، يجب عليك تحديث uTLS نفسه بانتظام (كل 2-3 أشهر) لمواكبة التغييرات في بصمة Chrome.
بدائل عالية المستوى: CycleTLS وazuretls-client
إذا كنت لا تريد التعامل مع DialTLSContext يدويًا، هناك مكتبتان توفران تجريدًا أعلى:
| المكتبة | المستوى | بصمة Chrome | بصمة Safari | البروكسي المدمج | صعوبة الإعداد |
|---|---|---|---|---|---|
| Go net/http | منخفض | ❌ | ❌ | ✅ (HTTP/SOCKS5) | سهل |
| uTLS مباشر | متوسط | ✅ | ✅ | يدوي | متوسط |
| CycleTLS | عالٍ | ✅ | ✅ | ✅ | سهل |
| azuretls-client | عالٍ | ✅ | ✅ | ✅ | سهل |
CycleTLS
import "github.com/Danny-Dasilva/CycleTLS/cycletls"
client := cycletls.Init()
resp, err := client.Do("https://tls.peet.ws/api/all", cycletls.Options{
Ja3: "771,4865-4866-4867-49195-49199-49196-49200...",
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
}, "GET")
CycleTLS يسمح بتمرير JA3 كنص حر، مما يمنحك تحكمًا دقيقًا في البصمة، لكنه يتطلب منك الحفاظ على JA3 محدثًا يدويًا عند كل تحديث لـ Chrome.
azuretls-client
import "github.com/Noooste/azuretls-client"
session := azuretls.NewSession()
session.OrderedHeaders = azuretls.DefaultOrderedHeaders
session.Browser = azuretls.Chrome
resp, err := session.Get("https://tls.peet.ws/api/all")
azuretls-client أبسط: حدد المتصفح وستتولى المكتبة الباقي، بما في ذلك ترتيب الـ headers وبصمة TLS. كما يدعم البروكسي بشكل مدمج عبر session.SetProxy().
دمج البروكسي السكني: الجمع بين uTLS وProxyHat
مطابقة بصمة TLS وحدها لا تكفي. إذا كان عنوان IP المصدر يخص مركز بيانات (datacenter) معروف، فإن Cloudflare وAkamai سيُعلمون أن "Chrome" الذي يتصل من IP مركز بيانات هو على الأرجح بوت. الحل هو توجيه عميل uTLS عبر بروكسي سكني من ProxyHat حتى تبدو سمعة IP وكذلك JA3 كأنهما من مستخدم منزلي حقيقي.
مثال كامل: uTLS + بروكسي سكني من ProxyHat
package main
import (
"context"
"encoding/base64"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
utls "github.com/refraction-networking/utls"
)
func main() {
proxyURL, _ := url.Parse(
"http://user-country-US-session-abc123:pass@gate.proxyhat.com:8080",
)
transport := &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, _, _ := net.SplitHostPort(addr)
// 1. الاتصال بالبروكسي
conn, err := net.Dial("tcp", proxyURL.Host)
if err != nil {
return nil, err
}
// 2. إرسال CONNECT للبروكسي
auth := base64.StdEncoding.EncodeToString(
[]byte(proxyURL.User.String()),
)
connectReq := fmt.Sprintf(
"CONNECT %s HTTP/1.1\r\n"+
"Host: %s\r\n"+
"Proxy-Authorization: Basic %s\r\n\r\n",
addr, addr, auth,
)
if _, err = conn.Write([]byte(connectReq)); err != nil {
conn.Close()
return nil, err
}
// 3. قراءة رد البروكسي (200 Connection Established)
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
if !strings.Contains(string(buf[:n]), "200") {
conn.Close()
return nil, fmt.Errorf("proxy CONNECT failed: %s", string(buf[:n]))
}
// 4. uTLS handshake عبر النفق
uConn := utls.UClient(conn, &utls.Config{
ServerName: host,
}, utls.HelloChrome_Auto)
if err := uConn.HandshakeContext(ctx); err != nil {
conn.Close()
return nil, err
}
return uConn, nil
},
}
client := &http.Client{Transport: transport}
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))
}
نصيحة: استخدمuser-country-US-session-abc123في اسم المستخدم للحصول على IP سكني ثابت من الولايات المتحدة. هذا يضمن أن JA3 + IP + الموقع الجغرافي جميعها تبدو كأنها من مستخدم منزلي في أمريكا. جرّب أيضًاuser-country-DE-city-berlinللاستهداف على مستوى المدينة. راجع قائمة المواقع المتاحة للمزيد.
لماذا البروكسي السكني وليس Datacenter؟
البروكسي السكني يمنحك عنوان IP مسجل لدى ISP حقيقي (مثل AT&T أو Comcast)، بينما البروكسي Datacenter يمنحك IP من نطاقات معروفة لـ AWS أو DigitalOcean. WAFs تحتفظ بقوائم سوداء لنطاقات مراكز البيانات وتُعطي درجة ثقة أقل لأي طلب يأتي منها، حتى لو كانت بصمة TLS صحيحة. الجمع بين uTLS (JA3 نظيف) + بروكسي سكني (IP نظيف) يرفع نسبة نجاح الطلبات من 30-40% إلى أكثر من 95% في كثير من المواقع المحمية.
يمكنك الاطلاع على أسعار ProxyHat لاختيار الخطة المناسبة، أو زيارة صفحة حالات استخدام الـ scraping لفهم المزيد حول سيناريوهات التطبيق العملية.
JA4 يحل محل JA3: كيف تبقى محدثًا
JA3 كان المعيار لسنوات، لكنه يواجه مشاكل: ترتيب cipher suites يُحسب كـ hash واحد، مما يعني أن أي تغيير طفيف يُنتج hash مختلف تمامًا. JA4 هو المعيار الأحدث الذي يحل هذه المشكلة بتقسيم البصمة إلى أقسام منفصلة (JA4_c للـ cipher، JA4_s للـ supported groups، JA4_h للـ hash النهائي) قابلة للمقارنة جزئيًا.
لماذا يهمك هذا؟ لأن WAFs تنتقل تدريجيًا من JA3 إلى JA4. إذا كنت تستخدم uTLS مع HelloChrome_Auto، فالبصمة JA4 ستتحدث تلقائيًا مع تحديث المكتبة. لكن إذا كنت تستخدم JA3 ثابت مع CycleTLS، فستحتاج لتحديث قيمة JA4 يدويًا عند كل تحديث لـ Chrome.
أفضل الممارسات للبقاء محدثًا
- حدّث uTLS كل 2-3 أشهر لمواكبة تغييرات بصمة Chrome.
- اختبر بصمتك على
tls.peet.ws/api/allبعد كل تحديث للتأكد من أن JA3/JA4 يطابقان Chrome الحالي. - راقب معدل نجاح طلباتك: إذا انخفض فجأة من 95% إلى 60%، فهذا يعني أن البصمة أصبحت قديمة.
- فكر في التبديل إلى
HelloChrome_120أو إصدار محدد إذا كانHelloChrome_Autoغير مستقر. - تجنب إرسال 100 طلب متزامن من نفس IP السكني — وزّع الحمل عبر جلسات متعددة.
الخلاصة الرئيسية
- بصمة TLS في Go net/http ثابتة ومميزة — WAFs تكتشفها فورًا عبر JA3/JA4 في أقل من 200ms.
- uTLS مع HelloChrome_Auto يحل المشكلة بمطابقة بصمة Chrome الحقيقية على مستوى ClientHello، بما في ذلك GREASE وkey shares متعددة.
- البروكسي السكني ضروري — JA3 نظيف + IP من datacenter = حظر. استخدم ProxyHat على
gate.proxyhat.com:8080معuser-country-US-session-abc123.- JA4 يحل محل JA3 — تأكد من تحديث uTLS بانتظام لمواكبة التغييرات في بصمة Chrome.
- CycleTLS وazuretls-client بديلان أبسط إذا كنت لا تريد إدارة DialTLSContext يدويًا.
ابدأ اليوم بتجربة ProxyHat أو اطلع على حالات تتبع SERP لفهم كيف يدمج المهندسون uTLS مع البروكسي السكني في الإنتاج. للحصول على وثائق تقنية كاملة، راجع وثائق ProxyHat.






