Updating to v0.3

This commit is contained in:
root
2014-08-10 16:31:21 +02:00
parent 481972a6cb
commit 156fc12a4c
18 changed files with 7241 additions and 396 deletions

View File

@@ -1,44 +1,55 @@
Typo-Enumerator
===============
Find out the Typo3 Version, Login-URL and Extensions
Typo-Enumerator is an open source penetration testing tool that automates the process of detecting the [Typo3](https://typo3.org) CMS and its 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.
This tool allows you to check, if a domain uses Typo3.<br>
If so, it will try to find out the Typo3 version and the installed extensions.<br>
If the --top parameter is set to a value, only the [value] top downloaded extensions are tested.<br><br>
Usage:<br>
./typoenum.py -d DOMAIN [DOMAIN ...] [--user_agent USER-AGENT] [--top VALUE] [-v] [--tor] <br>
or <br>
./typoenum.py -f FILE [--user_agent USER-AGENT] [--top VALUE] [-v] [--tor]
<br>
<br>
It is possible to use POST instead of GET Requests and do all requests through the [TOR Hidden Service](https://www.torproject.org/) network, with the help of [Privoxy](www.privoxy.org), in order to prevent DNS leakage.
## ChangeLog:
v0.1 Prototype
v0.2 Added version search for Typo3
v0.3 Added version guessing
v0.4 Optimized requests
v0.5 Added support for Typo v6.X
v0.6 Added extension search
v0.7 Added version search for extensions
v0.8 Added support for Privoxy and TOR
```
-> It is now possible to use Typo-Enumerator with Privoxy and TOR (--tor)
Privoxy is used to prevent dns leakage ;)
Please make sure the Privoxy config (/etc/privoxy/config) is set to something like:
listen-address 127.0.0.1:8118
forward-socks5 / 127.0.0.1:9050 .
These are the standart ports for Privoxy and TOR
If TOR is used, threads will be set to 2 in order to minimize errors
Installation
----
-> Version search for extensions is now more reliable
```
v0.8.1
```
-> Bugfixing
You can download the latest tarball by clicking [here](https://github.com/whoot/Typo-Enumerator/tarball/master) or latest zipball by clicking [here](https://github.com/whoot/Typo-Enumerator/zipball/master).
-> It is now possible to specifiy the threads
Default is 10.
I strongly recommend to use only 2 or even 1 thread when using TOR!
This is because TOR is (extremely) slow and will produce connection errors if too many threads are used.
```
Preferably, you can download Type-Enumerator by cloning the [Git](https://github.com/whoot/Typo-Enumerator) repository:
git clone https://github.com/whoot/Typo-Enumerator.git
Typo-Enumerator works out with [Python](http://www.python.org/download/) version **2.6.x** and **2.7.x** on any platform.
If you want to use Typo-Enumerator with TOR, you need the [SocksiPy](http://socksipy.sourceforge.net/) module.
On Debian/Ubuntu you can install it with apt-get:
sudo apt-get install python-socksipy
Usage
----
To get a list of all options use:
python typoenum.py -h
You can use Typo-Enumerator with domains:
python typoenum.py -d DOMAIN [DOMAIN ...] [--user_agent USER-AGENT] [--top VALUE] [-v] [--tor]
Or with a file with a list of domains:
python typoenum.py -f FILE [--user_agent USER-AGENT] [--top VALUE] [-v] [--tor]
Example:
Test if Typo3 and top 20 downloaded extensions are installed on localhost:
python typoenum.py -d 127.0.0.1 --top 20
Bug Reporting
----
Bug reports are welcome! Please report all bugs on the [issue tracker](https://github.com/whoot/Typo-Enumerator/issues).
Links
----
* Download: [.tar.gz](https://github.com/whoot/Typo-Enumerator/tarball/master) or [.zip](https://github.com/whoot/Typo-Enumerator/archive/master)
* Changelog: https://github.com/whoot/Typo-Enumerator/doc/CHANGELOG.md
* TODO: https://github.com/whoot/Typo-Enumerator/doc/TODO.md
* Issue tracker: https://github.com/whoot/Typo-Enumerator/issues

59
doc/CHANGELOG.md Normal file
View File

@@ -0,0 +1,59 @@
# Version 0.3
* Using modules instead of one class
* Accepting now strg+c when in multithreaded mode
* Update function will now generate a list with extensions instead of an xml. This list is sorted by default (download count). Loading this file is much faster than parsing the xml everytime.
* It is now possible to use only TOR, Privoxy, or TOR with Privoxy (in order to prevent DNS leakage).
* Max. threads are set to 10 to prevent connection issues and DoS, default threads are now 7.
* Connection timeout can be set with '--timeout VALUE'. Default is 20.
* Verbose mode lists also not installed extensions (but trust me, you don´t want to scroll through over 6400 entries).
* Typo3 version search is more accurate
* If the backend login page could not be found, but Typo3 is used, the user is asked, if he want to proceed. This will mostly lead to "no extensions are installed".
# Version 0.2.1
* Fixed some bugs
* It is now possible to specifiy threads
Default is 10.
I strongly recommend to use only 2 or even 1 thread when using TOR!
This is because TOR is (extremely) slow and will produce connection errors if too many threads are used.
# Version 0.2
* Added support for Privoxy and TOR
* It is now possible to use Typo-Enumerator with Privoxy and TOR (--tor)
Privoxy is used to prevent dns leakage ;)
Please make sure the Privoxy config (/etc/privoxy/config) is set to something like:
listen-address 127.0.0.1:8118
forward-socks5 / 127.0.0.1:9050 .
These are the standart ports for Privoxy and TOR
If TOR is used, threads will be set to 2 in order to minimize errors
* Version search for extensions is now more reliable
# Version 0.1.6
* Added version search for extensions
# Version 0.1.5
* Added extension search
# Version 0.1.4
* Added support for Typo v6.X
# Version 0.1.3
* Optimized requests
# Version 0.1.2
* Added version guessing
# Version 0.1.1
* Added version search for Typo3
# Version 0.1
* Prototype

23
doc/LICENSE Normal file
View File

@@ -0,0 +1,23 @@
Copyright (c) 2014, Jan Rude
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

7
doc/TODO.md Normal file
View File

@@ -0,0 +1,7 @@
# TODO
* Find better searching algorithm for Typo3 login.
* Search for Typo3 version-specific extensions
* Some extensions don't have any version information. These extensions must be listed in settings.NO_VERSIONINFO.
* Use http:// or https:// plus the domain ?
* Maybe use one library for all requests

6414
extensions Normal file

File diff suppressed because it is too large Load Diff

3
lib/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env python
pass

93
lib/extensions.py Normal file
View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python
"""
Copyright (c) 2014 Jan Rude
"""
import re
import time
import urllib2
from Queue import Queue
from colorama import Fore
from os.path import isfile
from threading import Thread, Lock
from lib import settings
def generate_list():
if not isfile('extensions'):
print(Fore.RED + "\nExtensionfile not found!\nPlease update Typo-Enumerator (python typoenum.py -u)" + Fore.RESET)
sys.exit(-2)
with open('extensions', 'r') as f:
count = 0
for extension in f:
if settings.TOP_EXTENSION > count:
settings.EXTENSION_LIST.append(extension.split('\n')[0])
count += 1
else:
f.close()
return
def copy():
for extension in settings.EXTENSION_LIST:
settings.in_queue.put(extension)
# Searching installed extensions
# Check on version if we get 200 or 403.
def check_extension():
while True:
extension = settings.in_queue.get()
for path in settings.EXTENSION_PATHS:
try:
req = urllib2.Request('http://' + settings.DOMAIN + path + extension + '/', None, settings.user_agent)
connection = urllib2.urlopen(req, timeout = settings.TIMEOUT)
connection.close()
check_extension_version(path, extension)
settings.in_queue.task_done()
return
except urllib2.HTTPError, e:
if e.code == 403:
check_extension_version(path, extension)
settings.in_queue.task_done()
return
except urllib2.URLError, e:
pass
#retry = raw_input('Error on checking ' + extension + ': ' + str(e.reason) + '\nRetrying? (y/n) ')
#if retry:
# settings.in_queue.put(extension)
# if extension is not in any given path, it's not installed
if settings.verbose:
settings.out_queue.put(extension.ljust(32) + Fore.RED + 'not installed' + Fore.RESET)
settings.in_queue.task_done()
# Searching version of installed extension
def check_extension_version(path, extension):
# if no version information is available, skip version search
if extension in settings.NO_VERSIONINFO:
if settings.verbose:
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + 'installed' + Fore.RESET + ' (no version information available)')
else:
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + 'installed' + Fore.RESET)
else:
try:
request = urllib2.Request('http://' + settings.DOMAIN + path + extension +'/ChangeLog', None, settings.user_agent)
response = urllib2.urlopen(request, timeout = settings.TIMEOUT)
changelog = response.read(1500)
response.close()
try:
regex = re.compile("(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?[' '\n])")
searchVersion = regex.search(changelog)
version = searchVersion.groups()
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + 'installed (v' + version[0].split()[0] + ')' + Fore.RESET)
except:
try:
regex = re.compile("(\d{2,4}[\.\-]\d{1,2}[\.\-]\d{1,4})")
search = regex.search(changelog)
version = search.groups()
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + 'installed (last entry from ' + version[0] + ')' + Fore.RESET)
except:
if settings.verbose:
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + "installed" + Fore.RESET + " (no version information found)")
else:
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + "installed" + Fore.RESET)
except:
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + "installed" + Fore.RESET)

100
lib/login.py Normal file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env python
"""
Copyright (c) 2014 Jan Rude
"""
import re
import sys
import requests
import urllib2
from colorama import Fore
from lib import settings
# Searching Typo3 login page
def search_login(domain):
try:
r = requests.get('http://' + domain + '/typo3/index.php', allow_redirects=False, timeout=settings.TIMEOUT, headers=settings.user_agent)
statusCode = r.status_code
httpResponse = r.text
if statusCode == 200:
return check_title(httpResponse, r.url)
elif (statusCode == 301) or (statusCode == 302):
location = r.headers['location']
if ("http://") in location:
locsplit = location.split("//")
new_location = locsplit[1].split("/")
search_login(new_location[0])
elif ("https://") in location:
r = requests.get(location, timeout=settings.TIMEOUT, headers=settings.user_agent, verify=False)
statusCode = r.status_code
httpResponse = r.text
return check_title(httpResponse, r.url)
elif statusCode == 404:
return check_main_page()
else:
print "Oops! Got unhandled code:".ljust(32) + str(statusCode) + ": " + str(r.raise_for_status())
except requests.exceptions.Timeout:
print Fore.RED + "Connection timed out" + Fore.RESET
except requests.exceptions.TooManyRedirects:
print Fore.RED + "Too many redirects" + Fore.RESET
except requests.exceptions.RequestException as e:
print Fore.RED + str(e) + Fore.RESET
# Searching for Typo3 references in title
def check_title(response, url):
try:
regex = re.compile("<title>(.*)</title>", re.IGNORECASE)
searchTitle = regex.search(response)
title = searchTitle.groups()[0]
if 'TYPO3' in title or 'TYPO3 SVN ID:' in response:
print "Typo3 Login:".ljust(32) + Fore.GREEN + url + Fore.RESET
return True
except:
pass
return check_main_page()
# Searching for Typo3 references in HTML comments
def check_main_page():
req = urllib2.Request('http://' + settings.DOMAIN, None, settings.user_agent)
req.add_header('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
try:
connection = urllib2.urlopen(req, timeout = settings.TIMEOUT)
response = connection.read()
connection.close()
try:
cookie = connection.info().getheader('Set-Cookie')
if 'fe_typo_user' in cookie:
return bad_url()
except:
try:
regex = re.compile("TYPO3(.*)", re.IGNORECASE)
searchHTML = regex.search(response)
searchHTML.groups()[0]
try:
regex = re.compile("[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.[0-9][0-9]?[' '\n])")
searchVersion = regex.search(response)
version = searchVersion.groups()
settings.TYPO_VERSION = 'Typo3 ' + version[0].split()[0]
except:
pass
return bad_url()
except:
pass
except Exception, e:
if "404" in str(e):
print Fore.RED + str(e) + "\nPlease ensure you entered the right url" + Fore.RESET
else:
print Fore.RED + "Got \"" + str(e) + "\" on testing main page." + Fore.RESET
return False
print "Typo3 Login:".ljust(32) + Fore.RED + "Typo3 is not used on this domain" + Fore.RESET
return False
def bad_url():
print "Typo3 Login:".ljust(32) + Fore.GREEN + "Typo3 is used, but could not find login" + Fore.RESET
print "".ljust(32) + "This will mostly result in \"no extensions are installed\"."
print "".ljust(32) + "Seems like something is wrong with the given url."
var = raw_input("".ljust(32) + "Try anyway (y/n)? ")
if var is 'y':
return True
return False

20
lib/output.py Normal file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python
"""
Copyright (c) 2014 Jan Rude
"""
from Queue import Queue
from colorama import Fore
from threading import Thread, Lock
from lib import settings
# Output thread
def thread():
while settings.out_queue is not settings.out_queue.empty():
try:
extension = settings.out_queue.get()
print(extension)
settings.out_queue.task_done()
except Exception, e:
print "Oops! Got:", e

43
lib/privoxy_only.py Normal file
View File

@@ -0,0 +1,43 @@
import socket
import urllib2
import os, sys
import re
from colorama import Fore
try:
import socks
except:
print "The module 'SocksiPy' is not installed.\nPlease install it with: sudo apt-get install python-socksipy"
sys.exit(-2)
def start_daemon():
os.system('service privoxy start')
print '[' + Fore.GREEN + ' ok ' + Fore.RESET + '] Starting privoxy daemon...done.'
# Using Privoxy for all connections
def connect(port):
print "\nChecking connection..."
socks.setdefaultproxy(socks.PROXY_TYPE_HTTP, "127.0.0.1", port, True)
socket.socket = socks.socksocket
try:
url = urllib2.Request('https://check.torproject.org/')
torcheck = urllib2.urlopen(url)
response = torcheck.read()
torcheck.close()
except:
print Fore.RED + "Failed to connect through Privoxy!" + Fore.RESET
print "Please make sure your configuration is right!\n"
sys.exit(-2)
try:
# TODO: Check on privoxy at http://ha.ckers.org/weird/privoxy.html
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:
print "It seems like Privoxy is not used.\nAborting...\n"
sys.exit(-2)
def stop():
print "\n"
os.system('service privoxy stop')
print '[' + Fore.GREEN + ' ok ' + Fore.RESET + '] Stopping privoxy daemon...done.'

94
lib/settings.py Normal file
View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python
"""
Copyright (c) 2014 Jan Rude
"""
from Queue import Queue
from threading import Thread, Lock
# Domain to check
# Valid: string
DOMAIN = ""
# Maximum number of threads (avoiding connection issues and/or DoS)
MAX_NUMBER_OF_THREADS = 10
# Default port used by Tor
DEFAULT_TOR_PORT = 9050
# Default ports used in Tor proxy bundles
DEFAULT_PRIVOXY_PORT = 8118
# List with selected extensions
EXTENSION_LIST = []
# List with extensions, where no versioninformation is available
NO_VERSIONINFO = ['wt_spamshield', 'introduction'] #introduction has ChangeLog, but will use "Typo3 4.5.0" as version info!
# Check only top X extensions
# Default: all
TOP_EXTENSION = 'all'
# HTTP User-Agent header value. Useful to fake the HTTP User-Agent header value at each HTTP request
# Default: Mozilla/5.0
user_agent = {'User-Agent' : "Mozilla/5.0"}
# Maximum number of concurrent HTTP(s) requests (handled with Python threads)
# Valid: integer
# Default: 7
THREADS = 7
# Verbosity.
verbose = False
#Input and output queues
in_queue = ""
in_queue2 = ""
out_queue = ""
# Seconds to wait before timeout connection.
# Valid: int
# Default: 20
TIMEOUT = 20
# Possible paths to Typo3 loginpage !! not used atm !!
LOGIN_PATHS = ()
# Possible paths and regex to typo3 version information
TYPO3_VERSION_INFO = {'/typo3_src/ChangeLog':'RELEASE] Release of TYPO3 (.*)', '/typo3_src/NEWS.txt':'http://wiki.typo3.org/TYPO3_(\d{1,2}\.\d{1,2})', '/typo3_src/NEWS.md':"(.*) - WHAT'S NEW",
'/ChangeLog':'RELEASE] Release of TYPO3 (.*)', '/NEWS.txt':'http://wiki.typo3.org/TYPO3_(\d{1,2}\.\d{1,2})', '/NEWS.md':"(.*) - WHAT'S NEW"}
# Typo3 verision details
TYPO_VERSION = None
# Possible paths to an extension
EXTENSION_PATHS = ('/typo3conf/ext/', '/typo3/sysext/')
# Possible version info file
EXTENSION_VERSION_INFO = ('ChangeLog', 'README.txt')
EXTENSIONS_FOUND = {}
## Not used atm ##
# Maximum total number of redirections (regardless of URL) - before assuming we're in a loop
MAX_TOTAL_REDIRECTIONS = 10
# Maximum number of connection retries (to prevent problems with recursion)
MAX_CONNECT_RETRIES = 100
# Delay in seconds between each HTTP request.
# Valid: float
# Default: 0
delay = 0
# Maximum number of retries when the HTTP connection timeouts.
# Valid: integer
# Default: 3
retries = 3
# Use persistent HTTP(s) connections.
KEEPALIVE = False

64
lib/start.py Normal file
View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python
"""
Copyright (c) 2014 Jan Rude
"""
import time
from Queue import Queue
from os.path import isfile
from threading import Thread, Lock
from colorama import Fore, Back
from lib import settings
from lib import versioninfo
from lib import login
from lib import output
from lib import extensions
# Startmethod
def start(domain):
settings.in_queue = Queue()
settings.out_queue = Queue()
settings.DOMAIN = domain
print '\n\n' + Fore.CYAN + '[ Checking ' + domain + ' ]' + '\n' + "-"* 70 + Fore.RESET
if login.search_login(domain) is True:
versioninfo.search_version_info()
versioninfo.output()
if not settings.EXTENSION_LIST:
extensions.generate_list()
extensions.copy()
extensions_to_check = settings.in_queue.qsize()
if extensions_to_check is not 0:
print '\nChecking', extensions_to_check, 'extension(s)...'
# Thanks to 'RedSparrow': http://stackoverflow.com/questions/17991033/python-cant-kill-main-thread-with-keyboardinterrupt
try:
while True:
if settings.in_queue.empty() == False:
time.sleep(0.1)
for i in xrange(0, settings.THREADS):
t = Thread(target=extensions.check_extension, args=())
t.daemon = True
t.start()
else:
break
settings.in_queue.join()
except KeyboardInterrupt:
print Fore.RED + "\nReceived keyboard interrupt.\nQuitting..." + Fore.RESET
exit(-1)
installed_ext = settings.out_queue.qsize()
if installed_ext is 0:
print Fore.RED + "No extensions installed" + Fore.RESET
else:
t = Thread(target=output.thread, args=())
t.daemon = True
t.start()
settings.out_queue.join()
print Fore.GREEN + '\n', str(installed_ext) + '/' + str(extensions_to_check),'extension(s) installed' + Fore.RESET
else:
print '\nSkipping check for extensions...'

BIN
lib/tor.pyc Normal file

Binary file not shown.

45
lib/tor_only.py Normal file
View File

@@ -0,0 +1,45 @@
import socket
import urllib2
import os, sys
import re
from colorama import Fore
try:
import socks
except:
print "The module 'SocksiPy' is not installed.\nPlease install it with: sudo apt-get install python-socksipy"
sys.exit(-2)
def start_daemon():
os.system('service tor start')
# Using TOR for all connections
def connect(port):
print "\nChecking connection..."
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, "127.0.0.1", port, True)
socket.socket = socks.socksocket
try:
url = urllib2.Request('https://check.torproject.org/')
torcheck = urllib2.urlopen(url)
response = torcheck.read()
torcheck.close()
except Exception, e:
print e
print Fore.RED + "Failed to connect through TOR!" + Fore.RESET
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 Fore.GREEN + "Connection to TOR established" + Fore.RESET
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:
print "It seems like TOR is not used.\nAborting...\n"
sys.exit(-2)
def stop():
print "\n"
os.system('service tor stop')

49
lib/tor_with_privoxy.py Normal file
View File

@@ -0,0 +1,49 @@
import socket
import urllib2
import os, sys
import re
from colorama import Fore
try:
import socks
except:
print "The module 'SocksiPy' is not installed.\nPlease install it with: sudo apt-get install python-socksipy"
sys.exit(-2)
def start_daemon():
os.system('service tor start')
os.system('service privoxy start')
print '[' + Fore.GREEN + ' ok ' + Fore.RESET + '] Starting privoxy daemon...done.'
# Using Privoxy and TOR for all connections
def connect(port):
print "\nChecking connection..."
socks.setdefaultproxy(socks.PROXY_TYPE_HTTP, "127.0.0.1", port, True)
socket.socket = socks.socksocket
try:
url = urllib2.Request('https://check.torproject.org/')
torcheck = urllib2.urlopen(url)
response = torcheck.read()
torcheck.close()
except:
print Fore.RED + "Failed to connect through Privoxy and/or TOR!" + Fore.RESET
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 Fore.GREEN + "Connection to TOR established" + Fore.RESET
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, e:
print e
print "It seems like TOR is not used.\nAborting...\n"
sys.exit(-2)
def stop():
print "\n"
os.system('service tor stop')
os.system('service privoxy stop')
print '[' + Fore.GREEN + ' ok ' + Fore.RESET + '] Stopping privoxy daemon...done.'

44
lib/update.py Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os, sys, gzip, urllib
from collections import OrderedDict
import xml.etree.ElementTree as ElementTree
# Progressbar
def dlProgress(count, blockSize, totalSize):
percent = int(count*blockSize*100/totalSize)
sys.stdout.write("\rDownloading extentions: " + "%d%%" % percent)
sys.stdout.flush()
# Download extensions from typo3 repository
def download_ext():
try:
urllib.urlretrieve('http://ter.sitedesign.dk/ter/extensions.xml.gz', 'extensions.gz', reporthook=dlProgress)
inf = gzip.open('extensions.gz', 'rb')
file_content = inf.read()
inf.close()
outF = file("extensions.xml", 'wb')
outF.write(file_content)
outF.close()
os.remove('extensions.gz')
except Exception, e:
print "Oops! Got:".ljust(32), e
# Parse extensions.xml and save extensions in file
def generate_list():
extension = 'extensions.xml'
print "\nParsing file..."
tree = ElementTree.parse(extension)
tag_dict = tree.getroot()
exten_Dict = {}
for extensions in tag_dict.getchildren():
ext = {extensions.get('extensionkey'):extensions[0].text}
exten_Dict.update(ext)
sorted_dict = sorted(exten_Dict.iteritems(), key=lambda x: int(x[1]), reverse=True)
f = open('extensions','w')
for i in xrange(0,len(exten_Dict)):
f.write(sorted_dict[i][0]+'\n')
f.close()
print 'Loaded', len(exten_Dict), 'extensions\n'
os.remove('extensions.xml')

35
lib/versioninfo.py Normal file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python
"""
Copyright (c) 2014 Jan Rude
"""
import re
import urllib2
from colorama import Fore
from lib import settings
# Searching for Typo3 version
def search_version_info():
for path, regex in settings.TYPO3_VERSION_INFO.iteritems():
try:
request = urllib2.Request('http://' + settings.DOMAIN + path, None, settings.user_agent)
response = urllib2.urlopen(request, timeout = settings.TIMEOUT)
news = response.read(700)
response.close()
regex = re.compile(regex)
search = regex.search(news)
version = search.groups()
if settings.TYPO_VERSION is None or (len('Typo3' + version[0]) > len(settings.TYPO_VERSION)):
settings.TYPO_VERSION = 'Typo3 ' + version[0]
return
except:
pass
# Output of Typo3 version
def output():
if settings.TYPO_VERSION is None:
print "Typo3 Version:".ljust(32) + Fore.RED + "Not found" + Fore.RESET
else:
print "Typo3 Version:".ljust(32) + Fore.GREEN + settings.TYPO_VERSION + Fore.RESET
print "Link to vulnerabilities:".ljust(32) + "http://www.cvedetails.com/version-search.php?vendor=&product=Typo3&version=" + settings.TYPO_VERSION.split()[1]

459
typoenum.py Normal file → Executable file
View File

@@ -1,389 +1,130 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################## ChangeLog ##################
## v0.1 Prototype ##
## v0.2 Added version search for Typo3 ##
## v0.3 Added version guessing ##
## v0.4 Optimized requests ##
## v0.5 Added support for Typo v6.X ##
## v0.6 Added extension search ##
## v0.7 Added version search for extensions ##
## v0.8 Added support for TOR Service ##
###############################################
############ Version information ##############
__version__ = "0.8.1"
__version__ = "0.3"
__program__ = "Typo-Enumerator v" + __version__
__description__ = 'Find out the Typo3 Version, Login-URL and Extensions'
__description__ = 'Automatic Typo3 and Typo3 extensions enumeration tool'
__author__ = "Jan Rude"
__licence__ = "BSD Licence"
__status__ = "Development" # ("Prototype", "Development", "Final")
__status__ = "Development"
###############################################
################## Imports ####################
import os
import re
import gzip
import time
import socket
import urllib
import urllib2
import requests
import argparse
import datetime
from Queue import Queue
from colorama import Fore
from os.path import isfile
from operator import itemgetter
from threading import Thread, Lock
from collections import OrderedDict
import xml.etree.ElementTree as ElementTree
###############################################
############### Global variables ##############
user_agent = {'User-Agent' : None}
extension_list = []
verbosity = False
###############################################
# Searching for Typo3 version in ChangeLog
def check_typo_version_ChangeLog(domain):
global user_agent
try:
url = urllib2.Request('http://' + domain + '/typo3_src/ChangeLog', None, user_agent)
f = urllib2.urlopen(url, timeout = 3.0)
changelog = f.read(200)
f.close()
regex = re.compile("RELEASE] Release of (.*)")
searchVersion = regex.search(changelog)
version = searchVersion.groups()
print "Typo3 Version:".ljust(32) + Fore.GREEN + version[0] + Fore.RESET
print "Link to vulnerabilities:".ljust(32) + "http://www.cvedetails.com/version-search.php?vendor=&product=Typo3&version=" + version[0].split()[1]
except Exception, e:
check_typo_version_NEWS_TXT(domain)
# Searching for Typo3 version in NEWS.txt
def check_typo_version_NEWS_TXT(domain):
global user_agent
try:
url = urllib2.Request('http://' + domain + '/typo3_src/NEWS.txt', None, user_agent)
f = urllib2.urlopen(url, timeout = 3.0)
changelog = f.read(500)
f.close()
regex = re.compile("This document contains information about (.*) which")
searchVersion = regex.search(changelog)
version = searchVersion.groups()
print "Typo3 Version:".ljust(32), Fore.GREEN + version[0] + '.XX' + Fore.RED + ' (only guessable)'+ Fore.RESET
print "Link to vulnerabilities:".ljust(32) + "http://www.cvedetails.com/version-search.php?vendor=&product=Typo3&version=" + version[0].split()[2]
except:
check_typo_version_NEWS_MD(domain)
# Searching for Typo3 version in NEWS.md
def check_typo_version_NEWS_MD(domain):
global user_agent
try:
url = urllib2.Request('http://' + domain + '/typo3_src/NEWS.md', None, user_agent)
f = urllib2.urlopen(url, timeout = 3.0)
changelog = f.read(80)
f.close()
regex = re.compile("(.*) - WHAT'S NEW")
searchVersion = regex.search(changelog)
version = searchVersion.groups()
print "Typo3 Version:\t\t", Fore.GREEN + version[0] + '.XX' + Fore.RED + ' (only guessable)'+ Fore.RESET
print "Link to vulnerabilities:".ljust(32) + "http://www.cvedetails.com/version-search.php?vendor=&product=Typo3&version=" + version[0].split()[2]
except:
print "Typo3 Version:".ljust(32) + Fore.RED + "Not found" + Fore.RESET
# Searching Typo3 login page
def check_typo_login(domain):
global user_agent
try:
return check_main_page(domain)
r = requests.get('http://' + domain + '/typo3/index.php', allow_redirects=False, timeout=8.0, headers=user_agent)
statusCode = r.status_code
httpResponse = r.text
if statusCode == 200:
return check_title(httpResponse, r.url)
elif (statusCode == 301) or (statusCode == 302):
location = r.headers['location']
if ("http://") in location:
new = location.split("//")
new2 = new[1].split("/")
check_typo_login(new2[0])
elif ("https://") in location:
r = requests.get(location, timeout=8.0, headers=user_agent, verify=False)
statusCode = r.status_code
httpResponse = r.text
return check_title(httpResponse, r.url)
elif statusCode == 404:
return check_main_page(domain)
else:
print "Oops! Got:".ljust(32) + str(statusCode) + ": " + str(r.raise_for_status())
except requests.exceptions.Timeout:
print Fore.RED + "Connection timed out" + Fore.RESET
except requests.exceptions.TooManyRedirects:
print Fore.RED + "Too many redirects" + Fore.RESET
except requests.exceptions.RequestException as e:
print Fore.RED + str(e) + Fore.RESET
# Searching for Typo3 references in title
def check_title(response, url):
regex = re.compile("<title>(.*)</title>", re.IGNORECASE)
searchTitle = regex.search(response)
title = searchTitle.groups()[0]
if 'TYPO3' in title or 'TYPO3 SVN ID:' in response:
print "Typo3 Login:".ljust(32) + Fore.GREEN + url + Fore.RESET
return True
else:
print "Typo3 Login:".ljust(32) + Fore.RED + "Typo3 is not used on this domain" + Fore.RESET
return False
# Searching for Typo3 references in HTML comments
def check_main_page(domain):
req = urllib2.Request('http://' + domain, None, user_agent)
connection = urllib2.urlopen(req)
response = connection.read()
connection.close()
if 'fe_typo_user' in connection.info().getheader('Set-Cookie'):
print "Typo3 Login:".ljust(32) + Fore.GREEN + "Typo3 is used, but could not find login" + Fore.RESET
return True
else:
try:
regex = re.compile("This website is powered by TYPO3(.*)", re.IGNORECASE)
searchHTML = regex.search(response)
searchHTML.groups()[0]
print "Typo3 Login:".ljust(32) + Fore.GREEN + "Typo3 is used, but could not find login" + Fore.RESET
return True
except:
print "Typo3 Login:".ljust(32) + Fore.RED + "Typo3 is not used on this domain" + Fore.RESET
return False
# Searching installed extensions
def check_extensions(domain, input_queue, output_queue):
global user_agent
global verbosity
while True:
extension = input_queue.get()
try:
req = urllib2.Request('http://' + domain + '/typo3conf/ext/' + extension + "/", None, user_agent)
connection = urllib2.urlopen(req)
connection.close()
print "TEST:"
check_extension_version(domain, extension, output_queue)
except urllib2.HTTPError, e:
print "CODE:", e.code
if e.code == 403:
check_extension_version(domain, extension, output_queue)
elif e.code == 404:
if verbosity:
output_queue.put(extension.ljust(32) + Fore.RED + "not installed" + Fore.RESET)
pass
except urllib2.URLError, e:
print "CODE:", e
print str(e.reason)
except Exception, e:
import traceback
print ('generic exception: ', traceback.format_exc())
input_queue.task_done()
# Searching version of installed extension
def check_extension_version(domain, extension, output_queue):
global verbosity
global user_agent
try:
url = urllib2.Request('http://' + domain + '/typo3conf/ext/' + extension + '/ChangeLog', None, user_agent)
connection = urllib2.urlopen(url, timeout = 15.0)
changelog = connection.read(1500)
connection.close()
regex = re.compile("(\d{1,2}\.\d{1,2}\.[0-9][0-9]?[' '\n])")
searchVersion = regex.search(changelog)
version = searchVersion.groups()
output_queue.put(extension.ljust(32) + Fore.GREEN + "installed (v" + version[0].split()[0] + ")" + Fore.RESET)
except:
try:
regex = re.compile("(\d{2,4}[\.\-]\d{1,2}[\.\-]\d{1,4})")
searchVersion = regex.search(changelog)
version = searchVersion.groups()
output_queue.put(extension.ljust(32) + Fore.GREEN + "installed (last entry from " + version[0] + ")" + Fore.RESET)
except:
if verbosity:
output_queue.put(extension.ljust(32) + Fore.GREEN + "installed" + Fore.RESET + " (no version information found)")
else:
output_queue.put(extension.ljust(32) + Fore.GREEN + "installed" + Fore.RESET)
# Output thread
def output_thread(q):
if q.empty():
print Fore.RED + "No extensions are installed" + Fore.RESET
else:
while q is not q.empty():
try:
extension = q.get()
print(extension)
q.task_done()
except Exception, e:
print "Oops! Got:", e
# Loading extensions
def generate_extensions_list(top):
global extension_list
extension = 'extensions.xml'
print "\nLoading extensions..."
if not isfile(extension):
print(Fore.RED + "File not found: " + extension + "\nAborting..." + Fore.RESET)
sys.exit(-2)
tree = ElementTree.parse(extension)
tag_dict = tree.getroot()
exten_Dict = {}
for extensions in tag_dict.getchildren():
ext = {extensions.get('extensionkey'):extensions[0].text}
exten_Dict.update(ext)
print 'Loaded ' , len(exten_Dict), ' extensions\n'
if top is not None:
sorted_dict = sorted(exten_Dict.iteritems(), key=lambda x: int(x[1]), reverse=True)
for i in xrange(0,top):
extension_list.append(sorted_dict[i][0])
else:
for extension_name in tag_dict:
extension_list.append(extension_name.get('extensionkey'))
# Copy selected extensions in queue
def copy_extensions(input_queue):
global extension_list
for ext in extension_list:
input_queue.put(ext)
# Progressbar
def dlProgress(count, blockSize, totalSize):
percent = int(count*blockSize*100/totalSize)
sys.stdout.write("\rDownloading extentions: " + "%d%%" % percent)
sys.stdout.flush()
# Update function
def update():
try:
urllib.urlretrieve('http://ter.sitedesign.dk/ter/extensions.xml.gz', 'extensions.gz', reporthook=dlProgress)
inf = gzip.open('extensions.gz', 'rb')
file_content = inf.read()
inf.close()
outF = file("extensions.xml", 'wb')
outF.write(file_content)
outF.close()
print "\n"
os.remove('extensions.gz')
except Exception, e:
print "Oops! Got:".ljust(32), e
# Using Privoxy and TOR for all connections
def setting_up_tor():
try:
import socks
except:
print "The module 'SocksiPy' is not installed.\nPlease install it with: sudo apt-get install python-socksipy"
sys.exit(-2)
print "Checking connection to TOR through Privoxy"
socks.setdefaultproxy(socks.PROXY_TYPE_HTTP, "127.0.0.1", 8118, True)
socket.socket = socks.socksocket
try:
url = urllib2.Request('https://check.torproject.org/')
torcheck = urllib2.urlopen(url)
response = torcheck.read()
torcheck.close()
except:
print "Failed to connect to Privoxy and/or TOR!\nPlease make sure they are running and configured!\nYou can start them with:\nservice privoxy start\nservice tor start\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"
except:
print "It seems like TOR is not used.\nAborting...\n"
sys.exit(-2)
# Startmethod
def start(domain, top, threads):
in_queue = Queue()
out_queue = Queue()
regex = re.compile("(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})")
searchIP = regex.search(domain)
if not (searchIP is None):
IP = searchIP.groups()[0]
hostname = socket.gethostbyaddr(IP)
print("\n\n[*] Check for " + domain + " (" + hostname[0] + ")")
else:
print("\n\n[*] Check for " + domain)
if check_typo_login(domain) is True:
if not extension_list:
generate_extensions_list(top)
check_typo_version_ChangeLog(domain)
copy_extensions(in_queue)
print '\nChecking', in_queue.qsize(), 'Extensions:\nThis may take a while...'
for i in xrange(0, threads):
t = Thread(target=check_extensions, args=(domain, in_queue, out_queue))
t.daemon = True
t.start()
in_queue.join()
t = Thread(target=output_thread, args=(out_queue,))
t.daemon = True
t.start()
out_queue.join()
import argparse
import warnings
from colorama import Fore, Style
warnings.filterwarnings(action="ignore", message=".*was already imported", category=UserWarning)
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
from lib import settings
from lib import update
from lib import start
# Main
def main(argv):
global user_agent
global verbosity
parser = argparse.ArgumentParser(add_help=False, usage='typoenum.py -d DOMAIN [DOMAIN ...] | -f FILE [--user_agent USER-AGENT] [--top VALUE] [-v] [--tor]')
parser = argparse.ArgumentParser(usage='typoenum.py [options]')
group = parser.add_mutually_exclusive_group()
anonGroup = parser.add_mutually_exclusive_group()
group.add_argument('-d', '--domain', dest='domain', type=str, nargs='+')
group.add_argument('-f', '--file', dest='file', help='File with a list of domains')
group.add_argument('-u', '--update', dest='update', action='store_true',help='Get/Update the extension file')
parser.add_argument('--user_agent', dest='user_agent', default='Mozilla/5.0', metavar='USER-AGENT (default: Mozilla/5.0)')
parser.add_argument('--top', type=int, dest='top', metavar='VALUE', help='Check only most X downloaded extensions (default: all)', default=None)
group.add_argument('-u', '--update', dest='update', action='store_true',help='Update the extension file')
parser.add_argument('--user_agent', dest='user_agent', metavar='USER-AGENT (default: Mozilla/5.0)')
parser.add_argument('--top', type=int, dest='top', metavar='VALUE', help='Check only most X downloaded extensions (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('-t', '--threads', dest='threads', default=settings.THREADS, type=int, help=' Threads for HTTP connections (default: 7)')
parser.add_argument('--timeout', dest='timeout', default=settings.TIMEOUT, type=int, help='(default: 20)')
parser.add_argument('-v', '--verbose', help='increase output verbosity', action='store_true')
parser.add_argument('--tor', help='using tor for connections', action='store_true')
parser.add_argument('-t', '--threads', dest='threads', default=10, type=int, help='(default: 10)')
args = parser.parse_args()
if not args.domain and not args.file and not args.update:
parser.print_help()
return True
try:
if args.update:
update.download_ext()
update.generate_list()
return True
if args.tor:
setting_up_tor()
if args.update:
update()
return True
user_agent = {'User-Agent' : args.user_agent}
verbosity = args.verbose
if args.domain and not args.file:
for dom in args.domain:
start(dom, args.top, args.threads)
elif not args.domain and args.file:
if not isfile(args.file):
print(Fore.RED + "\nFile not found: " + args.file + "\nAborting..." + Fore.RESET)
if args.threads > settings.MAX_NUMBER_OF_THREADS:
print Fore.RED + "Warning! Threads are set to", args.threads,"(max value is 10)\nThis can cause connection issues and/or DoS\nAborting...." + Fore.RESET
sys.exit(-2)
else:
with open(args.file, 'r') as f:
for line in f:
start(line.strip(), args.top, args.threads)
print '\n'
now = datetime.datetime.now()
print __program__ + ' finished at ' + now.strftime("%Y-%m-%d %H:%M:%S") + '\n'
return True
if args.tor:
from lib import tor_only
tor_only.start_daemon()
if args.port:
tor_only.connect(args.port)
else:
tor_only.connect(settings.DEFAULT_TOR_PORT)
elif args.privoxy:
from lib import privoxy_only
privoxy_only.start_daemon()
if args.port:
privoxy_only.connect(args.port)
else:
privoxy_only.connect(settings.DEFAULT_PRIVOXY_PORT)
elif args.tp:
from lib import tor_with_privoxy as tp
tp.start_daemon()
if args.port:
tp.connect(args.port)
else:
tp.connect(settings.DEFAULT_PRIVOXY_PORT)
if args.user_agent:
settings.user_agent.update({'User-Agent':args.user_agent})
if args.verbose:
settings.verbose = args.verbose
if args.top or args.top is 0:
settings.TOP_EXTENSION = args.top
if args.timeout:
settings.TIMEOUT = args.timeout
if args.domain:
for dom in args.domain:
start.start(dom)
elif args.file:
if not isfile(args.file):
print(Fore.RED + "\nFile not found: " + args.file + "\nAborting..." + Fore.RESET)
sys.exit(-2)
else:
with open(args.file, 'r') as f:
for line in f:
start.start(line.strip('\n')[0])
except Exception, e:
import traceback
print ('generic exception: ', traceback.format_exc())
finally:
if args.tor:
tor_only.stop()
elif args.privoxy:
privoxy_only.stop()
elif args.tp:
tp.stop()
print '\n'
now = datetime.datetime.now()
print __program__ + ' finished at ' + now.strftime("%Y-%m-%d %H:%M:%S") + '\n'
Style.RESET_ALL
return True
if __name__ == "__main__":
import sys
print('\n' + 70*'*')
print(Style.BRIGHT + '\n' + 70*'*')
print('\t' + __program__ )
print('\t' + __description__)
print('\t' + '(c)2014 by ' + __author__)
print('\t' + 'Status:\t' + __status__)
print('\t' + 'For legal purposes only!')
print(70*'*' + '\n')
sys.exit( not main( sys.argv ) )
print(70*'*')
sys.exit( not main( sys.argv ) )