freewvs
836c4db3
 #!/usr/bin/python -tO
cb10b7f6
 
 # freewvs 0.1 - the free web vulnerability scanner
 #
4f4fa14c
 # https://source.schokokeks.org/freewvs/
cb10b7f6
 #
4f4fa14c
 # Written 2007-2012 by schokokeks.org Hosting, https://schokokeks.org
cb10b7f6
 #
 # Contributions by
4f4fa14c
 # Hanno Boeck, https://hboeck.de/
 # Fabian Fingerle, https://fabian-fingerle.de/
 # Bernd Wurst, https://bwurst.org/
cb10b7f6
 #
0e73994d
 # To the extent possible under law, the author(s) have dedicated all copyright
 # and related and neighboring rights to this software to the public domain
 # worldwide. This software is distributed without any warranty.
cb10b7f6
 #
0e73994d
 # You should have received a copy of the CC0 Public Domain Dedication along
ec9afd63
 # with this software. If not, see
4f4fa14c
 # https://creativecommons.org/publicdomain/zero/1.0/
0e73994d
 # Nevertheless, in case you use a significant part of this code, we ask (but
 # not require, see the license) that you keep the authors' names in place and
 # return your changes to the public. We would be especially happy if you tell
 # us what you're going to do with this code.
cb10b7f6
 
ec9afd63
 try:					# python3
     import configparser
 except ImportError:			# python2
     import ConfigParser as configparser
     # overwrite default open() function
     # this one supports encoding='...'
     from codecs import open
 
 import os
 import glob
 import pprint
 import re
 import optparse
 import sys
 import gettext
554549e2
 from xml.sax.saxutils import escape
cb10b7f6
 
293deef3
 gettext.textdomain('freewvs')
 _ = gettext.gettext
cb10b7f6
 
ec9afd63
 
6e1a9333
 def versioncompare(safe_version, find_version):
     if safe_version == [""]:
         return True
     for i in range(min(len(find_version), len(safe_version))):
ec9afd63
         if int(find_version[i]) < int(safe_version[i]):
6e1a9333
             return True
ec9afd63
         if int(find_version[i]) > int(safe_version[i]):
6e1a9333
             return False
dbf6f222
     return len(find_version) < len(safe_version)
ec9afd63
 
cb10b7f6
 
ec9afd63
 def vulnprint(appname, version, safeversion, vuln, vfilename, subdir,
               style=None):
3db24287
     appdir = '/'.join(os.path.abspath(vfilename).split('/')[:-1 - subdir])
09bc4973
     if not style:
8366128b
         print("%(appname)s %(version)s (%(safeversion)s) %(vuln)s "
ec9afd63
               "%(appdir)s" % vars())
     elif style == '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())
225e687e
             else:
ec9afd63
                 print(_("Vulnerable %(appname)s %(version)s found, no fixed "
                         "version available.") % vars())
d07c5d59
             if vuln[:3] == "CVE":
4f4fa14c
                 print(_("https://cve.mitre.org/cgi-bin/cvename.cgi?name="
ec9afd63
                         "%(vuln)s") % vars())
d07c5d59
             else:
ec9afd63
                 print(vuln)
293deef3
         else:
ec9afd63
             print(_("%(appname)s %(version)s found.") % vars())
         print("")
     elif style == 'xml':
09bc4973
         state = 'vulnerable'
         if safeversion == 'ok':
             state = 'ok'
ec9afd63
         print('  <app state="%s">' % state)
         print('    <appname>%s</appname>' % escape(appname))
         print('    <version>%s</version>' % escape(version))
         print('    <directory>%s</directory>' % escape(appdir))
09bc4973
         if state == 'vulnerable':
ec9afd63
             print('    <safeversion>%s</safeversion>' % escape(safeversion))
             print('    <vulninfo>%s</vulninfo>' % escape(vuln))
         print('  </app>')
09bc4973
 
cb10b7f6
 
 pp = pprint.PrettyPrinter(indent=4)
 
 # Command-line options
ec9afd63
 parser = optparse.OptionParser(usage="usage: %prog [options] <path>"
                                "[<path2> ...]")
cb10b7f6
 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",
ec9afd63
                   help="Show lots of debugging output, mainly useful"
6e1a9333
                   "for development")
ec9afd63
 parser.add_option("-f", "--fancy", action="store_const", dest="OUTPUT",
                   const="fancy", help="Show more fancy output")
 parser.add_option("-x", "--xml", action="store_const", dest="OUTPUT",
                   const="xml", help="Output results as XML")
cb10b7f6
 opts, args = parser.parse_args()
 
 # Parse vulnerability database
836c4db3
 config = configparser.ConfigParser()
b0e4ad8e
 try:
     config.read(glob.glob('/usr/share/freewvs/*.freewvs'))
     config.read(glob.glob('/usr/local/share/freewvs/*.freewvs'))
3db24287
     config.read(glob.glob(os.path.dirname(sys.argv[0]) + '/freewvsdb/*.freewvs'))
b0e4ad8e
 except configparser.MissingSectionHeaderError as err:
ec9afd63
     print("Error parsing config files: %s" % err)
cb10b7f6
 
 vdb = []
 for sect in config.sections():
6e1a9333
     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'] = []
ec9afd63
     for var in config.get(sect, 'variable').split(","):
         item['variable'].append(re.compile(re.escape(var) +
ba40c23c
                                 r"[^0-9\n\r]*[.]*([0-9.]*[0-9])[^0-9.]"))
6e1a9333
 
     # optional options
ec9afd63
     if config.has_option(sect, 'extra_match'):
         item['extra_match'] = config.get(sect, 'extra_match')
6e1a9333
     else:
         item['extra_match'] = False
35a9bab2
     if config.has_option(sect, 'extra_nomatch'):
         item['extra_nomatch'] = config.get(sect, 'extra_nomatch')
     else:
         item['extra_nomatch'] = False
ec9afd63
     if config.has_option(sect, 'add_minor'):
         item['add_minor'] = config.get(sect, 'add_minor')
6e1a9333
     else:
         item['add_minor'] = False
ec9afd63
     if config.has_option(sect, 'old_safe'):
         item['old_safe'] = config.get(sect, 'old_safe').split(",")
6e1a9333
     else:
         item['old_safe'] = []
 
     vdb.append(item)
 if opts.DEBUG:
     pp.pprint(vdb)
cb10b7f6
 
09bc4973
 if opts.OUTPUT == 'xml':
ec9afd63
     print('<?xml version="1.0" ?>')
     print('<freewvs>')
cb10b7f6
 
 # start the search
 
6e1a9333
 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)
716ac2da
                     try:
ec9afd63
                         file = open(mfile)
3db24287
                     except Exception:
ec9afd63
                         continue
6e1a9333
                     filestr = file.read()
                     file.close()
 
                     if item['extra_match']:
dbf6f222
                         ematch = filestr.find(item['extra_match']) != -1
35a9bab2
                     elif item['extra_nomatch']:
dbf6f222
                         ematch = not filestr.find(item['extra_nomatch']) != -1
6e1a9333
                     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('.')
ec9afd63
                             findversion[-1] = str(int(findversion[-1]) +
                                                   int(item['add_minor']))
6e1a9333
                             findversion = '.'.join(findversion)
 
ec9afd63
                         if not (versioncompare(item['safe'].split('.'),
6e1a9333
                                 findversion.split('.'))) or \
ec9afd63
                                 item['old_safe'].count(findversion) > 0:
6e1a9333
                             if opts.ALL:
                                 if opts.DEBUG:
3db24287
                                     print("File " + mfile)
ec9afd63
                                 vulnprint(item['name'], findversion,
                                           "ok", "", mfile, item['subdir'],
09bc4973
                                           opts.OUTPUT)
6e1a9333
                         else:
                             if opts.DEBUG:
ec9afd63
                                 print("File " + mfile)
                             safev = "9999"
1b677752
                             for ver in item['old_safe']:
ec9afd63
                                 if(versioncompare(ver.split('.'),
                                    findversion.split('.')) and
                                    not versioncompare(ver.split('.'),
                                    safev.split('.'))):
                                     safev = ver
                             if safev == "9999":
                                 safev = item['safe']
 
                             vulnprint(item['name'], findversion,
                                       safev, item['vuln'],
                                       mfile, item['subdir'], opts.OUTPUT)
6e1a9333
 
                     else:
                         if opts.DEBUG:
ec9afd63
                             print("regexp failed for " +
e29fcf64
                                   item['name'] + " on " + mfile)
09bc4973
 
 if opts.OUTPUT == 'xml':
ec9afd63
     print('</freewvs>')