
أداة اكتشاف متوازٍ لخدمات SSH ومدقّق أمان تفحص أي منفذ، وتتحقق من لافتات SSH، وتدقق طرق المصادقة، والتشفير الضعيف، وثغرة Terrapin، ومفاتيح المضيف المعاد استخدامها عبر المضيفين ونطاقات CIDR.
اعثر على كل خدمة SSH في شبكتك، واحكم ما إذا كانت تلبي معيارك، وسيتم إعلامك عندما يتغير ذلك.
sshfinder هو ملف Python واحد بدون أي تبعيات مطلوبة. وجّهه إلى
نطاق CIDR وسيكتشف SSH أينما كان يستمع فعليًا — وليس فقط على
المنفذ 22 — ويؤكد أن كل واحد يتحدث SSH فعلًا، ويقيّم وضعه
التشفيري، ويعيد رمز خروج غير صفري عندما يفشل شيء ما في سياستك.
لا تستطيع معظم الفرق الإجابة عن ثلاثة أسئلة حول بنية SSH الخاصة بها:
الأدوات الحالية تجيب كل منها على جزء من هذا وتتوقف:
| الأداة | تكتشف SSH | تقيّمه | عبر أسطول كامل |
|---|---|---|---|
nmap | نعم | سطحي، عبر نصوص NSE | نعم |
ssh-audit | لا — تعطيه مضيفًا واحدًا | بعمق | لا |
masscan / zmap | على نطاق الإنترنت | لا | نعم |
sshfinder | نعم | نعم | نعم |
تلك الفجوة — الاكتشاف و التقييم و الحكم، في أداة واحدة — هي
ما وُجدت هذه الأداة لملئه. إذا كنت تحتاج فقط إلى تدقيق مضيف واحد
تعرفه بالفعل، استخدم ssh-audit؛ فهو
يتعمق أكثر في خدمة واحدة مما تفعله هذه الأداة.
سيجد مختبرو الاختراق التدقيق والانعطاف عبر SOCKS مفيدين، لكن الأداة مصممة حول تشغيل نفس الفحص بشكل متكرر ضد بنية تملكها، وليس حول مهمة لمرة واحدة.
git clone https://github.com/kabiri-labs/sshfinder.git
cd sshfinder
python sshfinder.py 10.0.0.0/24 -p 22,2222
لا تثبيت، لا تبعيات. يتطلب Python 3.9+.
الأشياء الثلاثة التي تفعلها، في ثلاثة أوامر:
# 1. الجرد — ما هو SSH الموجود هناك؟
python sshfinder.py 10.0.0.0/24 --audit --format csv -o ssh-inventory.csv
# 2. الحكم — هل يلبي معيارنا؟ (يخرج برمز 3 إذا لم يكن كذلك)
python sshfinder.py 10.0.0.0/24 -p 22,2222 --policy baseline
# 3. الانحراف — ما الذي تغيّر منذ الليلة الماضية؟
python sshfinder.py 10.0.0.0/24 -p 22,2222 --baseline yesterday.json \
--fail-on-drift
فحص جميع المنافذ 65535 هو الافتراضي، لأن خدمة SSH على منفذ غير قياسي هي تحديدًا تلك التي لم يكتبها أحد. كل منفذ مفتوح مُصنَّف، لذلك لا يُحتسب المنفذ المفتوح أبدًا بصمت كمنفذ SSH:
=== 10.0.0.5 ===
open: 10.0.0.5:22 [SSH], 10.0.0.5:8080 [not ssh]
SSH 10.0.0.5:22 (SSH-2.0-OpenSSH_7.4)
التأكيد هو تبادل تعريف RFC 4253 حقيقي، وليس نظرة على البايتات الأولى على السلك. الخوادم التي تطبع لافتة قانونية أولاً، أو التي تنتظر العميل لتقديم نفسه، أو التي تصل لافتتها مقسمة عبر مقاطع TCP — كلها تُعرَف بشكل صحيح — كل واحدة منها هي نتيجة سلبية خاطئة في تنفيذ ساذج.
أضف --audit للصورة الكاملة لكل خدمة:
SSH 10.0.0.5:22 (SSH-2.0-OpenSSH_7.4)
host key: ssh-ed25519 SHA256:T/ZM4jOL4amTsO5K3AaCdg2...
auth: publickey, password [!] password auth enabled
[!] Terrapin (CVE-2023-48795): VULNERABLE
[!] weak ciphers: aes128-cbc
aes128-cbc [weak]: CBC mode is vulnerable to the SSH plaintext-recovery attack (CVE-2008-5161) and, …
مفاتيح مضيف SSH المشتركة (مضيفون مشتركون/مستنسخون محتملون):
SHA256:T/ZM4jOL4amTsO5K3AaCdg2...
-> 10.0.0.5:22, 10.0.0.9:22
تلك الكتلة الأخيرة تستحق المعرفة: مفتاح مضيف مُعاد استخدامه عبر أجهزة يعني عادةً أجهزة VM مستنسخة أو صورة مشتركة، ويعني أن اختراق مضيف واحد يخترق هوية جميعها.
جعل OpenSSH 10.0 من mlkem768x25519-sha256 تبادل المفاتيح الافتراضي، و10.1
يحذر من أن الجلسات الكلاسيكية مفتوحة لالتقاط خزّن الآن، فكّك لاحقًا.
يجيب --pq-report على سؤال مستوى الأسطول مباشرة، باستخدام فقط
KEXINIT — لذلك لا يحتاج أي مكتبة طرف ثالث:
python sshfinder.py 10.0.0.0/24 -p 22,2222 --pq-report
Post-quantum readiness:
1/3 service(s) negotiate post-quantum key exchange with a current client
[!] no PQ key exchange offered (1):
10.0.0.2:22
[!] pre-standard PQ only (1) - looks post-quantum but is not:
10.0.0.3:22
2 service(s) exposed to store-now-decrypt-later capture; upgrade to OpenSSH 9.0+
فئة pre-standard هي التي توقع الناس. خادم
يعلن عن [email protected] أو مسودة Kyber يبدو
ما بعد الكمومي في تفريغ الخوارزميات، لكن OpenSSH أسقط تلك المجموعة
المسحوبة في 2020 — لذلك لا يجد العميل الحالي أي طريقة مشتركة ويعود
إلى التشفير الكلاسيكي. إذا عُدّ جاهزًا، فسيكون أسوأ من عدم النظر
إلى الأمر إطلاقًا.
يصف التقرير مشكلة. السياسة تؤكد واحدة، ويمكن أن تفشل بناء:
python sshfinder.py 10.0.0.0/24 -p 22,2222 --policy baseline; echo "exit $?"
Policy 'baseline':
No password login, no Terrapin exposure, no weak algorithms.
1/3 service(s) pass
[FAIL] 1 service(s):
10.0.0.3:22
- password_auth: password login accepted: publickey, password
- terrapin: vulnerable to Terrapin (CVE-2023-48795)
- post_quantum (warn): post-quantum readiness is absent, ready required
[warn] 1 service(s):
10.0.0.2:22
- post_quantum (warn): post-quantum readiness is absent, ready required
exit 3
ثلاث سياسات مدمجة — baseline وstrict وpq — مسماة وفق
النتيجة التي تفرضها وليس لتوزيعة. تحمل القواعد خطورة fail أو
warn ويقرر --fail-on أيها يتحكم في البوابة، لذلك يمكن للفريق
اعتماد معيار أكثر صرامة كتحذير أولاً وترقيته لاحقًا دون تعديل أي شيء.
اكتب سياستك الخاصة كـ JSON:
{
"name": "house-rules",
"description": "What we expect of every SSH service.",
"rules": [
{"check": "password_auth", "severity": "fail"},
{"check": "terrapin", "severity": "fail"},
{"check": "post_quantum", "require": "ready", "severity": "warn"},
{"check": "forbid", "field": "ciphers",
"algorithms": ["3des-cbc", "arcfour"], "severity": "fail"},
{"check": "require", "field": "kex_algorithms",
"algorithms": ["curve25519-sha256"], "severity": "fail"}
]
}
الفحوصات: password_auth وterrapin وweak_algorithms وpost_quantum (مع
require: ready أوlegacy أوabsent) وforbid / require عبر
field من kex_algorithms أوhost_key_algorithms أوciphers أوmacs.
أي شيء آخر هو خطأ صارم عند تحميل السياسة، قبل بدء الفحص. بوابة تتخطى بصمت قاعدة لا تفهمها أسوأ من لا بوابة: يعمل التشغيل باللون الأخضر ولا يتعلم أحد أن الفحص لم يُنفَّذ أبدًا.
$ sshfinder 10.0.0.0/24 --policy house.json
sshfinder: error: rule 1: unknown check 'pasword_auth'
(known: forbid, password_auth, post_quantum, require, terrapin, weak_algorithms)
شغّله ليلاً مقابل تقرير الأمس وشاهد فقط ما تحرك:
# Nightly, in cron:
python sshfinder.py 10.0.0.0/24 -p 22,2222 --audit --json -o today.json
python sshfinder.py 10.0.0.0/24 -p 22,2222 --baseline yesterday.json \
--fail-on-drift
Baseline drift (vs yesterday.json):
[alert] 2 change(s):
10.0.0.5:22 SHA256:T/ZM4jO... -> SHA256:9aKm2Qx...; expected only after a rebuild or key rotation
10.0.0.3:22 password login is now accepted
[added] 1 change(s):
10.0.0.9:2222 new SSH service (SSH-2.0-OpenSSH_9.6)
[improved] 1 change(s):
10.0.0.7:22 post-quantum readiness rose from absent to ready
مفتاح مضيف تغيّر هو الإشارة الأكثر أهمية هنا — متوقع فقط بعد إعادة بناء أو تدوير مفتاح، ويستحق نظرة في كل مرة أخرى.
فقط alert يتحكم في --fail-on-drift. المضيف المُخرَج من الخدمة هو
تغيير عادي، وفشل مهمة ليلية عليه سيدرب الجميع على تجاهل
النتيجة.
المقارنة حريصة على عدم اختراع تغييرات. حقل لم يقسه أي من الفحصين لا يُبلَّغ أبدًا كمتغير، فقط المضيفون الموجودون في كلا الفحصين يُقارنون، وخط الأساس الذي يحمل بصمات مفاتيح المضيف يجعل هذا الفحص يشغل الفحص العميق أيضًا — لذلك لا تُقرأ إعادة الفحص السطحية أبدًا وكأن كل مفتاح اختفى.
--format text|json|sarif|csv، مكتوب اختياريًا إلى ملف مع -o.
csv — صف واحد لكل خدمة SSH مؤكدة. الشكل الذي
يُفرز ويُصفى فيه جرد الأصول فعليًا.json — التقرير الأصلي، وتنسيق الإدخال لـ --baseline.sarif — SARIF 2.1.0، مُتحقق منه مقابل مخطط OASIS. النتائج
مثبتة على مواقع منطقية host:port وتحمل بصمات مستقرة، لذلك
يتتبع المستهلك نفس النتيجة عبر التشغيلات الليلية بدلاً من فتح
تنبيه جديد في كل مرة.--stream — أحداث JSON مفصولة بأسطر جديدة تُدفق عند فتح كل
منفذ وتأكيد كل خدمة، لذلك يمكن لخط أنابيب التصرف على النتيجة الأولى
بينما لا يزال الفحص يعمل:python sshfinder.py 10.0.0.0/24 --stream -q | jq -c 'select(.event=="ssh")'
{"event":"ssh","elapsed":0.164,"host":"10.0.0.5","port":22,"banner":"SSH-2.0-OpenSSH_9.6"}
{"event":"ssh","elapsed":0.881,"host":"10.0.0.9","port":2222,"banner":"SSH-2.0-dropbear"}
حول SARIF وفحص كود GitHub. يجب أن تحمل نتائج SARIF موقع قطعة أثرية غير فارغ أو يرفض
upload-sarifالملف، لذلك يُصدر URI اصطناعيssh://host:portبجانب الموقع المنطقي. لا يحل إلى ملف في مستودعك، لذلك تظهر التنبيهات بدون مرساة كود. عالج هذا كـ SARIF لأدوات الأمان عمومًا — عارض SARIF في VS Code وAzure DevOps والأرشفة — وليس كطريقة لتعليق فرق.
النقطة الكاملة لميزتي السياسة والانحراف، لذلك تستحق الذكر بدقة:
الخطأ الصارم يتفوق على حكم السياسة، وحكم السياسة يتفوق على الانحراف.
إذا لم يكن شيء قابلًا للوصول، لم يثبت الفحص شيئًا عن الامتثال
في أي اتجاه، لذلك تحصل على 1 بدلاً من نجاح أو فشل مضلل؛ وفشل
معيار معلن هو نتيجة أكثر تحديدًا من "شيء ما تغيّر".
الفحص الأساسي والتحقق من اللافتة والأجزاء الخالية من التبعيات في --audit
(جرد الخوارزميات وعلامات التشفير الضعيف وTerrapin والجاهزية ما بعد الكمومية)
لا تحتاج شيئًا سوى Python 3.9+.
# Recommended: unlocks host key fingerprints, auth-method enumeration and
# shared-key correlation in --audit, plus --validate paramiko.
pip install -r requirements.txt
# Optional, only for half-open SYN scans (needs root):
pip install scapy>=2.5
يختار --scan-method auto فحص SYN عند التشغيل كجذر مع تثبيت Scapy،
وإلا يتراجع إلى فحص الاتصال الخالي من الامتيازات.
لا يمكن دمج SOCKS مع فحص SYN — يحمل SOCKS5 تدفقات TCP، وليس
حزمًا خام.
python sshfinder.py 10.0.0.0/24 -p 22 --socks user:[email protected]:1080
الاكتشاف وتبادل اللافتة والتدقيق كلها تعبر الانعطاف، لذلك النتائج ليست نصف نفقية أبدًا. وكيل غير قابل للوصول يُبلَّغ كخطأ فحص، وليس أبدًا كـ "لا SSH موجود".
يحد --max-rate الاستطلاعات في الثانية عبر الفحص بأكمله. التوافقية
تحد كم عدد الاتصالات المفتوحة في وقت واحد؛ هذا يحد مدى سرعة بدء
جديدة، وهو السقف الذي تحتاج إلى القدرة على الوعد به قبل فحص
أي شيء تحت قواعد الاشتباك.
ملاحظات التنفيذ، عندما يحتاج السلوك أعلاه إلى شرح.
محرك الفحص. كل اتصال قيد التنفيذ يُدار من خيط واحد بواسطة
حلقة أحداث نظام التشغيل (epoll/kqueue/select)، لذلك تكلف التوافقية
واصف ملف بدلاً من خيط نظام تشغيل، ويُحل كل مضيف مرة واحدة بدلاً من
مرة لكل منفذ. مسح كامل 1–65535 يعمل أسرع بحوالي 6× من
تصميم تجمع الخيوط.
منافذ SSH أولاً. المنافذ القليلة التي يعيش عليها SSH فعليًا (22، 2222، 22222، …) تُستطلع في مقدمة كل مسح. في مسح كامل، تظهر أول خدمة SSH مؤكدة في حوالي 0.2 ثانية بدلاً من 16.
مصافحة واحدة لكل خدمة. المقبس الذي اكتشف منفذًا مفتوحًا يُسلَّم مباشرة إلى تبادل اللافتة، لذلك تكلف خدمة SSH مؤكدة مصافحة TCP واحدة بدلاً من اثنتين.
مهلة تكيفية. تنتظر الاستطلاعات بقدر ما يستحق المسار، باستخدام
مقدّر زمن الرحلة ذهابًا وإيابًا الممهّد من RFC 6298 — الذي يستخدمه TCP نفسه —
المُغذى بكل استطلاع مُجاب ومشترك عبر الفحص. يصبح --timeout
سقفًا بدلاً من تكلفة ثابتة: على مضيف حي لكن مُصفّى في الغالب، هذا
يستحق حوالي 5× بنتائج متطابقة. فقط إجابة قاطعة تعلمه شيئًا؛
المهلة لا تقول شيئًا عن المسار ولا تُغذى أبدًا مرة أخرى.
مكانان يحتفظان عمدًا بالسقف الكامل. تبادل اللافتة والتدقيق لا يتكيفان أبدًا، لأن مدى سرعة إكمال المضيف لمصافحة TCP لا يقول شيئًا عن مدى سرعة تكوين خادم SSH لتحية. ولا إعادة استطلاع منافذ SSH المعتادة، لأن SYN مسقطة هناك هي الخسارة الوحيدة التي تكلف هذه الأداة نتيجة فعليًا.
خروج مبكر. مضيف لا يجيب بشيء إطلاقًا عبر أول
بضع مئات من الاستطلاعات يُبلَّغ كغير مستجيب بدلاً من استهلاك مهلة واحدة
لكل منفذ متبقٍ. لأن منافذ SSH تُمسح أولاً، تُرى الخدمة الحية
دائمًا قبل أن يتعثر هذا؛ --no-early-exit يفرض النطاق الكامل.
محدود بالتصميم. ميزانية مقبس على مستوى العملية مشتقة من
حد واصف الملف تمنع فحصًا كبيرًا من استنفاد الواصفات و
الإبلاغ الخاطئ عن الخدمات الحية كمصفّاة. توسيع الهدف يتحقق من
حجم الشبكة قبل تجسيده، لذلك يُرفض /8 ضال في ميلي ثانية
بدلاً من استهلاك غيغابايت من الذاكرة.
أحكام خوارزميات منسقة. كل خوارزمية مُعلَّمة تأتي من
جدول صريح بخطورة وسبب معلن، وليس سلسلة اختبارات فرعية.
الأسماء تُطبَّع أولاً، لذلك لا يمكن للاحقة بائع أن تمرر واحدة عبر
فحص — [email protected] هو CBC بغض النظر عمن يشحنه —
وعلامات التفاوض مثل [email protected] لا تُقيَّم أبدًا كـ
خوارزميات. هذا الجدول هو ما تستند إليه بوابة السياسة ومقارنة الانحراف
في النهاية.
Ctrl+C قوي. مُكرَّم حتى على Windows، حيث يبتلع انتظار خيط غير محدود عادةً: الضغطة الأولى تتوقف بأمان وتعيد نتائج جزئية، والثانية تفرض خروجًا فوريًا.
OpenSSH_9.6p1 على Ubuntu 24.04 مُصحَّح
ضد معظم ما تنسبه قواعد البيانات العامة إلى 9.6p1. هذا
آلة نتائج إيجابية خاطئة — لهذا أزال ssh-audit اكتشاف CVE
القائم على الإصدار، ولهذا تشحن Tenable مكوّنًا إضافيًا وظيفته الكاملة
اكتشاف إعادة التطبيق التي تكسره. فقط ما يعلنه الخادم فعليًا
يُقيَّم.nmap في فحص المنافذ العام، أو masscan و
zmap على نطاق الإنترنت. تلك المشاكل محلولة.مجموعة الاختبارات مكتبة قياسية فقط، لذلك تعمل على مترجم فوري عارٍ:
python -m unittest discover -s tests
ثبّت تبعيات التشغيل أيضًا لاختبار تدقيقات Paramiko المدعومة، التي تتخطى نفسها عندما يكون Paramiko مفقودًا:
pip install -r requirements-dev.txt
python -m unittest discover -s tests
افحص فقط الأنظمة التي تملكها أو مُصرَّح لك صراحةً باختبارها. الفحص غير المصرح به قد يكون غير قانوني في منطقتك.
| الكود | المعنى |
|---|
0 | نجاح. لا شيء موجود لا يزال نجاحًا — البنية الفارغة ليست خطأ. |
1 | خطأ صارم: فشل فحص كل هدف، أو تعذر كتابة ملف الإخراج. |
2 | استدعاء سيئ (علامة غير معروفة، مواصفة منفذ غير صالحة، سياسة أو وكيل مشوه). |
3 | انتهاك سياسة عند أو فوق --fail-on. فقط مع --policy. |
4 | تنبيه انحراف خط الأساس. فقط مع --baseline --fail-on-drift. |
130 | مقاطعة بـ Ctrl+C. |
| الخيار | الوصف |
|---|
targets | عنوان IP واحد أو أكثر، أو أسماء مضيفين، أو شبكات CIDR. |
-iL, --target-file FILE | قراءة الأهداف من ملف (سطر واحد لكل هدف، تعليقات # مسموحة). |
-p, --ports SPEC | المنافذ للفحص، مثل 22,80,1000-2000 (الافتراضي: 1-65535). |
--audit | تدقيق كل خدمة SSH: الخوارزميات، مفتاح المضيف، طرق المصادقة، Terrapin، الجاهزية ما بعد الكمومية، ارتباط المفاتيح المشتركة. |
--pq-report | تقرير الجاهزية ما بعد الكمومية عبر البنية. لا يحتاج مكتبة طرف ثالث. |
--policy NAME_OR_PATH | فحص كل خدمة مقابل baseline أوstrict أوpq أو ملف سياسة JSON. يخرج برمز 3 عند الانتهاك. |
--fail-on {fail,warn,never} | أي خطورة سياسة تتحكم في رمز الخروج (الافتراضي: fail). |
--baseline FILE | المقارنة مع تقرير --json سابق وسرد ما تغيّر. |
--fail-on-drift | الخروج برمز 4 عندما تثير المقارنة تنبيهًا. |
--format {text,json,sarif,csv} | تنسيق الإخراج (الافتراضي: text). |
--json | اختصار لـ --format json. |
--stream | إصدار أحداث JSON مفصولة بأسطر جديدة عند العثور على النتائج. |
-o, --output FILE | كتابة النتائج إلى ملف بدلاً من stdout. |
--validate {banner,paramiko,none} | استراتيجية التحقق من SSH (الافتراضي: banner). |
--scan-method {auto,connect,syn} | الخلفية للفحص (الافتراضي: auto). |
--socks [user:pass@]host:port | الوصول إلى كل هدف عبر وكيل SOCKS5. |
--max-rate N | حد الاستطلاعات في الثانية عبر الفحص بأكمله (الافتراضي: بدون حد). |
-t, --timeout SECONDS | أقصى مدة قد ينتظرها استطلاع (الافتراضي: 2.0). |
--min-timeout SECONDS | الحد الأدنى لوقت الاستطلاع التكيفي (الافتراضي: 0.1). |
--no-adaptive-timeout | انتظار كامل --timeout على كل استطلاع. |
-w, --workers N | الاتصالات قيد التنفيذ لكل مضيف (الافتراضي: 512). |
--max-sockets N | سقف مقابس الاستطلاع المفتوحة في وقت واحد (الافتراضي: من حد واصف الملف). |
--host-concurrency N | المضيفون المفحوصون بالتوازي (الافتراضي: 16). |
-r, --retries N | إعادة المحاولة للاستطلاعات منتهية المهلة (الافتراضي: 0). |
--max-targets N | رفض قوائم الأهداف الأكبر من هذا (الافتراضي: 65536). |
--no-early-exit | مسح كل منفذ حتى على المضيفين الذين لا يجيبون بشيء إطلاقًا. |
--no-progress | تعطيل مؤشر التقدم المباشر. |
-v, --verbose | تسجيل مفصّل (-vv يزيل أيضًا كتم Paramiko). |
-q, --quiet | كتم التقدم والتسجيل المعلوماتي. |
--version | طباعة الإصدار والخروج. |