#!/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 ) )