Create typoenum.py
This commit is contained in:
336
typoenum.py
Normal file
336
typoenum.py
Normal file
@@ -0,0 +1,336 @@
|
||||
#!/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 ##
|
||||
###############################################
|
||||
|
||||
############ Version information ##############
|
||||
__version__ = "0.7"
|
||||
__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 argparse
|
||||
import datetime
|
||||
import requests
|
||||
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
|
||||
###############################################
|
||||
|
||||
# Checks the Typo version
|
||||
def check_typo_version_ChangeLog(domain):
|
||||
global verbosity
|
||||
try:
|
||||
url = 'http://' + domain + '/typo3_src/ChangeLog'
|
||||
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:
|
||||
if verbosity:
|
||||
print "Typo3 Version:".ljust(32) + "first check failed, trying second one..."
|
||||
check_typo_version_NEWS_TXT(domain)
|
||||
|
||||
def check_typo_version_NEWS_TXT(domain):
|
||||
global verbosity
|
||||
try:
|
||||
url = 'http://' + domain + '/typo3_src/NEWS.txt'
|
||||
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:
|
||||
if verbosity:
|
||||
print "Typo3 Version:".ljust(32) + "second check failed, trying third one..."
|
||||
check_typo_version_NEWS_MD(domain)
|
||||
|
||||
def check_typo_version_NEWS_MD(domain):
|
||||
try:
|
||||
url = 'http://' + domain + '/typo3_src/NEWS.md'
|
||||
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
|
||||
|
||||
# Checks the Typo login
|
||||
def check_typo_login(domain):
|
||||
global user_agent
|
||||
try:
|
||||
r = requests.get('http://' + domain + '/typo3/index.php', allow_redirects=False, timeout=3.0, headers=user_agent)
|
||||
statusCode = r.status_code
|
||||
httpResponse = r.text
|
||||
r.close()
|
||||
if statusCode == 200:
|
||||
return check_title(httpResponse, r.url)
|
||||
|
||||
elif (statusCode == 301) or (statusCode == 302):
|
||||
location = r.headers['location']
|
||||
if verbosity:
|
||||
print "Redirect to:".ljust(32) + 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=3.0, headers=user_agent, verify=False)
|
||||
statusCode = r.status_code
|
||||
httpResponse = r.text
|
||||
return check_title(httpResponse, r.url)
|
||||
elif statusCode == 404:
|
||||
print "Typo3 Login:".ljust(32) + Fore.RED + "Typo3 is not used on this domain" + Fore.RESET
|
||||
|
||||
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
|
||||
|
||||
# Checks, if URL is a Typo-Login
|
||||
def check_title(httpResponse, url):
|
||||
regex = re.compile("<title>(.*)</title>", re.IGNORECASE)
|
||||
searchTitle = regex.search(httpResponse)
|
||||
title = searchTitle.groups()[0]
|
||||
if 'TYPO3' in title or 'TYPO3 SVN ID:' in httpResponse:
|
||||
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
|
||||
|
||||
# Searches for installed extensions
|
||||
def check_extensions(domain, input_queue, output_queue):
|
||||
global user_agent
|
||||
global verbosity
|
||||
while True:
|
||||
extension = input_queue.get()
|
||||
try:
|
||||
r = requests.get('http://' + domain + '/typo3conf/ext/' + extension + "/", allow_redirects=False, timeout=8.0, headers=user_agent)
|
||||
statusCode = r.status_code
|
||||
r.close()
|
||||
if (statusCode == 200) or (statusCode == 403):
|
||||
check_extension_version(domain, extension, output_queue)
|
||||
elif statusCode == 404:
|
||||
if verbosity:
|
||||
output_queue.put(extension.ljust(32) + Fore.RED + "not installed" + Fore.RESET)
|
||||
pass
|
||||
else:
|
||||
print "Oops! Got:".ljust(32) + str(statusCode) + ": " + str(r.raise_for_status())
|
||||
except Exception, e:
|
||||
print "Oops! Got:".ljust(32), e
|
||||
input_queue.task_done()
|
||||
|
||||
# Searches for version of installed extension
|
||||
def check_extension_version(domain, extension, output_queue):
|
||||
global verbosity
|
||||
try:
|
||||
url = 'http://' + domain + '/typo3conf/ext/' + extension + '/ChangeLog'
|
||||
f = urllib2.urlopen(url, timeout = 3.0)
|
||||
changelog = f.read(500)
|
||||
f.close()
|
||||
regex = re.compile("\* (.*)")
|
||||
searchVersion = regex.search(changelog)
|
||||
version = searchVersion.groups()
|
||||
output_queue.put(extension.ljust(32) + Fore.GREEN + "installed (" + version[0] + ")" + Fore.RESET)
|
||||
except:
|
||||
if verbosity:
|
||||
output_queue.put(extension.ljust(32) + Fore.GREEN + "installed" + Fore.RESET + " (could not find version)")
|
||||
else:
|
||||
output_queue.put(extension.ljust(32) + Fore.GREEN + "installed" + Fore.RESET)
|
||||
|
||||
# Output
|
||||
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 "Loading 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'
|
||||
|
||||
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 used extensions in queue
|
||||
def copy_extensions(input_queue):
|
||||
global extension_list
|
||||
for ext in extension_list:
|
||||
input_queue.put(ext)
|
||||
|
||||
# Processbar
|
||||
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():
|
||||
global user_agent
|
||||
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
|
||||
|
||||
#Starting checks
|
||||
def start(domain):
|
||||
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:
|
||||
check_typo_version_ChangeLog(domain)
|
||||
copy_extensions(in_queue)
|
||||
print '\nChecking', in_queue.qsize(), 'Extensions:'
|
||||
for i in xrange(0, 10):
|
||||
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='typofinder.py -d DOMAIN [DOMAIN ...] | -f FILE [--user_agent USER-AGENT] [--top VALUE] [-v]')
|
||||
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")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.domain and not args.file and not args.update:
|
||||
parser.print_help()
|
||||
return True
|
||||
|
||||
if args.update:
|
||||
update()
|
||||
return True
|
||||
|
||||
user_agent = {'User-Agent' : args.user_agent}
|
||||
generate_extensions_list(args.top)
|
||||
verbosity = args.verbose
|
||||
|
||||
if args.domain and not args.file:
|
||||
for dom in args.domain:
|
||||
start(dom)
|
||||
|
||||
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())
|
||||
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 ) )
|
||||
Reference in New Issue
Block a user