Source

PeepingTom / peepingtom.py

Full commit
import sys
import urllib2
import subprocess
import re
import time
import os
import hashlib

#=================================================
# MAIN FUNCTION
#=================================================

def main():
    # depenency check
    if not all([os.path.exists('phantomjs'), os.path.exists('/usr/bin/curl')]):
        print '[!] PhantomJS and cURL required.'
        return
    # parse options
    import optparse
    usage = """

PeepingTom - Tim Tomes (@LaNMaSteR53) (www.lanmaster53.com)

Dependencies:
 - PhantomJS
 - cURL

$ %prog <mode> <path>"""
    parser = optparse.OptionParser(usage=usage)
    parser.add_option('-l', help='file input mode. Path to input file.', dest='list_file', type='string', action='store')
    parser.add_option('-n', help='Nessus input mode. Path to Nessus XML file.', dest='nessus_file', type='string', action='store')
    parser.add_option('-s', help='single input mode. Path to target, remote URL or local path.', dest='target', type='string', action='store')
    parser.add_option('-t', help='socket timeout in seconds. Default is 8 seconds.', dest='timeout', type='int', action='store')
    (opts, args) = parser.parse_args()

    # process options
    if opts.list_file:
        try:
            targets = open(opts.list_file).read().split()
        except IOError:
            print '[!] Invalid path to list file: \'%s\'' % opts.list_file
            return
    elif opts.nessus_file:
        # optimized portion of Peeper (https://github.com/invisiblethreat/peeper) by Scott Walsh (@blacktip)
        import xml.etree.ElementTree as ET
        try:
            tree = ET.parse(opts.nessus_file)
        except IOError:
            print '[!] Invalid path to Nessus file: \'%s\'' % opts.nessus_file
            return
        except ET.ParseError:
            print '[!] Not a valid Nessus file: \'%s\'' % opts.nessus_file
            return
        root = tree.getroot()
        targets = []
        for host in root.iter('ReportHost'):
            name = host.get('name')
            for item in host.iter('ReportItem'):
                svc = item.attrib['svc_name']
                plugname = item.attrib['pluginName']
                if (svc in ['www','http?','https?'] and plugname.lower().startswith('service detection')):
                    port = item.attrib['port']
                    output = item.find('plugin_output').text.strip()
                    proto = guessProto(output)
                    url = '%s://%s:%s' % (proto, name, port)
                    if not url in targets:
                        targets.append(url)
    elif opts.target:
        targets = [opts.target]
    else:
        print '[!] Input mode required.'
        return
    timeout = opts.timeout if opts.timeout else 8

    print '[*] Analyzing %d targets.' % (len(targets))

    # setup data storage location
    directory = time.strftime('%y%m%d_%H%M%S', time.localtime())
    print '[*] Storing data in \'%s/\'' % (directory)
    os.mkdir(directory)
    report = 'peepingtom.html'
    outfile = '%s/%s' % (directory, report)

    # logic to gather screenshots and headers for the given targets
    db = {'targets': []}
    cnt = 0
    tot = len(targets) * 2
    try:
        for target in targets:
            #print '[*] %s' % (target)
            printProgress(cnt, tot)
            filename = '%s.png' % re.sub('\W','',target)
            filepath = '%s/%s' % (directory, filename)
            getCapture(target, filepath, timeout)
            cnt += 1
            printProgress(cnt, tot)
            target_data = {}
            target_data['url'] = target
            target_data['path'] = filename
            target_data['hash'] = hashlib.md5(open(filepath).read()).hexdigest() if os.path.exists(filepath) else 'z'*32
            target_data['headers'] = getHeaders(target, timeout)
            db['targets'].append(target_data)
            cnt += 1
        print printProgress(1,1)
    except Exception as e:
        print '[!] %s' % (e.__str__())
    
    # build the report and exit
    buildReport(db, outfile)
    import webbrowser
    path = os.getcwd()
    w = webbrowser.get()
    w.open('file://%s/%s/%s' % (path, directory, report))
    print '[*] Done.'

#=================================================
# SUPPORT FUNCTIONS
#=================================================

def guessProto(output):
    # optimized portion of Peeper (https://github.com/invisiblethreat/peeper) by Scott Walsh (@blacktip)
    secure = re.search('TLS|SSL', output)
    if secure:
        return "https"
    return "http"

def getCapture(url, filename, timeout):
    cmd = './phantomjs --ignore-ssl-errors=yes ./capture.js %s %s %d' % (url, filename, timeout*1000)
    returncode, response = runCommand(cmd)
    return returncode

def getHeaders(url, timeout):
    cmd = 'curl -sILk %s --connect-timeout %d' % (url, timeout)
    returncode, response = runCommand(cmd)
    return response

def runCommand(cmd):
    proc = subprocess.Popen([cmd], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
    stdout, stderr = proc.communicate()
    response = ''
    if stdout: response += str(stdout)
    if stderr: response += str(stderr)
    return proc.returncode, response.strip()

def printProgress(cnt, tot):
    percent = 100 * float(cnt) / float(tot)
    sys.stdout.write('[%-40s] %d%%\r' % ('='*int(float(percent)/100*40), percent))
    sys.stdout.flush()
    return ''

def buildReport(db, outfile):
    live_markup = ''
    error_markup = ''
    dead_markup = ''
    # process markup for live targets
    for live in sorted(db['targets'], key=lambda k: k['hash']):
        live_markup += "<tr><td class='img'><a href='{0}'><img src='{0}' onerror=\"this.parentNode.parentNode.innerHTML='No image available.';\" /></a></td><td class='head'><a href='{1}' target='_blank'>{1}</a><br /><pre>{2}</pre></td></tr>\n".format(live['path'],live['url'],live['headers'])
    # add markup to the report
    file = open(outfile, 'w')
    file.write("""
<!doctype html>
<head>
<style>
table, td, th {border: 1px solid black;border-collapse: collapse;padding: 5px;font-size: .9em;font-family: tahoma;}
table {width: 100%%;table-layout: fixed;min-width: 1000px;}
td.img {width: 40%%;}
img {width: 100%%;}
td.head {vertical-align: top;word-wrap: break-word;}
</style>
</head>
<body>
<table>
%s
</table>
</body>
</html>""" % (live_markup))
    file.close()

#=================================================
# START
#=================================================

if __name__ == "__main__": main()