Update to 0.4.3

This commit is contained in:
Jan Rude
2015-08-27 21:16:49 +02:00
parent 58d616106c
commit ca74f659ef
11 changed files with 261 additions and 58 deletions

View File

@@ -4,7 +4,7 @@ Typo3-Enumerator
Typo3-Enumerator is an open source penetration testing tool that automates the process of detecting the [Typo3](https://typo3.org) CMS and it's installed [extensions](https://typo3.org/extensions/repository/?id=23&L=0&q=&tx_solr[filter][outdated]=outdated%3AshowOutdated) (also the outdated ones).
If the --top parameter is set to a value, only the specified most downloaded extensions are tested.
It is possible to do all requests through the [TOR Hidden Service](https://www.torproject.org/) network or [Privoxy](http://sourceforge.net/projects/ijbswa/files/). Also you can combine TOR with Privoxy in order to prevent DNS leakage.
It is possible to do all requests through the [TOR Hidden Service](https://www.torproject.org/) network.
Installation
----
@@ -81,4 +81,4 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
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/)

View File

@@ -1,3 +1,11 @@
## Version 0.4.3
* Added --threads
* Added --user-agent
* Added --timeout
* Added help message (-h, --help)
* No support for privoxy anymore
## Version 0.4.2
* Added new algorithms for Typo3 installation and used path
@@ -8,7 +16,7 @@
* Fixed link to socksipy for python 3
* Fixed bug in versionsearch
* Fixed TOR issues
* Fixed some little bugs
* Fixed some bugs
## Version 0.4
@@ -23,7 +31,6 @@
## Version 0.3.3
* Extensions are now saved into different files, separated by state (experimental | alpha | beta | stable | outdated | all). This makes it possible to check more specific ones.
* Colorama is only used if installed. It doesn't need to be installed anymore.
* Installed extensions are shown immediately
## Version 0.3.2

View File

@@ -1,8 +1,3 @@
# TODO
* Help message
* Documentation
* Stop extension enumeration with ctrl-c
* Privoxy check
* --threads
* --user-agent (default is Mozilla/5.0)
* Stop extension enumeration with ctrl-c

1
lib/config.json Normal file
View File

@@ -0,0 +1 @@
{"threads": 5, "timeout": 10, "agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0"}

View File

@@ -19,16 +19,24 @@
#-------------------------------------------------------------------------------
import os.path
import json
from lib.request import Request
from lib.output import Output
from lib.thread_pool import ThreadPool
class Extensions:
"""
Extension class
"""
def __init__(self, ext_state, top):
self.__ext_state = ext_state
self.__top = top
def load_extensions(self):
"""
This method loads the defined extensions from the extension file.
IF the extension file is not found, an error is raised.
"""
extensions = []
for state in self.__ext_state:
ext_file = state + '_extensions'
@@ -48,20 +56,29 @@ class Extensions:
return extensions
def search_extension(self, domain, extensions):
"""
This method searches for installed extensions.
/typo3conf/ext/: Local installation path. This is where extensions get usually installed.
/typo3/ext/: Global installation path (not used atm)
/typo3/sysext/: Extensions shipped with core (not used atm)
"""
config = json.load(open('lib/config.json'))
thread_pool = ThreadPool()
for ext in extensions:
# search local installation path
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)
thread_pool.start(config['threads'])
for installed_extension in thread_pool.get_result():
domain.set_installed_extensions(installed_extension[1][1])
def search_ext_version(self, domain, extension_dict):
"""
This method adds a job for every installed extension.
The goal is to find a ChangeLog or Readme in order to determine the version.
"""
config = json.load(open('lib/config.json'))
thread_pool = ThreadPool()
for extension_path in extension_dict:
thread_pool.add_job((Request.head_request, (domain.get_name(), extension_path + '/ChangeLog')))
@@ -70,7 +87,7 @@ class Extensions:
thread_pool.add_job((Request.head_request, (domain.get_name(), extension_path + '/README.md')))
thread_pool.add_job((Request.head_request, (domain.get_name(), extension_path + '/README.rst')))
thread_pool.start(6, True)
thread_pool.start(config['threads'], True)
for changelog_path in thread_pool.get_result():
ext, path = self.parse_extension(changelog_path)

View File

@@ -22,18 +22,19 @@ from colorama import Fore
class Output:
"""
This class handles the output
This class handles the output
"""
def __init__(self):
pass
def typo3_installation(domain):
"""
If TYPO3 is installed and the backend login was found, a link to a backend is printed.
Additionally, if the version search was successful, the version and a link to cvedetails is given.
"""
print('')
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)
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)
@@ -42,10 +43,17 @@ class Output:
print('')
def interesting_headers(name, value):
"""
This method prints interesting headers
"""
string = '[!] ' + name + ':'
print(string.ljust(30) + value)
def extension_output(path, extens):
"""
This method prints every found extension.
If a Readme or ChangeLog is found, it will print a link to the file.
"""
if not extens:
print(Fore.RED + ' | No extension found' + Fore.RESET)
else:

View File

@@ -18,23 +18,30 @@
# along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/)
#-------------------------------------------------------------------------------
import requests
import re
import json
import requests
from colorama import Fore
requests.packages.urllib3.disable_warnings()
from lib.output import Output
header = {'User-Agent' : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"}
timeout = 10
class Request:
"""
This class is used to make all server requests
This class is used to make all server requests
"""
@staticmethod
def get_request(domain_name, path):
"""
All GET requests are done in this method.
This method is not used, when searching for extensions and their Readmes/ChangeLogs
There are three error types which can occur:
Connection timeout
Connection error
anything else
"""
try:
r = requests.get(domain_name + path, timeout=timeout, headers=header, verify=False)
config = json.load(open('lib/config.json'))
r = requests.get(domain_name + path, timeout=config['timeout'], headers={'User-Agent' : config['agent']}, verify=False)
httpResponse = str((r.text).encode('utf-8'))
headers = r.headers
cookies = r.cookies
@@ -50,8 +57,17 @@ class Request:
@staticmethod
def head_request(domain_name, path):
"""
All HEAD requests are done in this method.
HEAD requests are used when searching for extensions and their Readmes/ChangeLogs
There are three error types which can occur:
Connection timeout
Connection error
anything else
"""
try:
r = requests.head(domain_name + path, timeout=timeout, headers=header, allow_redirects=False, verify=False)
config = json.load(open('lib/config.json'))
r = requests.head(domain_name + path, timeout=config['timeout'], headers={'User-Agent' : config['agent']}, allow_redirects=False, verify=False)
status_code = str(r.status_code)
if status_code == '405':
print("WARNING, (HEAD) method not allowed!!")
@@ -66,12 +82,27 @@ class Request:
@staticmethod
def interesting_headers(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.
be_typo_user: Backend cookie for TYPO3
fe_typo_user: Frontend cookie for TYPO3
"""
found_headers = {}
for header in headers:
if header == 'server':
found_headers['Server'] = headers.get('server')
elif header == 'x-powered-by':
found_headers['X-Powered-By'] = headers.get('x-powered-by')
elif header == 'x-runtime':
found_headers['X-Runtime'] = headers.get('x-runtime')
elif header == 'x-version':
found_headers['X-Version'] = headers.get('x-version')
elif header == 'x-aspnet-version':
found_headers['X-AspNet-Version'] = headers.get('x-aspnet-version')
elif header == 'via':
found_headers['Via'] = headers.get('via')
try:
@@ -88,7 +119,13 @@ class Request:
@staticmethod
def version_information(domain_name, path, regex):
r = requests.get(domain_name + path, stream=True, timeout=timeout, headers=header, verify=False)
"""
This method is used for version search only.
It performs a GET request, if the response is 200 - Found, it reads the first 400 bytes the response only,
because usually the TYPO3 version is in the first few lines of the response.
"""
config = json.load(open('lib/config.json'))
r = requests.get(domain_name + path, stream=True, timeout=config['timeout'], headers={'User-Agent' : config['agent']}, verify=False)
if r.status_code == 200:
try:
for content in r.iter_content(chunk_size=400, decode_unicode=False):

97
lib/tor.py Normal file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Typo3 Enumerator - Automatic Typo3 Enumeration Tool
# Copyright (c) 2015 Jan Rude
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/)
#-------------------------------------------------------------------------------
import socket
import os, sys
import re
from colorama import Fore
from lib.request import Request
try:
import socks
except:
print(Fore.RED + 'The module \'SocksiPy\' is not installed.')
if sys.platform.startswith('linux'):
print('Please install it with: sudo apt-get install python-socksipy' + Fore.RESET)
else:
print('You can download it from https://code.google.com/p/socksipy-branch/' + Fore.RESET)
sys.exit(-2)
class Tor:
"""
This class initiates the usage of TOR for all requests
port: TOR port
"""
def __init__(self, port=9150):
self.__port = port
def start_daemon(self):
"""
If the OS is linux, start TOR deamon.
If not, user needs to start it manually
"""
if sys.platform.startswith('linux'):
os.system('service tor start')
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
print('Please make sure TOR is running...')
else:
print('You are using', sys.platform, ', which is not supported (yet).')
sys.exit(-2)
# Using TOR for all connections
def connect(self):
"""
This method checks the connection with TOR.
If TOR is not used, the program will exit
"""
print('\nChecking connection...')
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', self.__port, True)
socks.socket.setdefaulttimeout(20)
socket.socket = socks.socksocket
try:
request = Request.get_request('https://check.torproject.org', '/')
response = request[0]
except:
print('Failed to connect through TOR!')
print('Please make sure your configuration is right!\n')
sys.exit(-2)
try:
regex = re.compile('Congratulations. This browser is configured to use Tor.')
searchVersion = regex.search(response)
version = searchVersion.groups()
print('Connection to TOR established')
regex = re.compile("(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})")
searchIP = regex.search(response)
IP = searchIP.groups()[0]
print('Your IP is: ', IP)
except Exception as e:
print(e)
print('It seems like TOR is not used.\nAborting...\n')
sys.exit(-2)
def stop(self):
"""
This method stops the TOR deamon if running under linux
"""
print('\n')
if sys.platform.startswith('linux'):
os.system('service tor stop')
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
print('You can stop TOR now...')

View File

@@ -36,12 +36,18 @@ class Update:
# Progressbar
def dlProgress(self, count, blockSize, totalSize):
"""
Progressbar for extension download
"""
percent = int(count*blockSize*100/totalSize)
sys.stdout.write('\r[+] Downloading extentions: ' + '%d%%' % percent)
sys.stdout.flush()
# Download extensions from typo3 repository
def download_ext(self):
"""
Download extensions from server and unpack the ZIP
"""
try:
urllib.request.urlretrieve('http://ter.sitedesign.dk/ter/extensions.xml.gz', 'extensions.gz', reporthook=self.dlProgress)
with gzip.open('extensions.gz', 'rb') as f:
@@ -56,6 +62,10 @@ class Update:
# Parse extensions.xml and save extensions in files
def generate_list(self):
"""
Parse the extension file and
sort them according to state and download count
"""
experimental = {} # 'experimental' and 'test'
alpha = {}
beta = {}

View File

@@ -25,8 +25,8 @@ from lib.request import Request
class VersionInformation:
"""
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.
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.
"""
def search_typo3_version(self, domain):
changelog = {'/typo3_src/ChangeLog':'[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)',

View File

@@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/)
#-------------------------------------------------------------------------------
__version__ = '0.4.2'
__version__ = '0.4.3'
__program__ = 'Typo-Enumerator'
__description__ = 'Automatic Typo3 enumeration tool'
__author__ = 'https://github.com/whoot'
@@ -27,6 +27,7 @@ import sys
import os.path
import datetime
import argparse
import json
from colorama import Fore, init, deinit, Style
from lib.check_installation import Typo3_Installation
from lib.version_information import VersionInformation
@@ -41,30 +42,75 @@ class Typo3:
self.__domain_list = []
self.__extensions = None
def print_help():
print(
"""\nUsage: python3 typoenum.py [options]
Options:
-h, --help Show this help message and exit
Target:
At least one of these options has to be provided to define the target(s)
-d [DOMAIN, ...], --domain [DOMAIN, ...] Target domain(s)
-f FILE, --file FILE Parse targets from file (one domain per line)
Optional:
You dont need to specify this arguments, but you may want to
--top TOP Test if top [TOP] downloaded extensions are installed
Default: every in list
--state STATE Extension state [all, experimental, alpha, beta, stable, outdated]
Default: all
--timeout TIMEOUT The timeout for all requests
Default: 10 seconds
--agent USER_AGENT The user-agent used for all requests
Default: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0
--threads THREADS The number of threads used for enumerating the extensions
Default: 5
Anonymity:
This options can be used to proxy all requests through TOR/Privoxy
--tor Using only TOR for connections
--port PORT Port for TOR
Default: 9050
General:
-u, --update Update TYPO3 extensions
""")
def run(self):
parser = argparse.ArgumentParser(usage='typoenum.py [options]', add_help=False)
parser = argparse.ArgumentParser(add_help=False)
group = parser.add_mutually_exclusive_group()
anonGroup = parser.add_mutually_exclusive_group()
help = parser.add_mutually_exclusive_group()
group.add_argument('-f', '--file', dest='file')
group.add_argument('-d', '--domain', dest='domain', type=str, nargs='+')
group.add_argument('-u', '--update', dest='update', action='store_true')
parser.add_argument('--top', type=int, dest='top', metavar='VALUE')
parser.add_argument('--state', dest='ext_state', choices = ['all', 'experimental', 'alpha', 'beta', 'stable', 'outdated'], nargs='+', default = ['all'])
anonGroup.add_argument('--tor', help='using only TOR for connections', action='store_true')
anonGroup.add_argument('--privoxy', help='using only Privoxy for connections', action='store_true')
anonGroup.add_argument('--tp', help='using TOR and Privoxy for connections', action='store_true')
parser.add_argument('-p', '--port', dest='port', help='Port for TOR/Privoxy (default: 9050/8118)', type=int)
parser.add_argument( '-h', '--help', action='help')
anonGroup.add_argument('--tor', action='store_true')
parser.add_argument('-p', '--port', dest='port', type=int)
parser.add_argument('--threads', dest='threads', type=int, default = 5)
parser.add_argument('--agent', dest='agent', type=str, default = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0')
parser.add_argument('--timeout', dest='timeout', type=int, default = 10)
help.add_argument( '-h', '--help', action='store_true')
args = parser.parse_args()
force = 'n'
if args.help:
Typo3.print_help()
return True
try:
if args.update:
Update()
return True
if args.tor:
from lib.tor_only import Tor
from lib.tor import Tor
if args.port:
tor = Tor(args.port)
else:
@@ -72,24 +118,6 @@ class Typo3:
tor.start_daemon()
tor.connect()
elif args.privoxy:
from lib.privoxy_only import Privoxy
if args.port:
privoxy = Privoxy(args.port)
else:
privoxy = Privoxy()
privoxy.start_daemon()
privoxy.connect()
elif args.tp:
from lib.tor_with_privoxy import Tor_with_Privoxy
if args.port:
tp = Tor_with_Privoxy(args.port)
else:
tp = Tor_with_Privoxy()
tp.start_daemon()
tp.connect()
if args.domain:
for dom in args.domain:
self.__domain_list.append(Domain(dom, args.ext_state, args.top))
@@ -102,6 +130,9 @@ class Typo3:
for line in f:
self.__domain_list.append(Domain(line.strip('\n'), args.ext_state, args.top))
config = {'threads':args.threads, 'agent':args.agent, 'timeout':args.timeout}
json.dump(config, open('lib/config.json', 'w'))
for domain in self.__domain_list:
print('\n\n' + Fore.CYAN + Style.BRIGHT + '[ Checking ' + domain.get_name() + ' ]' + '\n' + '-'* 73 + Fore.RESET + Style.RESET_ALL)
Typo3_Installation.run(domain)