diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index d0b37a1..4b41eb4 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,3 +1,10 @@ +## Version 0.5.2 + +* Removed 'interesting header' output +* Added output of extension state +* Bugfixes +* Cosmetic output changes + ## Version 0.5.1 * Output and version detection fix diff --git a/doc/core_vulns.jpg b/doc/core_vulns.jpg index 8da6c31..4d5f8b1 100644 Binary files a/doc/core_vulns.jpg and b/doc/core_vulns.jpg differ diff --git a/doc/ext_vulns.jpg b/doc/ext_vulns.jpg index bde743a..5f597c1 100644 Binary files a/doc/ext_vulns.jpg and b/doc/ext_vulns.jpg differ diff --git a/lib/config.json b/lib/config.json index adb517a..86777f4 100644 --- a/lib/config.json +++ b/lib/config.json @@ -1 +1 @@ -{"threads": 5, "timeout": 10, "cookie": "", "auth": "", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"} \ No newline at end of file +{"threads": 5, "timeout": 10, "cookie": "", "auth": "", "User-Agent": "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.9200"} \ No newline at end of file diff --git a/lib/domain.py b/lib/domain.py index 504e579..cfc2801 100644 --- a/lib/domain.py +++ b/lib/domain.py @@ -33,7 +33,6 @@ class Domain: 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): @@ -44,7 +43,6 @@ class Domain: self.__typo3_version = '' self.__path = '' self.__installed_extensions = {} - self.__interesting_header = {} def get_name(self): return self.__name @@ -70,39 +68,6 @@ class Domain: 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. @@ -112,7 +77,6 @@ class Domain: """ 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']) @@ -166,17 +130,17 @@ class Domain: 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') + print('[+] Backend Login') # maybe /typo3_src/typo3/index.php too? response = request.get_request('{}/typo3/index.php'.format(self.get_path())) searchTitle = re.search('(.*)', response['html']) if searchTitle and 'Login' in searchTitle.group(0): - print(' \u2514', Fore.GREEN + '{}/typo3/index.php'.format(self.get_path()) + Fore.RESET) + print(' \u251c', Fore.GREEN + '{}/typo3/index.php'.format(self.get_path()) + Fore.RESET) elif ('Backend access denied: The IP address of your client' in response['html']) or (response['status_code'] == 403): - print(' \u251c', Fore.GREEN + '{}/typo3/index.php'.format(self.get_path()) + Fore.RESET) - print(' \u2514', Fore.YELLOW + 'But access is forbidden (IP Address Restriction)' + Fore.RESET) + print(' \u251c', Fore.GREEN + '{}/typo3/index.php'.format(self.get_path()) + Fore.RESET) + print(' \u251c', Fore.YELLOW + 'But access is forbidden (IP Address Restriction)' + Fore.RESET) else: - print(' \u251c', Fore.RED + 'Could not be found' + Fore.RESET) + print(' \u251c', Fore.RED + 'Could not be found' + Fore.RESET) def search_typo3_version(self): """ @@ -204,32 +168,32 @@ class Domain: version = response version_path = path - print('\n [+] Version Information') + print(' |\n[+] Version Information') if not (version is None): - print(' \u251c {}'.format(Fore.GREEN + version + Fore.RESET)) - print(' \u251c see: {}{}'.format(self.get_path(), version_path)) + print(' \u251c {}'.format(Fore.GREEN + version + Fore.RESET)) + print(' \u251c see: {}{}'.format(self.get_path(), version_path)) if len(version) == 3: - print(' \u251c Could not identify exact version.') - react = input(' \u251c Do you want to print all vulnerabilities for branch {}? (y/n): '.format(version)) + print(' \u251c Could not identify exact version.') + react = input(' \u251c Do you want to print all vulnerabilities for branch {}? (y/n): '.format(version)) if react.startswith('y'): version = version + '.0' else: return False - print(' \u2514 Known vulnerabilities\n \\') + print(' \u2514 Known vulnerabilities:\n') # sqlite stuff conn = sqlite3.connect('lib/typo3scan.db') c = conn.cursor() c.execute('SELECT advisory, vulnerability, subcomponent, affected_version_max, affected_version_min FROM core_vulns WHERE (?<=affected_version_max AND ?>=affected_version_min)', (version, version,)) data = c.fetchall() if not data: - print(' \u251c None.') + print(' \u251c None.') else: for vuln in data: # maybe instead use this: https://oraerr.com/database/sql/how-to-compare-version-string-x-y-z-in-mysql-2/ if parse_version(version) <= parse_version(vuln[3]): - print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET)) - print(' \u251c Vulnerability Type:'.ljust(29), vuln[1]) - print(' \u251c Subcomponent:'.ljust(29), vuln[2]) - print(' \u2514 Affected Versions:'.ljust(29), '{} - {}\n'.format(vuln[3], vuln[4])) + print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET)) + print(' \u251c Vulnerability Type:'.ljust(29), vuln[1]) + print(' \u251c Subcomponent:'.ljust(29), vuln[2]) + print(' \u2514 Affected Versions:'.ljust(29), '{} - {}\n'.format(vuln[3], vuln[4])) else: - print(' \u2514', Fore.RED + 'No version information found.' + Fore.RESET) \ No newline at end of file + print(' \u2514', Fore.RED + 'No version information found.' + Fore.RESET) \ No newline at end of file diff --git a/lib/extensions.py b/lib/extensions.py index 1316d39..f88c79b 100644 --- a/lib/extensions.py +++ b/lib/extensions.py @@ -66,15 +66,15 @@ class Extensions: thread_pool.add_job((request.version_information, (values['url'] + 'Settings.yml', '(?:release:)\s?([0-9]+\.[0-9]+\.?[0-9]?[0-9]?)'))) thread_pool.add_job((request.version_information, (values['url'] + 'Documentation/ChangeLog', None))) thread_pool.add_job((request.version_information, (values['url'] + 'Documentation/Index.rst', None))) - thread_pool.add_job((request.version_information, (values['url'] + 'composer.json', '(?:"dev-master":|"version":)\s?"([0-9]+\.[0-9]+\.[0-9x][0-9x]?)'))) + thread_pool.add_job((request.version_information, (values['url'] + 'composer.json', '(?:"dev-master":|"version":)\s?"([0-9]+\.[0-9]+\.?[0-9x]?[0-9x]?)'))) thread_pool.add_job((request.version_information, (values['url'] + 'Index.rst', None))) thread_pool.add_job((request.version_information, (values['url'] + 'doc/manual.sxw', None))) thread_pool.add_job((request.version_information, (values['url'] + 'ChangeLog', None))) thread_pool.add_job((request.version_information, (values['url'] + 'CHANGELOG.md', None))) thread_pool.add_job((request.version_information, (values['url'] + 'ChangeLog.txt', None))) - thread_pool.add_job((request.version_information, (values['url'] + 'Readme.txt', None))) - thread_pool.add_job((request.version_information, (values['url'] + 'README.md', None))) - thread_pool.add_job((request.version_information, (values['url'] + 'README.rst', None))) + #thread_pool.add_job((request.version_information, (values['url'] + 'Readme.txt', None))) + #thread_pool.add_job((request.version_information, (values['url'] + 'README.md', None))) + #thread_pool.add_job((request.version_information, (values['url'] + 'README.rst', None))) thread_pool.start(threads, version_search=True) @@ -95,24 +95,27 @@ class Extensions: def output(self, extension_dict, database): conn = sqlite3.connect(database) c = conn.cursor() - print('\n |\n [+] Extension information\n \\') + print('\n\n [+] Extension information') + print(' -------------------------') for extension,info in extension_dict.items(): - c.execute('SELECT title FROM extensions where extensionkey=?', (extension,)) - title = c.fetchone()[0] + c.execute('SELECT title,state FROM extensions where extensionkey=?', (extension,)) + data = c.fetchone() print(' [+] Name: {}'.format(Fore.GREEN + extension + Fore.RESET)) - print(' \u251c Title: {}'.format(title)) + print(' \u251c Title: {}'.format(data[0])) + print(' \u251c State (of current version): {}'.format(data[1])) if info['version']: c.execute('SELECT advisory, vulnerability, affected_version_max, affected_version_min FROM extension_vulns WHERE (extensionkey=? AND ?<=affected_version_max AND ?>=affected_version_min)', (extension, info['version'], info['version'],)) data = c.fetchall() print(' \u251c Version: {}'.format(Fore.GREEN + info['version'] + Fore.RESET)) if data: print(' \u251c see: {}'.format(info['file'])) - print(' \u2514 Known vulnerabilities\n \\') + print(' \u2514 Known vulnerabilities:\n') for vuln in data: if parse_version(info['version']) <= parse_version(vuln[2]): print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET)) print(' \u251c Vulnerability Type:'.ljust(29), vuln[1]) print(' \u2514 Affected Versions:'.ljust(29), '{} - {}'.format(vuln[2], vuln[3])) + print() else: print(' \u2514 see: {}'.format(info['file'])) else: diff --git a/lib/request.py b/lib/request.py index d78c30f..6152ccb 100644 --- a/lib/request.py +++ b/lib/request.py @@ -29,7 +29,7 @@ requests.packages.urllib3.disable_warnings(InsecureRequestWarning) def get_request(url): """ All GET requests are done in this method. - This method is not used, when searching for extensions and their Readmes/ChangeLogs + This method is not used, when searching for extensions and their version info There are three error types which can occur: Connection timeout Connection error @@ -55,12 +55,9 @@ def get_request(url): response['headers'] = r.headers response['cookies'] = r.cookies return response - except requests.exceptions.Timeout: + except requests.exceptions.Timeout as e: print(e) - print(Fore.RED + '[x] Connection timed out' + Fore.RESET) - except requests.exceptions.ConnectionError as e: - print(e) - print(Fore.RED + '[x] Connection error\n | Please make sure you provided the right URL' + Fore.RESET) + print(Fore.RED + '[x] Connection error\n Please make sure you provided the right URL\n' + Fore.RESET) exit(-1) except requests.exceptions.RequestException as e: print(Fore.RED + str(e) + Fore.RESET) @@ -68,7 +65,7 @@ def get_request(url): def head_request(url): """ All HEAD requests are done in this method. - HEAD requests are used when searching for extensions and their Readmes/ChangeLogs + HEAD requests are used when searching for extensions and their version info There are three error types which can occur: Connection timeout Connection error @@ -91,13 +88,11 @@ def head_request(url): r = requests.head(url, timeout=config['timeout'], headers=custom_headers, allow_redirects=False, verify=False) status_code = str(r.status_code) if status_code == '405': - print('[x] WARNING: \'HEAD\' method not allowed!') + print(' [x] WARNING: \'HEAD\' method not allowed!') exit(-1) return status_code except requests.exceptions.Timeout: - print(Fore.RED + '[x] Connection timed out' + Fore.RESET) - except requests.exceptions.ConnectionError as e: - print(Fore.RED + '[x] Connection aborted.\n Please make sure you provided the right URL' + Fore.RESET) + print(Fore.RED + ' [x] Connection timed out on "{}"'.format(url) + Fore.RESET) except requests.exceptions.RequestException as e: print(Fore.RED + str(e) + Fore.RESET) @@ -114,29 +109,34 @@ def version_information(url, regex): auth = config['auth'] cookie = config['cookie'] custom_headers = {'User-Agent' : config['User-Agent']} - if cookie != '': - name = cookie.split('=')[0] - value = cookie.split('=')[1] - custom_headers[name] = value - if auth != '': - r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, auth=(auth.split(':')[0], auth.split(':')[1]), verify=False) - else: - r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, verify=False) - if r.status_code == 200: - if 'manual.sxw' in url: - return 'check manually' - try: - for content in r.iter_content(chunk_size=400, decode_unicode=False): - search = re.search(regex, str(content)) - version = search.group(1) - r.close() - return version - except: + try: + if cookie != '': + name = cookie.split('=')[0] + value = cookie.split('=')[1] + custom_headers[name] = value + if auth != '': + r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, auth=(auth.split(':')[0], auth.split(':')[1]), verify=False) + else: + r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, verify=False) + if r.status_code == 200: + if ('manual.sxw' in url) and not ('Page Not Found' in r.text): + return 'check manually' try: - search = re.search('([0-9]+-[0-9]+-[0-9]+)', str(content)) - version = search.group(1) - r.close() - return version + for content in r.iter_content(chunk_size=400, decode_unicode=False): + search = re.search(regex, str(content)) + version = search.group(1) + r.close() + return version except: - r.close() - return None \ No newline at end of file + try: + search = re.search('([0-9]+-[0-9]+-[0-9]+)', str(content)) + version = search.group(1) + r.close() + return version + except: + r.close() + return None + except requests.exceptions.Timeout: + print(Fore.RED + ' [x] Connection timed out on "{}"'.format(url) + Fore.RESET) + except requests.exceptions.RequestException as e: + print(Fore.RED + str(e) + Fore.RESET) \ No newline at end of file diff --git a/lib/typo3scan.db b/lib/typo3scan.db index 176c1b1..085655a 100644 Binary files a/lib/typo3scan.db and b/lib/typo3scan.db differ diff --git a/typo3scan.py b/typo3scan.py index 5b5f8f8..b5061c1 100644 --- a/typo3scan.py +++ b/typo3scan.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/) #------------------------------------------------------------------------------- -__version__ = '0.5.1' +__version__ = '0.5.2' __program__ = 'Typo3Scan' __description__ = 'Automatic Typo3 enumeration tool' __author__ = 'https://github.com/whoot' @@ -29,8 +29,6 @@ import sqlite3 import os.path import argparse from lib.domain import Domain -from lib.update import Update -from lib.initdb import DB_Init from lib.extensions import Extensions from colorama import Fore, init, deinit, Style init() @@ -41,6 +39,88 @@ class Typo3: self.__path = os.path.dirname(os.path.abspath(__file__)) self.__extensions = [] + def run(self): + database = os.path.join(self.__path, 'lib', 'typo3scan.db') + conn = sqlite3.connect(database) + c = conn.cursor() + c.execute('SELECT * FROM UserAgents ORDER BY RANDOM() LIMIT 1;') + user_agent = c.fetchone()[0] + c.close() + config = {'threads': args.threads, 'timeout': args.timeout, 'cookie': args.cookie, 'auth': args.auth, 'User-Agent': user_agent} + json.dump(config, open(os.path.join(self.__path, 'lib', 'config.json'), 'w')) + try: + if args.domain: + for dom in args.domain: + self.__domain_list.append(dom) + elif args.file: + if not os.path.isfile(args.file): + print(Fore.RED + '\n[x] File not found: {}\n | Aborting...'.format(args.file) + Fore.RESET) + sys.exit(-1) + else: + with open(args.file, 'r') as f: + for line in f: + self.__domain_list.append(line.strip()) + + for domain in self.__domain_list: + print(Fore.CYAN + Style.BRIGHT + '\n\n[ Checking {} ]\n'.format(domain) + '-'* 73 + Fore.RESET + Style.RESET_ALL) + check = Domain(domain) + check.check_root() + default_files = check.check_default_files() + if not default_files: + check_404 = check.check_404() + if not check.is_typo3(): + print(Fore.RED + '\n[x] It seems that Typo3 is not used on this domain\n' + Fore.RESET) + else: + # check for typo3 information + print('\n[+] Typo3 Installation') + print('----------------------') + check.search_login() + check.search_typo3_version() + + # Search extensions + print('\n [+] Extension Search') + if not self.__extensions: + conn = sqlite3.connect(database) + c = conn.cursor() + if args.vuln: + for row in c.execute('SELECT extensionkey FROM extension_vulns'): + self.__extensions.append(row[0]) + self.__extensions = set(self.__extensions) + else: + for row in c.execute('SELECT extensionkey FROM extensions'): + self.__extensions.append(row[0]) + conn.close() + print (' \u251c Brute-Forcing {} extensions'.format(len(self.__extensions))) + extensions = Extensions() + ext_list = extensions.search_extension(check.get_path(), self.__extensions, args.threads) + if ext_list: + print ('\n \u251c Found {} extensions'.format(len(ext_list))) + print (' |\n \u251c Brute-Forcing version information'.format(len(self.__extensions))) + ext_list = extensions.search_ext_version(ext_list, args.threads) + extensions.output(ext_list, database) + else: + print ('\n [!] No extensions found.\n') + except KeyboardInterrupt: + print('\nReceived keyboard interrupt.\nQuitting...') + exit(-1) + finally: + deinit() + +if __name__ == '__main__': + print('\n' + 73*'=' + Style.BRIGHT) + print(Fore.CYAN) + print('________ ________ _________ '.center(73)) + print('\_ _/__ __ ______ _____\_____ \ / _____/ ____ _____ ___ '.center(73)) + print(' | | | | |\____ \| _ | _(__ < \_____ \_/ ___\\\\__ \ / \ '.center(73)) + print(' | | |___ || |_) | (_) |/ \/ \ \___ / __ \| | \ '.center(73)) + print(' |__| / ____|| __/|_____|________/_________/\_____|_____/|__|__/ '.center(73)) + print(' \/ |__| '.center(73)) + print(Fore.RESET + Style.RESET_ALL) + print(__description__.center(73)) + print(('Version ' + __version__).center(73)) + print((__author__).center(73)) + print(73*'=') + def print_help(): print( """\nUsage: python typo3scan.py [options] @@ -76,118 +156,32 @@ Options: -r | --reset Reset the database. """) - def run(self): - parser = argparse.ArgumentParser(add_help=False) - group = parser.add_mutually_exclusive_group() - help = parser.add_mutually_exclusive_group() - group.add_argument('-f', '--file', dest='file') - group.add_argument('-d', '--domain', dest='domain', type=str, nargs='+') - group.add_argument('-u', '--update', dest='update', action='store_true') - group.add_argument('-r', '--reset', dest='reset', action='store_true') - parser.add_argument('--vuln', dest='vuln', action='store_true') - parser.add_argument('--threads', dest='threads', type=int, default=5) - parser.add_argument('--auth', dest='auth', type=str, default='') - parser.add_argument('--cookie', dest='cookie', type=str, default='') - parser.add_argument('--timeout', dest='timeout', type=int, default=10) - help.add_argument( '-h', '--help', action='store_true') - args = parser.parse_args() + parser = argparse.ArgumentParser(add_help=False) + group = parser.add_mutually_exclusive_group() + help = parser.add_mutually_exclusive_group() + group.add_argument('-f', '--file', dest='file') + group.add_argument('-d', '--domain', dest='domain', type=str, nargs='+') + group.add_argument('-u', '--update', dest='update', action='store_true') + group.add_argument('-r', '--reset', dest='reset', action='store_true') + parser.add_argument('--vuln', dest='vuln', action='store_true') + parser.add_argument('--threads', dest='threads', type=int, default=5) + parser.add_argument('--auth', dest='auth', type=str, default='') + parser.add_argument('--cookie', dest='cookie', type=str, default='') + parser.add_argument('--timeout', dest='timeout', type=int, default=10) + help.add_argument( '-h', '--help', action='store_true') + args = parser.parse_args() - if args.help or not len(sys.argv) > 1: - Typo3.print_help() + if args.help or not len(sys.argv) > 1: + print_help() - elif args.reset: - DB_Init() + elif args.reset: + from lib.initdb import DB_Init + DB_Init() - elif args.update: - Update() + elif args.update: + from lib.update import Update + Update() - else: - database = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib', 'typo3scan.db') - conn = sqlite3.connect(database) - c = conn.cursor() - c.execute('SELECT * FROM UserAgents ORDER BY RANDOM() LIMIT 1;') - user_agent = c.fetchone()[0] - c.close() - config = {'threads': args.threads, 'timeout': args.timeout, 'cookie': args.cookie, 'auth': args.auth, 'User-Agent': user_agent} - json.dump(config, open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib', 'config.json'), 'w')) - try: - if args.domain: - for dom in args.domain: - self.__domain_list.append(dom) - elif args.file: - if not os.path.isfile(args.file): - print(Fore.RED + '\n[x] File not found: {}\n | Aborting...'.format(args.file) + Fore.RESET) - sys.exit(-1) - else: - with open(args.file, 'r') as f: - for line in f: - self.__domain_list.append(line.strip()) - - for domain in self.__domain_list: - print(Fore.CYAN + Style.BRIGHT + '\n\n[ Checking {} ]\n'.format(domain) + '-'* 73 + Fore.RESET + Style.RESET_ALL) - check = Domain(domain) - check.check_root() - default_files = check.check_default_files() - if not default_files: - check_404 = check.check_404() - if not check.is_typo3(): - print(Fore.RED + '\n[x] It seems that Typo3 is not used on this domain' + Fore.RESET) - else: - # print interesting headers - print('\n[+] Interesting Headers') - for key, value in check.get_interesting_headers().items(): - string = ' \u251c {}:'.format(key) - print(string.ljust(30) + value) - - # check for typo3 information - print('\n[+] Typo3 Information') - check.search_login() - check.search_typo3_version() - - # Search extensions - print('\n [+] Extension Search') - if not self.__extensions: - conn = sqlite3.connect(database) - c = conn.cursor() - if args.vuln: - for row in c.execute('SELECT extensionkey FROM extension_vulns'): - self.__extensions.append(row[0]) - self.__extensions = set(self.__extensions) - else: - for row in c.execute('SELECT extensionkey FROM extensions'): - self.__extensions.append(row[0]) - conn.close() - print (' \u251c Brute-Forcing {} extensions'.format(len(self.__extensions))) - extensions = Extensions() - ext_list = extensions.search_extension(check.get_path(), self.__extensions, args.threads) - if ext_list: - print ('\n \u251c Found {} extensions'.format(len(ext_list))) - print (' |\n \u251c Brute-Forcing version information'.format(len(self.__extensions))) - ext_list = extensions.search_ext_version(ext_list, args.threads) - extensions.output(ext_list, database) - else: - print ('\n [!] No extensions found.') - except KeyboardInterrupt: - print('\nReceived keyboard interrupt.\nQuitting...') - exit(-1) - finally: - deinit() - print() - -if __name__ == '__main__': - print('\n' + 73*'=' + Style.BRIGHT) - print(Fore.CYAN) - print('________ ________ _________ '.center(73)) - print('\_ _/__ __ ______ _____\_____ \ / _____/ ____ _____ ___ '.center(73)) - print(' | | | | |\____ \| _ | _(__ < \_____ \_/ ___\\\\__ \ / \ '.center(73)) - print(' | | |___ || |_) | (_) |/ \/ \ \___ / __ \| | \ '.center(73)) - print(' |__| / ____|| __/|_____|________/_________/\_____|_____/|__|__/ '.center(73)) - print(' \/ |__| '.center(73)) - print(Fore.RESET + Style.RESET_ALL) - print(__description__.center(73)) - print(('Version ' + __version__).center(73)) - print((__author__).center(73)) - print(73*'=') - - main = Typo3() - main.run() + else: + main = Typo3() + main.run()