#!/usr/bin/python3 -O
# freewvs 0.1 - the free web vulnerability scanner
#
# https://source.schokokeks.org/freewvs/
#
# Written by schokokeks.org Hosting, https://schokokeks.org
#
# Contributions by
# Hanno Boeck, https://hboeck.de/
# Fabian Fingerle, https://fabian-fingerle.de/
# Bernd Wurst, https://bwurst.org/
#
# 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.
#
# You should have received a copy of the CC0 Public Domain Dedication along
# with this software. If not, see
# https://creativecommons.org/publicdomain/zero/1.0/
# 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.
import configparser
import os
import glob
import pprint
import re
import optparse
import sys
import gettext
from xml.sax.saxutils import escape
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,
style=None):
appdir = '/'.join(os.path.abspath(vfilename).split('/')[:-1 - subdir])
if not style:
print("%(appname)s %(version)s (%(safeversion)s) %(vuln)s "
"%(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())
else:
print(_("Vulnerable %(appname)s %(version)s found, no fixed "
"version available.") % vars())
if vuln[:3] == "CVE":
print(_("https://cve.mitre.org/cgi-bin/cvename.cgi?name="
"%(vuln)s") % vars())
else:
print(vuln)
else:
print(_("%(appname)s %(version)s found.") % vars())
print("")
elif style == 'xml':
state = 'vulnerable'
if safeversion == 'ok':
state = 'ok'
print(' ' % state)
print(' %s' % escape(appname))
print(' %s' % escape(version))
print(' %s' % escape(appdir))
if state == 'vulnerable':
print(' %s' % escape(safeversion))
print(' %s' % escape(vuln))
print(' ')
pp = pprint.PrettyPrinter(indent=4)
# Command-line options
parser = optparse.OptionParser(usage="usage: %prog [options] "
"[ ...]")
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_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")
parser.add_option("-3", "--thirdparty", action="store_true", dest="THIRDPARTY",
help="Scan for third-party components like jquery")
opts, args = parser.parse_args()
# Parse vulnerability database
config = configparser.ConfigParser()
try:
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'))
except configparser.MissingSectionHeaderError as err:
print("Error parsing config files: %s" % err)
vdb = []
for sect in config.sections():
item = {}
if (config.getboolean(sect, 'thirdparty', fallback=False)
and not opts.THIRDPARTY):
continue
# 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'] = re.compile(re.escape(config.get(sect, 'variable'))
+ r"[^0-9\n\r]*[.]*([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, 'extra_nomatch'):
item['extra_nomatch'] = config.get(sect, 'extra_nomatch')
else:
item['extra_nomatch'] = False
if config.has_option(sect, 'path_match'):
item['path_match'] = config.get(sect, 'path_match')
else:
item['path_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)
if opts.OUTPUT == 'xml':
print('')
print('')
# 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)
try:
file = open(mfile, errors='replace')
except Exception:
continue
filestr = file.read()
file.close()
if ((item['extra_match']
and item['extra_match'] not in filestr)
or (item['extra_nomatch']
and item['extra_nomatch'] not in filestr)
or (item['path_match']
and not root.endswith(item['path_match']))):
continue
findversion = item['variable'].search(filestr)
if not findversion:
continue
findversion = findversion.group(1)
# 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.OUTPUT)
else:
if opts.DEBUG:
print("File " + mfile)
safev = "9999"
for ver in item['old_safe']:
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)
if opts.OUTPUT == 'xml':
print('')