#!/usr/bin/env python3 # -*- coding: utf-8 -*- #------------------------------------------------------------------------------- # Typo3Scan - Automatic Typo3 Enumeration Tool # Copyright (c) 2014-2020 Jan Rude # # This program 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, either version 3 of the License, or # (at your option) any later version. # # This program 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 this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/) #------------------------------------------------------------------------------- import re import string import random import sqlite3 from colorama import Fore import lib.request as request from pkg_resources import parse_version class Domain: """ This class stores following information about a domain: name: URL of the domain typo3: If Typo3 is installed typo3_version: Typo3 Version path: Full path to Typo3 installation installed_extensions: List of all installed extensions interesting_header: List of interesting headers """ def __init__(self, name): if not ('http' in name): self.__name = 'https://' + name else: self.__name = name self.__typo3 = False self.__typo3_version = '' self.__path = '' self.__installed_extensions = {} self.__interesting_header = {} def get_name(self): return self.__name def set_name(self, name): self.__name = name def is_typo3(self): return self.__typo3 def set_typo3(self): self.__typo3 = True def set_typo3_version(self, version): self.__typo3_version = version def get_typo3_version(self): return self.__typo3_version def set_path(self, path): self.__path = path def get_path(self): return self.__path def set_interesting_headers(self, headers, cookies): """ This method searches for interesing headers in the HTTP response Server: Displays the name of the server X-Powered-By: Information about Frameworks (e.g. ASP, PHP, JBoss) used by the web application X-*: Version information in other technologies Via: Informs the client of proxies through which the response was sent X-Forwarded-For: Originating IP address of a client connecting through an HTTP proxy or load balancer fe_typo_user: Frontend cookie for TYPO3 """ for header in headers: if header == 'Server': self.__interesting_header['Server'] = headers.get('Server') elif header == 'X-Powered-By': self.__interesting_header['X-Powered-By'] = headers.get('X-Powered-By') elif header == 'X-Runtime': self.__interesting_header['X-Runtime'] = headers.get('X-Runtime') elif header == 'X-Version': self.__interesting_header['X-Version'] = headers.get('X-Version') elif header == 'X-AspNet-Version': self.__interesting_header['X-AspNet-Version'] = headers.get('X-AspNet-Version') elif header == 'Via': self.__interesting_header['Via'] = headers.get('Via') elif header == 'X-Forwarded-For': self.__interesting_header['X-Forwarded-For'] = headers.get('X-Forwarded-For') if 'fe_typo_user' in cookies.keys(): self.__interesting_header['fe_typo_user'] = cookies['fe_typo_user'] self.set_typo3() def get_interesting_headers(self): return self.__interesting_header def check_root(self): """ This method requests the root page and searches for a specific string. Usually there are some TYPO3 notes in the HTML comments. If found, it searches for a Typo3 path reference in order to determine the Typo3 installation path. """ response = request.get_request('{}'.format(self.get_name())) full_path = self.get_name() self.set_interesting_headers(response['headers'], response['cookies']) if re.search('powered by TYPO3', response['html']): self.set_typo3() path = re.search('="/?(\S*?)/?(?:typo3temp|typo3conf)/'.format(self.get_name()), response['html']) if path and path.groups()[0] != '': path = path.groups()[0].replace(self.get_name(), '') if path != '': full_path = '{}/{}'.format(self.get_name(), path) if full_path.endswith('/'): full_path = full_path[:-1] self.set_path(full_path) def check_default_files(self): """ This method requests different files, which are generated on installation. Note: They are not accessible anymore on newer Typo3 installations """ files = {'typo3_src/README.md':'TYPO3 CMS', 'typo3_src/README.txt':'TYPO3 CMS', 'typo3_src/INSTALL.md':'INSTALLING TYPO3', 'typo3_src/INSTALL.txt':'INSTALLING TYPO3', 'typo3_src/LICENSE.txt':'TYPO3', 'typo3_src/CONTRIBUTING.md':'TYPO3 CMS' } for path, regex in files.items(): try: response = request.get_request('{}/{}'.format(self.get_path(), path)) regex = re.compile(regex) searchInstallation = regex.search(response['html']) installation = searchInstallation.groups() self.set_typo3() return True except: pass return False def check_404(self): """ This method requests a site which is not available by using a random generated string. TYPO3 installations usually generate a default error page, which can be used as an indicator. """ random_string = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) response = request.get_request('{}/{}'.format(self.get_path(), random_string)) search404 = re.search('[Tt][Yy][Pp][Oo]3 CMS', response['html']) if search404: self.set_typo3() def search_login(self): """ This method requests the default login page and searches for a specific string in the title or the response. If the access is forbidden (403), extension search is still possible. """ print(' \\\n [+] Backend Login') # maybe /typo3_src/typo3/index.php too? response = request.get_request('{}/typo3/index.php'.format(self.get_path())) searchTitle = re.search('