اسکریپتی به زبان پایتون نوشتهاید. از یک پروکسی بیرون میرود، آدرس IP در هر درخواست عوض میشود و در هدر User-Agent هم یک رشته بهروز از کروم گذاشتهاید. با این حال سایت همان نخستین درخواست را با 403 پاسخ میدهد. وقتی همان آدرس را با همان IP در یک مرورگر واقعی باز میکنید، صفحه میآید. تفاوت در بستهای است که پیش از هر هدر HTTP حرکت میکند: پیام ClientHello که اتصال رمزگذاریشده را آغاز میکند. سایت از روی چیدمان همین پیام تشخیص میدهد که طرف مقابلش کروم است، پایتون است یا curl، بیآنکه حتی یک هدر را بخواند.
در این نوشته دستدادن TLS، فیلدهای داخل ClientHello، شیوه محاسبه رشته JA3 از این فیلدها و تفاوت کار JA4 را میبینید. سپس به پرسشی میرسیم که بیش از همه شنیدهایم: آیا پروکسی اثر انگشت TLS را عوض میکند؟ در پایان یک نمونه آزمودهشده پایتون هست که رشته JA3 کلاینت خودتان را بهصورت محلی نشان میدهد.
دستدادن TLS چیست؟
TLS پروتکلی است که ترافیک میان مرورگر و سایت را رمزگذاری میکند؛ همان https:// نوار آدرس نشان میدهد که از آن استفاده میکنید. پیش از آغاز رمزگذاری، دو طرف باید بر سر نسخه، مجموعه رمز و کلید به توافق برسند. به این چانهزنی کوتاه دستدادن (handshake) میگویند. نسخه کنونی، یعنی TLS 1.3، در RFC 8446 تعریف شده است و دستدادن تقریباً چنین پیش میرود:
- کلاینت با TCP به سرور وصل میشود و پیام
ClientHelloمیفرستد. در آن مجموعههای رمز پشتیبانیشده، افزونهها و سهم کلید قرار دارد. - سرور از فهرست یکی را برمیگزیند و با
ServerHelloپاسخ میدهد. از این نقطه هر دو طرف میتوانند کلید مشترک را استخراج کنند. - سرور گواهی خود و پیام
Finishedرا که درستی دستدادن را ثابت میکند، هر دو رمزگذاریشده، میفرستد. - کلاینت گواهی را بررسی میکند و پیام
Finishedخودش را میفرستد. - نخستین درخواست HTTP (
GET /، هدرها،User-Agent) تنها پس از این مرحله و از درون کانال رمزگذاریشده عبور میکند.
از نگاه اثر انگشت، گام نخست اهمیت دارد. ClientHello پیش از وجود هر کلید مشترکی فرستاده میشود، پس رمزگذارینشده است؛ سرور، CDN جلوی آن و هر دستگاه شبکه در مسیر میتواند آن را همانگونه که هست بخواند، پیش از هر هدر، هر کوکی و هر خط جاوااسکریپت.
پیام ClientHello چه فیلدهایی دارد؟
ClientHello فهرستی است که کلاینت در آن میگوید «اینها را میتوانم صحبت کنم». فیلدهایی که در اثر انگشت به کار میروند اینها هستند:
| فیلد | چه چیزی دارد؟ | چرا متمایزکننده است؟ |
|---|---|---|
legacy_version | فیلد نسخه قدیمی. کلاینتهای TLS 1.3 برای سازگاری هنوز مقدار TLS 1.2 یعنی 771 را اینجا مینویسند | بهتنهایی اطلاعات کمی دارد؛ نسخه واقعی در یک افزونه است |
cipher_suites | مجموعههای رمز پشتیبانیشده، بهترتیب اولویت | هم فهرست و هم ترتیب از کتابخانهای به کتابخانه دیگر فرق میکند |
extensions | شماره نوع افزونهها: server_name (0)، supported_groups (10)، signature_algorithms (13)، ALPN (16)، supported_versions (43)، key_share (51) و بقیه | اینکه چه افزونههایی هستند و با چه ترتیبی، ویژه هر نرمافزار است |
supported_groups | منحنیها و گروههای قابل استفاده در تبادل کلید (29 = x25519، 23 = secp256r1) | گروههای تازه زودتر به مرورگرها میرسند |
ec_point_formats | قالبهای نقطه منحنی بیضوی | برخی کتابخانهها سه مقدار میفرستند و برخی تنها یکی |
signature_algorithms | الگوریتمهای امضای پذیرفتهشده، بهترتیب | JA3 از آن استفاده نمیکند، JA4 میکند |
| ALPN | پروتکل کاربردی که کلاینت میخواهد صحبت کند (h2، http/1.1) | مرورگر h2 میخواهد؛ کلاینتهای ساده بیشتر وقتها هیچ نمیخواهند |
پرکننده این فهرستها برنامه شما نیست، بلکه کتابخانه TLS زیر آن است. کروم از BoringSSL و فایرفاکس از NSS استفاده میکند. ماژول ssl پایتون و Node.js روی OpenSSL بنا شدهاند. curl همراه ویندوز از Schannel، یعنی پشته TLS خود سیستمعامل، بهره میبرد. هر کتابخانه فهرست رمز پیشفرض، مجموعه افزونه و ترتیب متفاوتی دارد؛ هویت کلاینت اینگونه به نخستین بسته دستدادن نشت میکند.
اثر انگشت TLS چیست؟
اثر انگشت TLS همین فهرستهای ClientHello است که به رشتهای کوتاه و قابل مقایسه فشرده شدهاند. سه ویژگی آن را از دیگر سیگنالها جدا میکند:
- منفعل است. سایت چیزی را در کلاینت اجرا نمیکند، فقط نخستین بسته رسیده را میخواند. خاموش کردن جاوااسکریپت یا پاک کردن کوکیها در نتیجه اثری ندارد.
- از هدرها مستقل است.
User-Agentیک خط متن است و در هر کتابخانه HTTP با یک خط کد عوض میشود.ClientHelloاز رفتار کامپایلشده کتابخانه میآید. - نرمافزار را میشناساند، نه شخص را. هر کسی که نسخه یکسانی از کروم را روی سیستمعامل یکسانی اجرا کند همان مقدار را تولید میکند. لایه جاوااسکریپت (canvas، فونت، WebGL) که هدفش جدا کردن تکتک دستگاههاست را در اثر انگشت مرورگر چیست؟ شرح دادهایم.
این روش برای تشخیص بات ابداع نشد. نخستین کاربردش امنیت شبکه بود: بدافزار ترافیکش را رمز میکند، اما کتابخانه TLS و تنظیماتش بیشتر وقتها ثابت است. تیم امنیتی که محتوا را نمیبیند، همان خانواده نرمافزاری را از چیدمان ClientHello آن بازمیشناسد.
JA3 چیست و چگونه محاسبه میشود؟
JA3 نخستین روش فراگیری است که این ایده را به قالبی استاندارد بست. در سال 2017 در Salesforce به دست John Althouse، Jeff Atkinson و Josh Atkins ساخته و بهصورت متنباز منتشر شد. آنگونه که در مخزن JA3 در Salesforce توضیح داده شده، محاسبه پنج گام دارد:
- از پیام
ClientHelloپنج فیلد برداشته میشود: نسخه TLS، مجموعههای رمز، افزونهها، منحنیها (supported_groups) و قالبهای نقطه منحنی بیضوی. - مقدارهای هر فیلد به عدد دهدهی تبدیل و بهترتیب حضورشان در پیام با
-به هم چسبانده میشوند. - پنج فیلد با
,به هم میپیوندند. اگر فیلدی خالی باشد، جایش خالی میماند. - مقدارهای GREASE (پایینتر توضیح میدهیم) اصلاً وارد فهرستها نمیشوند.
- هش MD5 رشته حاصل گرفته میشود. این هش 32 نویسهای همان اثر انگشت JA3 است.
نمونه خود مخزن چنین است:
769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0
→ ada70206e40642a3e4461f35503241d5از چپ که بخوانیم: 769 یعنی TLS 1.0؛ سپس دوازده مجموعه رمز، سه افزونه، سه منحنی و یک قالب نقطه میآید. MD5 اینجا برای امنیت نیست، برای تبدیل رشتهای بلند به کلیدی جستجوپذیر با طول ثابت است.
دو نکته: چون کلاینتهای TLS 1.3 مقدار TLS 1.2 را در فیلد نسخه مینویسند، تقریباً همه رشتههای JA3 امروزی با 771 آغاز میشوند. Salesforce مخزن را در 1 مه 2025 بایگانی کرد؛ ابزارهایی مانند Wireshark همچنان این مقدار را محاسبه میکنند، اما روش دیگر نگهداری نمیشود.
GREASE چیست و چرا JA3 آن را نادیده میگیرد؟
GREASE سازوکاری برای استحکام است که در RFC 8701 تعریف شده. کلاینت به فهرست مجموعههای رمز، افزونهها و گروههای خود چند مقدار رزروشده تصادفی با الگوی 0x0A0A، 0x1A1A … 0xFAFA میافزاید. این مقدارها هیچ معنایی ندارند. هدف آن است که پیوسته آزموده شود آیا سرورها مقدارهای ناشناخته را بیصدا نادیده میگیرند یا نه: سروری معیوب که با دیدن مقدار ناشناخته اتصال را قطع میکند، امروز شناسایی میشود، نه روزی که قابلیت تازهای در TLS منتشر شود.
چون این مقدارها تصادفی انتخاب میشوند، اگر JA3 آنها را به حساب میآورد همان مرورگر در هر اتصال هش دیگری میساخت. به همین دلیل مستندات روش میخواهد مقدارهای GREASE کنار گذاشته شوند؛ در کد پایین این کار را تابع is_grease انجام میدهد.
چرا کروم ترتیب افزونهها را به هم میریزد؟
GREASE محتوای فهرستها را نگه میدارد، نه ترتیبشان را. تکیه نرمافزارهای سرور و دستگاههای میانی بر ترتیب ثابت افزونههای کروم همان خطر را داشت، پس تیم کروم ترتیب را هم تصادفی کرد. بنا بر رکورد موجود در Chrome Platform Status، قابلیت «TLS ClientHello extension permutation» در Chrome 110 بهصورت پیشفرض روشن شد. دلیل ثبتشده فرار از اثر انگشت نیست، جلوگیری از شکنندگی اکوسیستم است: ترتیب ثابت، توسعهدهندگان سرور را به شناختن کروم و فرض کردن رفتاری مشخص میراند و تغییرهای آینده TLS را دشوار میکند. RFC 8446 از پیش میگوید افزونهها میتوانند به هر ترتیبی بیایند؛ تنها استثنا pre_shared_key است که در صورت حضور باید آخر بایستد.
چون JA3 افزونهها را بهترتیب پیام به هم میچسباند، این تغییر هش JA3 مرورگرهای مبتنی بر Chromium را ناپایدار کرد. با شنونده پایین اندازه گرفتیم: 24 اتصال پیاپی یک مرورگر مبتنی بر Chromium به 24 هش JA3 متفاوت رسید. وقتی شماره افزونهها را مرتب کردیم، در همه یک مجموعه بود؛ تنها چیزی که عوض میشد ترتیب بود.
JA4 چیست و با JA3 چه فرقی دارد؟
JA4 قالب تازهتری است که FoxIO منتشر کرده و به این مشکل پاسخ میدهد. بنا بر سند فنی JA4 اثر انگشت از سه بخش به شکل a_b_c ساخته میشود. نمونه داخل سند، یعنی t13d1516h2_8daaf6152771_e5627efa2ab1، چنین خوانده میشود:
t: TLS روی TCP (qیعنی QUIC وdیعنی DTLS).13: TLS 1.3. JA4 نسخه را از افزونهsupported_versionsمیخواند، نه از فیلد قدیمی.d: افزونه SNI هست، یعنی کلاینت به یک نام دامنه وصل میشود (iنبودن SNI را نشان میدهد).15و16: 15 مجموعه رمز و 16 افزونه، بدون شمردن GREASE.h2: نویسه نخست و آخر نخستین مقدار فهرست ALPN، یعنیHTTP/2.8daaf6152771: کدهای شانزدهشانزدهی مجموعههای رمز مرتب میشوند، با SHA-256 هش میشوند و 12 نویسه نخست نگه داشته میشود.e5627efa2ab1: کدهای افزونه مرتب میشوند (بدون SNI و ALPN)، الگوریتمهای امضا با ترتیب اصلی به آن افزوده و نتیجه به همان شکل هش میشود.
مرتبسازی اثر بههمریختن افزونهها را از میان میبرد. بخش نخست خوانا هم اجازه میدهد بدون نگاه به هشها تفکیکی درشت انجام دهید: دو بخش نخست که یکی به h2 و دیگری بهدلیل نفرستادن ALPN به 00 ختم میشود، از یک نرمافزار نیامدهاند.
| JA3 | JA4 | |
|---|---|---|
| منتشرکننده | Salesforce (2017)، مخزن در 2025 بایگانی شد | FoxIO، توسعه ادامه دارد |
| قالب | یک هش MD5 با 32 نویسه | a_b_c: پیشوند خوانا + دو هش کوتاهشده SHA-256 |
| ترتیب افزونهها | ترتیب داخل پیام | مرتبشده؛ از بههمریختن اثر نمیگیرد |
| نسخه TLS | فیلد نسخه قدیمی (771 حتی در TLS 1.3) | افزونه supported_versions |
| ALPN و SNI | فقط بهشکل شماره افزونه دیده میشوند | هر دو در بخش نخست نوشته میشوند |
| الگوریتمهای امضا | استفاده نمیشود | وارد بخش سوم میشود |
| GREASE | نادیده گرفته میشود | نادیده گرفته میشود |
بنا بر یادداشت مجوز در مخزن FoxIO، JA4 که اثر انگشت کلاینت TLS است با مجوز BSD 3-Clause باز است؛ دیگر اعضای خانواده (JA4S، JA4H، JA4X، JA4T و ادامه آن) به مجوز جداگانه FoxIO وابستهاند.
آیا پروکسی اثر انگشت TLS را عوض میکند؟
نه. دلیلش در شیوه حمل ترافیک HTTPS به دست پروکسی است.
در پروکسی HTTP، کلاینت نخست درخواست CONNECT example.com:443 را به پروکسی میفرستد. پروکسی یک اتصال TCP به مقصد باز میکند، 200 Connection Established میگوید و از آن پس تنها بایت جابهجا میکند. ClientHello از درون این تونل میگذرد: کلاینت شما آن را میسازد، سایت مقصد آن را میخواند و پروکسی نه محتوایش را عوض میکند و نه دوباره مینویسد. در SOCKS5 هم وضع همین است؛ پروتکل اتصال TCP را یک لایه پایینتر جابهجا میکند و اصلاً نمیداند دادهای که حمل میکند TLS است. جریان گامبهگام هر دو پروتکل را در تفاوت پروکسی SOCKS و HTTP شرح دادهایم.
نتیجه این است: سایت همزمان آدرس IP پروکسی و اثر انگشت TLS کلاینت شما را میبیند. استفاده از پروکسی HTTPS یا پروکسی SOCKS5 این را عوض نمیکند؛ رزیدنشیال، موبایل یا دیتاسنتر بودن پروکسی هم فرقی نمیسازد. با یک پروکسی آزمایشی محلی امتحان کردیم: همان کلاینت curl به شنونده پایین یک بار مستقیم، یک بار با تونل CONNECT در HTTP و یک بار روی SOCKS5 وصل شد و شنونده هر سه بار همان رشته JA3 را ثبت کرد.
تنها حالتی که اثر انگشت عوض میشود، وجود واسطهای در میانه است که TLS را پایان میدهد:
| چه چیزی در میانه است | چه کسی TLS را با مقصد برقرار میکند؟ | سایت کدام اثر انگشت را میبیند؟ |
|---|---|---|
پروکسی HTTP (تونل CONNECT) | کلاینت شما | اثر انگشت کلاینت شما |
| پروکسی SOCKS5 | کلاینت شما | اثر انگشت کلاینت شما |
| VPN | کلاینت شما | اثر انگشت کلاینت شما |
| دروازه سازمانی با بازرسی TLS | دروازه | اثر انگشت دروازه |
| آنتیویروس با پویش HTTPS روشن | آنتیویروس | اثر انگشت آنتیویروس |
| سرویسی که صفحه را بهجای شما میگیرد | کلاینت خود سرویس | اثر انگشت کلاینت سرویس |
سطر پنجم را هنگام آماده کردن همین نوشته روی دستگاه خودمان دیدیم: curl در ویندوز وقتی مستقیم به سرویس بازتاب وصل میشد یک اثر انگشت و وقتی از تونل پروکسی محلی میگذشت اثر انگشت دیگری برمیگرداند. علت پویش HTTPS آنتیویروس بود که اتصال مستقیم را با پشته TLS خودش دوباره برقرار میکرد. اگر مقدار سرویس بازتاب به کتابخانهای که انتظار داشتید شبیه نیست، ببینید گواهی را چه کسی امضا کرده است.
رشته JA3 خودتان را چگونه ببینید؟
کوتاهترین راه یک سرویس بازتاب است: صفحه TLS در BrowserLeaks را در مرورگر باز کنید تا مقدارهای JA3 و JA4 خود را ببینید. اگر میخواهید در سطح بسته نگاه کنید، Wireshark این مقدارها را روی بستههای ClientHello خودش محاسبه میکند؛ در مرجع فیلترهای نمایش این فیلدها با نامهای tls.handshake.ja3 و tls.handshake.ja4 فهرست شدهاند.
برای دیدن خود محاسبه میتوانید از اسکریپت پایین استفاده کنید. تنها با کتابخانه استاندارد پایتون کار میکند و چیزی به بیرون نمیفرستد: روی پورت 8443 یک شنونده خام TCP باز میکند، پیام ClientHello هر کلاینت متصل را تجزیه میکند و رشته JA3 و هش آن را چاپ میکند. هنگام شروع، کلاینت urllib خود پایتون را یک بار وصل میکند. شنونده دستدادن را کامل نمیکند، نخستین بسته را میخواند و میبندد؛ خطای اتصال در سمت کلاینت وضعیتی مورد انتظار است.
import hashlib
import socket
import threading
import urllib.request
HOST, PORT = "127.0.0.1", 8443
def is_grease(value):
# RFC 8701: 0x0a0a, 0x1a1a, ... 0xfafa
return (value & 0x0F0F) == 0x0A0A and (value >> 8) == (value & 0xFF)
def read_client_hello(conn):
data = b""
while len(data) < 5:
data += conn.recv(4096)
if data[0] != 22: # 22 = handshake record
raise ValueError("not a TLS handshake")
record_length = int.from_bytes(data[3:5], "big")
while len(data) < 5 + record_length:
chunk = conn.recv(4096)
if not chunk:
break
data += chunk
return data[5 : 5 + record_length]
def ja3_from_client_hello(hello):
if hello[0] != 1: # 1 = ClientHello
raise ValueError("not a ClientHello")
pos = 4 # message type (1) + length (3)
version = int.from_bytes(hello[pos : pos + 2], "big")
pos += 2 + 32 # version + random
pos += 1 + hello[pos] # session_id
size = int.from_bytes(hello[pos : pos + 2], "big")
pos += 2
ciphers = [int.from_bytes(hello[i : i + 2], "big") for i in range(pos, pos + size, 2)]
pos += size
pos += 1 + hello[pos] # compression_methods
end = pos + 2 + int.from_bytes(hello[pos : pos + 2], "big")
pos += 2
extensions, groups, point_formats = [], [], []
while pos < end:
ext_type = int.from_bytes(hello[pos : pos + 2], "big")
ext_size = int.from_bytes(hello[pos + 2 : pos + 4], "big")
body = hello[pos + 4 : pos + 4 + ext_size]
pos += 4 + ext_size
extensions.append(ext_type)
if ext_type == 10: # supported_groups
groups = [int.from_bytes(body[i : i + 2], "big") for i in range(2, len(body), 2)]
elif ext_type == 11: # ec_point_formats
point_formats = list(body[1:])
def join(values):
return "-".join(str(v) for v in values if not is_grease(v))
fields = [str(version), join(ciphers), join(extensions), join(groups), join(point_formats)]
ja3_text = ",".join(fields)
return ja3_text, hashlib.md5(ja3_text.encode()).hexdigest()
def own_python_client():
try:
urllib.request.urlopen(f"https://localhost:{PORT}", timeout=5)
except OSError:
pass # the listener closes without replying, which is expected
def main():
server = socket.create_server((HOST, PORT))
print(f"listening on https://localhost:{PORT}, press Ctrl+C to quit")
threading.Thread(target=own_python_client, daemon=True).start()
while True:
conn, _ = server.accept()
with conn:
try:
ja3_text, ja3_hash = ja3_from_client_hello(read_client_hello(conn))
except (ValueError, IndexError, OSError) as exc:
print("could not read:", exc)
continue
print(ja3_hash, ja3_text)
if __name__ == "__main__":
main()هنگام اجرای اسکریپت، از ترمینالی دیگر کلاینتهای گوناگون را به همان آدرس بفرستید. در آدرس localhost بنویسید؛ کلاینتی که به آدرس IP وصل شود افزونه SNI را نمیفرستد و اثر انگشت عوض میشود.
curl -k https://localhost:8443
node -e "require('https').get('https://localhost:8443', { rejectUnauthorized: false }).on('error', () => {})"روی دستگاه ما (Windows 11، Python 3.13 با OpenSSL 3.0، Node.js 24 و curl 8 کامپایلشده با Schannel) خروجی چنین بود. برای خوانایی، فیلد مجموعههای رمز را کوتاه کردیم:
331a436afb23d4e31134c11b301bdcb5 771,4866-4867-4865-…,0-11-10-35-16-22-23-49-13-43-45-51-21,29-23-30-25-24-256-257-258-259-260,0-1-2
2e6c64f66822fc35b6a7a128b557f1de 771,4866-4865-49196-…,0-43-13-35-10-11-16-51-49-23-65281-45,29-23-24,0
944d1e1858cd278718f8a46b65d3212f 771,4866-4867-4865-…,65281-0-11-10-35-22-23-13-43-45-51,4588-29-23-30-24-25-256-257,0-1-2یک دستگاه، یک IP و سه هویت جدا: بهترتیب پایتون، curl و Node.js. سرویس بازتاب برای پایتون دقیقاً همان هش را برگرداند، یعنی تجزیهگر درست کار میکند. عدد 4588 در سطر Node.js همان گروه ترکیبی پساکوانتومی تبادل کلید است که در ثبتهای IANA با نام X25519MLKEM768 میآید؛ این نسخه پایتون آن را نمیفرستد. در همان Node.js، https.get و fetch درونساخت هم مقدار JA3 متفاوتی دادند، چون fetch افزونه ALPN را میافزاید: اثر انگشت به کتابخانه HTTP بستگی دارد، نه به زبان. تفاوت کتابخانهها در سمت پایتون را در مقایسه HTTPX، Requests و AIOHTTP سنجیدهایم.
برای سنجش اثر پروکسی، شنونده به کار نمیآید (پروکسی دوردست به localhost شما نمیرسد)، پس دو بار از سرویس بازتاب بپرسید:
import json
import urllib.request
PROXY = "http://user:pass@pr.proxynet.io:8000"
URL = "https://tls.browserleaks.com/json"
def fingerprint(opener):
with opener.open(URL, timeout=15) as response:
result = json.load(response)
return result["ja3_hash"], result["ja4"]
direct = urllib.request.build_opener(urllib.request.ProxyHandler({}))
proxied = urllib.request.build_opener(urllib.request.ProxyHandler({"https": PROXY}))
print("direct :", *fingerprint(direct))
print("proxied:", *fingerprint(proxied))در هر دو سطر مقدارهای یکسانی میبینید؛ تنها چیزی که عوض میشود آدرس IP است که سرویس میبیند.
صاحب سایت این داده را چگونه به کار میبرد؟
محصولات CDN و فایروال مقدار JA3 یا JA4 را در کنار هر درخواست بهصورت یک فیلد ارائه میکنند. صاحب سایت این فیلد را هنگام نوشتن قاعده و بررسی گزارشها به کار میبرد. کاربردهای رایج:
- بررسی سازگاری. اگر
User-Agentمیگوید «کروم» اما اثر انگشت با هیچ نسخه شناختهشده کروم جور نیست، هدر دستکاری شده است. بهتنهایی دلیل قطعی نیست، ولی نشانهای نیرومند است که بر امتیاز اثر میگذارد. خود این هدر را در User-Agent چیست؟ شرح دادهایم. - محدودیت نرخ مستقل از IP. اگر ترافیکی پخششده روی صدها IP همان اثر انگشت را داشته باشد، شمارنده بهجای IP به اثر انگشت بسته میشود. IP چرخشی این شمارنده را صفر نمیکند.
- شناختن ابزارهای شناختهشده. برای خانوادههای بدافزار و ابزارهای پویش، فهرستهای گردآوریشده اثر انگشت وجود دارد؛ تیمهای امنیتی آنها را با گزارشها تطبیق میدهند.
محدودیت هم دارد. سایتی که مقدار یک مرورگر بهروز را مسدود کند، همه بازدیدکنندگان آن مرورگر را مسدود کرده است. بهروزرسانی مرورگر مقدار را عوض میکند و دروازههای سازمانی و آنتیویروسها اثر انگشت خودشان را در میانه میگذارند. به همین دلیل اثر انگشت TLS بهتنهایی تصمیم نمیسازد؛ یکی از سیگنالهایی است که همراه اعتبار IP و رفتار وارد امتیاز میشود. کل این امتیازدهی را در تشخیص بات چگونه کار میکند؟ و لایههای Cloudflare را در Cloudflare Precursor چیست؟ بررسی کردهایم.
برای توسعهدهندهای که داده جمع میکند چه معنایی دارد؟
به وضعیت آغاز نوشته برگردیم. کلاینت پایتون در سطر User-Agent میگوید کروم است و ClientHello آن میگوید OpenSSL است. سایت این تناقض را در نخستین بسته میبیند. واکنش درست پنهان کردن تناقض نیست، برداشتن آن است:
- ادعا نکنید مرورگری هستید که نیستید. مقدار
User-Agentاسکریپت شما باید خود اسکریپت را معرفی کند: یک نام، یک نسخه و یک نشانی تماس. کلاینتی که با هویت صادقانه میآید، بهrobots.txtپایبند است و آرام کار میکند را صاحب سایت میشناسد و میتواند در فهرست مجاز بگذارد. قاعدهها را در فایل robots.txt چیست و چگونه آن را بخوانیم؟ شرح دادهایم. - اگر API رسمی هست، از آن استفاده کنید. در درخواستی که با کلید API میآید هویت شما از پیش روشن است؛ بحث اثر انگشت برداشته میشود.
- اگر صفحه واقعاً مرورگر میخواهد، مرورگر واقعی به کار ببرید. وقتی صفحهای را که با جاوااسکریپت ترسیم میشود با Playwright در یک Chromium واقعی باز میکنید، کلاینت شما واقعاً همان مرورگر است؛ هدرها، لایه TLS و محیط جاوااسکریپت با هم سازگارند. نصب آن در Playwright چیست و چگونه با پروکسی استفاده میشود؟ آمده است.
- سرعت را پایین بیاورید و اجازه بگیرید. برای کاری منظم و پرحجم، نوشتن به صاحب سایت بیشتر وقتها ماندگارترین راهحل است.
دیگر دلیلهای مسدود شدن و راههای مشروع آن را در وب اسکرپینگ بدون مسدود شدن گرد آوردهایم.
کاربردها
- تشخیص
403در اسکرپر: اگر با همان IP مرورگر رد میشود و اسکریپت نمیشود، تفاوت به احتمال زیاد در هویت کلاینت است. تفکیک کدهای وضعیت در کدهای وضعیت HTTP در وب اسکرپینگ و طرح کلی در صفحه راهکار استخراج داده ماست. - جدا کردن ترافیک بات در سایت خودتان: گروهبندی گزارشها بر پایه اثر انگشت، ترافیک پخششدهای را نشان میدهد که گروهبندی بر پایه IP از دست میدهد.
- معرفی خزنده (crawler) خودتان: نسخه ثابت کتابخانه اثر انگشتی پایدار میسازد و خزنده شما در گزارشها اینگونه بازشناخته میشود. سمت زیرساخت در صفحه راهکار وب کراولر ماست.
- درست کردن انتظار از پروکسی: پروکسی مسکونی اعتبار IP و موقعیت را عوض میکند؛ هویت کلاینت شما بر عهده خودتان میماند.
خطاهای رایج
- گمان اینکه با عوض کردن
User-Agentکلاینت عوض میشود. هدر یک متن است؛ClientHelloرفتار کتابخانه است و پیش از هدر میرود. - انتظار عوض شدن اثر انگشت TLS از پروکسی. پروکسی تونلساز IP را عوض میکند و به دستدادن کاری ندارد.
- انتظار ثابت ماندن هش JA3 در مرورگر مبتنی بر Chromium. ترتیب افزونهها در هر اتصال عوض میشود؛ برای مقایسه از JA4 یا فهرست افزونه مرتبشده استفاده کنید.
- پذیرفتن نتیجه سرویس بازتاب بدون پرسش. آنتیویروس یا دروازه سازمانی که HTTPS را میپوید، اثر انگشت خودش را به سرویس نشان میدهد.
- استفاده از
127.0.0.1در اندازهگیری. SNI فرستاده نمیشود و مقداری متفاوت با اتصالهای واقعی به دست میآید.
راهنمای انتخاب
| وضعیت | پیشنهاد |
|---|---|
با پروکسی IP عوض میشود ولی در نخستین درخواست 403 میگیرید | به هویت کلاینت نگاه کنید: User-Agent و کتابخانهای که به کار میبرید یک چیز میگویند؟ |
| میخواهید مقدار JA3 یا JA4 کلاینت خودتان را بدانید | سرویس بازتاب یا همان شنونده محلی بالا |
| میخواهید ترافیک کروم را در گزارشها گروهبندی کنید | JA4 بهجای JA3؛ از بههمریختن افزونه اثر نمیگیرد |
| صفحه جاوااسکریپت و مرورگر واقعی میخواهد | خودکارسازی با مرورگر واقعی (Playwright)، سرعت معقول و دامنهای که سایت اجازه میدهد |
| سرویس بازتاب مقداری غیرمنتظره نشان میدهد | ببینید گواهی را چه کسی امضا کرده؛ ممکن است نرمافزاری در میانه TLS را پایان دهد |
| سایت شما ترافیک بات پخششده دارد | محدودیت نرخ را بهجای IP به جفت IP + اثر انگشت ببندید و بر پایه یک هش مسدود نکنید |
پرسشهای متداول
تفاوت JA3 و JA3S چیست؟
JA3 از پیام ClientHello کلاینت و JA3S از پیام ServerHello سرور ساخته میشود. چون سرور به کلاینتهای گوناگون پاسخهای گوناگون میدهد، JA3S بهتنهایی سرور را نمیشناساند؛ اما پاسخ آن به یک کلاینت همیشه یکسان است. تیمهای امنیتی به همین دلیل این دو را جفتی به کار میبرند.
آیا استفاده از VPN اثر انگشت TLS را عوض میکند؟
نه. VPN ترافیک را از تونلی رمزگذاریشده میگذراند، اما دستدادن TLS با سایت را باز هم مرورگر یا اسکریپت شما انجام میدهد. سایت آدرس IP سرور VPN و اثر انگشت کلاینت شما را میبیند. تفاوت این دو ابزار را در تفاوت پروکسی و VPN شرح دادهایم.
آیا اثر انگشت TLS مرا شخصاً میشناساند؟
بهتنهایی نه. هر کسی که نسخه یکسانی از مرورگر را روی سیستمعامل یکسانی اجرا کند همان مقدار را میسازد. هویتی که به شخص نزدیک میشود از ترکیب این مقدار با آدرس IP، کوکیها و سیگنالهای لایه جاوااسکریپت پدید میآید.
آیا بهروزرسانی مرورگر اثر انگشت را عوض میکند؟
میتواند عوض کند. وقتی نسخه تازه یک مجموعه رمز را بردارد یا افزونهای تازه بیفزاید، فهرست و هش عوض میشود. فهرستهای اثر انگشت به همین دلیل نسخهبهنسخه نگه داشته میشوند.
آیا پنجره ناشناس یا پاک کردن کوکیها بر اثر انگشت TLS اثر دارد؟
نه. پیام ClientHello را کتابخانه TLS مرورگر میسازد؛ تاریخچه، کوکیها و نوع پنجره وارد این فهرستها نمیشوند.
اگر بهخاطر اثر انگشتم مسدود میشوم چه کنم؟
نخست اندازه بگیرید: ببینید در سرویس بازتاب چه مقداری دیده میشود و آیا نرمافزاری در میانه TLS را پایان میدهد. اگر اسکریپت مینویسید، هویت خود را صادقانه اعلام کنید، سرعت را پایین بیاورید و از API رسمی یا کانال اجازه سایت استفاده کنید. اگر با مرورگر معمولی مسدود میشوید، مشکل به احتمال زیاد در اعتبار IP است، نه در اثر انگشت.
خلاصه
اثر انگشت TLS از ClientHello بیرون میآید، یعنی نخستین بسته رمزگذارینشده یک اتصال رمزگذاریشده. JA3 پنج فهرست این پیام را بهترتیب به هم میچسباند و هش MD5 را میگیرد؛ وقتی کروم ترتیب افزونهها را به هم ریخت این هش ناپایدار شد و JA4 با مرتب کردن فهرستها آن را درست کرد. این مقدار نرمافزار را نشان میدهد نه شخص را، پیش از هدرها میرود و هیچ پروکسی تونلسازی به آن دست نمیزند: پروکسی IP را عوض میکند و هویت کلاینت نزد شما میماند. برای افزودن نوع درست IP در کنار هویت کلاینت پایدار و صادقانه میتوانید خدمات پروکسی ما را ببینید.




