All pastes #763705 Raw Edit

Stuff

public text v1 · immutable
·published 2007-11-06 18:08 UTC
rendered paste body
#! /usr/bin/env python

import sys
from xml.dom import minidom
from collections import deque
import smtplib
import socket
import os
import re
from optparse import OptionParser

class mRelayStructure:
    """
    Struct
    """
    pass


class mRelayMethodQueue:
    """
    Deque Wrapper
    Don't really need a wrapper, but it looks nicer this way
    Use it to store mRelayStructure objects
    Object Contains From/To Open Relay String Pairs
    """
    def __init__(self):
        self.queue = deque()
        
    def pushBack(self,methodPair):
        self.queue.append(methodPair)
        
    def pop(self):
        return self.queue.popleft()
    
    def isEmpty(self):
        return (len(self.queue)==0)
    
    def getQueue(self):
        return self.queue
            
    def getSize(self):
        return len(self.queue)
    
    def debugPrint(self):
        print "Size:" , len(self.queue)
        if self.isEmpty() == 0:
            for i in self.queue:
                print ("%s%s") % (i.sender,i.receipt)
            
    
class mRelayXMLParser:
    """
    Configuration Parser Base Class
    Only using XML Files at this stage
    Check if File is available
    Throw error if not and quit
    @param filename: Name of the XML File
    """
    
    def __init__(self, filename):
        try:
            self.fileObject = open(filename)
                
        except IOError:
            print "File %s is Not Available" % (filename)
            print usage()
            sys.exit(-1)
            
        else:
            self.xmlObject = minidom.parse(self.fileObject)

            
    def __del__(self):
        try:
            self.fileObject.close()
        
        except IOError:
            pass
            
    def getText(self, nodelist):
        rc=""
        for node in nodelist:
            if node.nodeType == node.TEXT_NODE:
                rc = rc + node.data
        return rc.replace(" ","")


        
class mRelayXMLMethods(mRelayXMLParser):
    """
    Parser for Open Relay Checking Methods
    Going through Xml file and pushing From/To SMTP Pairs onto queue reference
    Should Add XML Validation Really
    @param methodQueue: reference to mRelayMethodQueue Object
    @return None: Reference
    """
    def parseMethods(self,mQueue):
        
        methods = self.xmlObject.getElementsByTagName("Method")
        for method in methods:
            relayPair = mRelayStructure()
            relayPair.sender =    self.getText(method.getElementsByTagName("From")[0].childNodes)
            relayPair.receipt =  self.getText(method.getElementsByTagName("To")[0].childNodes)
            mQueue.pushBack(relayPair)
    
    
    
class mRelayXMLConfig(mRelayXMLParser):
    """
    Parser for Configuration File
    Assembles Data into Structure
    Looks a little ugly due to static params
    Should Add XML Validation Really
    @param confStruc: Reference to mRelayStructure Object
    @return None: Reference
    """
    def parseConfig(self,mConfig):
        configs = self.xmlObject.getElementsByTagName("Configurator")
        for config in configs:
            mConfig.Sender = self.getText(config.getElementsByTagName("SENDER")[0].childNodes)
            mConfig.LocalHost = self.getText(config.getElementsByTagName("LOCAL_HOST")[0].childNodes)
            mConfig.Receiver = self.getText(config.getElementsByTagName("RECEIVER")[0].childNodes)
            mConfig.RemoteHost = self.getText(config.getElementsByTagName("REMOTE_HOST")[0].childNodes)
            mConfig.VictimIP = self.getText(config.getElementsByTagName("VICTIM_HOST")[0].childNodes)
            mConfig.VictimPort = self.getText(config.getElementsByTagName("VICTIM_PORT")[0].childNodes)
            

class ProcessShellReport:
    def process(self,mailFrom,mailFrom_resp,rcpt_to,rcpt_to_resp, mresp, failRelay,info):
        
        print "\n\n"
        
        if failRelay == 0:
            print "WARNING WARNING WARNING"
        else:
            print "Attempt Failed"
            
        print (">>>  MAIL FROM: %s") % (mailFrom)
        print ("<<<  %s") % (mailFrom_resp)
        
        if failRelay < 3:
            
            print(">>>  RCPT TO: %s") % (rcpt_to)
            print("<<<  %s") % (rcpt_to_resp)
        else:
            pass
        
        if failRelay < 2:
            
            print(">>>  MSG SENT")
            print("<<<  STATUS OK")
        
        else:
            pass


class ProcessTMPLReport:
    def __init__(self,templateFile,reportFile):
        self.reportF = reportFile
        self.template = mRelayStructure()
        
        self.fileObject = self.openFile(templateFile,"r")
        reportTemplate = self.fileObject.read()
        
        
        pat = re.compile('-=HEAD=-(.*)-=HEAD=-.*-=EHEAD=-(.*)-=EHEAD=-.*-=MAIL_FROM=-(.*)-=MAIL_FROM=-.*-=MAIL_RCPT=-(.*)-=MAIL_RCPT=-.*-=MAIL_MSG=-(.*)-=MAIL_MSG=-.*-=TAIL=-(.*)-=TAIL=-',re.DOTALL)
        (self.template.pHead,self.template.pEhead,self.template.pMailFrom,self.template.pMailRcpt,self.template.pMailMsg,self.template.pTail) = pat.search(reportTemplate).groups()
        
        self.fileObject.close()
        self.fileObject = self.openFile(reportFile,"w")
        self.fileObject.write(self.template.pHead)
        
        
    def openFile(self,fileName,mode):
        try:
            self.fileObject = open(fileName,mode)   
        except IOError:
            print "File %s is Not Available" % (fileName)
            print usage()
            sys.exit(-1)
            
        else:
            return self.fileObject
    
    def __del__(self):
        self.fileObject.write(self.template.pTail)
        print ("Report File %s has being created\n") % (self.reportF)
        self.fileObject.close()
        
    
        """
        A processing method for HTML and XML Templates.
        Currently HTML Template is available, I will do the XML At the finish
        @param mailFrom: MAIL FROM Request to Server
        @param mailFromResp: Server Response to Mail From
        @param rcpt_to: RCPT Request for The server
        @param rcpt_to_resp: RCPT Response From the Server
        @param failRelay: at which point relay failed, if it did
        @param struct: mostly contains integer data, message codes, exploit counter etc
        """
        
    def process(self,mailFrom,mailFrom_resp,rcpt_to,rcpt_to_resp, mresp, failRelay,info):
        newEhead = self.template.pEhead
        newMailFrom = self.template.pMailFrom
        
        if failRelay == 0:
            newEhead = newEhead.replace("-RESULT-", " WARNING - Possible Relay Found" )
        else:
            newEhead = newEhead.replace("-RESULT-", " Relay Refused")
            
        newEhead = re.sub('-COUNT-', str(info.currentTest),newEhead)   
        self.fileObject.write(newEhead)
        
        newMailFrom = re.sub('-MAIL_FROM_HEAD-','MAIL FROM SEND:', newMailFrom)
        newMailFrom = re.sub('-MAIL_FROM_BODY-', self.replaceBrackets(mailFrom),newMailFrom)
        newMailFrom = re.sub('-MAIL_FROM_HEAD_R-','MAIL FROM:', newMailFrom)
        newMailFrom = re.sub('-MAIL_FROM_CODE_S-',str(info.scode),newMailFrom)
        newMailFrom = re.sub('-MAIL_FROM_BODY_R-',self.replaceBrackets(mailFrom_resp),newMailFrom)
        self.fileObject.write(newMailFrom)
        
        if failRelay < 3:
            newMailRcpt = self.template.pMailRcpt
            newMailRcpt = re.sub('-MAIL_RCPT_HEAD-','MAIL RCPT SEND:', newMailRcpt)
            newMailRcpt = re.sub('-MAIL_RCPT_BODY-',self.replaceBrackets(rcpt_to),newMailRcpt)
            newMailRcpt = re.sub('-MAIL_RCPT_HEAD_R-','MAIL RCPT:', newMailRcpt)
            newMailRcpt = re.sub('-MAIL_RCPT_CODE_R-',str(info.rcode),newMailRcpt)
            newMailRcpt = re.sub('-MAIL_RCPT_BODY_R-',self.replaceBrackets(rcpt_to_resp),newMailRcpt)
            self.fileObject.write(newMailRcpt)
            
            if failRelay < 2:
                newMailMsg = self.template.pMailMsg
                newMailMsg = re.sub('-MAIL_MSG_HEAD-','MAIL MSG SEND:', newMailMsg)
                newMailMsg = re.sub('-MAIL_MSG_BODY-','DATA',newMailMsg)
                newMailMsg = re.sub('-MAIL_MSG_HEAD_R-','MAIL MSG:', newMailMsg)
                newMailMsg = re.sub('-MAIL_MSG_CODE_M-',str(info.mcode),newMailMsg)
                newMailMsg = re.sub('-MAIL_MSG_BODY_R-',self.replaceBrackets(mresp),newMailMsg)
            else:
                pass
        else:
            pass
            
        
        
        
    def replaceBrackets(self,mObject):
        return mObject.replace("<","&lt;").replace(">","&gt;")
    
class mRelayNetwork:
    """
    Open Relay Checker class
    - Creates a Connection To the Server
    - Validates Server Name 
    - Substitute CodeNames for Variables in Dequeue
    
    --- Should Do ---
    Check if auth is required, if yes Drop
    Maintain Connection, Exim4 and Sendmail May Drop Connection due to errors
    
    --- Params ---
    mConfig.Sender  - user name sending mail
    mConfig.Receiver - user to recieve mail is successfule
    mConfig.RemoteHost - users host to handle receive mail
    mConfig.VictimIP - IP Address of WebServer to Test, this is a VICTIM_HOST variable in xml
    mConfig.VictimPort - Port

    --- Will Evaluate ---
    mConfig.VictimHost - From Helo
    
    --- At the End --- 
    Implement Delegate for Selective report and actual mail send
    @param mQueue: Queue with Open Relay Rules
    @param mConfig: Struct with Collected Variables
    """
    def __init__(self,mQueue,mConfig,delegate):
        self.d=delegate
        
        self.info = mRelayStructure()
        self.info.totalTests = mQueue.getSize()
        self.info.currentTest = 0
        self.connect(mQueue,mConfig)

    def connect(self,mQueue,mConfig):

        try:

            self.s = smtplib.SMTP(mConfig.VictimIP,int(mConfig.VictimPort))
            #self.s.debuglevel = 1;
            
            try:
                self.helo()
                mConfig.VictimHost = self.assignRemVictim(mConfig.VictimIP)
                self.setupVars(mQueue,mConfig)
                self.testRelay(mQueue,"This is a Test Message")
                
            finally:
                self.s.quit()

        except smtplib.SMTPException, e:
            print "Error: %s" % e
            print "Reconnect:"
            print self.info.currentTest,self.info.totalTests              
            self.connect(mQueue,mConfig)


            
                
        except (os.error, socket.error), e:
            print ("Connection Failed: %s") % (str(e))
            sys.exit(-2)
    
    """
    smtplib strip to introduce to server
    """
    def helo(self):
        if self.s.helo_resp is None and self.s.ehlo_resp is None:
            if not (200 <= self.s.ehlo()[0] <= 299):
                (code,resp) = self.s.helo()
                if not (200 <= code <= 299):
                    raise smtplib.SMTPHeloError(code, resp)
                
        
    
    """
    Should try to resolve ip first, if it doesn't work,
    which it doesn't for me, use the response by ehlo. 
    Should actually be better to implement both if both available
    """
    def assignRemVictim(self,ip):
        hostName = ""
        try:
            hostName = socket.gethostbyaddr(ip)[0]
            print "AllFine"
        except socket.error, e:
            print "Host Reolution Problem: %s" % (e)
            print "Will use ehlo() report instead"
            #Need to think about going from the back for improvement
            hostName = str(re.search("^(\w+[.]\w+[.\w]*).*",self.s.ehlo_resp,re.DOTALL).groups()[0])
            print "HostName: %s" % (hostName)

        return hostName
            
     
    """
    Replace Rules by the actual Values
    Need to think of something more elegant
    
    """
    def setupVars(self,mQueue,mConfig):
        for i in mQueue.getQueue():
            i.sender = i.sender.replace("$LB","<")
            i.sender = i.sender.replace("$RB",">")
            i.sender = i.sender.replace("$SENDER",mConfig.Sender)
            i.sender = i.sender.replace("$REMOTE_HOST",mConfig.RemoteHost)
            i.sender = i.sender.replace("$VICTIM_HOST",mConfig.VictimHost)
            i.sender = i.sender.replace("$VICTIM_IP",mConfig.VictimIP)
            i.sender = i.sender.replace("$LOCAL_HOST",mConfig.LocalHost)
            i.sender = i.sender.replace("\n","")
            i.receipt = i.receipt.replace("$LB","<")
            i.receipt = i.receipt.replace("$RB",">")
            i.receipt = i.receipt.replace("$VICTIM_IP",mConfig.VictimIP)
            i.receipt = i.receipt.replace("$VICTIM_HOST",mConfig.VictimHost)
            i.receipt = i.receipt.replace("$REMOTE_HOST",mConfig.RemoteHost)
            i.receipt = i.receipt.replace("$RECEIVER",mConfig.Receiver)
            i.receipt = i.receipt.replace("$SENDER",mConfig.Sender)
            i.receipt = i.receipt.replace("\n","")
            
            
    """
    Iterate through methods
    Sequentially implementing commands
    """
    def testRelay(self,mQueue,msg):
        scode = rcode = mcode = mresp = rresp = sresp = 0
        while mQueue.isEmpty()==False:
            i=mQueue.pop()
            self.s.rset()
            failRelay = 0
            (scode,sresp) = self.s.mail(i.sender)
            if scode == 250:
                (rcode,rresp) = self.s.rcpt(i.receipt)
                if rcode == 250:
                    (mcode,mresp) = self.s.data(msg)
                    if mcode == 250:
                        print "Nice"
                    else:
                        failRelay = 1
                else:
                    failRelay = 2
            else:
                failRelay = 3
                
            
            self.info.currentTest += 1
            self.info.scode = scode
            self.info.rcode = rcode
            self.info.mcode = mcode 
            self.process(i.sender,sresp,i.receipt,rresp,mresp,failRelay,self.info)


    def __getattr__(self,name):
        return getattr(self.d, name)


def usage():
    print __doc__
    
def main(argv):

    
    
    optParser = OptionParser()
    optParser.add_option("-c", "--conf", dest="conf",default="./config.xml",help="Main Configuration File",metavar="FILE")
    optParser.add_option("-a", "--attack", dest="attack",default="./attacks.xml",help="Attacks Configuration File", metavar="FILE")
    optParser.add_option("-t", "--template",dest="template", default="./reportHTML.tmpl",help="Template for Report",metavar="FILE")
    optParser.add_option("-r", "--report" , dest="report", default="./report.html", help="A file for the report to be written to", metavar="FILE")
    
    (options, args) = optParser.parse_args()
    
        
    
    mQueue = mRelayMethodQueue()
    mConfig = mRelayStructure()
    
    
    # xml Parser For Configuration
    r = mRelayXMLConfig(options.conf)
    r.parseConfig(mConfig)

    # attack methods Reader
    r = mRelayXMLMethods(options.attack)
    r.parseMethods(mQueue)
    
    # set a report Delegate, can use ProcessTMPLReport (FILE) Or ProcessShellReport (Shell)
    report = ProcessTMPLReport(options.template,options.report)
    
    mNet = mRelayNetwork(mQueue,mConfig,report)

    return 0
    
    
if __name__ == "__main__":
    main(sys.argv[1:])