Created
March 10, 2013 22:42
-
-
Save andresriancho/5130841 to your computer and use it in GitHub Desktop.
HTTP-Traceroute.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import getopt, sys, re, urllib2, urllib, BaseHTTPServer | |
from urllib2 import Request, urlopen, URLError, HTTPError | |
################## HEADER ################################### | |
# | |
# Traceroute-like HTTP scanner | |
# Using the "Max-Forwards" header | |
# RFC 2616 - HTTP/1.1 - Section 14.31 | |
# RFC 3261 - SIP - Section 8.1.1.6 | |
# | |
# | |
# By Nicolas Gregoire ([email protected]) | |
# | |
# 0.5 : First public release | |
# 0.4 : Private release, looking for bugs - More heuristics | |
# 0.3 : A lot more options - More verbosity levels - Some heuristics | |
# | |
# By Julien Cayssol ([email protected]) | |
# | |
# 0.2 : Add extract of headers | |
# 0.1 : Initial version | |
# | |
# | |
# Heuristics : | |
# - Status Codes : | |
# - HTTP Status Code == 502 | |
# - HTTP Status Code == 483 | |
# - Specific data in body or headers : | |
# - X-Forwarded-For in body when using TRACE | |
# - Via or X-Via in headers | |
# - Differences between hops : | |
# - HTTP Status Codes | |
# - Server headers | |
# - Content-Type headers | |
# - Via headers | |
# - HTML titles | |
# - HTML <address> tags | |
# - X-Forwarded-For values when using TRACE | |
# | |
############## GLOBAL VARIABLES ################################### | |
global_data = { 'StatusCode':{}, 'Server':{} , 'Content-Type':{}, 'Title':{}, 'Address':{}, 'X-Fwd':{}, 'Via':{} } | |
score = 0 | |
verbosity = 0 | |
scheme = 'http' | |
host = '127.0.0.1' | |
port = '80' | |
path = '/' | |
method = 'TRACE' | |
body_content = None | |
max_fwds = 3 | |
userAgent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.2) Gecko/20060502 Firefox/1.5.0.2' | |
contentType = 'text/html' | |
############## FUNCTIONS ################################### | |
# Pretty printing | |
def zprint(string, flag = '=='): | |
print '[' + flag + '] ' + string | |
# Increment the heuristic score | |
def inc_score(): | |
global score | |
score = score + 1 | |
if verbosity: | |
zprint('Score : ' + str(score), '!!') | |
# Help | |
def showUsage(): | |
print 'Usage : ' + sys.argv[0] + ' [-h] [-m method] [-s scheme] [-t target] [-p port] [-P path] [-v 0|1|2] [-f forwards]' | |
print '\t[-h] Help (this text)' | |
print '\t[-m] HTTP Method : default is "TRACE"' | |
print '\t[-s] Scheme : default is "http"' | |
print '\t[-t] Target host : default is "127.0.0.1"' | |
print '\t[-p] Port : default is "80"' | |
print '\t[-P] Path : default is "/"' | |
print '\t[-f] Max # of forwards : default is "3"' | |
print '\t[-v] Verbosity : 0 = default, 1 = verbose, 2 = debug' | |
print 'Examples :' | |
print sys.argv[0] + ' -t www.example.org' | |
print ' => TRACE /' | |
print sys.argv[0] + ' -t www.example.org -m GET -s https -p 443 -v 1' | |
print ' => GET / on a SSL host' | |
print sys.argv[0] + ' -t www.example.org -m POST -P /axis2/checkacc -v 2 -f 5' | |
print ' => Debug mode on a specific end-point' | |
sys.exit(1) | |
# Parse CLI args | |
def getArguments(): | |
try: | |
if len(sys.argv) < 2: | |
zprint('No arguments ? Probably a bad choice. Use "-h" ...', '!!') | |
sys.exit(1) | |
optlist, list = getopt.getopt(sys.argv[1:], 'hm:s:t:p:P:v:f:') | |
except getopt.GetoptError: | |
showUsage() | |
for opt in optlist: | |
if opt[0] == '-h': | |
showUsage() | |
if opt[0] == '-m': | |
global method | |
method = opt[1] | |
if opt[0] == '-s': | |
global scheme | |
scheme = opt[1] | |
if opt[0] == '-t': | |
global host | |
host = opt[1] | |
if opt[0] == '-p': | |
global port | |
port = opt[1] | |
if opt[0] == '-P': | |
global path | |
path = opt[1] | |
if opt[0] == '-v': | |
global verbosity | |
verbosity = int(opt[1]) | |
if opt[0] == '-f': | |
global max_fwds | |
max_fwds = int(opt[1]) | |
# Extract some interesting data from the headers | |
def analyse_headers(data): | |
if verbosity: | |
zprint('Analyzing headers', '**') | |
wanted_headers = [ | |
'Server', | |
'Via', | |
'X-Via', | |
'Set-Cookie', | |
'X-Forwarded-For', | |
'Content-Type', | |
'Content-Length', | |
'Last-Modified', | |
'Location', | |
'Date', | |
] | |
for h_name in wanted_headers: | |
h_value = data.getheader(h_name) | |
if h_value != None: | |
# Print the value | |
if verbosity: | |
zprint(h_value, h_name) | |
# Add it to the global structure if needed | |
if h_name == 'Server' or h_name == 'Content-Type': | |
global_data[h_name][hop] = h_value | |
# Some heuristics | |
if h_name == 'Via' or h_name == 'X-Via': | |
zprint('"Via" header : Probably a reverse proxy', '++') | |
global_data['Via'][hop] = h_value | |
inc_score() | |
# Extract some interesting data from the body | |
def analyse_body(data): | |
if verbosity: | |
zprint('Analyzing body', '**') | |
wanted_patterns = [ | |
'<title>(.*)</title>', | |
'<address>(.*)</address>', | |
'Reason: <strong>(.*)</strong>', | |
'X-Forwarded-For: (.*)', | |
] | |
for p_name in wanted_patterns: | |
# Case insensitive search | |
p_value = re.search(p_name, data, re.IGNORECASE) | |
if p_value != None: | |
# Only the 1st group, without newlines | |
value = p_value.groups()[0].strip('\r\n') | |
if verbosity: | |
zprint(value, p_name) | |
# Add it to the global structure if needed | |
if p_name == '<title>(.*)</title>': | |
global_data['Title'][hop] = value | |
if p_name == '<address>(.*)</address>': | |
global_data['Address'][hop] = value | |
# Some heuristics | |
if re.search('X-Forwarded-For:' , p_name): | |
global_data['X-Fwd'][hop] = value | |
if method == 'TRACE': | |
zprint('"X-Forwarded-For" in body when using TRACE : Probably a reverse proxy', '++') | |
inc_score() | |
# Analyse the data returned by urllib2.*open() | |
def debug_and_parse(data): | |
# Get data | |
headers = data.info() | |
body = data.read() | |
# Debug | |
if verbosity == 2: | |
zprint(str(headers), 'DEBUG HEADERS') | |
zprint(str(body), 'DEBUG BODY') | |
# Extract some intersting info | |
codes = BaseHTTPServer.BaseHTTPRequestHandler.responses | |
global_data['StatusCode'][hop] = str(data.code) + ' ' + codes[data.code][0] | |
analyse_headers(headers) | |
analyse_body(body) | |
############## SCAN ################################### | |
# Init | |
getArguments() | |
# Current target | |
url = scheme + '://' + host + ':' + port + path | |
zprint('Target URL : ' + url) | |
zprint('Used method : ' + method) | |
zprint('Max number of hops : ' + str(max_fwds)) | |
# Scan | |
for hop in range(0, max_fwds): | |
# Create the request object | |
request = urllib2.Request(url) | |
request.get_method = lambda: method | |
request.add_data(body_content) | |
request.add_header('Content-Type', contentType) | |
request.add_header('User-agent', userAgent) | |
# Add the 'Max-Forwards' header | |
request.add_header('Max-Forwards', hop) | |
if verbosity: | |
print('-' * 80) | |
zprint('Current value of "Max-Forwards" = ' + str(hop) + ' [' + '-' * 20 + ']', '-' * 19) | |
print('-' * 80) | |
try: | |
# Do the HTTP request | |
opener = urllib2.build_opener(urllib2.HTTPHandler) | |
result = opener.open(request) | |
# Found something | |
if verbosity: | |
zprint('Status Code => HTTP 200: OK', '**') | |
# Analyse it | |
debug_and_parse(result) | |
# Not a 200 OK | |
except HTTPError, e: | |
if verbosity: | |
zprint('Status Code => ' + str(e), '**') | |
# Some heuristics | |
if e.code == 502: | |
zprint('HTTP 502 : Probably a reverse proxy', '++') | |
inc_score() | |
if e.code == 483: | |
zprint('HTTP 483 : Probably a reverse proxy (SIP ?)', '++') | |
inc_score() | |
# Analyse it | |
debug_and_parse(e) | |
# Network problem | |
except URLError, e: | |
zprint('Network problem !', '!!') | |
zprint('Reason : ' + str(e.reason), '!!') | |
break | |
############## REPORT ################################### | |
print('-' * 80) | |
zprint('Heuristic Report [' + '-' * 31 + ']', '-' * 27) | |
print('-' * 80) | |
# For each key | |
for k in global_data.keys(): | |
string = k + ':\n' | |
previous = 'Undef' | |
# For each hop | |
ok = 0 | |
for i in range(0, max_fwds): | |
# Try this key | |
try: | |
current = global_data[k][i] | |
# We got a value ! | |
ok = 1 | |
except KeyError: | |
current = 'Undef' | |
# Some heuristics | |
if previous != current and i > 0: | |
inc_score() | |
# Then add it to the current string | |
string = string + '\tHop #' + str(i) + " : " + current + '\n' | |
previous = current | |
# Display this key only if values were found | |
if ok: | |
print string | |
# Final score | |
if score == 0: | |
zprint('No reverse proxy', '--') | |
else: | |
zprint('Found a reverse proxy, score is ' + str(score), '++') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment