Бывает, долго не заходил на сервер и накопилось какое-то количество обновлений. А что за обновления, какие проблемы они решают(или добавляют новых?) С помощью oscap можно увидеть текущие уязвимости в установленных пакетах. Заодно перейти на сайт дистрибутива и узнать статус уязвимости.
Для себя написал скрипт(поддерживает только Debian/Ubuntu)
#!/usr/bin/env python3
import subprocess as sp
import os
import xml.etree.ElementTree as ET
import json
import sys
from datetime import datetime
"""
Скрипт показывает текущие уязвимости в пакетах системы
создает html отчет
wget -O {filename_bz2} f"https://security-metadata.canonical.com/oval/com.ubuntu.{codename}.usn.oval.xml.bz2"
f"bzip2 -d {filename_bz2}"
f"oscap oval eval --results {report_xml} {xml_file}"
wget https://www.debian.org/security/oval/oval-definitions-$(lsb_release -cs).xml.bz2
bunzip oval-definitions-$(lsb_release -cs).xml.bz2
oscap oval eval --report report.html oval-definitions-$(lsb_release -cs).xml
"""
def run_cmd(cmd_: str) -> dict:
""" Выполняет команду в bash
""" ret = {'stdout': '', 'stderr': ''}
# print(f"bash: {cmd_}")
try:
if sys.version_info > (3, 9):
# result = sp.run(cmd_, capture_output=True, encoding='utf-8', shell=True, user=user, env={'HOME': home})
result = sp.run(cmd_, capture_output=True, encoding='utf-8', shell=True)
ret['stdout'] = result.stdout.strip().split('\n')[0]
ret['stderr'] = result.stderr.strip()
elif sys.version_info > (3, 8):
result = sp.run(cmd_, capture_output=True, encoding='utf-8', shell=True)
ret['stdout'] = result.stdout.strip().split('\n')[0]
ret['stderr'] = result.stderr.strip()
else:
result = sp.Popen(cmd_.split(), stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE, encoding='utf-8')
ret['stdout'], ret['stderr'] = result.communicate()
ret['stdout'] = ret['stdout'].strip().split('\n')[0]
except Exception as err:
ret['stderr'] = err
print(ret)
return ret
def get_os_info() -> (str, str):
"""Получает кодовое имя Ubuntu (например, focal, jammy)."""
cn = run_cmd("lsb_release -cs")['stdout'].lower()
osid = run_cmd("lsb_release -is")['stdout'].lower()
return cn, osid
def download_and_extract_oval(codename: str, os_id: str) -> str:
"""Скачивает и распаковывает OVAL-файл."""
url = {
'ubuntu': f'https://security-metadata.canonical.com/oval/com.ubuntu.{codename}.usn.oval.xml.bz2',
'debian': f'https://www.debian.org/security/oval/oval-definitions-{codename}.xml.bz2'
}
filename_bz2 = f"{codename}.oval.xml.bz2"
run_cmd(f"wget -O {filename_bz2} {url[os_id]}")
run_cmd(f"bzip2 -d {filename_bz2}")
print('Сигнатуры скачаны и распакованы')
return filename_bz2.replace('.bz2', '')
def check_deps(deb_pkg: str) -> bool:
ret = True
check_cmd = 'oscap -h'
if run_cmd(check_cmd)['stderr']:
run_cmd(f"apt install {deb_pkg} -y")
if run_cmd(check_cmd)['stderr']:
ret = False
check_cmd = 'bzip2 -h'
if run_cmd(check_cmd)['stderr']:
run_cmd('apt install bzip2 -y')
return ret
def run_oscap_eval(xml_file, hostname, codename):
"""Запускает oscap и генерирует отчет."""
report_xml = "oval-results.xml"
run_cmd(f"oscap oval eval --report {hostname}-{codename}.html --results {report_xml} {xml_file}")
print('oscap запущен')
return report_xml
def parse_oval_results(xml_file):
"""Парсит XML-результаты и возвращает список найденных уязвимостей (status == 'false')."""
tree = ET.parse(xml_file)
root = tree.getroot()
ns = {
"oval": "http://oval.mitre.org/XMLSchema/oval-results-5",
'def': 'http://oval.mitre.org/XMLSchema/oval-definitions-5',
}
# Найдем <results>
results = root.find('oval:results', ns)
# Найдем <system> внутри <results>
system = results.find('oval:system', ns)
# Найдем <definitions> внутри <system>
definitions = system.find('oval:definitions', ns)
results = []
for definition in definitions.findall('oval:definition', ns):
def_id = definition.get('definition_id')
res = definition.get('result')
# ver = definition.get('version')
if res == 'true':
# print(definition.keys())
# print(f"ID: {def_id}, Result: {res} Version: {ver}") path = f".//def:definition[@id='{def_id}']"
element = root.find(path, ns)
# element = defs.find(path, ns)
if element is not None and element.get('class') != 'inventory':
# print(element.attrib)
title = element.find(".//def:title", ns)
adv = element.find(".//def:advisory", ns)
if adv:
severity = adv.find(".//def:severity", ns)
# <issued date="2025-06-09"/>
issued_date = adv.find(".//def:issued", ns)
if isinstance(issued_date, ET.Element):
issued_date = issued_date.get('date')
else:
issued_date = None
cve_ = adv.find(".//def:cve", ns)
if isinstance(cve_, ET.Element):
cve = cve_.get('href')
else:
cve = None
else:
# debian
issued_date = element.find(".//def:date", ns)
if issued_date:
issued_date = issued_date.text
# issued_date = issued_date.text
cve_ = element.find(".//def:reference", ns)
if isinstance(cve_, ET.Element):
cve = cve_.get('ref_url')
else:
cve = None
severity = None
if title is not None:
t = title.text
else:
t = ''
if severity is not None:
s = severity.text
else:
s = ''
# if isinstance(cve, ET.Element):
print(f"{issued_date}\t[{s}]\t{cve}\t{t}")
# else:
# print(f"{issued_date}\t[{s}]\t\t{t}")
def main():
codename, os_id = get_os_info()
hostname = run_cmd("hostname")['stdout']
print(f"Сервер: {hostname}, ось: {os_id}, версия: {codename}")
oval_xml = download_and_extract_oval(codename, os_id)
dep_pkg = 'openscap-scanner' if codename == 'noble' or os_id == 'debian' else 'libopenscap8'
print(f"Зависимость: {dep_pkg}")
if check_deps(dep_pkg):
print('oscap установлен')
report_xml = run_oscap_eval(oval_xml, hostname, codename)
parse_oval_results(report_xml)
print('-' * 30)
print('обработка завершена')
else:
print('Ошибка! Зависимости не установлены')
if __name__ == "__main__":
main()