freewvs
f94d4dcc
 #!/usr/bin/python3 -O
cb10b7f6
 
d0fe2907
 # freewvs - a free web vulnerability scanner
cb10b7f6
 #
d0fe2907
 # https://freewvs.schokokeks.org/
cb10b7f6
 #
5de1f3b4
 # Written 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
 import os
 import glob
 import re
37777f96
 import argparse
ec9afd63
 import sys
2f775478
 import json
4a5f25b6
 import pathlib
a3dcf74c
 from xml.sax.saxutils import escape  # noqa: DUO107
cb10b7f6
 
ec9afd63
 
6e1a9333
 def versioncompare(safe_version, find_version):
59359902
     if safe_version == "":
         return True
ed9df186
     safe_version_tup = [int(x) for x in safe_version.split(".")]
     find_version_tup = [int(x) for x in find_version.split(".")]
     return find_version_tup < safe_version_tup
ec9afd63
 
cb10b7f6
 
ec9afd63
 def vulnprint(appname, version, safeversion, vuln, vfilename, subdir,
37777f96
               xml):
3db24287
     appdir = '/'.join(os.path.abspath(vfilename).split('/')[:-1 - subdir])
37777f96
     if not xml:
8366128b
         print("%(appname)s %(version)s (%(safeversion)s) %(vuln)s "
ec9afd63
               "%(appdir)s" % vars())
37777f96
     else:
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
 
 # Command-line options
37777f96
 parser = argparse.ArgumentParser()
 parser.add_argument("dirs", nargs="*",
                     help="Directories to scan")
 parser.add_argument("-a", "--all", action="store_true",
                     help="Show all webapps found, not just vulnerable")
 parser.add_argument("-x", "--xml", action="store_true",
                     help="Output results as XML")
 parser.add_argument("-3", "--thirdparty", action="store_true",
                     help="Scan for third-party components like jquery")
 opts = parser.parse_args()
cb10b7f6
 
2f775478
 jdir = False
4a5f25b6
 for p in [os.path.dirname(sys.argv[0]) + '/freewvsdb', '/var/lib/freewvs',
           str(pathlib.Path.home()) + "/.cache/freewvs/"]:
2f775478
     if os.path.isdir(p):
         jdir = p
4a5f25b6
         break
2f775478
 if not jdir:
     print("Can't find freewvs json db")
     sys.exit(1)
 
 jconfig = []
 for cfile in glob.glob(jdir + '/*.json'):
     with open(cfile) as json_file:
         data = json.load(json_file)
         jconfig += data
 
f923b55d
 scanfiles = set()
2f775478
 for app in jconfig:
     for det in app['detection']:
         scanfiles.add(det['file'])
6e1a9333
 
cb10b7f6
 
37777f96
 if opts.xml:
ec9afd63
     print('<?xml version="1.0" ?>')
     print('<freewvs>')
cb10b7f6
 
 # start the search
 
37777f96
 for fdir in opts.dirs:
83a6b55c
     for root, dirs, files in os.walk(fdir):
         # this protects us against nested directories causing
         # an exception
         if root.count(os.sep) > 500:
             del dirs[:]
f923b55d
         for filename in scanfiles.intersection(files):
2f775478
             for item in jconfig:
                 if not opts.thirdparty and 'thirdparty' in item:
                     continue
                 for det in item['detection']:
                     if filename == det['file']:
                         mfile = os.path.join(root, filename)
                         try:
                             file = open(mfile, errors='replace')
313448ba
                         except OSError:
2f775478
                             continue
18bbf204
                         filestr = file.read(200000)
2f775478
                         file.close()
 
                         if (('extra_match' in det
5b902559
                              and det['extra_match'] not in filestr)
                                 or ('extra_nomatch' in det
                                     and det['extra_nomatch'] in filestr)):
                             continue
 
                         if ('path_match' in det
                                 and (not root.endswith(det['path_match']))):
2f775478
                             continue
 
                         findversion = re.search(re.escape(det['variable'])
                                                 + r"[^0-9\n\r]*[.]*"
                                                 "([0-9.]*[0-9])[^0-9.]",
                                                 filestr)
                         if not findversion:
                             continue
                         findversion = findversion.group(1)
 
                         # Very ugly phpbb workaround
                         if 'add_minor' in det:
                             findversion = findversion.split('.')
                             findversion[-1] = str(int(findversion[-1])
d9b52caa
                                                   + int(det['add_minor']))
2f775478
                             findversion = '.'.join(findversion)
 
5b902559
                         if ((not versioncompare(item['safe'], findversion))
                                 or ('old_safe' in item
                                     and findversion in
                                     item['old_safe'].split(','))):
2f775478
                             if opts.all:
                                 vulnprint(item['name'], findversion, "ok", "",
                                           mfile, det['subdir'], opts.xml)
                             continue
 
                         safev = item['safe']
                         if 'old_safe' in item:
                             for ver in item['old_safe'].split(','):
                                 if versioncompare(ver, findversion):
                                     safev = ver
 
                         vulnprint(item['name'], findversion, safev,
                                   item['vuln'], mfile, det['subdir'], opts.xml)
09bc4973
 
37777f96
 if opts.xml:
ec9afd63
     print('</freewvs>')