#!/usr/bin/python -tO

# freewvs 0.1 - the free web vulnerability scanner
#
# http://source.schokokeks.org/freewvs/
#
# Copyright 2007 Hanno Boeck, schokokeks.org <hanno@schokokeks.org>
#
# Contributions by
# Fabian Fingerle <fabian@datensalat.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.    If not, see <http://www.gnu.org/licenses/>.

import ConfigParser, os, glob, pprint, re, optparse, sys, gettext

gettext.textdomain('freewvs')
_ = gettext.gettext

def versioncompare(safe_version, find_version):
    if safe_version == [""]:
        return True
    for i in range(min(len(find_version), len(safe_version))):
        if int(find_version[i])<int(safe_version[i]):
            return True
        if int(find_version[i])>int(safe_version[i]):
            return False
    return (len(find_version)<len(safe_version))

def vulnprint(appname, version, safeversion, vuln, vfilename, subdir, fancy):
    appdir = '/'.join(os.path.abspath(vfilename).split('/')[:-1-subdir])
    if fancy:
        print _("Directory: %(appdir)s") % vars()
        if safeversion!="ok":
            if safeversion!="":
                print _("Vulnerable %(appname)s %(version)s found, please update to " \
                        "%(safeversion)s or above.") % vars()
            else:
                print _("Vulnerable %(appname)s %(version)s found, no fixed version available." \
                        ) % vars()
            if vuln[:3] == "CVE":
                print _("http://cve.mitre.org/cgi-bin/cvename.cgi?name=%(vuln)s") \
                        % vars()
            else:
                print (vuln)
        else:
            print _("%(appname)s %(version)s found." ) % vars()
        print
    else:
        print "%(appname)s %(version)s (%(safeversion)s) %(vuln)s %(appdir)s" \
              % vars()

pp = pprint.PrettyPrinter(indent=4)

# Command-line options
parser = optparse.OptionParser()
parser.add_option("-a", "--all", action="store_true", dest="ALL",
                  help="Show all webapps found, not just vulnerable")
parser.add_option("-d", "--debug", action="store_true", dest="DEBUG",
                  help="Show lots of debugging output, mainly useful"+ \
                  "for development")
parser.add_option("-f", "--fancy", action="store_true", dest="FANCY",
                  help="Show more fancy output")
opts, args = parser.parse_args()

# Parse vulnerability database
config = ConfigParser.ConfigParser()
config.read(glob.glob('/usr/share/freewvs/*.freewvs'))
config.read(glob.glob('/usr/local/share/freewvs/*.freewvs'))
config.read(glob.glob(os.path.dirname(sys.argv[0])+'/freewvsdb/*.freewvs'))

vdb = []
for sect in config.sections():
    item = {}

    # base options
    item['name'] = sect
    item['safe'] = config.get(sect, 'safe')
    item['file'] = config.get(sect, 'file')
    item['vuln'] = config.get(sect, 'vuln')
    item['subdir'] = int(config.get(sect, 'subdir'))

    # match magic
    item['variable'] = []
    for var in config.get(sect,'variable').split(","):
        item['variable'].append(re.compile(re.escape(var)+
                                r"[^0-9.]*[.]*([0-9.]*[0-9])[^0-9.]"))

    # optional options
    if config.has_option(sect,'extra_match'):
        item['extra_match'] = config.get(sect,'extra_match')
    else:
        item['extra_match'] = False
    if config.has_option(sect,'add_minor'):
        item['add_minor'] = config.get(sect,'add_minor')
    else:
        item['add_minor'] = False
    if config.has_option(sect,'old_safe'):
        item['old_safe'] = config.get(sect,'old_safe').split(",")
    else:
        item['old_safe'] = []

    vdb.append(item)
if opts.DEBUG:
    pp.pprint(vdb)


# start the search

for fdir in args:
    for root, NULL, files in os.walk(fdir):
        for filename in files:
            for item in vdb:
                if filename == item['file']:
                    mfile = os.path.join(root, filename)
                    file = open(mfile)
                    filestr = file.read()
                    file.close()

                    if item['extra_match']:
                        ematch = (filestr.find(item['extra_match']) != -1)
                    else:
                        ematch = True

                    findversion = []
                    for var in item['variable']:
                        var = var.search(filestr)
                        if not var:
                            findversion = False
                            break
                        else:
                            findversion.append(var.group(1))

                    if findversion and ematch:
                        findversion = '.'.join(findversion)

                        # Very ugly phpbb workaround
                        if item['add_minor']:
                            findversion = findversion.split('.')
                            findversion[-1] = str(int(findversion[-1])+
                                            int(item['add_minor']))
                            findversion = '.'.join(findversion)

                        if not (versioncompare(item['safe'].split('.'), \
                                findversion.split('.'))) or \
                                item['old_safe'].count(findversion)>0:
                            if opts.ALL:
                                if opts.DEBUG:
                                    print "File "+mfile
                                vulnprint(item['name'], findversion, \
                                          "ok", "", mfile, item['subdir'], \
                                          opts.FANCY)
                        else:
                            if opts.DEBUG:
                                print "File "+mfile
                            safev=""
                            for ver in item['old_safe']:
                                if (versioncompare(ver.split('.'), \
                                    findversion.split('.') ) ):
                                    safev=ver
                            if safev=="":
                                safev=item['safe']

                            vulnprint (item['name'], findversion, \
                                       safev, item['vuln'], \
                                       mfile, item['subdir'], opts.FANCY)

                    else:
                        if opts.DEBUG:
                            print "regexp failed for " + \
                                  item['name'] + " on " + mfile