├── web ├── static │ ├── robots.txt │ ├── logo.png │ ├── favicon.ico │ ├── netscape3.gif │ ├── Peter_Sellers.jpg │ └── new.css ├── templates │ ├── 404.html │ ├── main.html │ ├── test.user.html │ ├── page_for_listings_main.html │ ├── new.html │ ├── nmapips.html │ ├── portlist.html │ ├── screens.html │ ├── servicelist.html │ ├── nmapout.html │ ├── all_users.html │ ├── user_info.html │ ├── footer.html │ ├── tree.html │ ├── tree_user.html │ ├── users_sort_by_total.html │ ├── users_with_2p_ip.html │ ├── port_search.html │ ├── iplist.html │ ├── ip_info.html │ ├── tables.html │ ├── about.html │ └── header.html └── serv.py ├── outputdata.py ├── test ├── nmap.to.mysql.py ├── config.ini ├── sqlclass.py ├── rdnsthings.py ├── sshrank.py ├── README.md └── LICENSE /web/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /ssh_rank/* 3 | -------------------------------------------------------------------------------- /web/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pronto/SSH-Ranking/HEAD/web/static/logo.png -------------------------------------------------------------------------------- /web/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pronto/SSH-Ranking/HEAD/web/static/favicon.ico -------------------------------------------------------------------------------- /web/static/netscape3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pronto/SSH-Ranking/HEAD/web/static/netscape3.gif -------------------------------------------------------------------------------- /web/static/Peter_Sellers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pronto/SSH-Ranking/HEAD/web/static/Peter_Sellers.jpg -------------------------------------------------------------------------------- /web/templates/404.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 |

404'd bro.

3 | {% include 'footer.html' %} 4 | -------------------------------------------------------------------------------- /web/templates/main.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 |

lets rank things

3 | 4 | {% include 'footer.html'%} 5 | -------------------------------------------------------------------------------- /web/templates/test.user.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | {% for user in user_list -%} {{ user }}, {% endfor -%} 3 | -------------------------------------------------------------------------------- /web/templates/page_for_listings_main.html: -------------------------------------------------------------------------------- 1 | 2 | {% include 'header.html' %} 3 | 4 | {% include 'tables.html' %} 5 | {% include 'footer.html'%} 6 | -------------------------------------------------------------------------------- /web/templates/new.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | 3 |
4 | 5 |
    6 |
  1. the crappy list stuff
  2. 7 |
  3. will go here
  4. 8 |
9 | -------------------------------------------------------------------------------- /web/templates/nmapips.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | 3 |
4 | {% for ip in nmapips -%} 5 | {{ip}}, 6 | 7 | 8 | {% endfor %} 9 | 10 | {% include 'footer.html' %} 11 | -------------------------------------------------------------------------------- /web/templates/portlist.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | 3 | port#,count 4 |
5 | 6 | {% for port in ports %} 7 | {{port[0]}}:[{{port[1]}}], 8 | {% endfor %} 9 | 10 | 11 | {% include 'footer.html' %} 12 | -------------------------------------------------------------------------------- /web/templates/screens.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | {% for screen in screenshots -%} 3 | 4 | 5 | 6 | {% endfor -%} 7 | {% include 'footer.html' %} 8 | 9 | 10 | -------------------------------------------------------------------------------- /web/templates/servicelist.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | 3 |
4 |
 5 | {% for serv in servlist -%}
 6 | {{serv[0]}}:{{serv[1]}}
 7 | {% endfor %}
 8 | 
9 | 10 | {% include 'footer.html' %} 11 | -------------------------------------------------------------------------------- /web/templates/nmapout.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | 3 | 4 | {{ url_for('static', filename=xmlfile) }} 5 | {% autoescape False %} 6 | {{ showxml }} 7 | {% endautoescape %} 8 | 9 | {% include 'footer.html' %} 10 | -------------------------------------------------------------------------------- /web/templates/all_users.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | {% for letter,userlist in users|groupby(0) -%} 3 |

{{ letter }}:

4 | {% for name in userlist -%} 5 | {{ name }}, 6 | {% endfor -%} 7 | {% endfor -%} 8 | 9 | 10 | {% include 'footer.html' %} 11 | -------------------------------------------------------------------------------- /web/templates/user_info.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | 3 | 4 |

{{user}}

5 |

tree for {{ user}} 6 |

IP's that have used this user name:

7 | {% for ip in other_ips -%} {{ ip }}, {% endfor -%} 8 | {%include 'footer.html' %} 9 | -------------------------------------------------------------------------------- /web/templates/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /web/templates/tree.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 |

{{ ip }}

3 |

This crazy part takes the inputted in (eg: {{ ip }} )

4 |

Gets each user for that IP.

5 |

then for each user that IP has,

6 |

looks up each IP that also has that user @_@. cray.

7 | 8 |
 9 | {% for user,list in tree %}
10 | {{user}} : {% for ip2 in list -%} {% if ip2 != ip -%}{{ip2}}{%else-%} {{ip2}}{%endif-%}, {% endfor -%}  
11 | 12 | {% endfor -%} 13 |
14 |

15 | {% include 'footer.html' %} 16 | -------------------------------------------------------------------------------- /web/templates/tree_user.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 |

{{ user }}

3 |

This crazy part takes the inputted in (eg: {{ user }} )

4 |

Gets each IP for that user.

5 |

then for each IP that uesr has,

6 |

looks up each user that also has that ip @_@. cray.

7 | 8 |
 9 | {% for ip,list in tree %}
10 | {{ip}} : {% for user2 in list -%} {% if user2 != user -%}{{user2}}{%else-%} {{user2}}{%endif-%}, {% endfor -%}  
11 | 12 | {% endfor -%} 13 |
14 |

15 | {% include 'footer.html' %} 16 | -------------------------------------------------------------------------------- /web/templates/users_sort_by_total.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 |
3 | 4 |

Every user listed by attempts, where attempts > 1

5 | 6 | 7 | 8 | 9 | 10 | 11 | {% set tableid=cycler('table2','table1') %} 12 | 13 | {% for user in users -%} 14 | 15 | 16 | 17 | 18 | {% endfor -%} 19 |
UserAttempts
{{user[0]}} {{'%05s' % user[1]}}
20 | {% include 'footer.html' %} 21 | -------------------------------------------------------------------------------- /web/templates/users_with_2p_ip.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | 3 |
4 | 5 |

shows each user that was attemped by 2 or more IPs, and the count of total ip....This page is going to be slow...

6 | 7 | 8 | 9 | 10 | 11 | {% set tableid=cycler('table2','table1') %} 12 | 13 | {% for user in users -%} 14 | 15 | 16 | 17 |
UserIP Count
{{user[0]}}{{user[1]}} 18 | {% endfor -%} 19 |
20 | 21 | {% include 'footer.html' %} 22 | -------------------------------------------------------------------------------- /web/templates/port_search.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | 3 | 4 | 5 | {% set tableid=cycler('table2','table1') %} 6 | {% for port in ports -%} 7 | 8 | 9 | 12 | 13 | 14 | {% endfor -%} 15 | 16 |
iptimeport#stateprotoserviceverinfo
{{port.ip}}{{port.dtime}}{{port.portnum}} 10 | {% if port.state == "open" -%}{% endif %} 11 | {{port.state}}{{port.proto}}{{port.service}}{{port.verinfo}}
17 |

18 | 19 | other ports: {% for oport in otherports -%} {{oport[0]}}, {% endfor -%} 20 | 21 | {% include 'footer.html' %} 22 | -------------------------------------------------------------------------------- /outputdata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.6 2 | import os 3 | from ConfigParser import SafeConfigParser 4 | from datetime import datetime 5 | par = SafeConfigParser() 6 | #will be /etc/ssh-rank.ini or whereever you want it 7 | par.read(os.getcwd()+"/config.ini") 8 | mysqluser=par.get("sshrank","mysqluser") 9 | mysqlserv=par.get("sshrank","mysqlserv") 10 | mysqlpass=par.get("sshrank","mysqlpass") 11 | user_cnt=int(par.get("sshrank","user_cnt")) 12 | total_ip=par.get("sshrank","total_ip") 13 | stats_ip=par.get("sshrank","stats_ip") 14 | 15 | from sqlclass import * 16 | 17 | uniq_ips=Session.query(ips.ip,func.count(ips.ip).label('total')).group_by(ips.ip).order_by('total DESC').limit(int(total_ip)).all() 18 | 19 | for a in uniq_ips: 20 | date=Session.query(ips.dtime).filter(ips.ip==a[0]).order_by(-ips.pk).limit(1).scalar() 21 | date=datetime.strptime(str(date),'%Y-%m-%d %H:%M:%S') 22 | date=date.strftime('%Y-%m-%d %H:%M:%S') 23 | print "\033[1m"+a[0] + "\033[0m attempted \033[1m" + str(a[1]) + "\033[0m Last Attempt: \033[1m"+ date+" \033[0mtimes with users: " 24 | print '\t', 25 | users = Session.query(ips.user,func.count(ips.user).label('total')).\ 26 | filter(ips.ip==str(a[0])).group_by(ips.user).order_by('total DESC').limit(user_cnt).all() 27 | for b in users: 28 | print "\033[1m"+b[0] + "\033[0m:" + str(b[1]) + ", ", 29 | print ' \n' 30 | #code.interact(local=locals()) 31 | -------------------------------------------------------------------------------- /web/templates/iplist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | {% for ip in uniq_ips -%} 5 | {% set rowip = ip[0] -%} 6 |
  1. 7 |
    {{ ip[0] }} 8 | attempted {{ ip[1] }} times.
    9 |
    last attempt: {% for lasta in datelist -%} 10 | {% if lasta[0] == ip[0] -%} 11 | {% if lasta[1] == newest -%} 12 |
    {{ lasta[1] }}
    13 | {% else %} 14 | {{ lasta[1] }} 15 | {% endif -%} 16 | {% endif -%} 17 | {% endfor -%}
    18 | {% for res in alldns -%} 19 | {% if res.ip == rowip -%} 20 | {% if res.good != '-' %} 21 |
    {{res.rdns}} is 22 | {% if res.good == 'good' -%} 23 | 24 | {% else -%} 25 | 26 | {% endif -%} 27 | {{ res.good }} 28 | 29 | {% endif -%} 30 | {% endif -%} 31 | {% endfor -%} 32 |
    33 | {% for user in userlist -%} 34 | {% if user[0][0] == rowip -%} 35 | {{ user[1] }}:{{user[2]}} 36 | {% endif -%} 37 | {% endfor -%} 38 |
    39 |
  2. 40 | {% endfor -%} 41 |
42 | 43 | -------------------------------------------------------------------------------- /web/templates/ip_info.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 |
3 |

dates/times attempted:

.

4 |
 5 | year:mon
6 | --------> day - attemps/day
7 | {% for year, year_group in dates|groupby('year') -%} 8 | {% for month, month_list in year_group|groupby('month') -%} 9 | {{year}}:{{month}} 10 | {% for day , day_list in month_list|groupby('day') -%} 11 | ------->{{'%03s' % day}} - {{'%05s' % day_list|count}} 12 | {%endfor-%} 13 | {% endfor -%} 14 | {%endfor -%} 15 |
16 |
17 | 18 | 19 | 20 | 21 |
22 |

Tree view for {{ ip }}

23 |

RDNS: 24 | {% if rdns_res == [] -%} 25 | not checked 26 | {% else -%} 27 | {{rdns_res[0].rdns}}{% endif -%}

28 | {% if hasnmap == True -%} 29 |

nmap scans:

30 | 31 | 32 | 33 | 34 | {% set tableid=cycler('table2','table1') %} 35 | {% for port in nmapstuff -%} 36 | 37 | 38 | 41 | 42 | 43 | {% endfor -%} 44 | 45 |
timeport#stateprotoserviceverinfo
{{port.dtime}}{{port.portnum}} 39 | {% if port.state == "open" -%}{% endif %} 40 | {{port.state}}{{port.proto}}{{port.service}}{{port.verinfo}}
46 |
47 | {% endif -%} 48 | {% if screenshots != False-%} 49 | {% for screen in screenshots -%} 50 | 51 | {% endfor -%} 52 | {% endif -%} 53 |

Attempted users:

54 |
55 | {% for user in users-%} 
56 | {{ user[0] }}:{{user[1]-}}, {% endfor -%}
57 | 
58 |
59 | {% include 'footer.html' %} 60 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | 116.124.128.226 attempted 5153 Last Attempt: 2013-08-25 02:30:50 times with users: 2 | root:153, test:108, user:87, minecraft:82, nagios:81, 3 | 4 | 153.127.226.81 attempted 3023 Last Attempt: 2013-08-24 03:12:39 times with users: 5 | root:1344, test:19, guest:18, oracle:17, ftp:16, 6 | 7 | 61.172.243.73 attempted 2685 Last Attempt: 2013-08-05 15:44:51 times with users: 8 | ftptest:76, userftp:76, backup:76, support:76, redmine:76, 9 | 10 | 218.85.135.29 attempted 2626 Last Attempt: 2013-08-27 22:14:59 times with users: 11 | root:462, test:86, nagios:73, mysql:68, oracle:64, 12 | 13 | 109.73.5.76 attempted 1891 Last Attempt: 2013-09-05 16:18:41 times with users: 14 | root:1699, bin:16, test:6, oracle:6, webmaster:5, 15 | 16 | 65.111.165.201 attempted 1188 Last Attempt: 2013-08-17 08:23:10 times with users: 17 | root:1158, bin:16, oracle:2, test:1, be:1, 18 | 19 | 124.207.3.121 attempted 1087 Last Attempt: 2013-08-21 22:48:41 times with users: 20 | root:903, bin:12, oracle:7, mp3:5, news:5, 21 | 22 | 201.244.16.146 attempted 1009 Last Attempt: 2013-09-02 20:20:22 times with users: 23 | root:13, test:4, postgres:3, eggdrop:3, luciana:3, 24 | 25 | 12.9.147.220 attempted 920 Last Attempt: 2013-09-05 04:22:33 times with users: 26 | root:911, rekinu:2, boot:1, xxxxx:1, celso:1, 27 | 28 | 87.106.69.118 attempted 823 Last Attempt: 2013-08-26 08:14:45 times with users: 29 | root:582, bin:9, haqr:9, oracle:7, postgres:5, 30 | 31 | -------------------------------------------------------------------------------- /nmap.to.mysql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | import sys,os,pwd,argparse,gzip,time 3 | from datetime import datetime 4 | from multiprocessing import Process 5 | from sqlclass import * 6 | import re 7 | try: 8 | from ConfigParser import SafeConfigParser 9 | except ImportError as exc: 10 | sys.stderr.write("Error: failed to import settings module ({})".format(exc)) 11 | 12 | 13 | #set up the config file things 14 | 15 | par = SafeConfigParser() 16 | #will be /etc/ssh-rank.ini or whereever you want it 17 | par.read(os.getcwd()+"/config.ini") 18 | 19 | 20 | mysqluser=par.get("sql","sqluser") 21 | mysqlserv=par.get("sql","sqlserv") 22 | mysqlpass=par.get("sql","sqlpass") 23 | 24 | arg=argparse.ArgumentParser() 25 | arg.add_argument('-f', action='store', dest='nfile', default='showhelp', help='the file name, put it here') 26 | argres = arg.parse_args() 27 | #def insert_portsql(ip,dtime,portnum,state,proto,service,verinfo): 28 | def insert_portsql(insertline): 29 | # nmapinsert=nmapSQL(ip,dtime,portnum,state,proto,service,verinfo) 30 | sqlsess.add(nmapinser) 31 | sqlsess.commit() 32 | 33 | 34 | gnmap=open(argres.nfile, 'r').readlines() 35 | #port number, State, Protocol, Owner, Service, SunRPC info, Version info 36 | ddate=re.search(r'initiated (.*?)as',gnmap[0]).group(0).replace(' as','').replace('initiated ','') 37 | ddate=datetime.strptime(ddate,"%a %b %d %H:%M:%S %Y") 38 | ddate=ddate.strftime('%Y-%m-%d %H:%M:%S') 39 | 40 | ip=re.search(r'Host: (.*?)\(',gnmap[1]).group(0).replace('Host: ','').replace(' (','') 41 | nsplit=gnmap[2].split('Ports: ')[1].split(', ') 42 | for a in nsplit: 43 | b = a.split(',') 44 | for c in b: 45 | d=c.split('/') 46 | print "port:\t" + d[0], 47 | print "| state:\t" + d[1], 48 | print "| proto:\t" + d[2], 49 | print "| serv:\t" + d[4], 50 | print "| verinfo:\t" + d[6] 51 | #ip, dtime, portnum, state, proto, service, verinfo) 52 | insertstuff=nmapSQL(ip, ddate, d[0], d[1], d[2], d[4], d[6]) 53 | # ip time port# state proto serv ver 54 | # 1 2 3 4 5 6 7 55 | #print insertstuff 56 | sqlsess.add(insertstuff) 57 | sqlsess.commit() 58 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [sshrank] 2 | # SSH-Rank Configuration File 3 | 4 | # Set debugging. 1 is on, 0 is off 5 | debugging=1 6 | 7 | # Absolute path to SSH-Rank on your file system. Change to 8 | # match where you downloaded SSH-Rank. Include the trailing / 9 | sqlclassPath=/home/pronto/git/SSH-Ranking/ 10 | 11 | screenshotpath=/home/pronto/git/SSH-Ranking/web/static/screens/ 12 | 13 | 14 | [logs] 15 | # Location of auth.log or the log you want to monitor. 16 | # Include the trailing / 17 | authlogpath=/var/log/ 18 | 19 | # Name of log you want to monitor. 20 | # For Debian-based systems you probably want auth.log. 21 | # For Red Hat systems, secure 22 | logname=auth.log 23 | 24 | # User who can read the log file; probably will be root 25 | userneed=root 26 | 27 | 28 | [sql] 29 | # SQL Settings 30 | # valid options include 'mysql' 31 | sqlservertype=mysql 32 | sqluser=sshrank 33 | sqlserv=localhost 34 | sqlpass=blargpass 35 | 36 | [portscan] 37 | # for the port scanning script; at some point its going to be automated 38 | # and update on its own on, for now it just a on off run 39 | on=True 40 | 41 | #no spaces! please 42 | #if you change ports; you're going to have to edit sqlcass.py 43 | ports=21,22,53,80,443,2082,2083,2086,8880,10000 44 | #in order: ftp, ssh, dns, http, https, cpanel, cpanel ssl, whm, plesk, webmin 45 | 46 | #update when current results are older then hours 47 | age=24 48 | 49 | 50 | [web] 51 | # To run the web server portion, you can either run 52 | # it on port 80 as root (not really recommended) or 53 | # you can run it on an unprivileged port (eg: port 5000). 54 | # Change this to suit your needs 55 | 56 | webUI_port=10080 57 | 58 | # How many IPs total do you want to show? Default 15 59 | total_ip=20 60 | 61 | # How many IPs do yo uwant to do extra stats on (eg top 10) 62 | stats_ip=60 63 | 64 | # How many user names to you want listed? 65 | user_cnt=5 66 | 67 | #where to look for xml files from nmap scans 68 | nmap_xml=/home/pronto/git/SSH-Ranking/web/static/xml/ 69 | #do rdns thangs 70 | rdns=yes 71 | #since there will be a log of the rdns info, updating every so 72 | #often is a good idea; 73 | #when an IP is in the topX when the rdns script runs 74 | #it'll check again when the current info is X seconds old 75 | rdns_age=48 76 | -------------------------------------------------------------------------------- /web/templates/tables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% set tableid=cycler('table2','table1') %} 11 | {% for ip in uniq_ips -%} 12 | {% set rowip = ip[0] -%} 13 | 14 | 15 | 21 | 22 | 37 | 38 | 39 | 40 | 41 | 47 | 59 | {% endfor -%} 60 |
IPAttemptsrdnsgeoIP/Last attempt
{{ ip[0] }} 16 | | (t) 17 | {% if ip[0] in nmapips -%} 18 | | scanned 19 | {% endif -%} 20 | {{ip[1]}} {% for res in alldns -%} 23 | {% if res.ip == rowip -%} 24 | {% if res.good != '-' %} 25 | {{res.rdns}} is 26 | {% if res.good == 'good' -%} 27 | Forward-confirmed 28 | {% else -%} 29 | unconfirmed 30 | {% endif -%} 31 | 32 | {% endif -%} 33 | {% endif -%} 34 | {% endfor -%} 35 | 36 | the moon
Users:{% for user in userlist -%} 42 | {% if user[0][0] == rowip -%} 43 | {{ user[1] }}:{{user[2]}} 44 | {% endif -%} 45 | {% endfor -%} 46 | 48 | last attempt: {% for lasta in datelist -%} 49 | {% if lasta[0] == ip[0] -%} 50 | {% if lasta[1] == newest -%} 51 |
{{ lasta[1] }}
52 | {% else %} 53 | {{ lasta[1] }} 54 | {% endif -%} 55 | {% endif -%} 56 | {% endfor -%} 57 | 58 |
61 |
62 | 63 | -------------------------------------------------------------------------------- /web/templates/about.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | 3 |

4 | This project, SSHRanking, is just a fun way to be able to play with: SQLAlchemy, jinja2, flask and python. 5 |
6 |
7 | 8 | the first script, sshrank.py has two flags: 9 |

10 | server# ./sshrank.py -h
11 | root
12 | usage: sshrank.py [-h] [-f FIRSTRUN] [-w WATCH]
13 | 
14 | optional arguments:
15 |   -h, --help   show this help message and exit
16 |   -f FIRSTRUN  set first run(default off), do -f on
17 |   -w WATCH     set to start watching (default off), do -w on
18 | 
19 |

normally you're going to want to run it as 'sshrank.py -f on -w on' so it'll scan all the auth.log files and drop into watching auth.log for new attempts.

20 |

This takes everything from /var/log/auth.log* and tosses into a DB (I use mysql, but SQLAlchemy should make it easy for you to change that)

21 |

22 | then theres ./rdnsthings.py which has no flags, you just run it and it updates rdns info for the IPs. 23 |

24 |

next you can show the data easily on the cli with ./outputdata.py

25 |

ouputdata.py still needs to be updated so it shows rdns, and ability to select date rage

26 |
27 | % ./outputdata.py 
28 | 177.154.132.99 attempted 45053 Last Attempt: 2013-10-03 13:51:13 times with users: 
29 |         root:1244,  admin:105,  apache:94,  Tsai:40,  user:35,  kim:32,  vincent:31,  linda:30,  laura:30,  julia:30,   
30 | 
31 | 184.22.118.11 attempted 6193 Last Attempt: 2013-10-11 16:10:52 times with users: 
32 |         root:4871,  ftpuser:70,  admin:62,  test:50,  svn:40,  www-data:36,  user:34,  www:30,  teamspeak:28,  oracle:24,   
33 | 
34 | 37.59.161.13 attempted 2278 Last Attempt: 2013-10-31 13:52:47 times with users: 
35 |         buttfart:548,  chickenfucker:485,  cockmonster:483,  dumpsterslut:483,  tittysprinkles:276,  <script>alert(xss)<,   
36 | 
37 | 190.14.40.14 attempted 1318 Last Attempt: 2013-10-18 12:14:41 times with users: 
38 |         root:451,  lady:9,  testuser:3,  greg:3,  aaron:3,  trinity:3,  live:2,  oracle:2,  dummy:2,  arno:2,   
39 | 
40 | 218.25.90.27 attempted 1144 Last Attempt: 2013-10-25 13:57:44 times with users: 
41 |         root:841,  test:224,  test1:24,  hirata:14,  tamada:13,  satoshi:10,  nakae:9,  schwarze:9,   
42 | 
43 | a 44 | 45 |

46 | {% include 'footer.html' %} 47 | -------------------------------------------------------------------------------- /web/templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | SSHRanking 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 |

SSH Ranking!

16 |

17 | {% if subhead == 'all' -%} 18 | All time 19 | {% elif subhead == 'week' -%} 20 | 7 days 21 | {% elif subhead == 'main' -%} 22 | Hi. 23 | {% elif subhead == '30day' -%} 24 | Last 30 days 25 | {% elif subhead =='24hr' -%} 26 | Last 24 hours 27 | {% elif subhead == 'about' -%} 28 | About this website. 29 | {% elif subhead == 'ipinfo' -%} 30 | IP info for {{ ip }} 31 | {% elif subhead == 'users' -%} 32 | Info page for {{ user }} 33 | {% elif subhead == 'userlist' -%} 34 | List of all attempted users 35 | {% elif subhead == 'tree' -%} 36 | Tree list for {{ ip }}{{ user}} 37 | {% elif subhead == '2pip' %} 38 | users with 2+ IP's 39 | {% elif subhead == 'port search' -%} 40 | port search: {{num}} 41 | {% elif subhead == 'service search' -%} 42 | service search: {{num}} 43 | {% elif subhead == 'service listing' -%} 44 | List of all services 45 | {% elif subhead == 'port listing' -%} 46 | List of all ports 47 | {% elif subhead == 'IPs nmap' -%} 48 | List of IPs with nmap scans 49 | {% elif subhead == 'screens' -%} 50 | Screenshots of HTTP 51 | {% else -%} 52 | 404'd | {{subhead}} 53 | {% endif %} 54 |

55 | 56 |
57 | 68 |
69 |
70 | 76 |
77 | 78 |
79 |

notice: Celery is tasty.

80 | -------------------------------------------------------------------------------- /sqlclass.py: -------------------------------------------------------------------------------- 1 | #classes 2 | 3 | import sqlalchemy 4 | from ConfigParser import SafeConfigParser 5 | from sqlalchemy.ext.declarative import declarative_base 6 | from sqlalchemy import Column, Integer, String,VARCHAR,TEXT,DATETIME, Sequence,func,Boolean, ForeignKey 7 | from sqlalchemy.orm import relationship 8 | 9 | # Config files! Yay! 10 | config = SafeConfigParser() 11 | config.read('config.ini') 12 | 13 | sqlserver = config.get('sql', 'sqlserv') 14 | sqlservertype = config.get('sql', 'sqlservertype') 15 | sqluser = config.get('sql', 'sqluser') 16 | sqlpass = config.get('sql', 'sqlpass') 17 | 18 | Base = declarative_base() 19 | query_string = sqlservertype + '://' + sqluser + ':' + sqlpass + '@' + sqlserver 20 | eng = sqlalchemy.create_engine(query_string) 21 | eng.execute("USE db_sshrank") 22 | eng.execute("select 1").scalar() 23 | Session =sqlalchemy.orm.sessionmaker(bind=eng) 24 | sqlsess = Session() 25 | class ips(Base): 26 | __tablename__ = 'ips_alc2' 27 | ip = Column(VARCHAR(39)) 28 | user = Column(TEXT) 29 | dtime = Column(DATETIME) 30 | pk = Column(Integer,Sequence('pk'), primary_key=True) 31 | 32 | def __init__(self,ip,user,dtime): 33 | self.ip = ip 34 | self.user = user 35 | self.dtime = dtime 36 | 37 | def __repr__(self): 38 | return "" % (self.ip, self.user, self.dtime) 39 | 40 | class rdns(Base): 41 | __tablename__= 'rdns_tbl' 42 | pk = Column(Integer,Sequence('pk'), primary_key=True) 43 | ip = Column(VARCHAR(39)) 44 | rdns = Column(TEXT) 45 | good = Column(VARCHAR(20)) 46 | dtime = Column(DATETIME) 47 | 48 | def __init__(self,ip,rdns,good,dtime): 49 | self.ip = ip 50 | self.rdns = rdns 51 | self.good = good 52 | self.dtime = dtime 53 | 54 | def __repr__(self): 55 | return "" % (self.ip, self.rdns, self.good, self.dtime) 56 | 57 | class nmapSQL(Base): 58 | __tablename__='nmapmysql' 59 | # |ignored| 60 | #ip, dtime, port number, State, Protocol, Owner, Service, SunRPC info, Version info 61 | pk = Column(Integer,Sequence('pk'), primary_key=True) 62 | ip = Column(VARCHAR(39)) 63 | dtime = Column(DATETIME) 64 | portnum = Column(VARCHAR(5)) 65 | state = Column(VARCHAR(10)) 66 | proto = Column(VARCHAR(5)) 67 | service = Column(VARCHAR(39)) 68 | verinfo = Column(TEXT) 69 | # 1 2 3 4 5 6 7 70 | def __init__(self, ip, dtime, portnum, state, proto, service, verinfo): 71 | self.ip = ip 72 | self.dtime = dtime 73 | self.portnum = portnum 74 | self.state = state 75 | self.proto = proto 76 | self.service = service 77 | self.verinfo = verinfo 78 | 79 | 80 | def __repr__(self): 81 | return "('%s','%s','%s','%s','%s','%s','%s')>" % ( self.ip, self.dtime, self.portnum, self.state, self.proto, self.service, self.verinfo) 82 | # 1 2 3 4 5 6 7 1 2 3 4 5 6 7 83 | 84 | 85 | #http://stackoverflow.com/questions/8839211/sqlalchemy-add-child-in-one-to-many-relationship 86 | #from sqlclass import * 87 | #a=ips('127.0.0.1', 'jkldj', '2013-10-28 15:10:51') 88 | #Session.add(a) 89 | #Session.commit() 90 | -------------------------------------------------------------------------------- /rdnsthings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | #this. 3 | #this will do the rdns look up+store 4 | #might look into making htis a class or something?that'd be neat 5 | 6 | #okay, lets get this started. 7 | import os, argparse,socket,time 8 | from ConfigParser import SafeConfigParser 9 | from datetime import datetime 10 | import code 11 | 12 | from sqlclass import * 13 | 14 | 15 | par = SafeConfigParser() 16 | #will be /etc/ssh-rank.ini or whereever you want it 17 | par.read(os.getcwd()+"/config.ini") 18 | 19 | mysqluser=par.get("sql","sqluser") 20 | mysqlserv=par.get("sql","sqlserv") 21 | mysqlpass=par.get("sql","sqlpass") 22 | 23 | user_cnt=int(par.get("web","user_cnt")) 24 | total_ip=par.get("web","total_ip") 25 | stats_ip=par.get("web","stats_ip") 26 | 27 | rdns_age =int(par.get("web","rdns_age")) 28 | 29 | def rdns_get(addr): 30 | try: 31 | return socket.gethostbyaddr(addr)[0] 32 | except socket.herror: 33 | return 'NO DNS HERE' 34 | 35 | def ipcheck(ip,dns): 36 | #print "\t" + str(ip) + "\t" + dns 37 | try: 38 | dns_ip=socket.gethostbyname_ex(dns)[2] 39 | except socket.gaierror: 40 | return "deleted" 41 | if ip not in dns_ip: 42 | return "bad" 43 | else: 44 | return "good" 45 | 46 | 47 | def insertsql_dns(i): 48 | rd=rdns(i[0],i[1],i[2],i[3]) 49 | sqlsess.add(rd) 50 | sqlsess.commit() 51 | 52 | def rdns_into_db(ip,run): 53 | #run is either 'first' or the rdns 54 | print "\n\n\n\t=====start of rdns into db func=====" 55 | rdns=rdns_get(ip) 56 | print "\t rdns: " + rdns 57 | #now make sure its good 58 | if rdns != 'NO DNS HERE': 59 | rcheck=ipcheck(ip,rdns) 60 | else: 61 | rcheck='-' 62 | timenow=datetime.now().strftime("%Y-%m-%d %H:%M:%S") 63 | print "\t == rcheck is:\t " + rcheck 64 | print "\t == run is:\t" + run 65 | #toss in mysql hell 66 | if run == 'first': 67 | insertsql_dns((ip,rdns,rcheck,timenow)) 68 | else: 69 | if rdns != run: 70 | #x.execute("""INSERT INTO rdns_tbl (ip,rdns,good,datetime) VALUES (%s,%s,%s,%s)""",(a[0],rdns,rcheck,timenow)) 71 | #insert=rdns(ip,rdns,rcheck,timenow) 72 | insertsql_dns((ip,rdns,rcheck,timenow)) 73 | print "=========================" 74 | 75 | #do a loopdaloop; check to see if the ip is alarady in the rdns_tbl 76 | #if it is; check to see when it was last updated; update if old 77 | #if it is not;do rdns, then if: 78 | # thre is rdns, check the IP of the name to see if its the same as IP 79 | 80 | 81 | #data = sqlsess.query(ips).order_by(-ips.pk).limit(stats_ip).all() 82 | data = sqlsess.query(ips.ip,ips.dtime,func.count(ips.ip).\ 83 | label('total')).group_by(ips.ip).order_by('total DESC').limit(int(total_ip)).all() 84 | for line in data: 85 | print "================== "+line.ip +" start of FOR LOOP===========================" 86 | #x.execute("""SELECT * FROM rdns_tbl WHERE ip='%s'""" % a[0]) 87 | #rdns_data=x.fetchall() 88 | # 89 | rdns_data=sqlsess.query(rdns).order_by(-rdns.pk).filter(rdns.ip==line.ip).limit(1).scalar() 90 | print rdns_data 91 | #if list is empty lets do some things! 92 | if not rdns_data: 93 | print 'no daters :(' 94 | print str(line.ip) + "=======================" 95 | #so lets add some 96 | #take IP, check for rdns: 97 | rdns_into_db(line.ip,'first') 98 | else: 99 | print 'okay' 100 | print '---;---;;' 101 | #so there's already an entry for rdns 102 | #we wanna get when it was last updated and compare :D 103 | elasped=int(time.time()) - int(rdns_data.dtime.strftime("%s")) 104 | #print elasped +","+rdns_age 105 | if elasped > rdns_age: 106 | print 'update bro' 107 | rdns_into_db(line.ip,rdns_data.rdns) 108 | print "=============================+" 109 | #comparethedate 110 | 111 | #code.interact(local=locals()) 112 | 113 | -------------------------------------------------------------------------------- /sshrank.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | import sys,os,pwd,argparse,gzip,time 3 | from datetime import datetime 4 | from multiprocessing import Process 5 | from sqlclass import * 6 | try: 7 | from ConfigParser import SafeConfigParser 8 | except ImportError as exc: 9 | sys.stderr.write("Error: failed to import settings module ({})".format(exc)) 10 | 11 | 12 | #set up the config file things 13 | 14 | par = SafeConfigParser() 15 | #will be /etc/ssh-rank.ini or whereever you want it 16 | par.read(os.getcwd()+"/config.ini") 17 | 18 | logpath=par.get("logs","authlogpath") 19 | logname=par.get("logs","logname") 20 | userneed=par.get("logs","userneed") 21 | 22 | mysqluser=par.get("sql","sqluser") 23 | mysqlserv=par.get("sql","sqlserv") 24 | mysqlpass=par.get("sql","sqlpass") 25 | 26 | arg=argparse.ArgumentParser() 27 | arg.add_argument('-f', action='store', dest='firstrun', default='off', help='set "first run" to on (default off), do: -f on') 28 | arg.add_argument('-w', action='store', dest='watch', default='off', help='set "start watching" to on (default off), do: -w on') 29 | arg.add_argument('-r', action='store', dest='resume', default='off', help='if you\'ve already done -f and -w, and some reason stopped it, use -r on to have it resume. note: probably won\'t work if log rotate went') 30 | year="2014" 31 | 32 | 33 | print pwd.getpwuid(os.getuid())[0] 34 | if userneed != pwd.getpwuid(os.getuid())[0]: 35 | print "please run as "+userneed 36 | sys.exit(1) 37 | 38 | def datafromline(line,year): 39 | #returns [date, user, ip] 40 | returnlist=[] 41 | l=filter(None,line.split(" ")) 42 | date=l[0]+" "+l[1]+" "+year+" "+l[2] #Sep 4 2013 19:31:55 43 | date=datetime.strptime(date,"%b %d %Y %H:%M:%S") 44 | date=date.strftime('%Y-%m-%d %H:%M:%S') 45 | user=line[line.find("user ")+5:line.find(" from")] 46 | ip=line[line.find("from ")+5:line.find(" port")] 47 | returnlist.append(ip) 48 | returnlist.append(user) 49 | returnlist.append(date) 50 | return returnlist 51 | 52 | 53 | def insertsql(i): 54 | user=ips(i[0],i[1],i[2]) 55 | sqlsess.add(user) 56 | sqlsess.commit() 57 | 58 | def openfile(logfile): 59 | if 'gz' in logfile: 60 | celery = gzip.open(logfile, 'r') 61 | else: 62 | celery = open(logfile, 'r') 63 | return celery 64 | 65 | 66 | def follow(thefile_name): 67 | thefile=open(thefile_name) 68 | thefile.seek(0,2) # Go to the end of the file 69 | while True: 70 | line = thefile.readline() 71 | test_1= os.path.getsize(thefile_name) 72 | if not line: 73 | time.sleep(0.1) # Sleep briefly 74 | test_2= os.path.getsize(thefile_name) 75 | if test_1 > test_2: 76 | thefile.close() 77 | thefile=open(thefile_name) 78 | thefile.seek(0,2) 79 | continue 80 | yield line 81 | 82 | 83 | argres = arg.parse_args() 84 | if argres.firstrun == "on": 85 | print "Initiating first run ..." 86 | log_list=[] 87 | for files in os.listdir(logpath): 88 | if logname in files: 89 | log_list.append(logpath+files) 90 | 91 | #we gonna need to loooooopdaloop the files :3 92 | #i'll deal with gzip'd 93 | #and for this i'll just be doing one auth.log 94 | #disco dance! 95 | for afile in log_list: 96 | start_time = time.time() 97 | for line in openfile(afile): 98 | if "Failed password for invalid user" in line: 99 | data=datafromline(line,year) 100 | #Process(target=insertsql,args=(data,)).start() 101 | insertsql(data) 102 | end_time = time.time() 103 | print("Elapsed time was %g seconds" % (end_time - start_time)) 104 | 105 | if argres.resume == 'on': 106 | print "Resuming from last:" 107 | last=sqlsess.query(ips).order_by(ips.pk.desc()).first() 108 | print "\tlast entry is:" +str(last) 109 | print "\t "+str(last.dtime) 110 | for line in openfile(logpath+logname): 111 | if "Failed password for invalid user" in line: 112 | data=datafromline(line,year) 113 | #now take the last attempt in DB date; and if the line is newer, add to DB 114 | if datetime.strptime(data[2],'%Y-%m-%d %H:%M:%S') > last.dtime: 115 | insertsql(data) 116 | 117 | 118 | if argres.watch == "on": 119 | print "\n\n==========Now Watching the logfile========" 120 | loglines=follow(logpath+logname) 121 | for line in loglines: 122 | if "Failed password for invalid user" in line: 123 | #print line 124 | data=datafromline(line,year) 125 | print str(data) 126 | #Process(target=insertsql,args=(data,)).start() 127 | insertsql(data) 128 | sqlsess.commit() 129 | -------------------------------------------------------------------------------- /web/static/new.css: -------------------------------------------------------------------------------- 1 | /* new css for thigns */ 2 | 3 | body{ 4 | background-color:#202021; 5 | } 6 | a { 7 | text-decoration:none; 8 | border-bottom:1px 9 | dotted; 10 | color:#9f9efe; 11 | } 12 | a:hover{ 13 | color:#e6e6e6; 14 | } 15 | 16 | h1,h2 { 17 | color:black; 18 | background:#454545; 19 | width:500px; 20 | margin-left:auto; 21 | margin-right:auto; 22 | -moz-border-radius: 15px; 23 | border-radius: 15px; 24 | text-align:center; 25 | } 26 | h2{ 27 | width:400px; 28 | } 29 | h3{ 30 | color:#9e9e9e; 31 | font-size:23pt; 32 | } 33 | #topLogo {float:left;margin-top:0px;margin-left:10px;} 34 | #topLogoRight {float:right;margin-top:0px;margin-right:10px;} 35 | 36 | #footer{ 37 | text-align:center; 38 | } 39 | 40 | #leftbar { 41 | width:300px; 42 | float:left; 43 | } 44 | #content { 45 | margin-left:300px; 46 | } 47 | 48 | #box{ 49 | width:99%; 50 | background:#373739; 51 | padding:10px; 52 | margin-left:auto; 53 | margin-right:auto; 54 | clear:both; 55 | } 56 | 57 | 58 | #footer a{ 59 | 60 | color:#9E9E9E; 61 | } 62 | 63 | #footer a:hover { 64 | color:darkred; 65 | text-decoration:underline; 66 | } 67 | #noticebox{ 68 | border-style:dotted; 69 | border-width:1px; 70 | border-color:yellow; 71 | width:400px; 72 | margin:1px; 73 | margin-right:auto; 74 | margin-left:auto; 75 | -moz-border-radius: 15px; 76 | border-radius: 15px; 77 | 78 | } 79 | 80 | p { 81 | color:#9E9E9E; 82 | padding-left:10px; 83 | } 84 | 85 | pre { 86 | font-size: 90%; 87 | line-height: 1.2em; 88 | font-family: "Courier 10 Pitch", Courier, monospace; 89 | white-space: pre; 90 | white-space: pre-wrap; 91 | white-space: -moz-pre-wrap; 92 | white-space: -o-pre-wrap; 93 | height:1%; 94 | width: auto; 95 | display: inline-block; 96 | clear: both; 97 | color: #6E6E6E; 98 | padding: 1em 1em; 99 | background: black; 100 | border: solid 1px #e1e1e1 101 | } 102 | #text{ 103 | padding:5px; 104 | color:green; 105 | 106 | } 107 | 108 | .newest{ 109 | color:blue; 110 | display: inline; 111 | } 112 | .userlist { 113 | font-size:10pt; 114 | 115 | } 116 | #listitem:hover{ 117 | background:#A8A8A8; 118 | 119 | } 120 | /* -------------------------------------------------- */ 121 | /* roundbar-grey */ 122 | /* http://matthewjamestaylor.com/centered-menus/# */ 123 | /* -------------------------------------------------- */ 124 | #roundbar-grey { 125 | clear:left; 126 | float:left; 127 | width:100%; 128 | background:#B7B7B7 ; 129 | font-family:Trebuchet MS, Helvetica, sans-serif; 130 | border-bottom:1px solid #A8A8A8; 131 | overflow:hidden; 132 | } 133 | #roundbar-grey ul { 134 | clear:left; 135 | float:left; 136 | list-style:none; 137 | margin:0; 138 | padding:0; 139 | position:relative; 140 | left:50%; 141 | text-align:center; 142 | } 143 | #roundbar-grey ul li { 144 | display:block; 145 | float:left; 146 | list-style:none; 147 | margin:0; 148 | padding:0; 149 | position:relative; 150 | right:50%; 151 | } 152 | #roundbar-grey li:hover{ 153 | background:#A8A8A8; 154 | } 155 | #roundbar-grey ul li.first { 156 | border-left:1px solid #A8A8A8; 157 | } 158 | #roundbar-grey ul li.last { 159 | border-right:1px solid #C8C8C8; 160 | } 161 | #roundbar-grey ul li a { 162 | display:block; 163 | margin:0; 164 | padding:.4em .8em; 165 | color:#000; 166 | text-decoration:none; 167 | border-left:1px solid #C8C8C8; 168 | border-right:1px solid #A8A8A8; 169 | line-height:1.3em; 170 | } 171 | #roundbar-grey ul li a span { 172 | display:block; 173 | } 174 | #roundbar-grey ul li.active a { 175 | font-weight:bold; 176 | } 177 | #roundbar-grey ul li a:hover { 178 | } 179 | #roundbar-grey ul li.test { 180 | color #999; 181 | line-height=0.9em; 182 | border-bottom:0px; 183 | font-size:13px; 184 | } 185 | 186 | 187 | /* crap for tables? */ 188 | 189 | table.gridtable { 190 | font-family: "Courier 10 Pitch", Courier, monospace; 191 | font-size:12px; 192 | color:#333333; 193 | border-width: 1px; 194 | width:99%; 195 | border-color: #666666; 196 | border-collapse: collapse; 197 | } 198 | table.gridtable th { 199 | border-width: 1px; 200 | padding: 8px; 201 | border-style: solid; 202 | border-color: #666666; 203 | background-color: #dedede; 204 | } 205 | table.gridtable td { 206 | border-width: 1px; 207 | padding: 8px; 208 | border-style: solid; 209 | border-color: #666666; 210 | 211 | } 212 | #prewrap{ 213 | white-space: pre; 214 | white-space: pre-wrap; 215 | white-space: -moz-pre-wrap; 216 | } 217 | 218 | table.gridtable a { 219 | color:#333333; 220 | } 221 | table.gridtable a:hover { 222 | color:darkred; 223 | text-decoration:overline underline; 224 | } 225 | 226 | #table1 { 227 | background:#B7B7B7 ; 228 | 229 | } 230 | #table2{ 231 | background:#808080; 232 | } 233 | /* 234 | #table2:hover, #table1:hover { 235 | background-color:#333333; 236 | color:white; 237 | 238 | } */ 239 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SSH-Ranking 2 | =========== 3 | 4 | SSH Ranking system! (re-write of ssh-fail-watcher) 5 | 6 | Demo of the webUI (when working) at: https://sshrank.in/g/lists/24hr 7 | 8 | Project website: https://github.com/pronto/SSH-Ranking 9 | 10 | This script will take your ssh connection logs and log them to MySQL, continuously watch authlog for new connection attempts, then rank them among previous authorization attempts. 11 | 12 | 13 | NOTE: changed length of the ip column; if you already had mysql going; do this: 14 | ``` 15 | ALTER TABLE ips_alc2 MODIFY ip VARCHAR(39); 16 | ALTER TABLE rdns_tbl MODIFY ip VARCHAR(39); 17 | 18 | ``` 19 | status: 20 |
 21 | Get info from auth.log.*           YES
 22 | 
 23 | Toss in MySQL                      YES (but still reworking how DB works)
 24 | 
 25 | Get RDNS and save RDNS history     Gets rdns, displays it. but not history
 26 | 
 27 | GeoIP                              NO
 28 | 
 29 | easy install method                kinda; read this..
 30 | 
 31 | watch for new things               YES
 32 | 
 33 | 
34 | 35 | # Installation Instructions 36 | ## Debian/Ubuntu 37 | This was made on Linux debian 3.2.0-4-amd64 #1 SMP Debian 3.2.51-1 x86_64 GNU/Linux 38 | should still work on other distros; you just won't be able to blindly follow this readme. 39 | $ cat /etc/debian_version 40 | 7.2 41 | ``` 42 | user@debian:~$ su 43 | Password: 44 | root@debian:/home/user# apt-get install git python-pip mysql-server python-mysqldb 45 | >hit yes for install 46 | 47 | >During this you will get asked for mysql-server root password; set that 48 | 49 | root@debian:/ # easy_install flask-sqlalchemy 50 | >You might get a lot of warnings and such but if you see the below; you should be good 51 | 52 | >Adding MarkupSafe 0.18 to easy-install.pth file 53 | 54 | >Installed /usr/local/lib/python2.7/dist-packages/MarkupSafe-0.18-py2.7.egg 55 | Finished processing dependencies for flask-sqlalchemy 56 | root@debian:/ # 57 | ``` 58 | 59 | ## Red Hat/CentOS/Fedora 60 | This was tested on CentOS 6.4 minimal but should work with all version 6 branches. Version 5.x may take some tweaking to make it work. 61 | 62 | Install required packages 63 | ``` 64 | [root@centos ~]# yum install git mysql-server MySQL-python python-setuptools-devel -y 65 | ``` 66 | Now install required Python modules 67 | ``` 68 | [root@centos ~]# easy_install flask-sqlalchemy argparse 69 | >You might get a lot of warnings and such but if you see the below; you should be good 70 | 71 | >Adding MarkupSafe 0.18 to easy-install.pth file 72 | >Installed /usr/lib/python2.6/site-packages/MarkupSafe-0.18-py2.6.egg 73 | >Finished processing dependencies for flask-sqlalchemy 74 | ``` 75 | Now startup MySQL server. During this procedure you will be asked to set your mysql-server root password. Other than entering a root password, you can accept the defaults. 76 | We also want to make sure mysqld starts next time the server starts. 77 | 78 | ``` 79 | [root@centos ~]# /etc/init.d/mysqld start 80 | [root@centos ~]# mysql_secure_installation 81 | [root@centos ~]# chkconfig mysqld on 82 | 83 | ``` 84 | 85 | ## Configure MySQL 86 | 87 | ``` 88 | root@debian:/# mysql -u root -p 89 | >Enter password: 90 | 91 | mysql> CREATE USER 'sshrank'@'localhost' IDENTIFIED BY 'blargpass'; 92 | >Query OK, 0 rows affected (0.01 sec) 93 | 94 | mysql> CREATE DATABASE db_sshrank; 95 | >Query OK, 1 row affected (0.00 sec) 96 | 97 | mysql> GRANT ALL PRIVILEGES ON db_sshrank.* TO sshrank@localhost; 98 | >Query OK, 0 rows affected (0.00 sec) 99 | 100 | mysql> exit 101 | Bye 102 | 103 | ``` 104 | 105 | ## SSH-Rank Configuration and Download 106 | Clone SSH-Rank directly from github or download to your system via the project website 107 | 108 | ``` 109 | user@debian:~$ mkdir git 110 | user@debian:~$ cd git 111 | user@debian:~/git$ git clone https://github.com/pronto/SSH-Ranking && cd SSH-Ranking 112 | Cloning into 'SSH-Ranking'... 113 | remote: Counting objects: 453, done. 114 | remote: Compressing objects: 100% (244/244), done. 115 | remote: Total 453 (delta 233), reused 422 (delta 202) 116 | Receiving objects: 100% (453/453), 158.32 KiB, done. 117 | Resolving deltas: 100% (233/233), done. 118 | user@debian:~/git/SSH-Ranking$ 119 | 120 | ``` 121 | 122 | Now edit config.ini
123 | Change sqlclassPath variable to match where you downloaded SSH-Rank. Include the trailing / 124 | 125 | ``` 126 | sqlclassPath=/home/user/git/SSH-Ranking/ 127 | ``` 128 | 129 | Modify the authlogpath and logname to point to the path and log name of the ssh log you want to watch. Include the trailing /
130 | For Debian-based systems you probably want auth.log. For Red Hat systems try secure 131 | 132 | ``` 133 | authlogpath=/var/log/ 134 | logname=auth.log 135 | ``` 136 | 137 | Change the following lines to match your SQL environment. Right now only MySQL is supported for sqlservertype. 138 | 139 | ``` 140 | sqlservertype=mysql 141 | sqluser=sshrank 142 | sqlsvr=localhost 143 | sqlpass=blargpass 144 | 145 | ``` 146 | Now at the command line: 147 | 148 | ``` 149 | user@debian:~/git/SSH-Ranking$ python 150 | Python 2.7.3 (default, Jan 2 2013, 13:56:14) 151 | [GCC 4.7.2] on linux2 152 | Type "help", "copyright", "credits" or "license" for more information. 153 | >>> from sqlclass import * 154 | >>> Base.metadata.create_all(eng) 155 | >>> exit () 156 | 157 | ``` 158 | 159 | ### Verify the MySQL tables were created: 160 | 161 | ``` 162 | user@debian:~$ mysql -u sshrank -A db_sshrank -p 163 | Enter password: 164 | mysql> show tables; 165 | +----------------------+ 166 | | Tables_in_db_sshrank | 167 | +----------------------+ 168 | | ips_alc2 | 169 | | rdns_tbl | 170 | +----------------------+ 171 | 2 rows in set (0.00 sec) 172 | 173 | mysql> exit; 174 | 175 | ``` 176 | 177 | ## Run SSH-Rank 178 | For the moment you have to run sshrank.py under a utility like tmux or screen. 179 | Screen is probably in your package manager; simply run apt-get install screen if you're on a Debian-based distribution or yum install screen for Red Hat. 180 | You can download tmux from http://tmux.sourceforge.net/ 181 | 182 | ``` 183 | root@debian:/home/user/git/SSH-Ranking# python ./sshrank.py -f on -w on 184 | root 185 | doing 1st run! yay! 186 | Elapsed time was 5.91278e-05 seconds 187 | 188 | 189 | ==========Now Watching the logfile======== 190 | ['::1', 'klfsjakl', '2013-11-13 12:59:01'] 191 | ['::1', 'klfsjakl', '2013-11-13 12:59:05'] 192 | ['192.168.1.50', 'kjskfjask', '2013-11-13 12:59:37'] 193 | ['192.168.1.50', 'kjskfjask', '2013-11-13 12:59:41'] 194 | 195 | ``` 196 | Those four entries are tests I did. As users try to connect with incorrect passwords, you'll see new entries show up. To test, attempt to ssh in to your server with an unknown user/pass and you should see it show up in this list. 197 | 198 | 199 | ## WebUI Setup 200 | To run the web server portion, you can either run it on port 80 as root (not really recommended) or you can run it on an unprivileged port (default is port 5000). Change this in config.ini to suit your needs 201 | 202 | ``` 203 | webUI_port=5000 204 | 205 | ``` 206 | For persistence, the WebUI should also be run under a screen/tmux session 207 | 208 | ``` 209 | user@debian:~/git/SSH-Ranking$ python web/serv.py 210 | * Running on http://0.0.0.0:5000/ 211 | 212 | ``` 213 | 214 | Now you should be able to go to the IP:port and see the webUI. 215 | http://: 216 | 217 | eg: http://127.0.0.1:5000/ 218 | 219 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /web/serv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | from flask import Flask, render_template, send_from_directory, request 3 | import socket,os,sys 4 | from datetime import datetime,timedelta 5 | from datetime import date as ddate 6 | from ConfigParser import SafeConfigParser 7 | import glob 8 | 9 | par = SafeConfigParser() 10 | #will be /etc/ssh-rank.ini or where ever you want it 11 | par.read(os.getcwd()+"/config.ini") 12 | from flask.ext.sqlalchemy import SQLAlchemy 13 | import code 14 | 15 | debug=par.get("sshrank","debugging") 16 | webUI_port=par.get("web","webUI_port") 17 | nmap_xml_path=par.get("web","nmap_xml") 18 | sqlclassPath=par.get("sshrank","sqlclassPath") 19 | screenshotpath=par.get("sshrank","screenshotpath") 20 | sys.path.append(sqlclassPath) 21 | 22 | from sqlclass import * 23 | 24 | mysqluser=par.get("sql","sqluser") 25 | mysqlserv=par.get("sql","sqlserv") 26 | mysqlpass=par.get("sql","sqlpass") 27 | user_cnt=int(par.get("web","user_cnt")) 28 | total_ip=par.get("web","total_ip") 29 | stats_ip=par.get("web","stats_ip") 30 | socket.setdefaulttimeout(3) 31 | 32 | 33 | def getlastattempt(ip): 34 | sqlsess.query(ips.datetime).filter(ips.ip==ip).order_by(-ips.pk).limit(1).scalar() 35 | date=datetime.strptime(str(date),'%Y-%m-%d %H:%M:%S') 36 | return date.strftime('%Y-%m-%d %H:%M:%S') 37 | def killtuple(lista): 38 | listb=[] 39 | for a in lista: 40 | listb.append(a[0]) 41 | return listb 42 | 43 | #im not even sure wtf im doing...neat i think? 44 | def tree_finder(thing): 45 | #lets just do it with start from ip 46 | list_ips=[] 47 | #make sure its a real ip... 48 | uniq_ips=killtuple(sqlsess.query(ips.ip).distinct()) 49 | if thing in uniq_ips: 50 | #get the users 51 | users = killtuple(sqlsess.query(ips.user).order_by(ips.ip).filter(ips.ip==str(thing)).distinct()) 52 | for user in users: 53 | list_ips.append([user,killtuple(sqlsess.query(ips.ip).filter(ips.user==str(user)).distinct())]) 54 | return list_ips 55 | else: 56 | return 'nope' 57 | def getlen(user): 58 | return len(killtuple(sqlsess.query(ips.ip).filter(ips.user==str(user)).distinct())) 59 | 60 | def tree_user(user): 61 | list_user=[] 62 | uniq_user=killtuple(sqlsess.query(ips.user).distinct()) 63 | if user in uniq_user: 64 | iplist = killtuple(sqlsess.query(ips.ip).order_by(ips.ip).filter(ips.user==str(user)).distinct()) 65 | for ip in iplist: 66 | list_user.append([ip,killtuple(sqlsess.query(ips.user).order_by(ips.user).filter(ips.ip==str(ip)).distinct())]) 67 | return list_user 68 | else: 69 | return 'nope' 70 | 71 | app=Flask(__name__) 72 | #app.debug=True 73 | if debug == 1: 74 | app.debug=True 75 | 76 | #date=sqlsess.query(ips.dtime).filter(ips.ip==a[0]).order_by(-ips.pk).limit(1).scalar() 77 | #date=datetime.strptime(str(date),'%Y-%m-%d %H:%M:%S') 78 | #date=date.strftime('%Y-%m-%d %H:%M:%S') 79 | # 80 | 81 | @app.route('/') 82 | def main(): 83 | subhead="main" 84 | return render_template('main.html',subhead=subhead) 85 | 86 | @app.route('/ssh_rank/lists/