''' localProxy.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 ''' import core.controllers.outputManager as om from core.controllers.basePlugin.baseDiscoveryPlugin import baseDiscoveryPlugin from core.controllers.w3afException import w3afException from core.controllers.w3afException import w3afRunOnce from core.controllers.daemons.proxy import * import core.data.parsers.urlParser as urlParser import cgi , urllib from core.controllers.basePlugin.baseManglePlugin import * import re class localProxy(baseDiscoveryPlugin): ''' This plugin is a local proxy for HTTP requests ( like paros ). @author: Andres Riancho ( andres.riancho@gmail.com ) ''' def __init__(self): baseDiscoveryPlugin.__init__(self) # Internal variables self._proxyd = None # User options self._userOptionfixContentLen = True self._interceptRegex = re.compile('.*') self._interceptImages = False self._w3afMarker = 'w3afMarker' self._proxyAddress = '127.0.0.1' self._proxyPort = 8080 self._css = 'plugins' + os.path.sep + 'discovery' + os.path.sep + 'localProxy' + os.path.sep +'default.css' def setUrlOpener( self, urlOpener ): self._urlOpener = urlOpener # Now, I'm starting the proxy server ! self._proxy() def discover(self, fuzzableRequest ): ''' Do nothing, this plugin is rather odd, cause it will never return new URIs to the core. This plugin is a local proxy server thats used for mangling requests. All the "magic" is done in the _proxy method and in the handler class. ''' # All i need to run is runned from setUrlOpener and setOptions # All i need to run is self._proxy() with the correct parameters raise w3afRunOnce() def setOptions( self, OptionsMap ): self._userOptionfixContentLen = OptionsMap['fixContentLen'] self._interceptImages = OptionsMap['interceptImages'] try: self._interceptRegex = re.compile( OptionsMap['interceptRegex'] ) except: raise w3afException('Invalid regular expression in parameter interceptRegex.') self._w3afMarker = OptionsMap['w3afMarker'] self._proxyPort = OptionsMap['proxyPort'] self._proxyAddress = OptionsMap['proxyAddress'] self._css = OptionsMap['css'] # Restart the proxy, with the new options self._proxy() class proxyHandler(w3afProxyHandler): imageExtensions = ['css','jpg', 'jpeg', 'gif', 'png', 'svg', 'ico', 'pgm' , 'tif', 'tiff', 'bmp', 'flv'] def _isImage( self, uri ): for i in self.imageExtensions: if uri.endswith( i ): return True return False def _mustBeEdited( self ): marker = True self._postData = '' if self.headers.dict.has_key('content-length'): cl = int( self.headers['content-length'] ) self._postData = self.rfile.read( cl ) qs = urlParser.getQueryString( 'http://a/b?' + self._postData ) if self._w3afMarker in qs: marker = False return marker and self._interceptRegex.match( self.path ) def doAll( self ): if ( self._isImage( self.path ) and not self._interceptImages ) or not self._interceptRegex.match( self.path ): om.out.debug('The URL: '+ self.path +' is sent directly to the server it MUST NOT be edited') try: res = self._sendToServer() except Exception, e: self._sendError( e ) else: self._sendToBrowser( res ) elif self._mustBeEdited(): # The URL hasnt been edited yet AND it MUST be edited # Return the data to the browser for editing res = self._editWithBrowser() else: # The URL was edited and we are ready to send the data to the server # We remove the w3afMarker before sending. self._sendEditedRequest() def _sendEditedRequest( self ): try: qs = urlParser.getQueryString( self._postData ) qs.pop( self._w3afMarker ) except: pass else: self._postData = str(qs) # The URI is now in self.path # The method is in the postData, I always get here after a POST from the form # created in editWithBrowser() qs = urlParser.getQueryString( 'http://a/b?' + self._postData ) request = qs['request'] request = urllib.unquote_plus( request ) try: requestLine = request.split('\n')[0] method = requestLine.split(' ')[0] request = request.replace( requestLine + '\n', '' ) headers = stringToHeaders( request.split('\n\n')[0] ) try: headers.pop( 'proxy-connection' ) except: pass editedPostData = '\n\n'.join( request.split('\n\n')[1:] ) except: self._sendError( 'Malformed request to localProxy.' ) else: try: # Send the request to the remove webserver res = self._mySendToServer( method, self.path, headers , editedPostData ) except Exception, e: self._sendError( e ) else: self._sendToBrowser( res ) do_GET = do_POST = do_HEAD = doAll def _mySendToServer( self, method, URI, headers, postData='' ): ''' Send a request to the server. ''' om.out.debug('Requesting ' + self.path + ' to server.') # The content-length is removed to be recalculated inside: # functionReference( URI, data=postData, headers=headers ) if 'content-length' in headers: del headers['content-length'] try: functionReference = getattr( self._urlOpener , method ) except: self._sendError( 'Unknown method: ' + method ) else: response = functionReference( URI, data=postData, headers=headers ) return response def _editWithBrowser( self ): ''' This method returns an HTML form to the user, where he can edit the request to the remote webserver. ''' om.out.debug('Editing ' + self.path + ' with browser.') self.send_response( 200 ) self.send_header( 'Content-type', 'text/html') self.send_header( 'Connection', 'close') self.end_headers() action = self.path request = self.requestline + '\n' request += cgi.escape( headersToString( self.headers ) ) + '\n\n' request += self._postData rows = request.count('\n') script = ''' ''' style = '' self.wfile.write( 'Local Proxy - Request Editor'+ script + style +'

Request Editor


' ) self.wfile.close() def _proxy( self ): ''' This method starts the proxy server with the configured options. ''' if self._proxyd != None and self._proxyd.isRunning(): self._proxyd.stop() # Pass some parameters to the handler self.proxyHandler._interceptRegex = self._interceptRegex self.proxyHandler._w3afMarker = self._w3afMarker self.proxyHandler._css = self._css self.proxyHandler._interceptImages = self._interceptImages self._proxyd = proxy( self._proxyAddress, self._proxyPort , self._urlOpener, proxyHandler=self.proxyHandler ) self._proxyd.start2() om.out.information('Local proxy listening on ' + self._proxyAddress + ':' + str(self._proxyPort) ) 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/output.xsd ''' return '\ \ \ \ \ \ \ \ \ \ ' def getPluginDeps( self ): ''' @return: A list with the names of the plugins that should be runned before the current one. ''' return [] def getLongDesc( self ): ''' @return: A DETAILED description of the plugin functions and features. ''' return ''' This plugin is a local proxy ( like paros proxy ). Seven configurable parameters exist: - proxyPort - proxyAddress - fixContentLen - interceptRegex - interceptImages - w3afMarker - css This plugin is usefull for manual testing, when this plugin is enabled a proxy server is runned, the w3af user should configure that proxy on their browser and navigate the site changing the parameters on the fly. '''