هذا الالتزام موجود في:
mazen hassan
2026-07-28 03:31:18 +03:00
الأصل f1d75f5fe7
التزام dbf6c8a033
6 ملفات معدلة مع 382 إضافات و0 حذوفات

12
q4-siem/alerts.json Normal file
عرض الملف

@@ -0,0 +1,12 @@
[
{
"ip": "192.168.1.50",
"threat_score": 100,
"events": [
"Web Brute Force Attempt",
"SSH Failed Login",
"SQL Injection Attempt"
],
"status": "Critical"
}
]

129
q4-siem/analyzer.py Normal file
عرض الملف

@@ -0,0 +1,129 @@
"""
Ghaymah SIEM - Log Correlation Analyzer
-----------------------------------------
Correlates events across 3 log sources (Web/Nginx, SSH/Auth, Database) to
detect a single attacker IP behind multiple attack patterns.
Usage:
python3 analyzer.py -> runs on built-in demo data
python3 analyzer.py web.log auth.log db.log -> runs on real log files
When real file paths are given, only NEW lines since the last run are
processed (tracked via analyzer_state.json), so this script is safe to run
every minute from a cron job without re-scoring the same events twice.
"""
import json
import re
import os
import sys
from collections import defaultdict
STATE_FILE = "analyzer_state.json"
# --- Demo data (used only when no real log paths are provided) ---
DEMO_WEB_LOGS = [
"192.168.1.50 - POST /login HTTP/1.1 401 Unauthorized",
"192.168.1.50 - POST /login HTTP/1.1 401 Unauthorized",
"192.168.1.50 - POST /login HTTP/1.1 401 Unauthorized",
"10.0.0.5 - GET /index.html HTTP/1.1 200 OK",
]
DEMO_SSH_LOGS = [
"Failed password for root from 192.168.1.50 port 22 ssh2",
"Accepted password for admin from 10.0.0.2 port 22 ssh2",
]
DEMO_DB_LOGS = [
"Query executed by 192.168.1.50: SELECT * FROM users WHERE id = '1' OR '1'='1'",
"Query executed by 10.0.0.2: SELECT name FROM products WHERE id = 5",
]
def load_state():
if os.path.exists(STATE_FILE):
with open(STATE_FILE) as f:
return json.load(f)
return {}
def save_state(state):
with open(STATE_FILE, "w") as f:
json.dump(state, f)
def read_new_lines(path, state):
"""Read only the lines appended to `path` since the last recorded offset."""
if not path or not os.path.exists(path):
return []
last_offset = state.get(path, 0)
with open(path, "r", errors="ignore") as f:
f.seek(last_offset)
new_lines = f.readlines()
state[path] = f.tell()
return [line.strip() for line in new_lines if line.strip()]
def flag(bucket, reason, weight):
"""Add score/reason to an IP bucket, without duplicating the same reason twice."""
bucket["score"] += weight
if reason not in bucket["reasons"]:
bucket["reasons"].append(reason)
def analyze_logs(web_path=None, ssh_path=None, db_path=None):
using_real_files = any([web_path, ssh_path, db_path])
state = load_state() if using_real_files else {}
web_logs = read_new_lines(web_path, state) if web_path else DEMO_WEB_LOGS
ssh_logs = read_new_lines(ssh_path, state) if ssh_path else DEMO_SSH_LOGS
db_logs = read_new_lines(db_path, state) if db_path else DEMO_DB_LOGS
suspicious_ips = defaultdict(lambda: {"score": 0, "reasons": []})
# Web logs -> Brute Force detection
for log in web_logs:
if "401 Unauthorized" in log:
ip = log.split()[0]
flag(suspicious_ips[ip], "Web Brute Force Attempt", 10)
# SSH logs -> server intrusion attempts
for log in ssh_logs:
if "Failed password" in log:
match = re.search(r"from (\d+\.\d+\.\d+\.\d+)", log)
if match:
flag(suspicious_ips[match.group(1)], "SSH Failed Login", 20)
# DB logs -> SQL Injection patterns
for log in db_logs:
if "OR '1'='1'" in log or "DROP TABLE" in log.upper():
match = re.search(r"by (\d+\.\d+\.\d+\.\d+):", log)
if match:
flag(suspicious_ips[match.group(1)], "SQL Injection Attempt", 50)
# Build alerts with severity tiers instead of a flat "Critical" for everything
alerts = []
for ip, data in suspicious_ips.items():
if data["score"] >= 30:
severity = "Critical" if data["score"] >= 50 else "Warning"
alerts.append({
"ip": ip,
"threat_score": data["score"],
"events": data["reasons"],
"status": severity,
})
alerts.sort(key=lambda a: a["threat_score"], reverse=True)
with open("alerts.json", "w") as f:
json.dump(alerts, f, indent=4)
if using_real_files:
save_state(state)
print(f"Analysis complete. {len(alerts)} alert(s) saved to alerts.json")
if __name__ == "__main__":
args = sys.argv[1:]
web_p = args[0] if len(args) > 0 else None
ssh_p = args[1] if len(args) > 1 else None
db_p = args[2] if len(args) > 2 else None
analyze_logs(web_p, ssh_p, db_p)

79
q4-siem/dashboard.html Normal file
عرض الملف

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Ghaymah SIEM Dashboard</title>
<style>
body { font-family: system-ui, sans-serif; background-color: #1e1e2f; color: #fff; padding: 20px; }
h1 { color: #00d2ff; text-align: center; }
.meta { text-align: center; color: #9a9ab5; font-size: 0.9em; margin-bottom: 10px; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; background-color: #2a2a40; }
th, td { padding: 15px; text-align: right; border-bottom: 1px solid #444; }
th { background-color: #3f3f5a; }
.critical { color: #ff4d4d; font-weight: bold; }
.warning { color: #ffb84d; font-weight: bold; }
.score-critical { display: inline-block; padding: 5px 10px; background-color: #ff4d4d; border-radius: 5px; color: white; }
.score-warning { display: inline-block; padding: 5px 10px; background-color: #ffb84d; border-radius: 5px; color: #1e1e2f; }
</style>
</head>
<body>
<h1> لوحة تحكم SIEM - التنبيهات الأمنية</h1>
<p class="meta" id="last-updated">جاري التحميل...</p>
<table>
<thead>
<tr>
<th>عنوان IP المشبوه</th>
<th>درجة الخطورة</th>
<th>الأحداث المرصودة (الأنماط)</th>
<th>الحالة</th>
</tr>
</thead>
<tbody id="alerts-table">
<!-- سيتم حقن البيانات هنا بواسطة الجافاسكريبت -->
</tbody>
</table>
<script>
const REFRESH_INTERVAL_MS = 30000; // نفس منطق الـ cron كل دقيقة: نراجع كل 30 ثانية
function loadAlerts() {
fetch('alerts.json', { cache: 'no-store' })
.then(response => response.json())
.then(data => {
const tableBody = document.getElementById('alerts-table');
document.getElementById('last-updated').textContent =
`آخر تحديث: ${new Date().toLocaleTimeString('ar-EG')} | عدد التنبيهات: ${data.length}`;
if (data.length === 0) {
tableBody.innerHTML = '<tr><td colspan="4" style="text-align:center;">لا توجد تهديدات حالياً ✅</td></tr>';
return;
}
tableBody.innerHTML = '';
data.forEach(alert => {
const isCritical = alert.status === 'Critical';
const statusClass = isCritical ? 'critical' : 'warning';
const scoreClass = isCritical ? 'score-critical' : 'score-warning';
const icon = isCritical ? '⛔' : '⚠️';
const row = `<tr>
<td style="font-family: monospace; font-size: 1.1em;">${alert.ip}</td>
<td><span class="${scoreClass}">${alert.threat_score}</span></td>
<td>${alert.events.join(' <strong>+</strong> ')}</td>
<td class="${statusClass}">${icon} ${alert.status}</td>
</tr>`;
tableBody.innerHTML += row;
});
})
.catch(error => {
document.getElementById('alerts-table').innerHTML =
'<tr><td colspan="4" style="text-align:center;">تعذر تحميل alerts.json — تأكد إنك شغّلت analyzer.py وإن الصفحة متفتحة عن طريق سيرفر (مش file:// مباشرة).</td></tr>';
});
}
loadAlerts();
setInterval(loadAlerts, REFRESH_INTERVAL_MS);
</script>
</body>
</html>

عرض الملف

@@ -0,0 +1,30 @@
# SIEM Deployment Strategy on Ghaymah Cloud
## 0. اختبار محلي قبل النشر (Local Testing Note)
`dashboard.html` بيستخدم `fetch()` لقراءة `alerts.json`، وده **مش هيشتغل لو فتحت الملف مباشرة بدبل كليك** (`file://`) بسبب سياسة الـ CORS في المتصفحات الحديثة. للتجربة محلياً قبل الرفع:
```bash
python3 analyzer.py # يولّد alerts.json
python3 -m http.server 8000 # يشغّل الملفين على HTTP بدل file://
```
ثم فتح `http://localhost:8000/dashboard.html`. بعد النشر على غيمة، NGINX هيحل المشكلة دي تلقائياً لأنه بيقدّم الملفات عبر HTTP فعلياً (تفاصيل في القسم 3).
## 1. Storage Architecture (هيكلية التخزين)
نظراً لأن سجلات النظام (Logs) تتضخم بسرعة، لا يجب تخزينها على القرص الأساسي للخادم (OS Disk).
- **الخطوة:** سيتم إنشاء `Block Storage Volume` بحجم مناسب (مثلاً 500GB) من لوحة تحكم "غيمة".
- **التجهيز:** سيتم ربط (Attach) البلوك بالسيرفر وعمل Mount له في مسار مخصص، وليكن `/var/log/ghaymah_siem/`.
- **الميزة:** هذا يضمن عدم توقف السيرفر عن العمل إذا امتلأت مساحة السجلات (Disk Full)، ويسمح بأخذ نسخ احتياطية للـ Volume بشكل مستقل.
- **التشفير:** يتم تفعيل تشفير الـ Block Storage نفسه (Encryption at Rest)، ويُفضّل أن تكون النسخ الاحتياطية للـ Volume غير قابلة للحذف (Immutable) لمدة محددة، لضمان بقاء الأدلة الجنائية (Forensic Evidence) سليمة حتى في حال اختراق السيرفر نفسه.
## 2. Automation & Execution (التشغيل التلقائي)
- سيتم تشغيل `analyzer.py` كـ `Cron Job` يعمل كل دقيقة، مع تمرير مسارات ملفات الـ Logs الحقيقية على الـ Block Storage كـ arguments:
```bash
* * * * * /usr/bin/python3 /opt/siem/analyzer.py /var/log/ghaymah_siem/web.log /var/log/ghaymah_siem/auth.log /var/log/ghaymah_siem/db.log
```
- السكريبت بيحتفظ بموقع آخر سطر اتقرا لكل ملف في `analyzer_state.json`، فكل تشغيل بيعالج **الأسطر الجديدة بس** — ده مهم جداً لأن الـ Logs بتتزايد باستمرار، ولو كل تشغيل أعاد قراءة الملف كامل، الـ CPU هتتحمّل فوق طاقتها والـ Alerts هتتكرر لنفس الحدث.
- السكريبت بيحدّث `alerts.json` بشكل دوري، والداشبورد بيعمل Auto-Refresh كل 30 ثانية.
## 3. Dashboard Hosting (استضافة الواجهة)
- سيتم استخدام `NGINX` كـ Web Server خفيف لاستضافة `dashboard.html` و `alerts.json` (وده بيحل مشكلة الـ CORS في القسم 0 تلقائياً لأن الاستضافة بقت عبر HTTP وليس فتح ملف محلي).
- سيتم توجيه NGINX لقراءة الواجهة وملف الـ JSON من المسار المعزول على الـ Block Storage.
- سيتم تأمين مسار الداشبورد باستخدام Basic Authentication و HTTPS (شهادة Let's Encrypt) لضمان عدم وصول أي شخص غير مصرح له لبيانات التنبيهات الأمنية.
- **تكامل مع Rate Limiting:** بما إن الداشبورد بيعرض بيانات حساسة عن هجمات فعلية، يُنصح بتطبيق نفس مبدأ Rate Limiting المستخدم في تأمين الـ API (راجع تقرير الـ Postmortem) على مسار تسجيل الدخول لصفحة الداشبورد نفسها.