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:
Khalid MOUTAOUAKIL 2026-04-06 22:38:56 +02:00
parent 40b0307f62
commit f04d04224d
3 changed files with 90 additions and 2 deletions

View File

@ -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"),
})

View File

@ -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

View File

@ -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 %}