#! /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("<","<").replace(">",">") 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:])