'''
wordpress_theme_auditor.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

# options
from core.data.options.optionList import optionList

from core.controllers.basePlugin.baseDiscoveryPlugin import baseDiscoveryPlugin
from core.controllers.w3afException import w3afException, w3afRunOnce
from core.controllers.coreHelpers.fingerprint_404 import is_404

import core.data.kb.knowledgeBase as kb
import core.data.kb.info as info
from core.data.bloomfilter.bloomfilter import scalable_bloomfilter

import re
import os

class wordpress_theme_auditor(baseDiscoveryPlugin):
    '''
    Attempt to find which wordpress themes are available and check them for some known vulnerabilities
    @author Stephen Breen
    '''
    def __init__(self):
        baseDiscoveryPlugin.__init__(self)
        
        # Internal variables
        self._CSS_RE = re.compile('(?:\s*\S+\s*{[^}]*})+')
        self._already_tested = scalable_bloomfilter()
        self._exec = True
        self._fuzzableRequests = []
        self._wordpressBase = None
        self._runningTheme = None
        self._foundThemes = []
        self._themeList = eval(open('plugins' + os.path.sep + 'discovery' + os.path.sep + 'wordpress_theme_auditor'+os.path.sep+'theme_vulns.dict').read())
        
    def discover(self, fuzzableRequest ):
        '''
        First find the root wordpress directory (if it exists) and the current theme that this
        wordpress installation is running. Then try to enumerate themes from
        a list. For each theme found, report it and also check/report if it is open to any of
        the vulnerabilities listed in the functions here.
        
        @parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test.
        '''
        if not self._exec:
            raise w3afRunOnce()
            
        wp_root_info = kb.kb.getData('wordpress_fingerprint','wordpress_root')
        if(wp_root_info):
            self._wordpressBase = wp_root_info[0].getURL()

        if(self._wordpressBase):
            self._exec = False
            self._runningTheme = self._findRunningTheme(self._wordpressBase)
            ''' Check if the running theme is in our list and vulnerable to anything'''
            self._checkTheme(self._runningTheme,running=True)
            self._findInstalledThemes(self._wordpressBase)
            
            if(self._foundThemes):
                for theme in self._foundThemes:
                    self._run_async(meth=self._checkTheme, args=(theme,False) )
                self._join()
                
        return self._fuzzableRequests

    def _do_request(self,url,analyzeFunction,analyzeParams=None):
        ret = None
        try:
            response = self._urlOpener.GET(url, useCache=False)
        except w3afException,  w3:
            msg = 'Failed to GET url: "'
            msg += url + '". Exception: "' + str(w3) + '".'
            om.out.debug( msg )
        else:
            if(analyzeParams):
                ret = analyzeFunction(response,analyzeParams)
            else:
                ret = analyzeFunction(response)
            if(ret):
                verifiedResponse = ret.pop(0)
                if(verifiedResponse and url not in self._already_tested):
                    self._already_tested.add(url)
                    self._fuzzableRequests.extend( self._createFuzzableRequests(verifiedResponse) )   
        return ret
    
    def _analyzeResponse(self,response,params):
        '''A general method to analyze a response when no global variables need to be set and 
        nothing needs to be returned. Can be used when checking for most vulnerabilities
        @param response The response
        @param params A dictionary containing some parameters'''
        
        shortCodeMatch = re.search(params['regex'], response.getBody(), re.IGNORECASE)
        if(shortCodeMatch):
            i = info.info()
            i.setPluginName(self.getName())
            i.setId( response.id )
            i.setName( params['name'] )
            i.setURL( response.getURL() )
            i.setDesc( params['desc'] )
            kb.kb.append( self, params['code'], i )
            om.out.information( i.getDesc() )
            return [response] 
    
    def _checkTheme(self,theme,running):
        if theme in self._themeList.keys():
            for vuln in self._themeList[theme]:
                vuln(self._wordpressBase,theme,running)
                
    def _findInstalledThemes(self,wordpressBase):
        '''Checks for the existence of a theme by grabbing its style.css file'''
        for theme in self._themeList.keys():
            testUrl = wordpressBase.urlJoin('wp-content/themes/'+theme+'/style.css')
            self._run_async(meth=self._do_request, args=(testUrl, self._checkStyleCss,theme))        
        self._join()
    
    def _findRunningTheme(self,wordpressBase):
        '''Find which theme this wordpress site is running'''
        runningTheme = None
        if(self._wordpressBase):
            testUrl = self._wordpressBase
            runningTheme = self._do_request(testUrl,self._findPageTheme)
        return runningTheme
              
    def _findPageTheme(self,response):
        '''Find the theme on a WP page by matching a regex since the page must include things like
           the stylesheet from the themes directory'''
        '''Chop of the http:// incase they prefix internal links with www'''
        regex_str = response.getURL().allButScheme()+'wp-content/themes/([^\"\'/]+)'
        themeMatch = re.search(regex_str, response.getBody(), re.IGNORECASE)
        if(themeMatch):
            runningTheme = themeMatch.group(1)
            i = info.info()
            i.setPluginName(self.getName())
            i.setId( response.id )
            i.setName( 'Wordpress Running Theme' )
            if(self._wordpressBase):
                i.setURL( self._wordpressBase )
            i.setDesc( 'Wordpress running theme was found to be '+runningTheme+'.' )
            kb.kb.append( self, 'wordpress_runningtheme', i )
            om.out.information( i.getDesc() )
            return [response,runningTheme]         
        
    def _checkStyleCss(self,response,theme):
        cssMatch = self._CSS_RE.search(response.getBody(),re.IGNORECASE)
        if not is_404(response) and cssMatch:
            self._foundThemes.append(theme)
            i = info.info()
            i.setPluginName(self.getName())
            i.setId( response.id )
            i.setName( 'Wordpress Installed Theme' )
            i.setURL( response.getURL().getDomainPath() )
            i.setDesc( 'Wordpress installed theme found at '+response.getURL().getDomainPath().url_string )
            kb.kb.append( self, 'wordpress_installedtheme', i )
            om.out.information( i.getDesc() )
            return [response]      

    def _woo_shortcode(self,wordpressBase,theme,running):
        '''Checks for the WooThemes shortcode vulnerability https://gist.github.com/2523147
        The theme need only be installed, doesn't have to be running so check regardless of running param'''
        testUrl = wordpressBase.urlJoin('wp-content/themes/'+theme+'/functions/js/shortcode-generator/preview-shortcode-external.php?shortcode=[test]')
        params = {'regex':'[test]',
                      'name':'ShortCode Vulnerability in WooTheme',
                      'desc':'Wordpress WooTheme was found to be installed and is vulnerable to shortcode injection through the GET parameter at '+testUrl,
                      'code':'woo_shortcode_vuln'}
        self._do_request(testUrl, self._analyzeResponse,params)
    
    
    def getOptions( self ):
        '''
        @return: A list of option objects for this plugin.
        '''    
        ol = optionList()
        return ol
        
    def setOptions( self, OptionList ):
        '''
        This method sets all the options that are configured using the user interface 
        generated by the framework using the result of getOptions().
        
        @parameter OptionList: A dictionary with the options for the plugin.
        @return: No value is returned.
        ''' 
        pass

    def getPluginDeps( self ):
        '''
        @return: A list with the names of the plugins that should be run before the
        current one.
        '''
        return ['discovery.wordpress_fingerprint']
    
    def getLongDesc( self ):
        '''
        @return: A DETAILED description of the plugin functions and features.
        '''
        return '''
        This plugin finds the base installation of Wordpress Blogs, identifies the running theme, tries to
        identify installed but not running themes, and for all themes found checks them against a list
        of vulnerabilities. 
        '''
    
