#! /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:])