{"id":816,"date":"2008-05-29T07:01:51","date_gmt":"2008-05-29T14:01:51","guid":{"rendered":"http:\/\/www.gubatron.com\/blog\/?p=816"},"modified":"2008-05-29T07:01:51","modified_gmt":"2008-05-29T14:01:51","slug":"script-to-automatically-detect-and-ban-malicious-ips-that-try-to-brute-force-ssh-accounts","status":"publish","type":"post","link":"https:\/\/www.gubatron.com\/blog\/script-to-automatically-detect-and-ban-malicious-ips-that-try-to-brute-force-ssh-accounts\/","title":{"rendered":"Script to automatically detect and ban malicious IPs that try to brute force SSH accounts"},"content":{"rendered":"<p>We&#8217;ve noticed that most of our servers have been under heavy attack from random IP addresses to break via SSH.<\/p>\n<p>With the help of the last post on how to ban an IP, and the following python script, you&#8217;ll be able to have a cronjob that runs once or twice a day and automagically bans all the offending ips from ever trying to brute force their way in ever again.<\/p>\n<p>touch and make executable a file called &#8220;detect_ssh_hostiles&#8221;<\/p>\n<pre>\ntouch detect_ssh_hostiles\nchmod +x detect_ssh_hostiles\n<\/pre>\n<p>Then copy the following code inside:<\/p>\n<pre>\n# Usage:\n# python detect_ssh_hostiles [auth.log file path]\n#\n# Requirement: There should be \"ban_ip\" and \"unban_ip\" command availability on the path\n#\n# Note: you gotta have read permissions on the auth.log file and sudo\n#       permissions for the script to ban the ips.\n\n#If an IP meets this number of failed login attemmpts it will be banned\nBAN_THRESHOLD = 7\nSUSPECTS = {}\n\n#Put here IP addresses you trust, could be making genuine login errors\nSAFE_IPS = ['81.73.111.49','101.73.111.160','72.31.171.235','72.36.23.234','82.36.180.210','202.132.82.16']\n\nimport os\nimport sys\nimport re\n\nBANNED = {}\ndef loadBanned():\n  '''\n  This function will load all the banned IPS into the BANNED Dict.\n  It will also count how many times (by mistake) the same IP has\n  been banned, and it will unban it, so that it will appear only once.\n  '''\n  global BANNED\n  command = 'sudo iptables --list --numeric'\n  try:\n    p = os.popen(command,'rb')\n  except Exception,e:\n    print e\n    sys.exit(1)\n\n  line = '-'\n\n  while line != '':\n    line = p.readline().strip()\n\n    if line.startswith(\"DROP\"):\n      parts = line.split()\n      ip = parts[3]\n\n      #add hit or register banned ip\n      if BANNED.has_key(ip):\n        BANNED[ip]+=1\n      else:\n        BANNED[ip]=1\n\n  #Make sure banned IPs are banned only once\n  for ip in BANNED:\n    if BANNED[ip] > 1:\n      print \"IP %s has been banned %d times\" % (ip, BANNED[ip])\n      n=BANNED[ip]-1\n      while n > 0:\n        os.system(\"unban_ip %s\" % ip)\n        print (\"unban_ip %s\" % ip)\n        n=n-1\n\n  p.close()\n\n# ---- here we go ----\nloadBanned()\n\n#read auth log\nlogfile = '\/var\/log\/auth.log'\n\nif len(sys.argv)==2:\n  logfile = sys.argv[1]\n\ncommand = 'grep \"Failed password for \" %s' % logfile\n#print command\n\ntry:\n  p = os.popen(command,'rb')\nexcept Exception,e:\n  print e\n  sys.exit(1)\n\nline = \"123\"\n\nwhile line != '':\n  line = p.readline()\n\n  #Sample line:\n  # May 25 03:29:49 main sshd[6933]: Failed password for root from 202.118.236.132 port 54863 ssh2\n  pattern = \"(.*)(froms)(d+.d+.d+.d+)(.*)\"\n  matchObject = re.match(pattern, line)\n\n  suspect = None\n  if matchObject is not None:\n    suspect = matchObject.groups()[2]\n\n    #skip safe IPs\n    if suspect in SAFE_IPS:\n      continue\n\n    if SUSPECTS.has_key(suspect):\n      #add a hit\n      SUSPECTS[suspect] += 1\n    else:\n      #add first hit\n      SUSPECTS[suspect] = 1\n\np.close() #close the pipe\n\nprint \"==\"*30\n\nimport time\nt = time.localtime()\n#(2008, 6, 6, 9, 35, 21, 4, 158, 1)\n\ntimestr = \"%d-%d-%d@%d:%d:%d\" % (t[0],t[1],t[2],t[3],t[4],t[5])\nprint timestr\nprint \"--\"*30\nif len(SUSPECTS) > 0:\n  for suspect in SUSPECTS:\n    if SUSPECTS[suspect] >= BAN_THRESHOLD and not BANNED.has_key(suspect):\n      print \"Banning %s with %d attempts\" % (suspect,SUSPECTS[suspect])\n      BANNED[suspect]=1\n      os.system(\"ban_ip %s\" % suspect)\n    elif BANNED.has_key(suspect):\n      print \"Ip %s has already been banned\" % (suspect)\n    else:\n      print \"Suspect candidate? %s with %d attempts\" % (suspect,SUSPECTS[suspect])\nelse:\n  print \"Found no suspects to ban\"\n\nprint \"==\"*30<\/pre>\n<p>Then add this as a cronjob of your root user, and it will automatically ban all those IPs that have tried to break in. See the script for configuration. You can always make some IPs immune to banning by adding them on the SAFE_IPS list.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We&#8217;ve noticed that most of our servers have been under heavy attack from random IP addresses to break via SSH. With the help of the last post on how to ban an IP, and the following python script, you&#8217;ll be able to have a cronjob that runs once or twice a day and automagically bans [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[15,43,65],"tags":[165,173,177,230,1458,929],"class_list":["post-816","post","type-post","status-publish","format-standard","hentry","category-code","category-linux","category-python","tag-automatic-banning","tag-ban","tag-banning","tag-brute-force","tag-python","tag-ssh"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p5Unzf-da","jetpack-related-posts":[{"id":1457,"url":"https:\/\/www.gubatron.com\/blog\/map-commands-to-servers-via-ssh\/","url_meta":{"origin":816,"position":0},"title":"Quick N Dirty way to Map Commands to remote servers via ssh","author":"gubatron","date":"October 10, 2009","format":false,"excerpt":"You may be running several independent but similar servers at the same time and wasting time by executing commands in all of them one by one. Wouldn't it be nice to send a command to all of them at once? or to monitor all of them at once. The following\u2026","rel":"","context":"In &quot;Code&quot;","block_context":{"text":"Code","link":"https:\/\/www.gubatron.com\/blog\/category\/code\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":481,"url":"https:\/\/www.gubatron.com\/blog\/svn-cant-create-tunnel-the-system-cannot-find-the-file-specified\/","url_meta":{"origin":816,"position":1},"title":"svn: Can&#8217;t create tunnel: The system cannot find the file specified.","author":"gubatron","date":"March 15, 2007","format":false,"excerpt":"I was trying to checkout a project from a subversion repository using Eclipse's Subversive, and I was having problems with a subversion url that starts with \"svn+ssh:\/\/\" This means all the transport has to be done using a \"ssh\" agent. Eclipse's Subclipse plugin was giving me the error: svn: Can't\u2026","rel":"","context":"In &quot;Code&quot;","block_context":{"text":"Code","link":"https:\/\/www.gubatron.com\/blog\/category\/code\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":814,"url":"https:\/\/www.gubatron.com\/blog\/how-to-banunban-ips-in-linux\/","url_meta":{"origin":816,"position":2},"title":"How to ban\/unban ips in linux","author":"gubatron","date":"May 29, 2008","format":false,"excerpt":"In case you're not an iptables guru, you might want to create a couple scripts and put em somewhere on your $PATH. I've created two scripts called ban_ip and unban_ip. Create a file called ban_ip touch ban_ip chmod +x ban_ip Edit it and copy the following code inside: #!\/bin\/bash sudo\u2026","rel":"","context":"In &quot;Code&quot;","block_context":{"text":"Code","link":"https:\/\/www.gubatron.com\/blog\/category\/code\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1998,"url":"https:\/\/www.gubatron.com\/blog\/startkeychain-bash-utility-to-start-ssh-agent\/","url_meta":{"origin":816,"position":3},"title":"startKeychain &#8211; bash utility to start ssh-agent","author":"gubatron","date":"June 3, 2010","format":false,"excerpt":"For my (and your) future reference, here's a function to put on your .bashrc or .bash_profile, you can invoke it later at any time to start\/re-start your ssh-agent. [bash] function startKeychain { killall ssh-agent rm ~\/.keychain\/* keychain id_rsa HOSTNAME=`hostname` source ~\/.keychain\/${HOSTNAME}-sh } [\/bash] Then at any time, the \"command\" startKeychain\u2026","rel":"","context":"In &quot;Code&quot;","block_context":{"text":"Code","link":"https:\/\/www.gubatron.com\/blog\/category\/code\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":4122,"url":"https:\/\/www.gubatron.com\/blog\/screw-configuring-gpg-to-sign-git-commits-do-it-with-your-same-ssh-key-like-this\/","url_meta":{"origin":816,"position":4},"title":"Screw configuring GPG to sign git commits, do it with your same SSH key like this","author":"gubatron","date":"January 30, 2025","format":false,"excerpt":"GPG gives a lot of shit on macos for some reason. It's best to just add the same SSH authentication key you have on github as a \"Signing Key\" in your github account signing configuration. Go to Settings > SSH and GPG Keys You can then configure your github client\u2026","rel":"","context":"In &quot;Code&quot;","block_context":{"text":"Code","link":"https:\/\/www.gubatron.com\/blog\/category\/code\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.gubatron.com\/blog\/wp-content\/uploads\/2025\/01\/ssh_sign.jpg?fit=736%2C441&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.gubatron.com\/blog\/wp-content\/uploads\/2025\/01\/ssh_sign.jpg?fit=736%2C441&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.gubatron.com\/blog\/wp-content\/uploads\/2025\/01\/ssh_sign.jpg?fit=736%2C441&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.gubatron.com\/blog\/wp-content\/uploads\/2025\/01\/ssh_sign.jpg?fit=736%2C441&ssl=1&resize=700%2C400 2x"},"classes":[]},{"id":764,"url":"https:\/\/www.gubatron.com\/blog\/python-ip2num-num2ip-store-an-ip-string-as-a-4-byte-int\/","url_meta":{"origin":816,"position":5},"title":"[Python] ip2num \/ num2ip &#8211; Store an IP string as a 4 byte int.","author":"gubatron","date":"March 27, 2008","format":false,"excerpt":"This is probably everywhere, maybe python also comes with it, but I wanted to have my own implementation, and I'll leave it here for future reference. Basically, sometimes you don't want to store IPs in Strings cause they take too much space, instead you want to be a good programmer\u2026","rel":"","context":"In &quot;Code&quot;","block_context":{"text":"Code","link":"https:\/\/www.gubatron.com\/blog\/category\/code\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.gubatron.com\/blog\/wp-json\/wp\/v2\/posts\/816","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.gubatron.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.gubatron.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.gubatron.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.gubatron.com\/blog\/wp-json\/wp\/v2\/comments?post=816"}],"version-history":[{"count":0,"href":"https:\/\/www.gubatron.com\/blog\/wp-json\/wp\/v2\/posts\/816\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.gubatron.com\/blog\/wp-json\/wp\/v2\/media?parent=816"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.gubatron.com\/blog\/wp-json\/wp\/v2\/categories?post=816"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.gubatron.com\/blog\/wp-json\/wp\/v2\/tags?post=816"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}