v0.5.2
This commit is contained in:
@@ -1,3 +1,10 @@
|
|||||||
|
## Version 0.5.2
|
||||||
|
|
||||||
|
* Removed 'interesting header' output
|
||||||
|
* Added output of extension state
|
||||||
|
* Bugfixes
|
||||||
|
* Cosmetic output changes
|
||||||
|
|
||||||
## Version 0.5.1
|
## Version 0.5.1
|
||||||
|
|
||||||
* Output and version detection fix
|
* Output and version detection fix
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 150 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 118 KiB |
@@ -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"}
|
||||||
@@ -33,7 +33,6 @@ class Domain:
|
|||||||
typo3_version: Typo3 Version
|
typo3_version: Typo3 Version
|
||||||
path: Full path to Typo3 installation
|
path: Full path to Typo3 installation
|
||||||
installed_extensions: List of all installed extensions
|
installed_extensions: List of all installed extensions
|
||||||
interesting_header: List of interesting headers
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
if not ('http' in name):
|
if not ('http' in name):
|
||||||
@@ -44,7 +43,6 @@ class Domain:
|
|||||||
self.__typo3_version = ''
|
self.__typo3_version = ''
|
||||||
self.__path = ''
|
self.__path = ''
|
||||||
self.__installed_extensions = {}
|
self.__installed_extensions = {}
|
||||||
self.__interesting_header = {}
|
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return self.__name
|
return self.__name
|
||||||
@@ -70,39 +68,6 @@ class Domain:
|
|||||||
def get_path(self):
|
def get_path(self):
|
||||||
return self.__path
|
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):
|
def check_root(self):
|
||||||
"""
|
"""
|
||||||
This method requests the root page and searches for a specific string.
|
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()))
|
response = request.get_request('{}'.format(self.get_name()))
|
||||||
full_path = self.get_name()
|
full_path = self.get_name()
|
||||||
self.set_interesting_headers(response['headers'], response['cookies'])
|
|
||||||
if re.search('powered by TYPO3', response['html']):
|
if re.search('powered by TYPO3', response['html']):
|
||||||
self.set_typo3()
|
self.set_typo3()
|
||||||
path = re.search('="/?(\S*?)/?(?:typo3temp|typo3conf)/'.format(self.get_name()), response['html'])
|
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.
|
and searches for a specific string in the title or the response.
|
||||||
If the access is forbidden (403), extension search is still possible.
|
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?
|
# maybe /typo3_src/typo3/index.php too?
|
||||||
response = request.get_request('{}/typo3/index.php'.format(self.get_path()))
|
response = request.get_request('{}/typo3/index.php'.format(self.get_path()))
|
||||||
searchTitle = re.search('<title>(.*)</title>', response['html'])
|
searchTitle = re.search('<title>(.*)</title>', response['html'])
|
||||||
if searchTitle and 'Login' in searchTitle.group(0):
|
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):
|
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.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.YELLOW + 'But access is forbidden (IP Address Restriction)' + Fore.RESET)
|
||||||
else:
|
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):
|
def search_typo3_version(self):
|
||||||
"""
|
"""
|
||||||
@@ -204,32 +168,32 @@ class Domain:
|
|||||||
version = response
|
version = response
|
||||||
version_path = path
|
version_path = path
|
||||||
|
|
||||||
print('\n [+] Version Information')
|
print(' |\n[+] Version Information')
|
||||||
if not (version is None):
|
if not (version is None):
|
||||||
print(' \u251c {}'.format(Fore.GREEN + version + Fore.RESET))
|
print(' \u251c {}'.format(Fore.GREEN + version + Fore.RESET))
|
||||||
print(' \u251c see: {}{}'.format(self.get_path(), version_path))
|
print(' \u251c see: {}{}'.format(self.get_path(), version_path))
|
||||||
if len(version) == 3:
|
if len(version) == 3:
|
||||||
print(' \u251c Could not identify exact version.')
|
print(' \u251c Could not identify exact version.')
|
||||||
react = input(' \u251c Do you want to print all vulnerabilities for branch {}? (y/n): '.format(version))
|
react = input(' \u251c Do you want to print all vulnerabilities for branch {}? (y/n): '.format(version))
|
||||||
if react.startswith('y'):
|
if react.startswith('y'):
|
||||||
version = version + '.0'
|
version = version + '.0'
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
print(' \u2514 Known vulnerabilities\n \\')
|
print(' \u2514 Known vulnerabilities:\n')
|
||||||
# sqlite stuff
|
# sqlite stuff
|
||||||
conn = sqlite3.connect('lib/typo3scan.db')
|
conn = sqlite3.connect('lib/typo3scan.db')
|
||||||
c = conn.cursor()
|
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,))
|
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()
|
data = c.fetchall()
|
||||||
if not data:
|
if not data:
|
||||||
print(' \u251c None.')
|
print(' \u251c None.')
|
||||||
else:
|
else:
|
||||||
for vuln in data:
|
for vuln in data:
|
||||||
# maybe instead use this: https://oraerr.com/database/sql/how-to-compare-version-string-x-y-z-in-mysql-2/
|
# 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]):
|
if parse_version(version) <= parse_version(vuln[3]):
|
||||||
print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET))
|
print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET))
|
||||||
print(' \u251c Vulnerability Type:'.ljust(29), vuln[1])
|
print(' \u251c Vulnerability Type:'.ljust(29), vuln[1])
|
||||||
print(' \u251c Subcomponent:'.ljust(29), vuln[2])
|
print(' \u251c Subcomponent:'.ljust(29), vuln[2])
|
||||||
print(' \u2514 Affected Versions:'.ljust(29), '{} - {}\n'.format(vuln[3], vuln[4]))
|
print(' \u2514 Affected Versions:'.ljust(29), '{} - {}\n'.format(vuln[3], vuln[4]))
|
||||||
else:
|
else:
|
||||||
print(' \u2514', Fore.RED + 'No version information found.' + Fore.RESET)
|
print(' \u2514', Fore.RED + 'No version information found.' + Fore.RESET)
|
||||||
@@ -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'] + '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/ChangeLog', None)))
|
||||||
thread_pool.add_job((request.version_information, (values['url'] + 'Documentation/Index.rst', 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'] + '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'] + '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', None)))
|
||||||
thread_pool.add_job((request.version_information, (values['url'] + 'CHANGELOG.md', 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'] + '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.txt', None)))
|
||||||
thread_pool.add_job((request.version_information, (values['url'] + 'README.md', 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.rst', None)))
|
||||||
|
|
||||||
thread_pool.start(threads, version_search=True)
|
thread_pool.start(threads, version_search=True)
|
||||||
|
|
||||||
@@ -95,24 +95,27 @@ class Extensions:
|
|||||||
def output(self, extension_dict, database):
|
def output(self, extension_dict, database):
|
||||||
conn = sqlite3.connect(database)
|
conn = sqlite3.connect(database)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
print('\n |\n [+] Extension information\n \\')
|
print('\n\n [+] Extension information')
|
||||||
|
print(' -------------------------')
|
||||||
for extension,info in extension_dict.items():
|
for extension,info in extension_dict.items():
|
||||||
c.execute('SELECT title FROM extensions where extensionkey=?', (extension,))
|
c.execute('SELECT title,state FROM extensions where extensionkey=?', (extension,))
|
||||||
title = c.fetchone()[0]
|
data = c.fetchone()
|
||||||
print(' [+] Name: {}'.format(Fore.GREEN + extension + Fore.RESET))
|
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']:
|
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'],))
|
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()
|
data = c.fetchall()
|
||||||
print(' \u251c Version: {}'.format(Fore.GREEN + info['version'] + Fore.RESET))
|
print(' \u251c Version: {}'.format(Fore.GREEN + info['version'] + Fore.RESET))
|
||||||
if data:
|
if data:
|
||||||
print(' \u251c see: {}'.format(info['file']))
|
print(' \u251c see: {}'.format(info['file']))
|
||||||
print(' \u2514 Known vulnerabilities\n \\')
|
print(' \u2514 Known vulnerabilities:\n')
|
||||||
for vuln in data:
|
for vuln in data:
|
||||||
if parse_version(info['version']) <= parse_version(vuln[2]):
|
if parse_version(info['version']) <= parse_version(vuln[2]):
|
||||||
print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET))
|
print(' [!] {}'.format(Fore.RED + vuln[0] + Fore.RESET))
|
||||||
print(' \u251c Vulnerability Type:'.ljust(29), vuln[1])
|
print(' \u251c Vulnerability Type:'.ljust(29), vuln[1])
|
||||||
print(' \u2514 Affected Versions:'.ljust(29), '{} - {}'.format(vuln[2], vuln[3]))
|
print(' \u2514 Affected Versions:'.ljust(29), '{} - {}'.format(vuln[2], vuln[3]))
|
||||||
|
print()
|
||||||
else:
|
else:
|
||||||
print(' \u2514 see: {}'.format(info['file']))
|
print(' \u2514 see: {}'.format(info['file']))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
|||||||
def get_request(url):
|
def get_request(url):
|
||||||
"""
|
"""
|
||||||
All GET requests are done in this method.
|
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:
|
There are three error types which can occur:
|
||||||
Connection timeout
|
Connection timeout
|
||||||
Connection error
|
Connection error
|
||||||
@@ -55,12 +55,9 @@ def get_request(url):
|
|||||||
response['headers'] = r.headers
|
response['headers'] = r.headers
|
||||||
response['cookies'] = r.cookies
|
response['cookies'] = r.cookies
|
||||||
return response
|
return response
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout as e:
|
||||||
print(e)
|
print(e)
|
||||||
print(Fore.RED + '[x] Connection timed out' + Fore.RESET)
|
print(Fore.RED + '[x] Connection error\n Please make sure you provided the right URL\n' + 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)
|
|
||||||
exit(-1)
|
exit(-1)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
print(Fore.RED + str(e) + Fore.RESET)
|
print(Fore.RED + str(e) + Fore.RESET)
|
||||||
@@ -68,7 +65,7 @@ def get_request(url):
|
|||||||
def head_request(url):
|
def head_request(url):
|
||||||
"""
|
"""
|
||||||
All HEAD requests are done in this method.
|
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:
|
There are three error types which can occur:
|
||||||
Connection timeout
|
Connection timeout
|
||||||
Connection error
|
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)
|
r = requests.head(url, timeout=config['timeout'], headers=custom_headers, allow_redirects=False, verify=False)
|
||||||
status_code = str(r.status_code)
|
status_code = str(r.status_code)
|
||||||
if status_code == '405':
|
if status_code == '405':
|
||||||
print('[x] WARNING: \'HEAD\' method not allowed!')
|
print(' [x] WARNING: \'HEAD\' method not allowed!')
|
||||||
exit(-1)
|
exit(-1)
|
||||||
return status_code
|
return status_code
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
print(Fore.RED + '[x] Connection timed out' + Fore.RESET)
|
print(Fore.RED + ' [x] Connection timed out on "{}"'.format(url) + 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)
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
print(Fore.RED + str(e) + Fore.RESET)
|
print(Fore.RED + str(e) + Fore.RESET)
|
||||||
|
|
||||||
@@ -114,29 +109,34 @@ def version_information(url, regex):
|
|||||||
auth = config['auth']
|
auth = config['auth']
|
||||||
cookie = config['cookie']
|
cookie = config['cookie']
|
||||||
custom_headers = {'User-Agent' : config['User-Agent']}
|
custom_headers = {'User-Agent' : config['User-Agent']}
|
||||||
if cookie != '':
|
try:
|
||||||
name = cookie.split('=')[0]
|
if cookie != '':
|
||||||
value = cookie.split('=')[1]
|
name = cookie.split('=')[0]
|
||||||
custom_headers[name] = value
|
value = cookie.split('=')[1]
|
||||||
if auth != '':
|
custom_headers[name] = value
|
||||||
r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, auth=(auth.split(':')[0], auth.split(':')[1]), verify=False)
|
if auth != '':
|
||||||
else:
|
r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, auth=(auth.split(':')[0], auth.split(':')[1]), verify=False)
|
||||||
r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, verify=False)
|
else:
|
||||||
if r.status_code == 200:
|
r = requests.get(url, stream=True, timeout=config['timeout'], headers=custom_headers, verify=False)
|
||||||
if 'manual.sxw' in url:
|
if r.status_code == 200:
|
||||||
return 'check manually'
|
if ('manual.sxw' in url) and not ('Page Not Found' in r.text):
|
||||||
try:
|
return 'check manually'
|
||||||
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:
|
try:
|
||||||
search = re.search('([0-9]+-[0-9]+-[0-9]+)', str(content))
|
for content in r.iter_content(chunk_size=400, decode_unicode=False):
|
||||||
version = search.group(1)
|
search = re.search(regex, str(content))
|
||||||
r.close()
|
version = search.group(1)
|
||||||
return version
|
r.close()
|
||||||
|
return version
|
||||||
except:
|
except:
|
||||||
r.close()
|
try:
|
||||||
return None
|
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)
|
||||||
BIN
lib/typo3scan.db
BIN
lib/typo3scan.db
Binary file not shown.
222
typo3scan.py
222
typo3scan.py
@@ -18,7 +18,7 @@
|
|||||||
# along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/)
|
# along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/)
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
|
|
||||||
__version__ = '0.5.1'
|
__version__ = '0.5.2'
|
||||||
__program__ = 'Typo3Scan'
|
__program__ = 'Typo3Scan'
|
||||||
__description__ = 'Automatic Typo3 enumeration tool'
|
__description__ = 'Automatic Typo3 enumeration tool'
|
||||||
__author__ = 'https://github.com/whoot'
|
__author__ = 'https://github.com/whoot'
|
||||||
@@ -29,8 +29,6 @@ import sqlite3
|
|||||||
import os.path
|
import os.path
|
||||||
import argparse
|
import argparse
|
||||||
from lib.domain import Domain
|
from lib.domain import Domain
|
||||||
from lib.update import Update
|
|
||||||
from lib.initdb import DB_Init
|
|
||||||
from lib.extensions import Extensions
|
from lib.extensions import Extensions
|
||||||
from colorama import Fore, init, deinit, Style
|
from colorama import Fore, init, deinit, Style
|
||||||
init()
|
init()
|
||||||
@@ -41,6 +39,88 @@ class Typo3:
|
|||||||
self.__path = os.path.dirname(os.path.abspath(__file__))
|
self.__path = os.path.dirname(os.path.abspath(__file__))
|
||||||
self.__extensions = []
|
self.__extensions = []
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
database = os.path.join(self.__path, 'lib', 'typo3scan.db')
|
||||||
|
conn = sqlite3.connect(database)
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute('SELECT * FROM UserAgents ORDER BY RANDOM() LIMIT 1;')
|
||||||
|
user_agent = c.fetchone()[0]
|
||||||
|
c.close()
|
||||||
|
config = {'threads': args.threads, 'timeout': args.timeout, 'cookie': args.cookie, 'auth': args.auth, 'User-Agent': user_agent}
|
||||||
|
json.dump(config, open(os.path.join(self.__path, 'lib', 'config.json'), 'w'))
|
||||||
|
try:
|
||||||
|
if args.domain:
|
||||||
|
for dom in args.domain:
|
||||||
|
self.__domain_list.append(dom)
|
||||||
|
elif args.file:
|
||||||
|
if not os.path.isfile(args.file):
|
||||||
|
print(Fore.RED + '\n[x] File not found: {}\n | Aborting...'.format(args.file) + Fore.RESET)
|
||||||
|
sys.exit(-1)
|
||||||
|
else:
|
||||||
|
with open(args.file, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
self.__domain_list.append(line.strip())
|
||||||
|
|
||||||
|
for domain in self.__domain_list:
|
||||||
|
print(Fore.CYAN + Style.BRIGHT + '\n\n[ Checking {} ]\n'.format(domain) + '-'* 73 + Fore.RESET + Style.RESET_ALL)
|
||||||
|
check = Domain(domain)
|
||||||
|
check.check_root()
|
||||||
|
default_files = check.check_default_files()
|
||||||
|
if not default_files:
|
||||||
|
check_404 = check.check_404()
|
||||||
|
if not check.is_typo3():
|
||||||
|
print(Fore.RED + '\n[x] It seems that Typo3 is not used on this domain\n' + Fore.RESET)
|
||||||
|
else:
|
||||||
|
# check for typo3 information
|
||||||
|
print('\n[+] Typo3 Installation')
|
||||||
|
print('----------------------')
|
||||||
|
check.search_login()
|
||||||
|
check.search_typo3_version()
|
||||||
|
|
||||||
|
# Search extensions
|
||||||
|
print('\n [+] Extension Search')
|
||||||
|
if not self.__extensions:
|
||||||
|
conn = sqlite3.connect(database)
|
||||||
|
c = conn.cursor()
|
||||||
|
if args.vuln:
|
||||||
|
for row in c.execute('SELECT extensionkey FROM extension_vulns'):
|
||||||
|
self.__extensions.append(row[0])
|
||||||
|
self.__extensions = set(self.__extensions)
|
||||||
|
else:
|
||||||
|
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)))
|
||||||
|
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)))
|
||||||
|
ext_list = extensions.search_ext_version(ext_list, args.threads)
|
||||||
|
extensions.output(ext_list, database)
|
||||||
|
else:
|
||||||
|
print ('\n [!] No extensions found.\n')
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('\nReceived keyboard interrupt.\nQuitting...')
|
||||||
|
exit(-1)
|
||||||
|
finally:
|
||||||
|
deinit()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('\n' + 73*'=' + Style.BRIGHT)
|
||||||
|
print(Fore.CYAN)
|
||||||
|
print('________ ________ _________ '.center(73))
|
||||||
|
print('\_ _/__ __ ______ _____\_____ \ / _____/ ____ _____ ___ '.center(73))
|
||||||
|
print(' | | | | |\____ \| _ | _(__ < \_____ \_/ ___\\\\__ \ / \ '.center(73))
|
||||||
|
print(' | | |___ || |_) | (_) |/ \/ \ \___ / __ \| | \ '.center(73))
|
||||||
|
print(' |__| / ____|| __/|_____|________/_________/\_____|_____/|__|__/ '.center(73))
|
||||||
|
print(' \/ |__| '.center(73))
|
||||||
|
print(Fore.RESET + Style.RESET_ALL)
|
||||||
|
print(__description__.center(73))
|
||||||
|
print(('Version ' + __version__).center(73))
|
||||||
|
print((__author__).center(73))
|
||||||
|
print(73*'=')
|
||||||
|
|
||||||
def print_help():
|
def print_help():
|
||||||
print(
|
print(
|
||||||
"""\nUsage: python typo3scan.py [options]
|
"""\nUsage: python typo3scan.py [options]
|
||||||
@@ -76,118 +156,32 @@ Options:
|
|||||||
-r | --reset Reset the database.
|
-r | --reset Reset the database.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def run(self):
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
parser = argparse.ArgumentParser(add_help=False)
|
group = parser.add_mutually_exclusive_group()
|
||||||
group = parser.add_mutually_exclusive_group()
|
help = parser.add_mutually_exclusive_group()
|
||||||
help = parser.add_mutually_exclusive_group()
|
group.add_argument('-f', '--file', dest='file')
|
||||||
group.add_argument('-f', '--file', dest='file')
|
group.add_argument('-d', '--domain', dest='domain', type=str, nargs='+')
|
||||||
group.add_argument('-d', '--domain', dest='domain', type=str, nargs='+')
|
group.add_argument('-u', '--update', dest='update', action='store_true')
|
||||||
group.add_argument('-u', '--update', dest='update', action='store_true')
|
group.add_argument('-r', '--reset', dest='reset', action='store_true')
|
||||||
group.add_argument('-r', '--reset', dest='reset', action='store_true')
|
parser.add_argument('--vuln', dest='vuln', action='store_true')
|
||||||
parser.add_argument('--vuln', dest='vuln', action='store_true')
|
parser.add_argument('--threads', dest='threads', type=int, default=5)
|
||||||
parser.add_argument('--threads', dest='threads', type=int, default=5)
|
parser.add_argument('--auth', dest='auth', type=str, default='')
|
||||||
parser.add_argument('--auth', dest='auth', type=str, default='')
|
parser.add_argument('--cookie', dest='cookie', type=str, default='')
|
||||||
parser.add_argument('--cookie', dest='cookie', type=str, default='')
|
parser.add_argument('--timeout', dest='timeout', type=int, default=10)
|
||||||
parser.add_argument('--timeout', dest='timeout', type=int, default=10)
|
help.add_argument( '-h', '--help', action='store_true')
|
||||||
help.add_argument( '-h', '--help', action='store_true')
|
args = parser.parse_args()
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.help or not len(sys.argv) > 1:
|
if args.help or not len(sys.argv) > 1:
|
||||||
Typo3.print_help()
|
print_help()
|
||||||
|
|
||||||
elif args.reset:
|
elif args.reset:
|
||||||
DB_Init()
|
from lib.initdb import DB_Init
|
||||||
|
DB_Init()
|
||||||
|
|
||||||
elif args.update:
|
elif args.update:
|
||||||
Update()
|
from lib.update import Update
|
||||||
|
Update()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
database = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib', 'typo3scan.db')
|
main = Typo3()
|
||||||
conn = sqlite3.connect(database)
|
main.run()
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('SELECT * FROM UserAgents ORDER BY RANDOM() LIMIT 1;')
|
|
||||||
user_agent = c.fetchone()[0]
|
|
||||||
c.close()
|
|
||||||
config = {'threads': args.threads, 'timeout': args.timeout, 'cookie': args.cookie, 'auth': args.auth, 'User-Agent': user_agent}
|
|
||||||
json.dump(config, open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib', 'config.json'), 'w'))
|
|
||||||
try:
|
|
||||||
if args.domain:
|
|
||||||
for dom in args.domain:
|
|
||||||
self.__domain_list.append(dom)
|
|
||||||
elif args.file:
|
|
||||||
if not os.path.isfile(args.file):
|
|
||||||
print(Fore.RED + '\n[x] File not found: {}\n | Aborting...'.format(args.file) + Fore.RESET)
|
|
||||||
sys.exit(-1)
|
|
||||||
else:
|
|
||||||
with open(args.file, 'r') as f:
|
|
||||||
for line in f:
|
|
||||||
self.__domain_list.append(line.strip())
|
|
||||||
|
|
||||||
for domain in self.__domain_list:
|
|
||||||
print(Fore.CYAN + Style.BRIGHT + '\n\n[ Checking {} ]\n'.format(domain) + '-'* 73 + Fore.RESET + Style.RESET_ALL)
|
|
||||||
check = Domain(domain)
|
|
||||||
check.check_root()
|
|
||||||
default_files = check.check_default_files()
|
|
||||||
if not default_files:
|
|
||||||
check_404 = check.check_404()
|
|
||||||
if not check.is_typo3():
|
|
||||||
print(Fore.RED + '\n[x] It seems that Typo3 is not used on this domain' + Fore.RESET)
|
|
||||||
else:
|
|
||||||
# print interesting headers
|
|
||||||
print('\n[+] Interesting Headers')
|
|
||||||
for key, value in check.get_interesting_headers().items():
|
|
||||||
string = ' \u251c {}:'.format(key)
|
|
||||||
print(string.ljust(30) + value)
|
|
||||||
|
|
||||||
# check for typo3 information
|
|
||||||
print('\n[+] Typo3 Information')
|
|
||||||
check.search_login()
|
|
||||||
check.search_typo3_version()
|
|
||||||
|
|
||||||
# Search extensions
|
|
||||||
print('\n [+] Extension Search')
|
|
||||||
if not self.__extensions:
|
|
||||||
conn = sqlite3.connect(database)
|
|
||||||
c = conn.cursor()
|
|
||||||
if args.vuln:
|
|
||||||
for row in c.execute('SELECT extensionkey FROM extension_vulns'):
|
|
||||||
self.__extensions.append(row[0])
|
|
||||||
self.__extensions = set(self.__extensions)
|
|
||||||
else:
|
|
||||||
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)))
|
|
||||||
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)))
|
|
||||||
ext_list = extensions.search_ext_version(ext_list, args.threads)
|
|
||||||
extensions.output(ext_list, database)
|
|
||||||
else:
|
|
||||||
print ('\n [!] No extensions found.')
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print('\nReceived keyboard interrupt.\nQuitting...')
|
|
||||||
exit(-1)
|
|
||||||
finally:
|
|
||||||
deinit()
|
|
||||||
print()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print('\n' + 73*'=' + Style.BRIGHT)
|
|
||||||
print(Fore.CYAN)
|
|
||||||
print('________ ________ _________ '.center(73))
|
|
||||||
print('\_ _/__ __ ______ _____\_____ \ / _____/ ____ _____ ___ '.center(73))
|
|
||||||
print(' | | | | |\____ \| _ | _(__ < \_____ \_/ ___\\\\__ \ / \ '.center(73))
|
|
||||||
print(' | | |___ || |_) | (_) |/ \/ \ \___ / __ \| | \ '.center(73))
|
|
||||||
print(' |__| / ____|| __/|_____|________/_________/\_____|_____/|__|__/ '.center(73))
|
|
||||||
print(' \/ |__| '.center(73))
|
|
||||||
print(Fore.RESET + Style.RESET_ALL)
|
|
||||||
print(__description__.center(73))
|
|
||||||
print(('Version ' + __version__).center(73))
|
|
||||||
print((__author__).center(73))
|
|
||||||
print(73*'=')
|
|
||||||
|
|
||||||
main = Typo3()
|
|
||||||
main.run()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user