Qualys: vulnérabilités severity 3/4/5 dans résultats recherche (API VMDR)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
40b0307f62
commit
f04d04224d
@ -8,7 +8,7 @@ from ..dependencies import get_db, get_current_user, get_user_perms, can_view, c
|
||||
from ..services.qualys_service import (
|
||||
sync_server_qualys, search_assets_api, get_all_tags_api,
|
||||
create_tag_api, delete_tag_api, add_tag_to_asset_api,
|
||||
remove_tag_from_asset_api, resync_all_tags,
|
||||
remove_tag_from_asset_api, resync_all_tags, get_vuln_counts,
|
||||
)
|
||||
from ..config import APP_NAME
|
||||
|
||||
@ -408,13 +408,23 @@ async def qualys_search(request: Request, db=Depends(get_db),
|
||||
|
||||
api_msg = f"{len(assets)} résultat(s) — source: {source}"
|
||||
|
||||
# Enrichir avec vulnérabilités (severity 3,4,5, Confirmed/Potential, Active)
|
||||
vuln_map = {}
|
||||
if assets:
|
||||
asset_ids = [str(a.qualys_asset_id) for a in assets if a.qualys_asset_id]
|
||||
if asset_ids:
|
||||
try:
|
||||
vuln_map = get_vuln_counts(db, ",".join(asset_ids[:50]))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
all_tags = db.execute(text("SELECT qualys_tag_id, name FROM qualys_tags ORDER BY name")).fetchall()
|
||||
|
||||
ctx = base_context(request, db, user)
|
||||
ctx.update({
|
||||
"app_name": APP_NAME, "assets": assets, "search": search,
|
||||
"field": field, "api_msg": api_msg,
|
||||
"all_tags": all_tags,
|
||||
"all_tags": all_tags, "vuln_map": vuln_map,
|
||||
"can_edit_qualys": can_edit(perms, "qualys"),
|
||||
"msg": request.query_params.get("msg"),
|
||||
})
|
||||
|
||||
@ -369,3 +369,69 @@ def _find_asset_by_hostname(qualys_url, qualys_user, qualys_pass, hostname, prox
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def get_vuln_counts(db, qualys_asset_ids):
|
||||
"""Recupere le nombre de vulnerabilites actives severity 3,4,5 pour un ou plusieurs assets.
|
||||
qualys_asset_ids: str (un ID ou liste separee par virgules)
|
||||
Retourne dict {asset_id: {severity3, severity4, severity5, total, confirmed, potential}}
|
||||
"""
|
||||
qualys_url, qualys_user, qualys_pass, qualys_proxy = _get_qualys_creds(db)
|
||||
if not qualys_user or not qualys_asset_ids:
|
||||
return {}
|
||||
proxies = {"https": qualys_proxy, "http": qualys_proxy} if qualys_proxy else None
|
||||
|
||||
try:
|
||||
r = requests.post(
|
||||
f"{qualys_url}/api/2.0/fo/asset/host/vm/detection/",
|
||||
data={
|
||||
"action": "list",
|
||||
"ids": str(qualys_asset_ids),
|
||||
"severities": "3,4,5",
|
||||
"status": "New,Active,Re-Opened",
|
||||
"show_results": "0",
|
||||
"output_format": "XML",
|
||||
},
|
||||
auth=(qualys_user, qualys_pass),
|
||||
verify=False, timeout=120, proxies=proxies,
|
||||
headers={"X-Requested-With": "Python"},
|
||||
)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
if r.status_code != 200:
|
||||
return {}
|
||||
|
||||
txt = r.text
|
||||
results = {}
|
||||
|
||||
for host_block in txt.split("<HOST>")[1:]:
|
||||
host_block = host_block.split("</HOST>")[0]
|
||||
host_id = (parse_xml(host_block, "ID") or [""])[0]
|
||||
if not host_id:
|
||||
continue
|
||||
|
||||
counts = {"severity3": 0, "severity4": 0, "severity5": 0,
|
||||
"total": 0, "confirmed": 0, "potential": 0}
|
||||
|
||||
for det_block in host_block.split("<DETECTION>")[1:]:
|
||||
det_block = det_block.split("</DETECTION>")[0]
|
||||
severity = (parse_xml(det_block, "SEVERITY") or ["0"])[0]
|
||||
det_type = (parse_xml(det_block, "TYPE") or [""])[0]
|
||||
|
||||
sev = int(severity) if severity.isdigit() else 0
|
||||
if sev < 3:
|
||||
continue
|
||||
if det_type not in ("Confirmed", "Potential"):
|
||||
continue
|
||||
|
||||
counts["total"] += 1
|
||||
if sev == 3: counts["severity3"] += 1
|
||||
elif sev == 4: counts["severity4"] += 1
|
||||
elif sev == 5: counts["severity5"] += 1
|
||||
if det_type == "Confirmed": counts["confirmed"] += 1
|
||||
elif det_type == "Potential": counts["potential"] += 1
|
||||
|
||||
results[str(host_id)] = counts
|
||||
|
||||
return results
|
||||
|
||||
@ -145,6 +145,7 @@ function updateBulkTag() {
|
||||
<th class="p-2">IP</th>
|
||||
<th class="p-2">OS</th>
|
||||
<th class="p-2">Agent</th>
|
||||
<th class="p-2">Vulns</th>
|
||||
<th class="text-left p-2">Tags</th>
|
||||
<th class="p-2">Actions</th>
|
||||
</tr></thead>
|
||||
@ -165,6 +166,17 @@ function updateBulkTag() {
|
||||
{% if agent %}<span class="badge {% if 'ACTIVE' in agent %}badge-green{% else %}badge-gray{% endif %}">{{ agent[:10] }}</span>
|
||||
{% else %}<span class="text-gray-600 text-xs">N/A</span>{% endif %}
|
||||
</td>
|
||||
<td class="p-2 text-center">
|
||||
{% set vc = vuln_map.get(qid|string, {}) if vuln_map else {} %}
|
||||
{% if vc and vc.total > 0 %}
|
||||
<span title="S3:{{ vc.severity3 }} S4:{{ vc.severity4 }} S5:{{ vc.severity5 }} | Confirmed:{{ vc.confirmed }} Potential:{{ vc.potential }}">
|
||||
{% if vc.severity5 > 0 %}<span class="badge badge-red">{{ vc.severity5 }} crit</span> {% endif %}
|
||||
{% if vc.severity4 > 0 %}<span class="badge badge-yellow">{{ vc.severity4 }} high</span> {% endif %}
|
||||
{% if vc.severity3 > 0 %}<span class="text-gray-400 text-xs">+{{ vc.severity3 }} med</span>{% endif %}
|
||||
</span>
|
||||
{% elif vc is mapping %}<span class="text-cyber-green text-xs">0</span>
|
||||
{% else %}<span class="text-gray-600 text-xs">-</span>{% endif %}
|
||||
</td>
|
||||
<td class="p-2 text-gray-400" style="max-width:300px">{{ (tl or '-')[:80] }}</td>
|
||||
<td class="p-2 text-center">
|
||||
{% if qid %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user