Update to v0.4.2

This commit is contained in:
Jan Rude
2015-08-21 12:13:52 +02:00
parent 71b1275c86
commit 57af1bb3a2
16 changed files with 6139 additions and 5817 deletions

View File

@@ -26,52 +26,119 @@ from lib.output import Output
class Typo3_Installation:
"""
This class checks, if Typo3 is used on the domain.
This class checks, if Typo3 is used on the domain with different approaches.
If Typo3 is used, a link to the login page is shown.
name: URL of the domain
extensions: Extensions to check for
typo3: If Typo3 is installed
"""
@staticmethod
def run(domain):
login_found = Typo3_Installation.search_login(domain)
if not login_found:
Typo3_Installation.check(domain)
root = Typo3_Installation.check_root(domain)
check_installation = Typo3_Installation.check_installation(domain)
if not root:
default_files = Typo3_Installation.check_default_files(domain)
if not default_files:
typo = Typo3_Installation.check_404(domain)
# Searching for Typo3 references in HTML comments
"""
This method requests the root page
and searches for a specific string in the response.
Usually there are some TYPO3 notes in the HTML comments.
"""
@staticmethod
def check(domain):
response = Request.get_request(domain.get_name(), '/')
Request.interesting_headers(domain, response[1], response[2])
def check_root(domain):
try:
regex = re.compile('[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.[0-9][0-9]?)')
response = Request.get_request(domain.get_name(), '/')
regex = re.compile('[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)')
searchVersion = regex.search(response[0])
version = searchVersion.groups()
version = searchVersion.groups()[0]
domain.set_typo3()
domain.set_typo3_version(version[0].split()[0])
domain.set_typo3_version(version)
return True
except:
return False
"""
This method requests the homepage
and searches for a Typo3 path reference
in order to determine the Typo3 installation path.
"""
@staticmethod
def check_installation(domain):
response = Request.get_request(domain.get_name(), '/')
if not response:
exit(-1)
headers = Request.interesting_headers(response[1], response[2])
for key in headers:
domain.set_interesting_headers(key, headers[key])
try:
path = re.search('(href|src|content)=(.{0,35})(typo3temp/|typo3conf/)', response[0])
if not (path.groups()[1] == '"' or '"../' in path.groups()[1]):
real_path = (path.groups()[1].split('"')[1])
if 'http' in real_path:
domain.set_name(real_path)
else:
domain.set_name(domain.get_name() + real_path)
domain.set_path(real_path)
domain.set_typo3()
return True
except:
return False
"""
This method requests different files, which are generated on installation.
Usually they are not deleted by admins
and can be used as an idicator of a TYPO3 installation.
"""
@staticmethod
def check_default_files(domain):
files = {'/README.md':'[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss]',
'/README.txt':'[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss]',
'/INSTALL.txt':'INSTALLING [Tt][Yy][Pp][Oo]3',
'/typo3_src/LICENSE.txt':'The [Tt][Yy][Pp][Oo]3 licensing conditions'
}
for path, regex in files.items():
try:
regex = re.compile('TYPO3 (\d{1,2}\.\d{1,2}) CMS')
searchHTML = regex.search(response[0])
version = searchHTML.groups()
response = Request.get_request(domain.get_name(), path)
regex = re.compile(regex)
searchInstallation = regex.search(response[0])
installation = searchInstallation.groups()
domain.set_typo3()
domain.set_typo3_version(version[0].split()[0])
return True
except:
return False
pass
return False
# Searching Typo3 login page
"""
This method requests a site which is not available.
TYPO3 installations usually generate a default error page,
which can be used as an indicator.
"""
@staticmethod
def check_404(domain):
domain_name = domain.get_name()
response = Request.get_request((domain_name.split('/')[0] + '//' + domain_name.split('/')[2]), '/idontexist')
try:
regex = re.compile('[Tt][Yy][Pp][Oo]3 CMS')
searchInstallation = regex.search(response[0])
installation = searchInstallation.groups()
domain.set_typo3()
return True
except:
return False
"""
This method requests the default login page
and searches for a specific string in the title or the response.
If the access is forbidden (403), extension search is still possible.
"""
@staticmethod
def search_login(domain):
try:
response = Request.get_request(domain.get_name(), '/typo3/index.php')
Request.interesting_headers(domain, response[1], response[2])
regex = re.compile('<title>(.*)</title>', re.IGNORECASE)
searchTitle = regex.search(response[0])
title = searchTitle.groups()[0]
if 'TYPO3' in title or 'TYPO3 SVN ID:' in response[0]:
if ('TYPO3' in title) or ('TYPO3 CMS' in response[0]) or (response[3] == 403):
domain.set_typo3()
domain.set_login_found()
return True

View File

@@ -24,9 +24,10 @@ class Domain(object):
name: URL of the domain
typo3: If Typo3 is installed
typo3_version: Typo3 Version
login_found: determines of the default login page was found or not
login_found: Determines of the default login page was found or not
extensions: List of extensions to check for
installed_extensions: List of all installed extensions
interesting_header: List of interesting headers
"""
def __init__(self, name, ext_state, top=False):
if not ('http' in name):
@@ -36,6 +37,7 @@ class Domain(object):
self.__typo3 = False
self.__typo3_version = ''
self.__login_found = False
self.__path = '/'
self.__extension_config = [ext_state, top]
self.__extensions = None
self.__installed_extensions = {}
@@ -77,6 +79,12 @@ class Domain(object):
def get_typo3_version(self):
return self.__typo3_version
def set_path(self, path):
self.__path = path
def get_path(self):
return self.__path
def get_login_found(self):
return self.__login_found

View File

@@ -54,6 +54,8 @@ class Extensions:
thread_pool.add_job((Request.head_request, (domain.get_name(), '/typo3conf/ext/' + ext)))
# search global installation path
thread_pool.add_job((Request.head_request, (domain.get_name(), '/typo3/ext/' + ext)))
# search extensions shipped with core
thread_pool.add_job((Request.head_request, (domain.get_name(), '/typo3/sysext/' + ext)))
thread_pool.start(6)
for installed_extension in thread_pool.get_result():

View File

@@ -29,21 +29,28 @@ class Output:
def typo3_installation(domain):
print('')
print('[+] Typo3 default login:'.ljust(30) + Fore.GREEN + domain.get_name() + '/typo3/index.php' + Fore.RESET)
if domain.get_login_found():
if domain.get_name().endswith('/'):
print('[+] Typo3 backend login:'.ljust(30) + Fore.GREEN + domain.get_name() + 'typo3/index.php' + Fore.RESET)
else:
print('[+] Typo3 backend login:'.ljust(30) + Fore.GREEN + domain.get_name() + '/typo3/index.php' + Fore.RESET)
else:
print('[+] Typo3 backend login:'.ljust(30) + Fore.RED + 'not found' + Fore.RESET)
print('[+] Typo3 version:'.ljust(30) + Fore.GREEN + domain.get_typo3_version() + Fore.RESET)
print(' | known vulnerabilities:'.ljust(30) + Fore.GREEN + 'http://www.cvedetails.com/version-search.php?vendor=&product=Typo3&version=' + domain.get_typo3_version() + Fore.RESET)
if (domain.get_typo3_version() != 'could not be determined'):
print(' | known vulnerabilities:'.ljust(30) + Fore.GREEN + 'http://www.cvedetails.com/version-search.php?vendor=&product=Typo3&version=' + domain.get_typo3_version() + Fore.RESET)
print('')
def interesting_headers(name, value):
string = '[!] ' + name + ':'
print(string.ljust(30) + value)
def extension_output(extens):
def extension_output(path, extens):
if not extens:
print(Fore.RED + ' | No extension found' + Fore.RESET)
else:
for extension in extens:
print(Fore.BLUE + '\n[+] Name: ' + extension.split('/')[3] + '\n' + "-"* 25 + Fore.RESET)
print(' | Location:'.ljust(16) + extension)
print(Fore.BLUE + '\n[+] Name: ' + extension.split('/')[3] + '\n' + "-"* 31 + Fore.RESET)
print(' | Location:'.ljust(16) + path + extension[1:])
if not (extens[extension] == False):
print(' | ' + extens[extension].split('.')[0] + ':'.ljust(4) + (extension + '/'+ extens[extension]))
print(' | ' + extens[extension].split('.')[0] + ':'.ljust(4) + (path + extension[1:] + '/'+ extens[extension]))

View File

@@ -44,7 +44,7 @@ class Request:
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 error\n | Please make sure you provided the right URL' + Fore.RESET)
except requests.exceptions.RequestException as e:
print(Fore.RED + str(e) + Fore.RESET)
@@ -65,32 +65,36 @@ class Request:
print(Fore.RED + str(e) + Fore.RESET)
@staticmethod
def interesting_headers(domain, headers, cookies):
def interesting_headers(headers, cookies):
found_headers = {}
for header in headers:
if header == 'server':
domain.set_interesting_headers('Server', headers.get('server'))
found_headers['Server'] = headers.get('server')
elif header == 'x-powered-by':
domain.set_interesting_headers('X-Powered-By', headers.get('x-powered-by'))
found_headers['X-Powered-By'] = headers.get('x-powered-by')
elif header == 'via':
domain.set_interesting_headers('Via', headers.get('via'))
found_headers['Via'] = headers.get('via')
try:
typo_cookie = cookies['be_typo_user']
domain.set_interesting_headers('be_typo_user',typo_cookie)
found_headers['be_typo_user'] = typo_cookie
except:
pass
try:
typo_cookie = cookies['fe_typo_user']
domain.set_interesting_headers('fe_typo_user', typo_cookie)
found_headers['fe_typo_user'] = typo_cookie
except:
pass
return found_headers
@staticmethod
# not used atm because unreliable
def version_information(domain_name, path, regex):
r = requests.get(domain_name + path, stream=True, timeout=timeout, headers=header, verify=False)
if r.status_code == 200:
for content in r.iter_content(chunk_size=400, decode_unicode=False):
regex = re.compile(regex)
search = regex.search(str(content))
version = search.groups()[0]
return version
try:
for content in r.iter_content(chunk_size=400, decode_unicode=False):
regex = re.compile(regex)
search = regex.search(str(content))
version = search.groups()[0]
return version
except:
return None

View File

@@ -30,13 +30,14 @@ class Update:
unpack it and sort the extensions in different files
"""
def __init__(self):
print('')
self.download_ext()
self.generate_list()
# Progressbar
def dlProgress(self, count, blockSize, totalSize):
percent = int(count*blockSize*100/totalSize)
sys.stdout.write("\r[+] Downloading extentions: " + "%d%%" % percent)
sys.stdout.write('\r[+] Downloading extentions: ' + '%d%%' % percent)
sys.stdout.flush()
# Download extensions from typo3 repository
@@ -46,7 +47,7 @@ class Update:
with gzip.open('extensions.gz', 'rb') as f:
file_content = f.read()
f.close()
outF = open("extensions.xml", 'wb')
outF = open('extensions.xml', 'wb')
outF.write(file_content)
outF.close()
os.remove('extensions.gz')
@@ -62,7 +63,7 @@ class Update:
outdated = {} # 'obsolete' and 'outdated'
allExt = {}
print ("\n[+] Parsing file...")
print ('\n[+] Parsing file...')
tree = ElementTree.parse('extensions.xml')
root = tree.getroot()
extension = 0
@@ -89,7 +90,7 @@ class Update:
extension+=1
# sorting lists according to number of downloads
print ("[+] Sorting according to number of downloads...")
print ('[+] Sorting according to number of downloads...')
sorted_experimental = sorted(experimental.items(), key=lambda x: int(x[1]), reverse=True)
sorted_alpha = sorted(alpha.items(), key=lambda x: int(x[1]), reverse=True)
sorted_beta = sorted(beta.items(), key=lambda x: int(x[1]), reverse=True)
@@ -97,7 +98,7 @@ class Update:
sorted_outdated = sorted(outdated.items(), key=lambda x: int(x[1]), reverse=True)
sorted_allExt = sorted(allExt.items(), key=lambda x: int(x[1]), reverse=True)
print ("[+] Generating files...")
print ('[+] Generating files...')
f = open(os.path.join('extensions', 'experimental_extensions'),'w')
for i in range(0,len(sorted_experimental)):
f.write(sorted_experimental[i][0]+'\n')
@@ -128,5 +129,5 @@ class Update:
f.write(sorted_allExt[i][0]+'\n')
f.close()
print ('[+] Loaded', len(sorted_allExt), 'extensions\n')
print ('[+] Loaded', len(sorted_allExt), 'extensions')
os.remove('extensions.xml')

View File

@@ -19,32 +19,41 @@
#-------------------------------------------------------------------------------
import re
import time
from lib.request import Request
class VersionInformation:
"""
This class will search for version information
This class will search for version information.
The exact version can be found in the ChangeLog,
less specific version information can be found in the NEWS or INSTALL file.
"""
def search_typo3_version(self, domain):
ChangeLog = {'/typo3_src/ChangeLog':'(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)',
'/ChangeLog':'(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)'
changelog = {'/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]?)'
}
News = {'/typo3_src/NEWS.txt':'http://wiki.typo3.org/TYPO3_(\d{1,2}\.\d{1,2})',
'/typo3_src/NEWS.md':"TYPO3 CMS (\d{1,2}\.\d{1,2}) - WHAT'S NEW",
news = {'/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':"TYPO3 CMS (\d{1,2}\.\d{1,2}) - WHAT'S NEW"
'/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}) [Ll][Tt][Ss]'
}
version = 'could not determine'
for path, regex in ChangeLog.items():
version = 'could not be determined'
for path, regex in changelog.items():
response = Request.version_information(domain.get_name(), path, regex)
if not(response is None):
if not (response is None):
version = response
break
if version == 'could not determine':
for path, regex in News.items():
domain.set_typo3_version(version)
return True
if version == 'could not be determined':
for path, regex in news.items():
response = Request.version_information(domain.get_name(), path, regex)
if not(response is None):
version = response
break
if not (response is None):
if len(response) > len(domain.get_typo3_version()):
domain.set_typo3_version(version)
return True
domain.set_typo3_version(version)