script for stopping sshd attacks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I've been pretty happy with the script below and thought I'd share it.
I currently run this from rc.local.

It's written in lisp, which is my scripting language of choice, but
if you want to use some other language I hope it'll be easy enough
to translate.  If you want to use it as is and don't already have
clisp, it should be easy enough to download and install.
I suppose something similar could be done for http and perhaps
other services, but I currently use other approaches for those.

I actually run two versions, one for an older machine.  The
differences are that it reads /var/log/messages instead of secure
and that it uses "ipchains -A input -s ~a -j DENY" instead of 
"iptables -A INPUT -s ~a -j MIRROR".

The choice of mirror vs deny/drop is an interesting one.
I think mirror is justified by the fact that the attacking
IP address is evidently not spoofed (it has already managed to
send the right tcp sequence numbers several times).  
I've noticed that the ssh attacks in particular tend to stop 
after about 30 packets if you drop, whereas they sometimes go
on for thousands if you mirror.  While this may be viewed as
bad for the machine running the script (wasting its bandwidth)
I regard it as a public service to waste the time of attackers
who might otherwise be successfully attacking others.
Also, of course, there's some chance that the attacking machine
will break into itself, and might even do some damage, which I
would also view as a public service.

====
#! /usr/local/bin/clisp
;; Stop listening to machines that attack sshd.

(defvar *logfile* "/var/log/secure")
(defvar *last-log-write* 0)
(defvar *last-log-length* 0)
(defvar *tables* nil)
(defvar *ntables* 6)
(defvar *sleep* 10)
(defvar *nfailures* 5)

;; nfailures failures from the same IP address
;; within (ntables x sleep) seconds is considered an attack
;; (actually, it's typically a lot longer, since the log file
;; is not written continually - it's ntables reads of the log
;; file, which happen at least sleep sec. apart.)

;; for some reason running this from rc.local with >> file
;; sends only the dates to that file ...

(defvar *log-out* "/var/log/sshd-attacks")

(defun add-log (format-string &rest args)
  (with-open-file
   (f *log-out* :direction :output :if-exists :append
      :if-does-not-exist :create)
   (format f "~%~a: " (show-ut))
   (apply 'format f format-string args)))

(defun show-ut (&optional (ut (get-universal-time))) 
  (multiple-value-bind 
   (s m h d mo y) (decode-universal-time ut) 
   (format nil "~d/~d/~d ~2,'0d:~2,'0d:~2,'0d" y mo d h m s)))

;; avoid duplicate rules
(defvar *denied* (make-hash-table :test 'equal))

(defun incremental (&optional kill
			      &aux pos line
			      (ip-count (make-hash-table :test 'equal)))
  (when (> (file-write-date *logfile*) *last-log-write*)
    (setf *last-log-write* (file-write-date *logfile*))
    (with-open-file
     (f *logfile*)
     (when (< (file-length f) *last-log-length*)
       ;; assume we've started a new version of the log file
       (setf *last-log-length* 0))
     (file-position f *last-log-length*)
     (loop while (setf line (read-line f nil)) do
       ;; Here's a sample of what I'm looking for in the log
       ;; May 13 22:38:08 isis sshd[27594]: Failed password for root from 81.180.201.38 port 43642 ssh2
       (when (and (setf pos (search "sshd" line))
		  (setf pos (search ": Failed password for " line :start2 pos))
		  (setf pos (search "from " line :start2 pos)))
	 (incf pos #.(length "from "))
	 (incf (gethash (subseq line pos (search " " line :start2 pos))
			ip-count 0))))
     (setf *last-log-length* (file-position f))
     (push ip-count *tables*)
     (when (nthcdr *ntables* *tables*)
       (setf (cdr (nthcdr *ntables* *tables*)) nil))
     (loop for ip being the hash-keys of (car *tables*)
       as total = (loop for tab in *tables* sum (gethash ip tab 0))
       unless (gethash ip *denied*)
       when (>= total *nfailures*)
       do
       (when kill
	 (setf (gethash ip *denied*) t)
	 (add-log " ~a failures: ~a~%" total ip)
	 (run-shell-command
	  (format nil "iptables -A INPUT -s ~a -j MIRROR" ip))))
     (unless kill (pop *tables*)))))

(add-log "start")
(incremental) ;; first time we just list previous attacks
(loop while t do
  (incremental t) 
  (sleep *sleep*))


[Index of Archives]     [Linux Netfilter Development]     [Linux Kernel Networking Development]     [Netem]     [Berkeley Packet Filter]     [Linux Kernel Development]     [Advanced Routing & Traffice Control]     [Bugtraq]

  Powered by Linux