This commit is contained in:
whoot
2020-01-25 14:57:04 +01:00
parent 17d9b7b8e6
commit 002d1560ce
9 changed files with 180 additions and 212 deletions

View File

@@ -1 +1 @@
{"threads": 5, "timeout": 10, "cookie": "", "auth": "", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"}
{"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"}

View File

@@ -33,7 +33,6 @@ class Domain:
typo3_version: Typo3 Version
path: Full path to Typo3 installation
installed_extensions: List of all installed extensions
interesting_header: List of interesting headers
"""
def __init__(self, name):
if not ('http' in name):
@@ -44,7 +43,6 @@ class Domain:
self.__typo3_version = ''
self.__path = ''
self.__installed_extensions = {}
self.__interesting_header = {}
def get_name(self):
return self.__name
@@ -70,39 +68,6 @@ class Domain:
def get_path(self):
return self.__path
def set_interesting_headers(self, headers, cookies):
"""
This method searches for interesing headers in the HTTP response
Server: Displays the name of the server
X-Powered-By: Information about Frameworks (e.g. ASP, PHP, JBoss) used by the web application
X-*: Version information in other technologies
Via: Informs the client of proxies through which the response was sent
X-Forwarded-For: Originating IP address of a client connecting through an HTTP proxy or load balancer
fe_typo_user: Frontend cookie for TYPO3
"""
for header in headers:
if header == 'Server':
self.__interesting_header['Server'] = headers.get('Server')
elif header == 'X-Powered-By':
self.__interesting_header['X-Powered-By'] = headers.get('X-Powered-By')
elif header == 'X-Runtime':
self.__interesting_header['X-Runtime'] = headers.get('X-Runtime')
elif header == 'X-Version':
self.__interesting_header['X-Version'] = headers.get('X-Version')
elif header == 'X-AspNet-Version':
self.__interesting_header['X-AspNet-Version'] = headers.get('X-AspNet-Version')
elif header == 'Via':
self.__interesting_header['Via'] = headers.get('Via')
elif header == 'X-Forwarded-For':
self.__interesting_header['X-Forwarded-For'] = headers.get('X-Forwarded-For')
if 'fe_typo_user' in cookies.keys():
self.__interesting_header['fe_typo_user'] = cookies['fe_typo_user']
self.set_typo3()
def get_interesting_headers(self):
return self.__interesting_header
def check_root(self):
"""
This method requests the root page and searches for a specific string.
@@ -112,7 +77,6 @@ class Domain:
"""
response = request.get_request('{}'.format(self.get_name()))
full_path = self.get_name()
self.set_interesting_headers(response['headers'], response['cookies'])
if re.search('powered by TYPO3', response['html']):
self.set_typo3()
path = re.search('="/?(\S*?)/?(?:typo3temp|typo3conf)/'.format(self.get_name()), response['html'])
@@ -166,17 +130,17 @@ class Domain:
and searches for a specific string in the title or the response.
If the access is forbidden (403), extension search is still possible.
"""
print(' \\\n [+] Backend Login')
print('[+] Backend Login')
# maybe /typo3_src/typo3/index.php too?
response = request.get_request('{}/typo3/index.php'.format(self.get_path()))
searchTitle = re.search('<title>(.*)</title>', response['html'])
if searchTitle and 'Login' in searchTitle.group(0):
print(' \u2514', Fore.GREEN + '{}/typo3/index.php'.format(self.get_path()) + Fore.RESET)
print(' \u251c', 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(' \u2514', Fore.YELLOW + 'But access is forbidden (IP Address Restriction)' + Fore.RESET)
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)
else:
print(' \u251c', Fore.RED + 'Could not be found' + Fore.RESET)
print(' \u251c', Fore.RED + 'Could not be found' + Fore.RESET)
def search_typo3_version(self):
"""
@@ -204,32 +168,32 @@ class Domain:
version = response
version_path = path
print('\n [+] Version Information')
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))
print(' \u251c {}'.format(Fore.GREEN + version + Fore.RESET))
print(' \u251c see: {}{}'.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))
print(' \u251c Could not identify exact version.')
react = input(' \u251c Do you want to print all vulnerabilities for branch {}? (y/n): '.format(version))
if react.startswith('y'):
version = version + '.0'
else:
return False
print(' \u2514 Known vulnerabilities\n \\')
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.')
print(' \u251c None.')
else:
for vuln 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]))
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]))
else:
print(' \u2514', Fore.RED + 'No version information found.' + Fore.RESET)
print(' \u2514', Fore.RED + 'No version information found.' + Fore.RESET)

View File

@@ -66,15 +66,15 @@ class Extensions:
thread_pool.add_job((request.version_information, (values['url'] + 'Settings.yml', '(?:release:)\s?([0-9]+\.[0-9]+\.?[0-9]?[0-9]?)')))
thread_pool.add_job((request.version_information, (values['url'] + 'Documentation/ChangeLog', None)))
thread_pool.add_job((request.version_information, (values['url'] + 'Documentation/Index.rst', None)))
thread_pool.add_job((request.version_information, (values['url'] + 'composer.json', '(?:"dev-master":|"version":)\s?"([0-9]+\.[0-9]+\.[0-9x][0-9x]?)')))
thread_pool.add_job((request.version_information, (values['url'] + 'composer.json', '(?:"dev-master":|"version":)\s?"([0-9]+\.[0-9]+\.?[0-9x]?[0-9x]?)')))
thread_pool.add_job((request.version_information, (values['url'] + 'Index.rst', None)))
thread_pool.add_job((request.version_information, (values['url'] + 'doc/manual.sxw', None)))
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.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,24 +95,27 @@ class Extensions:
def output(self, extension_dict, database):
conn = sqlite3.connect(database)
c = conn.cursor()
print('\n |\n [+] Extension information\n \\')
print('\n\n [+] Extension information')
print(' -------------------------')
for extension,info in extension_dict.items():
c.execute('SELECT title FROM extensions where extensionkey=?', (extension,))
title = c.fetchone()[0]
c.execute('SELECT title,state FROM extensions where extensionkey=?', (extension,))
data = c.fetchone()
print(' [+] Name: {}'.format(Fore.GREEN + extension + Fore.RESET))
print(' \u251c Title: {}'.format(title))
print(' \u251c Title: {}'.format(data[0]))
print(' \u251c State (of current version): {}'.format(data[1]))
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))
if data:
print(' \u251c see: {}'.format(info['file']))
print(' \u2514 Known vulnerabilities\n \\')
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()
else:
print(' \u2514 see: {}'.format(info['file']))
else:

View File

@@ -29,7 +29,7 @@ requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
def get_request(url):
"""
All GET requests are done in this method.
This method is not used, when searching for extensions and their Readmes/ChangeLogs
This method is not used, when searching for extensions and their version info
There are three error types which can occur:
Connection timeout
Connection error
@@ -55,12 +55,9 @@ def get_request(url):
response['headers'] = r.headers
response['cookies'] = r.cookies
return response
except requests.exceptions.Timeout:
except requests.exceptions.Timeout as e:
print(e)
print(Fore.RED + '[x] Connection timed out' + Fore.RESET)
except requests.exceptions.ConnectionError as e:
print(e)
print(Fore.RED + '[x] Connection error\n | Please make sure you provided the right URL' + Fore.RESET)
print(Fore.RED + '[x] Connection error\n Please make sure you provided the right URL\n' + Fore.RESET)
exit(-1)
except requests.exceptions.RequestException as e:
print(Fore.RED + str(e) + Fore.RESET)
@@ -68,7 +65,7 @@ def get_request(url):
def head_request(url):
"""
All HEAD requests are done in this method.
HEAD requests are used when searching for extensions and their Readmes/ChangeLogs
HEAD requests are used when searching for extensions and their version info
There are three error types which can occur:
Connection timeout
Connection error
@@ -91,13 +88,11 @@ def head_request(url):
r = requests.head(url, timeout=config['timeout'], headers=custom_headers, allow_redirects=False, verify=False)
status_code = str(r.status_code)
if status_code == '405':
print('[x] WARNING: \'HEAD\' method not allowed!')
print(' [x] WARNING: \'HEAD\' method not allowed!')
exit(-1)
return status_code
except requests.exceptions.Timeout:
print(Fore.RED + '[x] Connection timed out' + Fore.RESET)
except requests.exceptions.ConnectionError as e:
print(Fore.RED + '[x] Connection aborted.\n Please make sure you provided the right URL' + Fore.RESET)
print(Fore.RED + ' [x] Connection timed out on "{}"'.format(url) + Fore.RESET)
except requests.exceptions.RequestException as e:
print(Fore.RED + str(e) + Fore.RESET)
@@ -114,29 +109,34 @@ def version_information(url, regex):
auth = config['auth']
cookie = config['cookie']
custom_headers = {'User-Agent' : config['User-Agent']}
if cookie != '':
name = cookie.split('=')[0]
value = cookie.split('=')[1]
custom_headers[name] = value
if auth != '':
r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, auth=(auth.split(':')[0], auth.split(':')[1]), verify=False)
else:
r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, verify=False)
if r.status_code == 200:
if 'manual.sxw' in url:
return 'check manually'
try:
for content in r.iter_content(chunk_size=400, decode_unicode=False):
search = re.search(regex, str(content))
version = search.group(1)
r.close()
return version
except:
try:
if cookie != '':
name = cookie.split('=')[0]
value = cookie.split('=')[1]
custom_headers[name] = value
if auth != '':
r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, auth=(auth.split(':')[0], auth.split(':')[1]), verify=False)
else:
r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, verify=False)
if r.status_code == 200:
if ('manual.sxw' in url) and not ('Page Not Found' in r.text):
return 'check manually'
try:
search = re.search('([0-9]+-[0-9]+-[0-9]+)', str(content))
version = search.group(1)
r.close()
return version
for content in r.iter_content(chunk_size=400, decode_unicode=False):
search = re.search(regex, str(content))
version = search.group(1)
r.close()
return version
except:
r.close()
return None
try:
search = re.search('([0-9]+-[0-9]+-[0-9]+)', str(content))
version = search.group(1)
r.close()
return version
except:
r.close()
return None
except requests.exceptions.Timeout:
print(Fore.RED + ' [x] Connection timed out on "{}"'.format(url) + Fore.RESET)
except requests.exceptions.RequestException as e:
print(Fore.RED + str(e) + Fore.RESET)

Binary file not shown.