#!/usr/bin/python3 -O # freewvs - a free web vulnerability scanner # # https://freewvs.schokokeks.org/ # # 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/ import argparse import glob import json import os import pathlib import re import sys from xml.sax.saxutils import escape # noqa: DUO107 def versioncompare(safe_version, find_version): if safe_version == "": return True 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 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 def vulnprint(appname, version, safeversion, vuln, vfilename, subdir, xml): appdir = "/".join(os.path.abspath(vfilename).split("/")[: -1 - subdir]) if not xml: print(f"{appname} {version} ({safeversion}) {vuln} {appdir}") else: state = "vulnerable" if safeversion == "ok": state = "ok" print(f' ') print(f" {escape(appname)}") print(f" {escape(version)}") print(f" {escape(appdir)}") if state == "vulnerable": print(f" {escape(safeversion)}") print(f" {escape(vuln)}") print(" ") # Command-line options 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() jdir = False for p in [ os.path.dirname(sys.argv[0]) + "/freewvsdb", "/var/lib/freewvs", str(pathlib.Path.home()) + "/.cache/freewvs/", ]: if os.path.isdir(p): jdir = p break if not jdir: print("Can't find freewvs json db") sys.exit(1) jconfig = [] for cfile in glob.glob(jdir + "/*.json"): with open(cfile, encoding="ascii") as json_file: data = json.load(json_file) jconfig += data scanfiles = set() for app in jconfig: for det in app["detection"]: scanfiles.add(det["file"]) if opts.xml: print('') print("") # start the search for fdir in opts.dirs: 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[:] for filename in scanfiles.intersection(files): 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, encoding="ascii", errors="replace") except OSError: continue filestr = file.read(200000) file.close() if ( "extra_match" in det 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"]) ): 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]) + int(det["add_minor"]) ) findversion = ".".join(findversion) if not versioncompare(item["safe"], findversion) or ( "old_safe" in item and checkoldsafe(item["old_safe"], findversion) ): 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, ) if opts.xml: print("")