diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3c3643ad4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Compiled source and other garbage # +##################################### +*.pyc +*.swp + +# Directories with potential license restrictions that prevent re-distribution # +################################################################################ +dictionaries/restricted/* +tools/restricted/* diff --git a/dictionaries/README b/dictionaries/README new file mode 100644 index 000000000..77d7606d8 --- /dev/null +++ b/dictionaries/README @@ -0,0 +1,4 @@ +Have a look at this for more background: + +http://pauldotcom.com/2011/08/dirbuster-to-burp-the-missing.html +http://www.mavitunasecurity.com/blog/svn-digger-better-lists-for-forced-browsing/ diff --git a/dictionaries/update_convert_cms_explorer_dicts.sh b/dictionaries/update_convert_cms_explorer_dicts.sh new file mode 100755 index 000000000..d0e91d7ed --- /dev/null +++ b/dictionaries/update_convert_cms_explorer_dicts.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Description: This script grabs all the excellent CMS Explorer dictionaries, updates them and converts them into DirBuster format (much faster than CMS Explorer) +# +# owtf is an OWASP+PTES-focused try to unite great tools and facilitate pen testing +# Copyright (c) 2011, Abraham Aranguren Twitter: @7a_ http://7-a.org +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the copyright owner nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +CMS_EXPLORER_DIR="/pentest/enumeration/web/cms-explorer" # Backtrack 5 location, you may need to change this +CMS_DICTIONARIES_DIR="/root/owtf/dictionaries/restricted/cms" # CMS dictionaries location, you may need to change this + +DICTIONARIES="$CMS_EXPLORER_DIR/drupal_plugins.txt +$CMS_EXPLORER_DIR/joomla_themes.txt +$CMS_EXPLORER_DIR/wp_plugins.txt +$CMS_EXPLORER_DIR/drupal_themes.txt +$CMS_EXPLORER_DIR/wp_themes.txt +$CMS_EXPLORER_DIR/joomla_plugins.txt" + +echo "[*] Going into directory: $CMS_EXPLORER_DIR" +cd $CMS_EXPLORER_DIR +echo "[*] Updating cms-explorer.pl dictionaries.." +./cms-explorer.pl -update + +echo "[*] Going into directory: $CMS_DICTIONARIES_DIR" +cd $CMS_DICTIONARIES_DIR + +echo "[*] Copying updated dictionaries from $CMS_EXPLORER_DIR to $CMS_DICTIONARIES_DIR" +for i in $(echo $DICTIONARIES); do + cp $i $CMS_DICTIONARIES_DIR # echo "[*] Copying $i .." +done + +DIRBUSTER_PREFIX="dir_buster" +for cms in $(echo "drupal joomla wp"); do + mkdir -p $cms # Create CMS specific directory + rm -f $cms/* # Remove previous dictionaries + mv $cms* $cms 2> /dev/null # Move relevant dictionaries to directory, getting rid of "cannot move to myself" error, which is ok + cd $cms # Enter directory + for dict in $(ls); do # Now process each CMS-specific dictionary and convert it + cat $dict | tr '/' "\n" | sort -u > $DIRBUSTER_PREFIX.$dict # Convert to DirBuster format (i.e. get rid of the "/" and duplicate parent directories) + rm -f $dict # Remove since this only works with CMS explorer + done + # Create all-in-one CMS-specific dictionaries: + cat $DIRBUSTER_PREFIX* > $DIRBUSTER_PREFIX.all.$cms.txt + cd .. +done + +echo "[*] Creating all-in-one CMS dictionaries for DirBuster and CMS Explorer" +for bruteforcer in $(echo "$DIRBUSTER_PREFIX"); do + ALLINONE_DICT="$bruteforcer.all_in_one.txt" + rm -f $ALLINONE_DICT # Remove previous, potentially outdated all-in-one dict + for all_dict in $(find . -name *all*.txt | grep $bruteforcer); do + cat $all_dict >> $ALLINONE_DICT + done + cat $ALLINONE_DICT | sort -u > $ALLINONE_DICT.tmp # Remove duplicates, just in case + mv $ALLINONE_DICT.tmp $ALLINONE_DICT +done + +echo "[*] Done!]" diff --git a/framework/config/config.py b/framework/config/config.py index f19dfa475..3cfc89632 100644 --- a/framework/config/config.py +++ b/framework/config/config.py @@ -305,39 +305,29 @@ def GetTXTTransacLog(self, Partial = False): def IsHostNameNOTIP(self): return self.Get('HOST_NAME') != self.Get('HOST_IP') # Host - def GetIPFromHostname(self, hostname): - ip = '' - try: - socket.inet_pton(socket.AF_INET, hostname) - ip = hostname - except socket.error: - # Check if it's an IPv6 address - not optimal - # may be better to regex for : and decide - # up front whether to check with AF_INET or AF_INET6 - try: - socket.inet_pton(socket.AF_INET6, ip) - ip = hostname - except socket.error: - # ideally we would not double up on this - wicky - ip = socket.gethostbyname(hostname) - else: - ip = socket.gethostbyname(hostname) - - #ip=self.Core.Shell.shell_exec('host '+hostname+'|grep "has address"|cut -f4 -d" "') - ipchunks = ip.strip().split("\n") + def GetIPFromHostname(self, Hostname): + IP = '' + for Socket in [ socket.AF_INET, socket.AF_INET6 ]: # IP validation based on @marcwickenden's pull request, thanks! + try: + socket.inet_pton(Socket, Hostname) + IP = Hostname + break + except socket.error: continue + if not IP: + try: IP = socket.gethostbyname(Hostname) + except socket.gaierror: self.Core.Error.FrameworkAbort("Cannot resolve Hostname: "+Hostname) + + ipchunks = IP.strip().split("\n") AlternativeIPs = [] if len(ipchunks) > 1: - ip = ipchunks[0] - cprint(hostname+" has several IP addresses: ("+", ".join(ipchunks)[0:-3]+"). Choosing first: "+ip+"") + IP = ipchunks[0] + cprint(Hostname+" has several IP addresses: ("+", ".join(ipchunks)[0:-3]+"). Choosing first: "+IP+"") AlternativeIPs = ipchunks[1:] self.Set('ALTERNATIVE_IPS', AlternativeIPs) - ip = ip.strip() - if len(ip.split('.')) != 4: # TODO: Add IPv6 support! - self.Core.Error.FrameworkAbort("Cannot resolve hostname: "+hostname) - # Good IPv4 IP - self.Set('INTERNAL_IP', self.Core.IsIPInternal(ip)) - cprint("The IP address for "+hostname+" is: '"+ip+"'") - return ip + IP = IP.strip() + self.Set('INTERNAL_IP', self.Core.IsIPInternal(IP)) + cprint("The IP address for "+Hostname+" is: '"+IP+"'") + return IP def GetAll(self, Key): # Retrieves a config setting value on all target configurations Matches = [] diff --git a/framework/config/config.pyc b/framework/config/config.pyc index f6571e762..c98c7cc93 100644 Binary files a/framework/config/config.pyc and b/framework/config/config.pyc differ diff --git a/framework/config/framework_config.cfg b/framework/config/framework_config.cfg index 3ef6bcacf..a04bc8ce4 100644 --- a/framework/config/framework_config.cfg +++ b/framework/config/framework_config.cfg @@ -1,4 +1,4 @@ -VERSION: 0.11 "Vienna" +VERSION: 0.12 "Wicky" INSTALL_SCRIPT: @@@FRAMEWORK_DIR@@@/install.sh WEB_TEST_GROUPS: @@@FRAMEWORK_DIR@@@/framework/config/web_testgroups.cfg @@ -45,11 +45,15 @@ RESPONSE_REGEXP_FOR_SSI: Server Side Includes_____) # The following icons _must_ exist, but you can change the icon if you wish # WARNING!!!: The following icons are best kept as they are, if a new icon is wanted it should have the same name (at least for now): +#FIXED_ICON_MATCHES: shopping_cart +FIXED_ICON_MATCHES: target FIXED_ICON_INFO: info +FIXED_ICON_PLUGIN_INFO: info24x24 FIXED_ICON_NOFLAG: envelope FIXED_ICON_UNSTRIKETHROUGH: eraser FIXED_ICON_STRICKETHROUGH: pencil FIXED_ICON_NOTES: lamp_active FIXED_ICON_REMOVE: delete FIXED_ICON_REFRESH: refresh -COLLAPSED_REPORT_SIZE: 90 +FIXED_ICON_OPTIONS: options +COLLAPSED_REPORT_SIZE: 64 diff --git a/framework/config/health_check.pyc b/framework/config/health_check.pyc index 43215b69c..c3f1daf72 100644 Binary files a/framework/config/health_check.pyc and b/framework/config/health_check.pyc differ diff --git a/framework/config/plugin.py b/framework/config/plugin.py index ff7c01882..53f934866 100644 --- a/framework/config/plugin.py +++ b/framework/config/plugin.py @@ -46,7 +46,7 @@ def GetTypesForGroup(self, PluginGroup): PluginTypes = [] for PluginType, Plugins in self.AllPlugins[PluginGroup].items(): PluginTypes.append(PluginType) - return PluginTypes + return sorted(PluginTypes) # Return list in alphabetical order def GetAllTypes(self): AllPluginTypes = [] diff --git a/framework/config/plugin.pyc b/framework/config/plugin.pyc index 90f6a9648..c637fd713 100644 Binary files a/framework/config/plugin.pyc and b/framework/config/plugin.pyc differ diff --git a/framework/db/db.pyc b/framework/db/db.pyc index b616b49dc..0beaad1ae 100644 Binary files a/framework/db/db.pyc and b/framework/db/db.pyc differ diff --git a/framework/db/plugin_register.py b/framework/db/plugin_register.py index 367215527..68233cfad 100644 --- a/framework/db/plugin_register.py +++ b/framework/db/plugin_register.py @@ -34,15 +34,16 @@ # Start, End, Runtime, Command, LogStatus = self.Core.DB.DBCache['RUN_DB'][-1]#.split(" | ") CODE = 0 TYPE = 1 -PATH = 2 -TARGET = 3 # The same plugin and type can be run against different targets, they should have different paths, but we need the target to get the right codes in the report -ARGS = 4 # Auxiliary plugins have metasploit-like arguments -REVIEW_OFFSET = 5 -START = 6 -END = 7 -RUNTIME = 8 +GROUP = 2 +PATH = 3 +TARGET = 4 # The same plugin and type can be run against different targets, they should have different paths, but we need the target to get the right codes in the report +ARGS = 5 # Auxiliary plugins have metasploit-like arguments +REVIEW_OFFSET = 6 +START = 7 +END = 8 +RUNTIME = 9 -NAME_TO_OFFSET = { 'Code' : CODE, 'Type' : TYPE, 'Path' : PATH, 'Target' : TARGET, 'Args' : ARGS, 'ReviewOffset' : REVIEW_OFFSET, 'Start' : START, 'End' : END, 'RunTime' : RUNTIME } +NAME_TO_OFFSET = { 'Code' : CODE, 'Type' : TYPE, 'Group' : GROUP, 'Path' : PATH, 'Target' : TARGET, 'Args' : ARGS, 'ReviewOffset' : REVIEW_OFFSET, 'Start' : START, 'End' : END, 'RunTime' : RUNTIME } class PluginRegister: def __init__(self, Core): @@ -56,7 +57,10 @@ def AlreadyRegistered(self, Plugin, Path, Target): def Add(self, Plugin, Path, Target): # Registers a Plugin/Path/Target combination only if not already registered if not self.AlreadyRegistered(Plugin, Path, Target): - self.Core.DB.Add('PLUGIN_REPORT_REGISTER', [ Plugin['Code'], Plugin['Type'], Path, Target, Plugin['Args'], self.Core.Config.Get('REVIEW_OFFSET'), Plugin['Start'], Plugin['End'], Plugin['RunTime'] ] ) + if 'RunTime' not in Plugin: + Plugin['RunTime'] = self.Core.Timer.GetElapsedTimeAsStr('Plugin') + Plugin['End'] = self.Core.Timer.GetEndDateTimeAsStr('Plugin') + self.Core.DB.Add('PLUGIN_REPORT_REGISTER', [ Plugin['Code'], Plugin['Type'], Plugin['Group'], Path, Target, Plugin['Args'], self.Core.Config.Get('REVIEW_OFFSET'), Plugin['Start'], Plugin['End'], Plugin['RunTime'] ] ) def Search(self, Criteria): return self.Core.DB.Search('PLUGIN_REPORT_REGISTER', Criteria, NAME_TO_OFFSET) diff --git a/framework/db/plugin_register.pyc b/framework/db/plugin_register.pyc index 4d2700dca..bca713dc0 100644 Binary files a/framework/db/plugin_register.pyc and b/framework/db/plugin_register.pyc differ diff --git a/framework/db/transaction_manager.py b/framework/db/transaction_manager.py index 307978628..e3ec1560a 100644 --- a/framework/db/transaction_manager.py +++ b/framework/db/transaction_manager.py @@ -287,4 +287,3 @@ def GrepMultiLineResponseRegexp(self, ResponseRegexp): return [ Command, RegexpName, Matches ] # Return All matches and the file they were retrieved from else: # wtf? raise PluginAbortException("ERROR: Inforrect Configuration setting for Response Regexp: '"+str(ResponseRegexp)+"'") - diff --git a/framework/db/transaction_manager.pyc b/framework/db/transaction_manager.pyc index 45a0784e6..d30ee6635 100644 Binary files a/framework/db/transaction_manager.pyc and b/framework/db/transaction_manager.pyc differ diff --git a/framework/http/requester.py b/framework/http/requester.py index ab6a69e76..0e48faaec 100644 --- a/framework/http/requester.py +++ b/framework/http/requester.py @@ -128,7 +128,7 @@ def StringToDict(self, String): Dict = defaultdict(list) Count = 0 PrevItem = '' - for Item in String.split('='): + for Item in String.strip().split('='): if Count % 2 == 1: # Key Dict[PrevItem] = Item else: # Value diff --git a/framework/http/requester.pyc b/framework/http/requester.pyc index eb64c4a88..db140dfad 100644 Binary files a/framework/http/requester.pyc and b/framework/http/requester.pyc differ diff --git a/framework/plugin/plugin_handler.py b/framework/plugin/plugin_handler.py index f3cb671b1..400f8ed18 100644 --- a/framework/plugin/plugin_handler.py +++ b/framework/plugin/plugin_handler.py @@ -210,8 +210,6 @@ def GetPluginFullPath(self, PluginDir, Plugin): return PluginDir+"/"+Plugin['Type']+"/"+Plugin['File'] # Path to run the plugin def RunPlugin(self, PluginDir, Plugin): - self.Core.Timer.StartTimer('Plugin') # Time how long it takes the plugin to execute - Plugin['Start'] = self.Core.Timer.GetStartDateTimeAsStr('Plugin') PluginPath = self.GetPluginFullPath(PluginDir, Plugin) (Path, Name) = os.path.split(PluginPath) #(Name, Ext) = os.path.splitext(Name) @@ -220,6 +218,8 @@ def RunPlugin(self, PluginDir, Plugin): self.SavePluginInfo(PluginOutput, Plugin) # Timer retrieved here def ProcessPlugin(self, PluginDir, Plugin, Status): + self.Core.Timer.StartTimer('Plugin') # Time how long it takes the plugin to execute + Plugin['Start'] = self.Core.Timer.GetStartDateTimeAsStr('Plugin') if not self.CanPluginRun(Plugin, True): return None # Skip Status['AllSkipped'] = False # A plugin is going to be run diff --git a/framework/plugin/plugin_handler.pyc b/framework/plugin/plugin_handler.pyc index e0245e230..66c1cd019 100644 Binary files a/framework/plugin/plugin_handler.pyc and b/framework/plugin/plugin_handler.pyc differ diff --git a/framework/plugin/plugin_helper.py b/framework/plugin/plugin_helper.py index 2d5b8ab93..c4fff4c5b 100644 --- a/framework/plugin/plugin_helper.py +++ b/framework/plugin/plugin_helper.py @@ -53,22 +53,49 @@ def DrawLinkList(self, LinkListName, Links): # Wrapper to allow rendering a bunc def DrawResourceLinkList(self, ResourceListName, ResourceList): # Draws an HTML Search box for defined Vuln Search resources LinkList = [] - List = [] + HTMLLinkList = [] for Name, Resource in ResourceList: URL = MultipleReplace(Resource.strip(), self.Core.Config.GetReplacementDict()).replace('"', '%22') - #URL = Resource.replace('@@@PLACE_HOLDER@@@', self.Core.Config.Get('HOST_NAME')).strip() LinkList.append(URL) cprint("Generating link for "+Name+"..") # Otherwise there would be a lovely python exception and we would not be here :) - #List += '
  • '+self.Core.Reporter.Render.DrawButtonLink(Name, URL)+"
  • \n" - List.append(self.Core.Reporter.Render.DrawButtonLink(Name, URL)) - #List += '' + HTMLLinkList.append(self.Core.Reporter.Render.DrawButtonLink(Name, URL)) + return self.DrawListPostProcessing(ResourceListName, LinkList, HTMLLinkList) + + def DrawListPostProcessing(self, ResourceListName, LinkList, HTMLLinkList): Content = '
    '+ResourceListName+': ' if len(LinkList) > 1: # Open All In Tabs only makes sense if num items > 1 Content += self.Core.Reporter.Render.DrawButton('Open All In Tabs', "OpenAllInTabs(new Array('"+"','".join(LinkList)+"'))") - #Content += '