108 lines
4.3 KiB
Python
108 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Crée des Saved Queries réutilisables pour la détection d'attaques web
|
|
(ModSecurity/OWASP, rules 100100-100396 — voir
|
|
config/wazuh_manager/rules/modsecurity_owasp_rules.xml) dans OpenSearch
|
|
Dashboards (Wazuh).
|
|
|
|
Usage:
|
|
docker cp create-saved-queries-web.py wazuh_dashboard:/tmp/
|
|
docker exec wazuh_dashboard python3 /tmp/create-saved-queries-web.py
|
|
|
|
Une fois créées, ouvrir n'importe quelle vue (Security Events, Discover...) >
|
|
barre de recherche > icone dossier (Saved Queries) > sélectionner la query.
|
|
"""
|
|
|
|
import json
|
|
import urllib.request
|
|
import urllib.error
|
|
import ssl
|
|
import base64
|
|
import sys
|
|
import os
|
|
|
|
# ── Configuration ────────────────────────────────────────────────────────────
|
|
DASHBOARD_HOST = "https://localhost:5601"
|
|
DASHBOARD_USER = "kibanaserver"
|
|
DASHBOARD_PASS = os.environ.get("DASHBOARD_PASSWORD", "E9Jpr6586kQ3wYrCS2!")
|
|
|
|
# ── Queries ───────────────────────────────────────────────────────────────────
|
|
QUERIES = [
|
|
{
|
|
"id": "web-attacks-all",
|
|
"title": "Attaques Web - Toutes",
|
|
"description": "Tous les événements ModSecurity/OWASP (bloqués + remontés en anomaly scoring).",
|
|
"kuery": "rule.groups: web",
|
|
},
|
|
{
|
|
"id": "web-attacks-blocked",
|
|
"title": "Attaques Web - Bloquées (WAF)",
|
|
"description": "Requêtes réellement bloquées par le WAF (exclut les anomaly scoring non bloquants, groupe 'warning').",
|
|
"kuery": "rule.groups: web AND NOT rule.groups: warning",
|
|
},
|
|
{
|
|
"id": "web-attacks-critical",
|
|
"title": "Attaques Web - Critiques",
|
|
"description": "Niveau >= 13 : SQLi/RCE/command injection bloquées, XXE, SSRF, patterns répétés (owasp_injection critical/brute_force).",
|
|
"kuery": "rule.groups: web AND rule.level >= 13",
|
|
},
|
|
{
|
|
"id": "web-attacks-scanners",
|
|
"title": "Attaques Web - Scanners / Recon",
|
|
"description": "Détection de scanners/bots (OWASP#9 — groupe scanner_detection).",
|
|
"kuery": "rule.groups: scanner_detection",
|
|
},
|
|
{
|
|
"id": "web-attacks-repeated",
|
|
"title": "Attaques Web - Patterns répétés (brute-force)",
|
|
"description": "Attaques soutenues détectées par fréquence : SQLi (100290), XSS (100291), RCE (100292), path traversal (100293), anomaly scoring répété (100394-100396).",
|
|
"kuery": "rule.id: 100290 OR rule.id: 100291 OR rule.id: 100292 OR rule.id: 100293 OR rule.id: 100394 OR rule.id: 100395 OR rule.id: 100396",
|
|
},
|
|
]
|
|
|
|
# ── Client HTTP ───────────────────────────────────────────────────────────────
|
|
ctx = ssl.create_default_context()
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
|
|
_auth = base64.b64encode(f"{DASHBOARD_USER}:{DASHBOARD_PASS}".encode()).decode()
|
|
_headers = {
|
|
"Content-Type": "application/json",
|
|
"osd-xsrf": "true",
|
|
"Authorization": f"Basic {_auth}",
|
|
}
|
|
|
|
def api(method, path, body=None):
|
|
data = json.dumps(body).encode() if body else None
|
|
req = urllib.request.Request(DASHBOARD_HOST + path, data=data, headers=_headers, method=method)
|
|
try:
|
|
with urllib.request.urlopen(req, context=ctx, timeout=15) as r:
|
|
return json.loads(r.read())
|
|
except urllib.error.HTTPError as e:
|
|
return {"error": e.code, "msg": e.read().decode()[:300]}
|
|
|
|
def main():
|
|
print("=== Creation Saved Queries: Attaques Web ===\n")
|
|
failed = False
|
|
for q in QUERIES:
|
|
body = {
|
|
"attributes": {
|
|
"title": q["title"],
|
|
"description": q["description"],
|
|
"query": {"language": "kuery", "query": q["kuery"]},
|
|
"filters": [],
|
|
}
|
|
}
|
|
r = api("POST", f"/api/saved_objects/query/{q['id']}?overwrite=true", body)
|
|
if "id" in r:
|
|
print(f" OK {q['title']}")
|
|
else:
|
|
print(f" ERR {q['title']}: {r}")
|
|
failed = True
|
|
if failed:
|
|
sys.exit(1)
|
|
print("\n=== Termine ===")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|