diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..43583a0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/lib/config.json
+*.pyc
\ No newline at end of file
diff --git a/README.md b/README.md
index c99769a..3a2b6f1 100644
--- a/README.md
+++ b/README.md
@@ -5,13 +5,6 @@ It also has a database with known vulnerabilities for core and extensions.
Typo3Scan does not exploit any vulnerabilities! It´s soley purpose was to enumerate version info and installed extensions in penetration tests ever since.
-**Note:**
-When I started this project many years ago, the version information could be easily read from text files (Readmes, Changelogs, etc.). Since then a lot has changed.
-Typo3 now restricts access to directories and files by default and version information of extensions may not available in files anymore.
-In addition, various basic functions have changed over time.
-For these reasons this tool will probably *not receive further major releases*.
-
-
## Installation
You can download the latest tarball by clicking [here](https://github.com/whoot/Typo3Scan/tarball/master) or latest zipball by clicking [here](https://github.com/whoot/Typo3Scan/zipball/master).
diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md
index 4b41eb4..78975cb 100644
--- a/doc/CHANGELOG.md
+++ b/doc/CHANGELOG.md
@@ -1,3 +1,8 @@
+## Version 0.6
+
+* Added version regex for composer installations
+* Output bugfix
+
## Version 0.5.2
* Removed 'interesting header' output
diff --git a/lib/config.json b/lib/config.json
index 86777f4..7fcf83f 100644
--- a/lib/config.json
+++ b/lib/config.json
@@ -1 +1 @@
-{"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"}
\ No newline at end of file
+{"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
diff --git a/lib/domain.py b/lib/domain.py
index cfc2801..ac5ea26 100644
--- a/lib/domain.py
+++ b/lib/domain.py
@@ -21,7 +21,7 @@ import re
import string
import random
import sqlite3
-from colorama import Fore
+from colorama import Fore, Style
import lib.request as request
from pkg_resources import parse_version
@@ -98,7 +98,8 @@ class Domain:
'typo3_src/INSTALL.md':'INSTALLING TYPO3',
'typo3_src/INSTALL.txt':'INSTALLING TYPO3',
'typo3_src/LICENSE.txt':'TYPO3',
- 'typo3_src/CONTRIBUTING.md':'TYPO3 CMS'
+ 'typo3_src/CONTRIBUTING.md':'TYPO3 CMS',
+ 'typo3_src/composer.json':'TYPO3'
}
for path, regex in files.items():
try:
@@ -135,12 +136,12 @@ class Domain:
response = request.get_request('{}/typo3/index.php'.format(self.get_path()))
searchTitle = re.search('
(.*)', response['html'])
if searchTitle and 'Login' in searchTitle.group(0):
- print(' \u251c', 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))
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.YELLOW + 'But access is forbidden (IP Address Restriction)' + 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))
else:
- print(' \u251c', Fore.RED + 'Could not be found' + Fore.RESET)
+ print(' \u251c {}'.format(Fore.RED + 'Could not be found' + Fore.RESET))
def search_typo3_version(self):
"""
@@ -148,30 +149,37 @@ 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/ChangeLog': '[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.?[0-9]?[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/typo3/sysext/backend/composer.json': '"typo3/cms-core": "(\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})',
+ '/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',
- '/INSTALL.md': '[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss] (\d{1,2}(.\d{1,2})?)',
- '/INSTALL.txt': '[Tt][Yy][Pp][Oo]3 v(\d{1})'
+ '/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)
- if not (response is None) and (version is None or (len(response) > len(version))):
+ if response and (version is None or (len(response) > len(version))):
version = response
version_path = path
print(' |\n[+] Version Information')
- if not (version is None):
- print(' \u251c {}'.format(Fore.GREEN + version + Fore.RESET))
- print(' \u251c see: {}{}'.format(self.get_path(), version_path))
+ if version:
+ print(' \u251c Identified Version: '.ljust(28) + '{}'.format(Style.BRIGHT + Fore.GREEN + version + Style.RESET_ALL))
+ print(' \u251c Version File: '.ljust(28) + '{}{}'.format(self.get_path(), version_path))
if len(version) == 3:
print(' \u251c Could not identify exact version.')
react = input(' \u251c Do you want to print all vulnerabilities for branch {}? (y/n): '.format(version))
@@ -179,21 +187,26 @@ class Domain:
version = version + '.0'
else:
return False
- print(' \u2514 Known vulnerabilities:\n')
# 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()
- if not data:
- print(' \u251c None.')
- else:
- for vuln in data:
+ vuln_list = []
+ if 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/
- if parse_version(version) <= parse_version(vuln[3]):
- print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET))
- print(' \u251c Vulnerability Type:'.ljust(29), vuln[1])
- print(' \u251c Subcomponent:'.ljust(29), vuln[2])
- print(' \u2514 Affected Versions:'.ljust(29), '{} - {}\n'.format(vuln[3], vuln[4]))
+ 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:
+ print(' \u2514 Known Vulnerabilities:\n')
+ for vulnerability in vuln_list:
+ print(vulnerability)
+ else:
+ print(' \u2514 No Known Vulnerabilities')
else:
- print(' \u2514', Fore.RED + 'No version information found.' + Fore.RESET)
\ No newline at end of file
+ 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 f88c79b..ca73e51 100644
--- a/lib/extensions.py
+++ b/lib/extensions.py
@@ -19,7 +19,7 @@
#-------------------------------------------------------------------------------
import sqlite3
-from colorama import Fore
+from colorama import Fore, Style
import lib.request as request
from lib.thread_pool import ThreadPool
from pkg_resources import parse_version
@@ -72,9 +72,6 @@ class Extensions:
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.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.rst', None)))
thread_pool.start(threads, version_search=True)
@@ -95,30 +92,35 @@ class Extensions:
def output(self, extension_dict, database):
conn = sqlite3.connect(database)
c = conn.cursor()
- print('\n\n [+] Extension information')
+ print('\n\n [+] Extension Information')
print(' -------------------------')
for extension,info in extension_dict.items():
- c.execute('SELECT title,state FROM extensions where extensionkey=?', (extension,))
+ c.execute('SELECT title,version,state FROM extensions where extensionkey=?', (extension,))
data = c.fetchone()
- print(' [+] Name: {}'.format(Fore.GREEN + extension + Fore.RESET))
- print(' \u251c Title: {}'.format(data[0]))
- print(' \u251c State (of current version): {}'.format(data[1]))
+ print(Style.BRIGHT + ' [+] {}'.format(Fore.GREEN + extension + Style.RESET_ALL))
+ 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]))
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'],))
data = c.fetchall()
- print(' \u251c Version: {}'.format(Fore.GREEN + info['version'] + Fore.RESET))
+ print(' \u251c Identified Version: '.ljust(28) + '{}'.format(Style.BRIGHT + Fore.GREEN + info['version'] + Style.RESET_ALL))
+ vuln_list = []
if data:
- print(' \u251c see: {}'.format(info['file']))
- print(' \u2514 Known vulnerabilities:\n')
- for vuln in data:
- if parse_version(info['version']) <= parse_version(vuln[2]):
- print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET))
- print(' \u251c Vulnerability Type:'.ljust(29), vuln[1])
- print(' \u2514 Affected Versions:'.ljust(29), '{} - {}'.format(vuln[2], vuln[3]))
- print()
+ 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()))
+ if vuln_list:
+ print(' \u251c Version File: '.ljust(28) + '{}'.format(info['file']))
+ print(' \u2514 Known Vulnerabilities:\n')
+ for vulnerability in vuln_list:
+ print(vulnerability)
else:
- print(' \u2514 see: {}'.format(info['file']))
+ print(' \u2514 Version File: '.ljust(28) + '{}'.format(info['file']))
else:
- print(' \u2514 Version: -unknown-')
+ print(' \u2514 Identified Version: '.ljust(28) + '-unknown-')
print()
conn.close()
\ No newline at end of file
diff --git a/lib/request.py b/lib/request.py
index 6152ccb..e766ff3 100644
--- a/lib/request.py
+++ b/lib/request.py
@@ -119,23 +119,23 @@ def version_information(url, regex):
else:
r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, verify=False)
if r.status_code == 200:
+ version = None
if ('manual.sxw' in url) and not ('Page Not Found' in r.text):
return 'check manually'
- 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):
+ try:
search = re.search(regex, str(content))
version = search.group(1)
- r.close()
- return version
- except:
- try:
- search = re.search('([0-9]+-[0-9]+-[0-9]+)', str(content))
- version = search.group(1)
- r.close()
- return version
except:
+ try:
+ search = re.search('([0-9]+-[0-9]+-[0-9]+)', str(content))
+ version = search.group(1)
+ except:
+ continue
+ if version:
r.close()
- return None
+ break
+ return version
except requests.exceptions.Timeout:
print(Fore.RED + ' [x] Connection timed out on "{}"'.format(url) + Fore.RESET)
except requests.exceptions.RequestException as e:
diff --git a/typo3scan.py b/typo3scan.py
index b5061c1..4d0a245 100644
--- 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.5.2'
+__version__ = '0.'
__program__ = 'Typo3Scan'
__description__ = 'Automatic Typo3 enumeration tool'
__author__ = 'https://github.com/whoot'
@@ -31,7 +31,7 @@ import argparse
from lib.domain import Domain
from lib.extensions import Extensions
from colorama import Fore, init, deinit, Style
-init()
+init(strip=False)
class Typo3:
def __init__(self):
@@ -90,12 +90,12 @@ class Typo3:
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)))
+ 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)))
+ 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)
else: