diff --git a/typoenum.py b/typoenum.py
new file mode 100644
index 0000000..80bc96f
--- /dev/null
+++ b/typoenum.py
@@ -0,0 +1,389 @@
+#!/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"
+__program__ = "Typo-Enumerator v" + __version__
+__description__ = 'Find out the Typo3 Version, Login-URL and Extensions'
+__author__ = "Jan Rude"
+__licence__ = "BSD Licence"
+__status__ = "Development" # ("Prototype", "Development", "Final")
+###############################################
+
+################## 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("
(.*)", 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()
+
+# 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]')
+ group = 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)
+ 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
+
+ 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)
+ 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 __name__ == "__main__":
+ import sys
+ print('\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 ) )