This commit is contained in:
whoot
2020-01-25 14:57:04 +01:00
parent 17d9b7b8e6
commit 002d1560ce
9 changed files with 180 additions and 212 deletions

View File

@@ -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 ## Version 0.5.1
* Output and version detection fix * Output and version detection fix

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

@@ -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"} {"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"}

View File

@@ -33,7 +33,6 @@ class Domain:
typo3_version: Typo3 Version typo3_version: Typo3 Version
path: Full path to Typo3 installation path: Full path to Typo3 installation
installed_extensions: List of all installed extensions installed_extensions: List of all installed extensions
interesting_header: List of interesting headers
""" """
def __init__(self, name): def __init__(self, name):
if not ('http' in name): if not ('http' in name):
@@ -44,7 +43,6 @@ class Domain:
self.__typo3_version = '' self.__typo3_version = ''
self.__path = '' self.__path = ''
self.__installed_extensions = {} self.__installed_extensions = {}
self.__interesting_header = {}
def get_name(self): def get_name(self):
return self.__name return self.__name
@@ -70,39 +68,6 @@ class Domain:
def get_path(self): def get_path(self):
return self.__path 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): def check_root(self):
""" """
This method requests the root page and searches for a specific string. 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())) response = request.get_request('{}'.format(self.get_name()))
full_path = self.get_name() full_path = self.get_name()
self.set_interesting_headers(response['headers'], response['cookies'])
if re.search('powered by TYPO3', response['html']): if re.search('powered by TYPO3', response['html']):
self.set_typo3() self.set_typo3()
path = re.search('="/?(\S*?)/?(?:typo3temp|typo3conf)/'.format(self.get_name()), response['html']) path = re.search('="/?(\S*?)/?(?:typo3temp|typo3conf)/'.format(self.get_name()), response['html'])
@@ -166,15 +130,15 @@ class Domain:
and searches for a specific string in the title or the response. and searches for a specific string in the title or the response.
If the access is forbidden (403), extension search is still possible. 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? # maybe /typo3_src/typo3/index.php too?
response = request.get_request('{}/typo3/index.php'.format(self.get_path())) response = request.get_request('{}/typo3/index.php'.format(self.get_path()))
searchTitle = re.search('<title>(.*)</title>', response['html']) searchTitle = re.search('<title>(.*)</title>', response['html'])
if searchTitle and 'Login' in searchTitle.group(0): 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): 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(' \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.YELLOW + 'But access is forbidden (IP Address Restriction)' + Fore.RESET)
else: else:
print(' \u251c', Fore.RED + 'Could not be found' + Fore.RESET) print(' \u251c', Fore.RED + 'Could not be found' + Fore.RESET)
@@ -204,7 +168,7 @@ class Domain:
version = response version = response
version_path = path version_path = path
print('\n [+] Version Information') print(' |\n[+] Version Information')
if not (version is None): if not (version is None):
print(' \u251c {}'.format(Fore.GREEN + version + Fore.RESET)) print(' \u251c {}'.format(Fore.GREEN + version + Fore.RESET))
print(' \u251c see: {}{}'.format(self.get_path(), version_path)) print(' \u251c see: {}{}'.format(self.get_path(), version_path))
@@ -215,7 +179,7 @@ class Domain:
version = version + '.0' version = version + '.0'
else: else:
return False return False
print(' \u2514 Known vulnerabilities\n \\') print(' \u2514 Known vulnerabilities:\n')
# sqlite stuff # sqlite stuff
conn = sqlite3.connect('lib/typo3scan.db') conn = sqlite3.connect('lib/typo3scan.db')
c = conn.cursor() c = conn.cursor()

View File

@@ -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'] + '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/ChangeLog', None)))
thread_pool.add_job((request.version_information, (values['url'] + 'Documentation/Index.rst', 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'] + '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'] + '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', None)))
thread_pool.add_job((request.version_information, (values['url'] + 'CHANGELOG.md', 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'] + '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.txt', None)))
thread_pool.add_job((request.version_information, (values['url'] + 'README.md', 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.rst', None)))
thread_pool.start(threads, version_search=True) thread_pool.start(threads, version_search=True)
@@ -95,24 +95,27 @@ class Extensions:
def output(self, extension_dict, database): def output(self, extension_dict, database):
conn = sqlite3.connect(database) conn = sqlite3.connect(database)
c = conn.cursor() c = conn.cursor()
print('\n |\n [+] Extension information\n \\') print('\n\n [+] Extension information')
print(' -------------------------')
for extension,info in extension_dict.items(): for extension,info in extension_dict.items():
c.execute('SELECT title FROM extensions where extensionkey=?', (extension,)) c.execute('SELECT title,state FROM extensions where extensionkey=?', (extension,))
title = c.fetchone()[0] data = c.fetchone()
print(' [+] Name: {}'.format(Fore.GREEN + extension + Fore.RESET)) 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']: 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'],)) 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() data = c.fetchall()
print(' \u251c Version: {}'.format(Fore.GREEN + info['version'] + Fore.RESET)) print(' \u251c Version: {}'.format(Fore.GREEN + info['version'] + Fore.RESET))
if data: if data:
print(' \u251c see: {}'.format(info['file'])) print(' \u251c see: {}'.format(info['file']))
print(' \u2514 Known vulnerabilities\n \\') print(' \u2514 Known vulnerabilities:\n')
for vuln in data: for vuln in data:
if parse_version(info['version']) <= parse_version(vuln[2]): if parse_version(info['version']) <= parse_version(vuln[2]):
print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET)) print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET))
print(' \u251c Vulnerability Type:'.ljust(29), vuln[1]) print(' \u251c Vulnerability Type:'.ljust(29), vuln[1])
print(' \u2514 Affected Versions:'.ljust(29), '{} - {}'.format(vuln[2], vuln[3])) print(' \u2514 Affected Versions:'.ljust(29), '{} - {}'.format(vuln[2], vuln[3]))
print()
else: else:
print(' \u2514 see: {}'.format(info['file'])) print(' \u2514 see: {}'.format(info['file']))
else: else:

View File

@@ -29,7 +29,7 @@ requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
def get_request(url): def get_request(url):
""" """
All GET requests are done in this method. 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: There are three error types which can occur:
Connection timeout Connection timeout
Connection error Connection error
@@ -55,12 +55,9 @@ def get_request(url):
response['headers'] = r.headers response['headers'] = r.headers
response['cookies'] = r.cookies response['cookies'] = r.cookies
return response return response
except requests.exceptions.Timeout: except requests.exceptions.Timeout as e:
print(e) print(e)
print(Fore.RED + '[x] Connection timed out' + Fore.RESET) print(Fore.RED + '[x] Connection error\n Please make sure you provided the right URL\n' + 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)
exit(-1) exit(-1)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
print(Fore.RED + str(e) + Fore.RESET) print(Fore.RED + str(e) + Fore.RESET)
@@ -68,7 +65,7 @@ def get_request(url):
def head_request(url): def head_request(url):
""" """
All HEAD requests are done in this method. 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: There are three error types which can occur:
Connection timeout Connection timeout
Connection error 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) r = requests.head(url, timeout=config['timeout'], headers=custom_headers, allow_redirects=False, verify=False)
status_code = str(r.status_code) status_code = str(r.status_code)
if status_code == '405': if status_code == '405':
print('[x] WARNING: \'HEAD\' method not allowed!') print(' [x] WARNING: \'HEAD\' method not allowed!')
exit(-1) exit(-1)
return status_code return status_code
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
print(Fore.RED + '[x] Connection timed out' + Fore.RESET) print(Fore.RED + ' [x] Connection timed out on "{}"'.format(url) + 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)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
print(Fore.RED + str(e) + Fore.RESET) print(Fore.RED + str(e) + Fore.RESET)
@@ -114,6 +109,7 @@ def version_information(url, regex):
auth = config['auth'] auth = config['auth']
cookie = config['cookie'] cookie = config['cookie']
custom_headers = {'User-Agent' : config['User-Agent']} custom_headers = {'User-Agent' : config['User-Agent']}
try:
if cookie != '': if cookie != '':
name = cookie.split('=')[0] name = cookie.split('=')[0]
value = cookie.split('=')[1] value = cookie.split('=')[1]
@@ -123,7 +119,7 @@ def version_information(url, regex):
else: else:
r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, verify=False) r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, verify=False)
if r.status_code == 200: if r.status_code == 200:
if 'manual.sxw' in url: if ('manual.sxw' in url) and not ('Page Not Found' in r.text):
return 'check manually' return 'check manually'
try: try:
for content in r.iter_content(chunk_size=400, decode_unicode=False): for content in r.iter_content(chunk_size=400, decode_unicode=False):
@@ -140,3 +136,7 @@ def version_information(url, regex):
except: except:
r.close() r.close()
return None 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)

Binary file not shown.

View File

@@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/) # 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' __program__ = 'Typo3Scan'
__description__ = 'Automatic Typo3 enumeration tool' __description__ = 'Automatic Typo3 enumeration tool'
__author__ = 'https://github.com/whoot' __author__ = 'https://github.com/whoot'
@@ -29,8 +29,6 @@ import sqlite3
import os.path import os.path
import argparse import argparse
from lib.domain import Domain from lib.domain import Domain
from lib.update import Update
from lib.initdb import DB_Init
from lib.extensions import Extensions from lib.extensions import Extensions
from colorama import Fore, init, deinit, Style from colorama import Fore, init, deinit, Style
init() init()
@@ -41,6 +39,88 @@ class Typo3:
self.__path = os.path.dirname(os.path.abspath(__file__)) self.__path = os.path.dirname(os.path.abspath(__file__))
self.__extensions = [] 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(): def print_help():
print( print(
"""\nUsage: python typo3scan.py [options] """\nUsage: python typo3scan.py [options]
@@ -76,7 +156,6 @@ Options:
-r | --reset Reset the database. -r | --reset Reset the database.
""") """)
def run(self):
parser = argparse.ArgumentParser(add_help=False) parser = argparse.ArgumentParser(add_help=False)
group = parser.add_mutually_exclusive_group() group = parser.add_mutually_exclusive_group()
help = parser.add_mutually_exclusive_group() help = parser.add_mutually_exclusive_group()
@@ -93,101 +172,16 @@ Options:
args = parser.parse_args() args = parser.parse_args()
if args.help or not len(sys.argv) > 1: if args.help or not len(sys.argv) > 1:
Typo3.print_help() print_help()
elif args.reset: elif args.reset:
from lib.initdb import DB_Init
DB_Init() DB_Init()
elif args.update: elif args.update:
from lib.update import Update
Update() Update()
else: 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 = Typo3()
main.run() main.run()