Skip to content

Commit

Permalink
add Microsoft Update Catalog lookup feature to filter potential vulne…
Browse files Browse the repository at this point in the history
…rabilties
  • Loading branch information
DominicBreuker committed Oct 9, 2019
1 parent 96ad543 commit dfdb5ea
Show file tree
Hide file tree
Showing 3 changed files with 283 additions and 0 deletions.
127 changes: 127 additions & 0 deletions .gitignore
@@ -0,0 +1,127 @@
#### Default Python gitignore ####

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
144 changes: 144 additions & 0 deletions muc_lookup.py
@@ -0,0 +1,144 @@
from __future__ import print_function

import sys
import re
import mechanicalsoup


# Progress is a simple progress bar
class Progress:
# __init__ creates a new progress bar
# it starts printing immediately, so create it the moment you
# want to display it
#
# name: a human readable name of the progress bar
# width: the number of steps needed to progress
def __init__(self, name="", width=40):
self.name = name
self.width = width
self.progress = 0

sys.stdout.write("{} [{}]".format(self.name, " " * self.width))
sys.stdout.flush()
sys.stdout.write("\b" * (self.width + 1))

# step moves progress forward
def step(self):
if self.progress >= self.width:
return

sys.stdout.write(".")
sys.stdout.flush()
self.progress += 1

# finish terminates the progress bar
#
# msg: a message to append to the output
def finish(self, msg=""):
sys.stdout.write("] " + msg + "\n")


# some header values Mirosoft seems to like in the header (not extensively tested)
default_headers = {
"authority": "www.catalog.update.microsoft.com",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",
"sec-fetch-mode": "navigate",
"sec-fetch-user": "?1",
"sec-fetch-site": "none",
}

# global browser used to scrape the Microsoft Update Catalog
browser = mechanicalsoup.StatefulBrowser()

# global dict of KBs with their superseeding KBs
superseeded_by = {}

# apply_muc_filter filters the CVEs found by looking up superseeding hotfixes in the
# Microsoft Update Catalog. It returns the CVE iff no superseeding hotfixes are installed.
#
# found: list of CVEs as created by the main script wes.py
# kbs_installed: list of installed hotfixes as seen in systeminfo output
def apply_muc_filter(found, kbs_installed):
kbs_installed = set(kbs_installed)

global superseeded_by
for cve in found:
kb = cve["BulletinKB"]
if kb not in superseeded_by:
superseeded_by[kb] = set(lookup_supersedence(kb))

return {
cve
for cve in found
if len(superseeded_by[cve["BulletinKB"]].intersection(kbs_installed)) == 0
}


# lookup_supersedence returns a list of all KBs superseeding the given KB
# iterates over entries for all Mirosoft products. That is, it does not
# attempt to identify the product. My assumption is that if we return KBs
# that do not apply to the system under anaylsis then this KB will not be
# present on that system so that the result remains the same.
# For example, if we return a KB only applicable to Windows Server 2016,
# not to Windows 10, then this KB will not be installed on the system
# and accordingly the CVE will not be filtered.
#
# kb: the KB to be looked up
def lookup_supersedence(kb):
browser.open(
"https://www.catalog.update.microsoft.com/Search.aspx?q={}".format(kb),
headers=default_headers,
)
rows = browser.get_current_page().find(id="ctl00_catalogBody_updateMatches")
updates = rows.find_all(
"a", {"onclick": re.compile(r"goToDetails\(\"[a-zA-Z0-9-]+\"\)")}
)
ids = [a["id"].split("_")[0] for a in updates]

kbids = set()
p = Progress(
name="Looking up superseeding hotfixes for potentiall missing KB"
+ kb
+ " in catalog.update.microsoft.com",
width=len(ids),
)
for uid in ids:
kbids = kbids.union(lookup_supersedence_by_uid(uid))
p.step()

p.finish(msg="found: [" + ", ".join(kbids) + "]")

return [kbid.lstrip("KB") for kbid in kbids]


# lookup_supersedence_by_uid looks up a list of superseeding KBs for a
# Microsoft Update ID. The Microsoft Update Catalog seems to close over
# transitive supersedence relationships so there is not need for recursive
# lookups.
#
# uid: the Microsoft Update ID of the update to check
def lookup_supersedence_by_uid(uid):
browser.open(
"https://www.catalog.update.microsoft.com/ScopedViewInline.aspx?updateid={}".format(
uid
),
headers=default_headers,
)
supers = browser.get_current_page().find_all("div", {"id": "supersededbyInfo"})
for s in supers:
kbids = re.findall("(KB[0-9]+)", s.text.strip())
if len(kbids) < 1:
kbids = []
return set(kbids)


# can also run standalone to check single KB
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Run this script with a Microsoft HotfixID as the single argument")
print("Example: python muc_lookup.py KB4515384")
sys.exit(1)

kb = sys.argv[1]
kbids = lookup_supersedence(kb)
print("{} is superseeded by {}".format(kb, ["KB{}".format(kbid) for kbid in kbids]))
12 changes: 12 additions & 0 deletions wes.py
Expand Up @@ -14,6 +14,9 @@
import logging
from collections import Counter, OrderedDict

from muc_lookup import apply_muc_filter
import copy


# Python 2 compatibility
if sys.version_info.major == 2:
Expand Down Expand Up @@ -162,6 +165,7 @@ def main():

# Append manually specified KBs to list of hotfixes
hotfixes = list(set(hotfixes + manual_hotfixes))
hotfixes_orig = copy.deepcopy(hotfixes)

# Load definitions from definitions.zip (default) or user-provided location
print('[+] Loading definitions')
Expand Down Expand Up @@ -198,6 +202,12 @@ def main():
else:
filtered = found

# If specified, lookup superseeding KBs in the Microsoft Update Catalog
# and remove CVEs if a superseeding KB is installed.
if args.muc_lookup:
print("[+] Looking up superseeding hotfixes in the Microsoft Update Catalog")
filtered = apply_muc_filter(filtered, hotfixes_orig)

# Split up list of KBs and the potential Service Packs/Cumulative updates available
kbs, sp = get_patches_servicepacks(filtered, cves, productfilter)

Expand Down Expand Up @@ -769,6 +779,8 @@ def parse_arguments():
parser.add_argument('-i', '--impact', dest='impacts', nargs='+', default='', help='Only display vulnerabilities with a given impact')
parser.add_argument('-s', '--severity', dest='severities', nargs='+', default='', help='Only display vulnerabilities with a given severity')
parser.add_argument('-o', '--output', action='store', dest='outputfile', nargs='?', help='Store results in a file')
parser.add_argument("--muc-lookup", dest="muc_lookup", action="store_true", help="Hide vulnerabilities if installed hotfixes are listed in the Microsoft Update Catalog as superseding hotfixes for the original BulletinKB",
)
parser.add_argument('-h', '--help', action='help', help='Show this help message and exit')

# Always show full help when no arguments are provided
Expand Down

0 comments on commit dfdb5ea

Please sign in to comment.