diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md
index 78975cb..d0c7e58 100644
--- a/doc/CHANGELOG.md
+++ b/doc/CHANGELOG.md
@@ -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
diff --git a/lib/config.json b/lib/config.json
index 7fcf83f..f44949d 100644
--- a/lib/config.json
+++ b/lib/config.json
@@ -1 +1 @@
-{"threads": 5, "timeout": 10, "cookie": "", "auth": "", "User-Agent": "Mozilla/5.0 (X11; Linux i686; rv:64.0) Gecko/20100101 Firefox/64.0"}
\ No newline at end of file
+{"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"}
\ No newline at end of file
diff --git a/lib/domain.py b/lib/domain.py
index ac5ea26..468fc7f 100644
--- a/lib/domain.py
+++ b/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 != '':
- 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('
(.*)', 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)
\ No newline at end of file
diff --git a/lib/extensions.py b/lib/extensions.py
index ca73e51..79dcc71 100644
--- a/lib/extensions.py
+++ b/lib/extensions.py
@@ -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()
\ No newline at end of file
+ conn.close()
+ return json_list
\ No newline at end of file
diff --git a/lib/request.py b/lib/request.py
index e766ff3..cd62c3c 100644
--- a/lib/request.py
+++ b/lib/request.py
@@ -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)
diff --git a/lib/typo3scan.db b/lib/typo3scan.db
index 085655a..bc7ef43 100644
Binary files a/lib/typo3scan.db and b/lib/typo3scan.db differ
diff --git a/typo3scan.py b/typo3scan.py
old mode 100644
new mode 100755
index 4192354..13eefe4
--- 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.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()