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
 
2f5e34d0
 def checkoldsafe(old_safe, find_version):
     find_version_tup = [int(x) for x in find_version.split(".")]
     for oldver in old_safe.split(","):
         oldver_tup = [int(x) for x in oldver.split(".")]
 
         if find_version_tup == oldver_tup:
             return True
         # handle special case where minor version is larger
         if (
             len(find_version_tup) >= 2
             and find_version_tup[:-1] == oldver_tup[:-1]
             and find_version_tup[-1] > oldver_tup[-1]
         ):
             return True
     return False
 
 
a3e985fc
 def vulnprint(appname, version, safeversion, vuln, vfilename, subdir, xml):
     appdir = "/".join(os.path.abspath(vfilename).split("/")[: -1 - subdir])
37777f96
     if not xml:
5aa229db
         print(f"{appname} {version} ({safeversion}) {vuln} {appdir}")
37777f96
     else:
a3e985fc
         state = "vulnerable"
         if safeversion == "ok":
             state = "ok"
5aa229db
         print(f'  <app state="{state}">')
a3e985fc
         print(f"    <appname>{escape(appname)}</appname>")
         print(f"    <version>{escape(version)}</version>")
         print(f"    <directory>{escape(appdir)}</directory>")
         if state == "vulnerable":
             print(f"    <safeversion>{escape(safeversion)}</safeversion>")
             print(f"    <vulninfo>{escape(vuln)}</vulninfo>")
         print("  </app>")
09bc4973
 
cb10b7f6
 
 # Command-line options
37777f96
 parser = argparse.ArgumentParser()
a3e985fc
 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",
 )
37777f96
 opts = parser.parse_args()
cb10b7f6
 
2f775478
 jdir = False
a3e985fc
 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 = []
a3e985fc
 for cfile in glob.glob(jdir + "/*.json"):
830d0457
     with open(cfile, encoding="ascii") as json_file:
2f775478
         data = json.load(json_file)
         jconfig += data
 
f923b55d
 scanfiles = set()
2f775478
 for app in jconfig:
a3e985fc
     for det in app["detection"]:
         scanfiles.add(det["file"])
6e1a9333
 
cb10b7f6
 
37777f96
 if opts.xml:
ec9afd63
     print('<?xml version="1.0" ?>')
a3e985fc
     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:
a3e985fc
                 if not opts.thirdparty and "thirdparty" in item:
2f775478
                     continue
a3e985fc
                 for det in item["detection"]:
                     if filename == det["file"]:
2f775478
                         mfile = os.path.join(root, filename)
                         try:
a3e985fc
                             file = open(mfile, encoding="ascii", errors="replace")
313448ba
                         except OSError:
2f775478
                             continue
18bbf204
                         filestr = file.read(200000)
2f775478
                         file.close()
 
a3e985fc
                         if (
                             "extra_match" in det and det["extra_match"] not in filestr
                         ) or (
                             "extra_nomatch" in det and det["extra_nomatch"] in filestr
                         ):
5b902559
                             continue
 
a3e985fc
                         if "path_match" in det and (
                             not root.endswith(det["path_match"])
                         ):
2f775478
                             continue
 
a3e985fc
                         findversion = re.search(
                             re.escape(det["variable"]) + r"[^0-9\n\r]*[.]*"
                             "([0-9.]*[0-9])[^0-9.]",
                             filestr,
                         )
2f775478
                         if not findversion:
                             continue
                         findversion = findversion.group(1)
 
                         # Very ugly phpbb workaround
a3e985fc
                         if "add_minor" in det:
                             findversion = findversion.split(".")
                             findversion[-1] = str(
                                 int(findversion[-1]) + int(det["add_minor"])
                             )
                             findversion = ".".join(findversion)
 
                         if not versioncompare(item["safe"], findversion) or (
                             "old_safe" in item
                             and checkoldsafe(item["old_safe"], findversion)
                         ):
2f775478
                             if opts.all:
a3e985fc
                                 vulnprint(
                                     item["name"],
                                     findversion,
                                     "ok",
                                     "",
                                     mfile,
                                     det["subdir"],
                                     opts.xml,
                                 )
2f775478
                             continue
 
a3e985fc
                         safev = item["safe"]
                         if "old_safe" in item:
                             for ver in item["old_safe"].split(","):
2f775478
                                 if versioncompare(ver, findversion):
                                     safev = ver
 
a3e985fc
                         vulnprint(
                             item["name"],
                             findversion,
                             safev,
                             item["vuln"],
                             mfile,
                             det["subdir"],
                             opts.xml,
                         )
09bc4973
 
37777f96
 if opts.xml:
a3e985fc
     print("</freewvs>")