v0.6.1
Committer: whoot
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"}
|
||||
103
lib/domain.py
103
lib/domain.py
@@ -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 != '':
|
||||
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)
|
||||
if full_path.endswith('/'):
|
||||
full_path = full_path[:-1]
|
||||
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
|
||||
@@ -187,25 +202,27 @@ class Domain:
|
||||
version = version + '.0'
|
||||
else:
|
||||
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)
|
||||
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:
|
||||
|
||||
@@ -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')
|
||||
@@ -124,3 +130,4 @@ class Extensions:
|
||||
print(' \u2514 Identified Version: '.ljust(28) + '-unknown-')
|
||||
print()
|
||||
conn.close()
|
||||
return json_list
|
||||
@@ -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)
|
||||
|
||||
BIN
lib/typo3scan.db
BIN
lib/typo3scan.db
Binary file not shown.
11
typo3scan.py
Normal file → Executable file
11
typo3scan.py
Normal file → Executable 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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user