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
* 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:
name: URL of the domain
typo3: If Typo3 is installed
typo3_version: Typo3 Version
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
"""
def __init__(self, name):
@@ -39,10 +41,12 @@ class Domain:
self.__name = 'https://' + name
else:
self.__name = name
self.__typo3 = False
self.__typo3_version = ''
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):
return self.__name
@@ -50,23 +54,35 @@ class Domain:
def set_name(self, name):
self.__name = name
def set_path(self, path):
self.__path = path
def get_path(self):
return self.__path
def is_typo3(self):
return self.__typo3
def set_typo3(self):
self.__typo3 = True
def set_backend(self, url):
self.__backend = url
def get_backend(self):
return self.__backend
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 set_typo3_vulns(self, vuln):
self.__typo3_vulnerabilities = vuln
def get_path(self):
return self.__path
def get_typo3_vulns(self):
return self.__typo3_vulnerabilities
def check_root(self):
"""
@@ -76,16 +92,13 @@ class Domain:
in order to determine the Typo3 installation path.
"""
response = request.get_request('{}'.format(self.get_name()))
self.set_name(response['url'][:-1])
full_path = self.get_name()
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]
path = re.search('="(?:{})/?(\S*?)/?(?:typo3temp|typo3conf)/'.format(self.get_name()), response['html'])
if path and path.group(1) != '':
full_path = '{}/{}'.format(self.get_name(), path)
self.set_path(full_path)
def check_default_files(self):
@@ -137,9 +150,11 @@ class Domain:
searchTitle = re.search('<title>(.*)</title>', response['html'])
if searchTitle and 'Login' in searchTitle.group(0):
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):
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))
self.set_backend('{}/typo3/index.php'.format(self.get_path()))
else:
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.
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]?)"',
'/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/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/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_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_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',
'/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',
'/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]?)',
'/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]?)'
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/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/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]?)',
'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_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_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',
'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',
'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]?)',
'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]?)'
}
version = None
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))):
version = response
version_path = path
@@ -186,27 +201,29 @@ class Domain:
if react.startswith('y'):
version = version + '.0'
else:
return False
return False
self.set_typo3_version(version)
# 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()
vuln_list = []
if data:
json_list = {}
for vulnerability 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(vulnerability[3]):
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 Subcomponent:'.ljust(28) + vulnerability[2])
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:
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())}
if json_list:
self.set_typo3_vulns(json_list)
print(' \u2514 Known Vulnerabilities:\n')
for vulnerability in vuln_list:
print(vulnerability)
else:
print(' \u2514 No Known Vulnerabilities')
for vulnerability in json_list.keys():
print(Style.BRIGHT + ' [!] {}'.format(Fore.RED + vulnerability + Style.RESET_ALL))
print(' \u251c Vulnerability Type:'.ljust(28) + json_list[vulnerability]['Type'])
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:
print(' \u2514', Fore.RED + 'No Version Information Found.' + Fore.RESET)

View File

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

View File

@@ -54,6 +54,7 @@ def get_request(url):
response['html'] = r.text
response['headers'] = r.headers
response['cookies'] = r.cookies
response['url'] = r.url
return response
except requests.exceptions.Timeout as 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/)
#-------------------------------------------------------------------------------
__version__ = '0.6'
__version__ = '0.6.1'
__program__ = 'Typo3Scan'
__description__ = 'Automatic Typo3 enumeration tool'
__author__ = 'https://github.com/whoot'
@@ -100,9 +100,13 @@ class Typo3:
print ('\n \u251c Found {} extensions'.format(len(ext_list)))
print (' \u251c Brute-Forcing Version Information'.format(len(self.__extensions)))
ext_list = extensions.search_ext_version(ext_list, args.threads)
extensions.output(ext_list, database)
json_ext = extensions.output(ext_list, database)
else:
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:
print('\nReceived keyboard interrupt.\nQuitting...')
exit(-1)
@@ -155,6 +159,8 @@ Options:
--threads THREADS The number of threads to use for enumerating extensions.
Default: 5
--json Output results to json file
General:
-u | --update Update the database.
-r | --reset Reset the database.
@@ -173,6 +179,7 @@ Options:
parser.add_argument('--cookie', dest='cookie', 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('--json', dest='json', action='store_true')
help.add_argument( '-h', '--help', action='store_true')
args = parser.parse_args()