Committer: whoot
This commit is contained in:
whoot
2020-05-29 18:03:13 +02:00
parent e528640e8a
commit a6ea0d09e5
7 changed files with 89 additions and 52 deletions

View File

@@ -1,3 +1,8 @@
## Version 0.6.1
* Bugfix of URL determination
* Support for JSON output
## Version 0.6 ## Version 0.6
* Added version regex for composer installations * Added version regex for composer installations

View File

@@ -1 +1 @@
{"threads": 5, "timeout": 10, "cookie": "", "auth": "", "User-Agent": "Mozilla/5.0 (X11; Linux i686; rv:64.0) Gecko/20100101 Firefox/64.0"} {"threads": 5, "timeout": 10, "cookie": "", "auth": "", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2"}

View File

@@ -29,9 +29,11 @@ class Domain:
""" """
This class stores following information about a domain: This class stores following information about a domain:
name: URL of the domain name: URL of the domain
typo3: If Typo3 is installed
typo3_version: Typo3 Version
path: Full path to Typo3 installation path: Full path to Typo3 installation
typo3: If Typo3 is installed
backend: URL to Typo3 backend login
typo3_version: Typo3 Version
typo3_vulnerabilities: List of known CORE vulnerabilities
installed_extensions: List of all installed extensions installed_extensions: List of all installed extensions
""" """
def __init__(self, name): def __init__(self, name):
@@ -39,10 +41,12 @@ class Domain:
self.__name = 'https://' + name self.__name = 'https://' + name
else: else:
self.__name = name self.__name = name
self.__typo3 = False
self.__typo3_version = ''
self.__path = '' self.__path = ''
self.__installed_extensions = {} self.__typo3 = False
self.__backend = 'Could not be found'
self.__typo3_version = 'Unknown'
self.__typo3_vulnerabilities = ''
self.__installed_extensions = {'installed': 0}
def get_name(self): def get_name(self):
return self.__name return self.__name
@@ -50,23 +54,35 @@ class Domain:
def set_name(self, name): def set_name(self, name):
self.__name = name self.__name = name
def set_path(self, path):
self.__path = path
def get_path(self):
return self.__path
def is_typo3(self): def is_typo3(self):
return self.__typo3 return self.__typo3
def set_typo3(self): def set_typo3(self):
self.__typo3 = True self.__typo3 = True
def set_backend(self, url):
self.__backend = url
def get_backend(self):
return self.__backend
def set_typo3_version(self, version): def set_typo3_version(self, version):
self.__typo3_version = version self.__typo3_version = version
def get_typo3_version(self): def get_typo3_version(self):
return self.__typo3_version return self.__typo3_version
def set_path(self, path): def set_typo3_vulns(self, vuln):
self.__path = path self.__typo3_vulnerabilities = vuln
def get_path(self): def get_typo3_vulns(self):
return self.__path return self.__typo3_vulnerabilities
def check_root(self): def check_root(self):
""" """
@@ -76,16 +92,13 @@ class Domain:
in order to determine the Typo3 installation path. in order to determine the Typo3 installation path.
""" """
response = request.get_request('{}'.format(self.get_name())) response = request.get_request('{}'.format(self.get_name()))
self.set_name(response['url'][:-1])
full_path = self.get_name() full_path = self.get_name()
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'])
if path and path.groups()[0] != '': if path and path.group(1) != '':
path = path.groups()[0].replace(self.get_name(), '') full_path = '{}/{}'.format(self.get_name(), path)
if path != '':
full_path = '{}/{}'.format(self.get_name(), path)
if full_path.endswith('/'):
full_path = full_path[:-1]
self.set_path(full_path) self.set_path(full_path)
def check_default_files(self): def check_default_files(self):
@@ -137,9 +150,11 @@ class Domain:
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(' \u251c {}'.format(Fore.GREEN + '{}/typo3/index.php'.format(self.get_path()) + Fore.RESET)) print(' \u251c {}'.format(Fore.GREEN + '{}/typo3/index.php'.format(self.get_path()) + Fore.RESET))
self.set_backend('{}/typo3/index.php'.format(self.get_path()))
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 {}'.format(Fore.GREEN + '{}/typo3/index.php'.format(self.get_path()) + Fore.RESET)) print(' \u251c {}'.format(Fore.GREEN + '{}/typo3/index.php'.format(self.get_path()) + Fore.RESET))
print(' \u251c {}'.format(Fore.YELLOW + 'But access is forbidden (IP Address Restriction)' + Fore.RESET)) print(' \u251c {}'.format(Fore.YELLOW + 'But access is forbidden (IP Address Restriction)' + Fore.RESET))
self.set_backend('{}/typo3/index.php'.format(self.get_path()))
else: else:
print(' \u251c {}'.format(Fore.RED + 'Could not be found' + Fore.RESET)) print(' \u251c {}'.format(Fore.RED + 'Could not be found' + Fore.RESET))
@@ -149,29 +164,29 @@ class Domain:
The exact version can be found in the ChangeLog, therefore it will be requested first. The exact version can be found in the ChangeLog, therefore it will be requested first.
Less specific version information can be found in the NEWS or INSTALL file. Less specific version information can be found in the NEWS or INSTALL file.
""" """
files = {'/typo3_src/composer.json': '(?:"typo3/cms-core":|"typo3/cms-backend":)\s?"([0-9]+\.[0-9]+\.?[0-9x]?[0-9x]?)"', files = {'typo3_src/composer.json': '(?:"typo3/cms-core":|"typo3/cms-backend":)\s?"([0-9]+\.[0-9]+\.?[0-9x]?[0-9x]?)"',
'/typo3_src/public/typo3/sysext/install/composer.json': '(?:"typo3/cms-core":|"typo3/cms-backend":)\s?"([0-9]+\.[0-9]+\.?[0-9x]?[0-9x]?)"', 'typo3_src/public/typo3/sysext/install/composer.json': '(?:"typo3/cms-core":|"typo3/cms-backend":)\s?"([0-9]+\.[0-9]+\.?[0-9x]?[0-9x]?)"',
'/typo3_src/typo3/sysext/adminpanel/composer.json': '(?:"typo3/cms-core":|"typo3/cms-backend":)\s?"([0-9]+\.[0-9]+\.?[0-9x]?[0-9x]?)"', 'typo3_src/typo3/sysext/adminpanel/composer.json': '(?:"typo3/cms-core":|"typo3/cms-backend":)\s?"([0-9]+\.[0-9]+\.?[0-9x]?[0-9x]?)"',
'/typo3_src/typo3/sysext/backend/composer.json': '(?:"typo3/cms-core":|"typo3/cms-backend":)\s?"(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)"', 'typo3_src/typo3/sysext/backend/composer.json': '(?:"typo3/cms-core":|"typo3/cms-backend":)\s?"(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)"',
'/typo3_src/typo3/sysext/info/composer.json': '(?:"typo3/cms-core":|"typo3/cms-backend":)\s?"(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)"', 'typo3_src/typo3/sysext/info/composer.json': '(?:"typo3/cms-core":|"typo3/cms-backend":)\s?"(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)"',
'/typo3_src/ChangeLog': '[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)', 'typo3_src/ChangeLog': '[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)',
'/ChangeLog': '[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)', 'ChangeLog': '[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)',
'/typo3/sysext/backend/ext_emconf.php': '(?:CMS |typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)', 'typo3/sysext/backend/ext_emconf.php': '(?:CMS |typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)',
'/typo3_src/typo3/sysext/install/Start/Install.php': '(?:CMS |typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)', 'typo3_src/typo3/sysext/install/Start/Install.php': '(?:CMS |typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)',
'/typo3/sysext/install/Start/Install.php': '(?:CMS |typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)', 'typo3/sysext/install/Start/Install.php': '(?:CMS |typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)',
'/typo3_src/NEWS.txt': 'http://wiki.typo3.org/TYPO3_(\d{1,2}\.\d{1,2})', 'typo3_src/NEWS.txt': 'http://wiki.typo3.org/TYPO3_(\d{1,2}\.\d{1,2})',
'/typo3_src/NEWS.md': '[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss] (\d{1,2}\.\d{1,2}) - WHAT\'S NEW', 'typo3_src/NEWS.md': '[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss] (\d{1,2}\.\d{1,2}) - WHAT\'S NEW',
'/NEWS.txt': 'http://wiki.typo3.org/TYPO3_(\d{1,2}\.\d{1,2})', 'NEWS.txt': 'http://wiki.typo3.org/TYPO3_(\d{1,2}\.\d{1,2})',
'/NEWS.md': '[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss] (\d{1,2}\.\d{1,2}) - WHAT\'S NEW', 'NEWS.md': '[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss] (\d{1,2}\.\d{1,2}) - WHAT\'S NEW',
'/typo3_src/INSTALL.md': '(?:typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9x]?[0-9]?)', 'typo3_src/INSTALL.md': '(?:typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9x]?[0-9]?)',
'/typo3_src/INSTALL.txt': '(?:typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9x]?[0-9]?)', 'typo3_src/INSTALL.txt': '(?:typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9x]?[0-9]?)',
'/INSTALL.md': '(?:typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9x]?[0-9]?)', 'INSTALL.md': '(?:typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9x]?[0-9]?)',
'/INSTALL.txt': '(?:typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9x]?[0-9]?)' 'INSTALL.txt': '(?:typo3_src-)(\d{1,2}\.\d{1,2}\.?[0-9x]?[0-9]?)'
} }
version = None version = None
for path, regex in files.items(): for path, regex in files.items():
response = request.version_information(self.get_path()+path, regex) response = request.version_information('{}/{}'.format(self.get_path(), path), regex)
if response and (version is None or (len(response) > len(version))): if response and (version is None or (len(response) > len(version))):
version = response version = response
version_path = path version_path = path
@@ -186,27 +201,29 @@ class Domain:
if react.startswith('y'): if react.startswith('y'):
version = version + '.0' version = version + '.0'
else: else:
return False return False
self.set_typo3_version(version)
# sqlite stuff # sqlite stuff
conn = sqlite3.connect('lib/typo3scan.db') conn = sqlite3.connect('lib/typo3scan.db')
c = conn.cursor() 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,)) 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() data = c.fetchall()
vuln_list = []
if data: if data:
json_list = {}
for vulnerability in data: for vulnerability in data:
# maybe instead use this: https://oraerr.com/database/sql/how-to-compare-version-string-x-y-z-in-mysql-2/ # 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(vulnerability[3]): if parse_version(version) <= parse_version(vulnerability[3]):
vuln_list.append(Style.BRIGHT + ' [!] {}'.format(Fore.RED + vulnerability[0] + Style.RESET_ALL)) json_list[vulnerability[0]] = {'Type': vulnerability[1], 'Subcomponent': vulnerability[2], 'Affected': '{} - {}'.format(vulnerability[3], vulnerability[4]), 'Advisory': 'https://typo3.org/security/advisory/{}'.format(vulnerability[0].lower())}
vuln_list.append(' \u251c Vulnerability Type:'.ljust(28) + vulnerability[1]) if json_list:
vuln_list.append(' \u251c Subcomponent:'.ljust(28) + vulnerability[2]) self.set_typo3_vulns(json_list)
vuln_list.append(' \u251c Affected Versions:'.ljust(28) + '{} - {}'.format(vulnerability[3], vulnerability[4]))
vuln_list.append(' \u2514 Advisory URL:'.ljust(28) + 'https://typo3.org/security/advisory/{}\n'.format(vulnerability[0].lower()))
if vuln_list:
print(' \u2514 Known Vulnerabilities:\n') print(' \u2514 Known Vulnerabilities:\n')
for vulnerability in vuln_list: for vulnerability in json_list.keys():
print(vulnerability) print(Style.BRIGHT + ' [!] {}'.format(Fore.RED + vulnerability + Style.RESET_ALL))
else: print(' \u251c Vulnerability Type:'.ljust(28) + json_list[vulnerability]['Type'])
print(' \u2514 No Known Vulnerabilities') print(' \u251c Subcomponent:'.ljust(28) + json_list[vulnerability]['Subcomponent'])
print(' \u251c Affected Versions:'.ljust(28) + json_list[vulnerability]['Affected'])
print(' \u2514 Advisory URL:'.ljust(28) + json_list[vulnerability]['Advisory'] + '\n')
else:
print(' \u2514 No Known Vulnerabilities')
else: else:
print(' \u2514', Fore.RED + 'No Version Information Found.' + Fore.RESET) print(' \u2514', Fore.RED + 'No Version Information Found.' + Fore.RESET)

View File

@@ -94,6 +94,7 @@ class Extensions:
c = conn.cursor() c = conn.cursor()
print('\n\n [+] Extension Information') print('\n\n [+] Extension Information')
print(' -------------------------') print(' -------------------------')
json_list = {}
for extension,info in extension_dict.items(): for extension,info in extension_dict.items():
c.execute('SELECT title,version,state FROM extensions where extensionkey=?', (extension,)) c.execute('SELECT title,version,state FROM extensions where extensionkey=?', (extension,))
data = c.fetchone() data = c.fetchone()
@@ -101,18 +102,23 @@ class Extensions:
print(' \u251c Extension Title: '.ljust(28) + '{}'.format(data[0])) print(' \u251c Extension Title: '.ljust(28) + '{}'.format(data[0]))
print(' \u251c Extension Repo: '.ljust(28) + 'https://extensions.typo3.org/extension/{}'.format(extension)) print(' \u251c Extension Repo: '.ljust(28) + 'https://extensions.typo3.org/extension/{}'.format(extension))
print(' \u251c Current Version: '.ljust(28) + '{} ({})'.format(data[1], data[2])) print(' \u251c Current Version: '.ljust(28) + '{} ({})'.format(data[1], data[2]))
json_list[extension] = {'Title': data[0], 'Repo': 'https://extensions.typo3.org/extension/{}'.format(extension), 'Current': '{} ({})'.format(data[1], data[2]), 'Version': '', 'Vulnerabilities':''}
if info['version']: if info['version']:
json_list[extension].update(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'],)) 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 Identified Version: '.ljust(28) + '{}'.format(Style.BRIGHT + Fore.GREEN + info['version'] + Style.RESET_ALL)) print(' \u251c Identified Version: '.ljust(28) + '{}'.format(Style.BRIGHT + Fore.GREEN + info['version'] + Style.RESET_ALL))
vuln_list = [] vuln_list = []
if data: if data:
vuln = {}
for vulnerability in data: for vulnerability in data:
if parse_version(info['version']) <= parse_version(vulnerability[2]): if parse_version(info['version']) <= parse_version(vulnerability[2]):
vuln_list.append(Style.BRIGHT + ' [!] {}'.format(Fore.RED + vulnerability[0] + Style.RESET_ALL)) vuln_list.append(Style.BRIGHT + ' [!] {}'.format(Fore.RED + vulnerability[0] + Style.RESET_ALL))
vuln_list.append(' \u251c Vulnerability Type: '.ljust(28) + vulnerability[1]) vuln_list.append(' \u251c Vulnerability Type: '.ljust(28) + vulnerability[1])
vuln_list.append(' \u251c Affected Versions: '.ljust(28) + '{} - {}'.format(vulnerability[2], vulnerability[3])) vuln_list.append(' \u251c Affected Versions: '.ljust(28) + '{} - {}'.format(vulnerability[2], vulnerability[3]))
vuln_list.append(' \u2514 Advisory URL:'.ljust(28) + 'https://typo3.org/security/advisory/{}\n'.format(vulnerability[0].lower())) vuln_list.append(' \u2514 Advisory URL:'.ljust(28) + 'https://typo3.org/security/advisory/{}\n'.format(vulnerability[0].lower()))
vuln[vulnerability[0]] = {'Type': vulnerability[1], 'Affected': '{} - {}'.format(vulnerability[2], vulnerability[3]), 'Advisory': 'https://typo3.org/security/advisory/{}'.format(vulnerability[0].lower())}
json_list[extension].update(Vulnerabilities = vuln)
if vuln_list: if vuln_list:
print(' \u251c Version File: '.ljust(28) + '{}'.format(info['file'])) print(' \u251c Version File: '.ljust(28) + '{}'.format(info['file']))
print(' \u2514 Known Vulnerabilities:\n') print(' \u2514 Known Vulnerabilities:\n')
@@ -121,6 +127,7 @@ class Extensions:
else: else:
print(' \u2514 Version File: '.ljust(28) + '{}'.format(info['file'])) print(' \u2514 Version File: '.ljust(28) + '{}'.format(info['file']))
else: else:
print(' \u2514 Identified Version: '.ljust(28) + '-unknown-') print(' \u2514 Identified Version: '.ljust(28) + '-unknown-')
print() print()
conn.close() conn.close()
return json_list

View File

@@ -54,6 +54,7 @@ def get_request(url):
response['html'] = r.text response['html'] = r.text
response['headers'] = r.headers response['headers'] = r.headers
response['cookies'] = r.cookies response['cookies'] = r.cookies
response['url'] = r.url
return response return response
except requests.exceptions.Timeout as e: except requests.exceptions.Timeout as e:
print(e) print(e)

Binary file not shown.

11
typo3scan.py Normal file → Executable file
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.6' __version__ = '0.6.1'
__program__ = 'Typo3Scan' __program__ = 'Typo3Scan'
__description__ = 'Automatic Typo3 enumeration tool' __description__ = 'Automatic Typo3 enumeration tool'
__author__ = 'https://github.com/whoot' __author__ = 'https://github.com/whoot'
@@ -100,9 +100,13 @@ class Typo3:
print ('\n \u251c Found {} extensions'.format(len(ext_list))) print ('\n \u251c Found {} extensions'.format(len(ext_list)))
print (' \u251c Brute-Forcing Version Information'.format(len(self.__extensions))) print (' \u251c Brute-Forcing Version Information'.format(len(self.__extensions)))
ext_list = extensions.search_ext_version(ext_list, args.threads) ext_list = extensions.search_ext_version(ext_list, args.threads)
extensions.output(ext_list, database) json_ext = extensions.output(ext_list, database)
else: else:
print ('\n [!] No extensions found.\n') print ('\n [!] No extensions found.\n')
if args.json:
json_log = {}
json_log[check.get_name()] = {'Backend': check.get_backend(), 'Version': check.get_typo3_version(), 'Vulnerabilities':check.get_typo3_vulns(), 'Extensions': json_ext}
json.dump(json_log, open('typo3scan.json', 'w'))
except KeyboardInterrupt: except KeyboardInterrupt:
print('\nReceived keyboard interrupt.\nQuitting...') print('\nReceived keyboard interrupt.\nQuitting...')
exit(-1) exit(-1)
@@ -155,6 +159,8 @@ Options:
--threads THREADS The number of threads to use for enumerating extensions. --threads THREADS The number of threads to use for enumerating extensions.
Default: 5 Default: 5
--json Output results to json file
General: General:
-u | --update Update the database. -u | --update Update the database.
-r | --reset Reset the database. -r | --reset Reset the database.
@@ -173,6 +179,7 @@ Options:
parser.add_argument('--cookie', dest='cookie', type=str, default='') parser.add_argument('--cookie', dest='cookie', type=str, default='')
parser.add_argument('--agent', dest='user_agent', type=str, default='') parser.add_argument('--agent', dest='user_agent', type=str, default='')
parser.add_argument('--timeout', dest='timeout', type=int, default=10) parser.add_argument('--timeout', dest='timeout', type=int, default=10)
parser.add_argument('--json', dest='json', action='store_true')
help.add_argument( '-h', '--help', action='store_true') help.add_argument( '-h', '--help', action='store_true')
args = parser.parse_args() args = parser.parse_args()