#!/usr/local/bin/wermit + ; (change the first line to point to C-Kermit 8.0 Beta.03 executable) ; (and give this file execute permission) ; p o r t l o g ; ; Absorbs and logs attacks on a given TCP Port, such as 80. ; Sends hourly summaries to the selected e-mail address. ; Uploads hourly logs to the selected FTP destination. ; ; Default port is 80. You can specify a different port as the ; first command-line arg, or you can tell kermit to "define port 443" ; (or whatever) and then "take portlog" or simply "take portlog 443". ; Runs forever. Stop it with Ctrl-C. To run in background, redirect ; stdout and stderr to a file or to /dev/null. ; ; Requires C-Kermit 8.0 Beta.03 or later. ; ; Default port is 80 (HTTP). Works for any HTTP attack: Code Red, ; Nimba, etc. Requires privilege for ports < 1000 in UNIX. ; Requires that no other process is listening on the same port. ; ; IMPORTANT: Change all lines marked (*) as needed. ; ; F. da Cruz, Columbia University, September 2001. ; Last update: Fri Sep 21 09:44:38 2001 ; if < \v(version) 800200 exit 1 Fatal - C-Kermit 8.0 required. local ftppass ; So it will disappear automatically when script terminates. ; PARAMETERS - ADJUST AS NEEDED .truncate := 0 ;(*) Truncate log records at 79 cols? .doftp := 1 ;(*) Upload full logs with FTP? .ftpuser := fdc ;(*) FTP info... CHANGE THIS .ftphost := ftp.kermit.columbia.edu ;(*) CHANGE THIS .ftpdir := ~kermit/cu/port80logs ;(*) CHANGE THIS .address := security@columbia.edu ;(*) E-mail address for summary .mailcmd := Mail ;(*) External mail command if match \v(platform) *HP-UX* .mailcmd = mailx if k-95 .mailcmd = echo ; No way to mail from Windows if def \%1 .port := \%1 ; Listen port if not def port .port := 80 ;(*) Default listen port ;(*) The following macro checks if its argument string contains ;(*) an IP address or IP hostname in the local administrative domain. ;(*) CHANGE THE PATTERN to match your own site's addresses. def CHKLOCAL { ;(*) if match \%1 *{128.59.*.,160.39.*.,columbia.edu,barnard.edu}* end 0 end 1 } ; END OF ADJUSTABLE PARAMETERS def NEWLOGNAME { ; Make new log file name .logfile := \v(host)_\m(port)_\v(ndate)_\freplace(\v(time),:,).log } .\%n = 0 ; Event count .hour := \fstripx(\fstripx(\v(time),:),:) ; Start hour .start := \fcvtdate() ; Start date-time newlogname ; Get new log name define ON_CTRLC { ; Ctrl-C trap echo INTERRUPTED AT \v(time)... echo Connections: \%n in \fdiffdate(now,\m(start)) getok "Do dump? [yes or no] " if success dodump getok "Debug? [yes or no] " if success prompt Debug> getok "Exit? [yes or no] " if success exit } ; Routine to summarize, upload, and reset log. define DODUMP { ; Send summary and start new log local a \%u \%c \%x echo "-----------------" echo REPORTING AT \v(time)... if ( doftp ) { set flag off ftp open \m(ftphost) /user:\m(ftpuser) /password:\m(ftppass) if \v(ftp_loggedin) { ftp cd \m(ftpdir) if success { ftp put \m(logfile) if success { set flag on ftp chmod 664 \m(logfile) } } } if not flag echo WARNING: FTP UPLOAD LOG FAILED ftp bye } fopen /read \%c \m(logfile) ; Open current log if fail end 1 \m(logfile): \f_errmsg() ; Check that we did .\%n := 0 ; Init record counter while not \f_eof(\%c) { ; Loop to read each record fread /line \%c line ; Read one record if fail break ; Check incr \%n ; Count .a := \s(line[19]) ; Remove timestamp .\%x ::= \findex({"},\m(a)) - 1 ; Remove attack string .a := \ftrim(\s(a[1:\%x])) ; Remove any surrounding whitespace .a := \fltrim(\m(a)) _increment aa<\m(a)> ; Count a hit from this host } fclose \%c ; Close log file .\%k := \faaconvert(aa,&a,&b) ; Convert to pair of regular arrays .\%u := 0 ; Local domain counter array sort /reverse /numeric b a ; Sort in descending order of hits .tmpfile := \freplace(\m(logfile),.log,.txt) fopen /write \%c \m(tmpfile) if fail end 1 Can't create report file for \%i 1 \%k 1 { ; Count hits from local domain chklocal {\&a[\%i]} if success incr \%u } fwrite /line \%c Port \m(port) probe summary on \v(host) - [\fname2addr(\v(host))] fwrite /line \%c Interval: \m(start) - \fcvtdate() fwrite /line \%c fwrite /line \%c " Hits: \flpad(\%n,5)" fwrite /line \%c " Unique hosts: \flpad(\%k,5)" fwrite /line \%c " Unique local hosts: \flpad(\%u,5)" fwrite /line \%c for \%i 1 \%k 1 { fwrite /line \%c \frpad(\&a[\%i],60) \flpad(\&b[\%i],5) } fclose \%c !\m(mailcmd) -s "Port \m(port) Probe Summary" \m(address) < \m(tmpfile) newlogname ; Get new log name .start := \fcvtdate() ; New start time etc .hour := \fstripx(\fstripx(\v(time),:),:) .\%n := 0 echo NEW LOG: \m(logfile) } if K-95 { ; K95: Appropriate window color set command color white red cls } echo LISTENING ON PORT \m(port)... ; Get FTP access info. if DOFTP { echo For uploading logs... echo Logs will be uploaded hourly by FTP to: echo Host: \m(ftphost) Directory: \m(ftpdir) getok " OK? [yes or no] " if fail .doftp := 0 while doftp { undef ftppass while not def ftppass { askq ftppass { FTP password for \m(ftpuser) at \m(ftphost): } } echo Testing... ftp open \m(ftphost) /user:\m(ftpuser) /password:\m(ftppass) if success if \v(ftp_loggedin) { ftp bye break } ftp bye echo { Test failed - try again.} } } ; Here goes... set input echo off ; Input is logged to a file set xfer display brief ; No showing off set tcp reverse-dns off ; For accurate source reporting while true { ; Loop forever .x := \fstripx(\fstripx(\v(time),:),:) ; Current hour if ( != \m(x) \m(hour) ) { ; If hour turned over dodump ; Send report and reset if fail echo WARNING: Report failed } echo "-----------------" .stamp := \fcvtdate() xecho \m(stamp)... set host * \m(port) ; Wait for incoming connection if fail { echo Net Open Failure: \v(errstring) if ( == \v(errno) 13 || == \v(errno) 48 ) exit 1 hangup continue } clear input ; Capture the attack string input 8 \10 ; (terminate at linefeed = ASCII 10) increment \%n ; Count this probe echo \flpad(\%n,4). \m(stamp): \v(line) ; Log to screen if ( = 0 \fverify(0123456789.,\v(line)) ) { ; Have IP address .addr := \v(line) .name := \faddr2name(\m(addr)) ; Lookup name in DNS } else { ; Have IP name .name := \v(line) .addr := \fname2addr(\m(name)) ; Get its address } hangup ; Close connection quickly .record := \m(stamp): \m(name) [\m(addr)] "\ftrim(\v(input))" if \m(truncate) if > \flen(\m(record)) 79 .record := \s(record[1:76]).." set flag off for \%i 1 3 1 { ; Write to log fopen /append \%c \m(logfile) ; It might be busy if fail { ; (can happen in Windows) echo LOG APPEND FAILURE: \f_errmsg() sleep \%i continue } fwrite /line \%c \m(record) if success set flag on fclose \%c if flag break } if not flag echo RECORD LOST: \m(record) }