
समानांतर SSH सेवा खोज और सुरक्षा ऑडिटर जो किसी भी पोर्ट को स्कैन करता है, SSH बैनरों को मान्य करता है, और होस्ट्स एवं CIDR रेंजों में प्रमाणीकरण विधियों, कमज़ोर क्रिप्टोग्राफ़ी, Terrapin भेद्यता, और पुनः उपयोग की गई होस्ट कुंजियों का ऑडिट करता है।
अपने नेटवर्क पर हर 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 के साथ), और kex_algorithms, host_key_algorithms, ciphers या macs के field पर forbid / require।
बाकी सब कुछ एक कठोर त्रुटि है जब नीति लोड होती है, स्कैन शुरू होने से पहले। एक द्वार जो चुपचाप एक नियम को छोड़ देता है जिसे वह समझता नहीं है, कोई द्वार न होने से भी बदतर है: रन हरा हो जाता है और कोई नहीं सीखता कि जाँच कभी निष्पादित नहीं हुई।
$ 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)
इसे कल की रिपोर्ट के खिलाफ रात में चलाएं और केवल वही देखें जो स्थानांतरित हुआ:
# रात में, 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फ़ाइल को अस्वीकार कर देता है, इसलिए तार्किक स्थान के साथ एक सिंथेटिकssh://host:portURI उत्सर्जित होता है। यह आपके रिपॉजिटरी में एक फ़ाइल को हल नहीं करता, इसलिए चेतावनियाँ कोड एंकर के बिना दिखाई देती हैं। इसे सामान्य रूप से सुरक्षा उपकरण के लिए SARIF के रूप में मानें — VS Code SARIF व्यूअर, Azure DevOps, संग्रह — एक अंतर को एनोटेट करने के तरीके के रूप में नहीं।
नीति और विचलन सुविधाओं का पूरा बिंदु, इसलिए वे सटीक रूप से बताने लायक हैं:
एक कठोर त्रुटि एक नीति निर्णय से ऊपर है, और एक नीति निर्णय विचलन से ऊपर है। यदि कुछ भी पहुंच योग्य नहीं था, तो स्कैन ने किसी भी तरह से अनुपालन के बारे में कुछ भी साबित नहीं किया, इसलिए आपको एक भ्रामक पास या विफल के बजाय 1 मिलता है; और एक बताई गई बार को विफल करना "कुछ बदला" से अधिक विशिष्ट निष्कर्ष है।
कोर स्कैन, बैनर सत्यापन और --audit के निर्भरता-मुक्त हिस्से (एल्गोरिदम इन्वेंटरी, कमजोर-क्रिप्टो फ्लैग, Terrapin, पोस्ट-क्वांटम तत्परता) को Python 3.9+ के अलावा कुछ भी नहीं चाहिए।
# अनुशंसित: --audit में होस्ट कुंजी फिंगरप्रिंट, प्रमाणीकरण विधि गणना और
# साझा-कुंजी सहसंबंध अनलॉक करता है, साथ ही --validate paramiko।
pip install -r requirements.txt
# वैकल्पिक, केवल आधे-खुले SYN स्कैन के लिए (रूट की आवश्यकता है):
pip install scapy>=2.5
--scan-method auto रूट के रूप में Scapy स्थापित होने पर SYN स्कैन का चयन करता है, और अन्यथा विशेषाधिकार-मुक्त कनेक्ट स्कैन पर वापस आ जाता है। SOCKS को SYN स्कैन के साथ नहीं जोड़ा जा सकता — SOCKS5 TCP स्ट्रीम ले जाता है, कच्चे पैकेट नहीं।
python sshfinder.py 10.0.0.0/24 -p 22 --socks user:[email protected]:1080
खोज, बैनर विनिमय और ऑडिट सभी पिवट से गुजरते हैं, इसलिए परिणाम कभी भी आधे-सुरंग नहीं होते। एक प्रॉक्सी जो पहुंच योग्य नहीं है, एक स्कैन त्रुटि के रूप में रिपोर्ट की जाती है, कभी "कोई SSH नहीं मिला" के रूप में नहीं।
--max-rate पूरे स्कैन में प्रति सेकंड प्रोब सीमित करता है। समवर्तीता सीमित करती है कि एक बार में कितने कनेक्शन खुले हैं; यह सीमित करता है कि नए कितनी तेजी से शुरू होते हैं, जो वह छत है जिसे आपको कार्रवाई के नियमों के तहत कुछ भी स्कैन करने से पहले वादा करने में सक्षम होना चाहिए।
कार्यान्वयन नोट्स, जब ऊपर के व्यवहार को समझाने की आवश्यकता हो।
स्कैन इंजन। उड़ान में हर कनेक्शन एक OS इवेंट लूप (epoll/kqueue/select) द्वारा एक थ्रेड से संचालित होता है, इसलिए समवर्तीता एक OS थ्रेड के बजाय एक फ़ाइल डिस्क्रिप्टर की लागत देती है, और प्रत्येक होस्ट को प्रति पोर्ट के बजाय एक बार हल किया जाता है। एक पूर्ण 1–65535 स्वीप थ्रेड-पूल डिज़ाइन से लगभग 6× तेज चलता है।
SSH पोर्ट पहले। SSH वास्तव में जिन कुछ पोर्टों पर रहता है (22, 2222, 22222, …) हर स्वीप के शीर्ष पर जांचे जाते हैं। एक पूर्ण स्वीप पर, पहली पुष्टि SSH सेवा लगभग 0.2 सेकंड में दिख
| कोड | अर्थ |
|---|
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 | संस्करण प्रिंट करें और बाहर निकलें। |