roglag / app /generator.py
deploy
deploy ROGLAG threat console
c4f5819
Raw
History Blame Contribute Delete
8.33 kB
"""Synthetic security-log generator (diverse, non-repeating).
Ported from Data/files/gen_logs.py but defaults to an *unseeded* RNG so every
call produces a different mix — used by the "Generate CSV" and "Send mock"
buttons to exercise the realtime pipeline with fresh data each time.
"""
from __future__ import annotations
import csv
import io
import random
from datetime import datetime, timedelta, timezone
from typing import Any, Callable
USERS = ["root", "admin", "oppa", "somchai", "nattapong",
"svc_backup", "www-data", "deploy", "guest", "postgres"]
ADMIN_USERS = {"root", "admin", "oppa"}
BAD_IPS = ["45.137.21.9", "193.27.228.114", "185.220.101.34",
"91.219.236.18", "209.141.56.12"]
PORTS = [22, 80, 443, 3306, 5432, 8080, 3389, 21, 25, 53, 6379, 27017]
HOSTS = ["web-01", "app-02", "db-03", "bastion-01"]
SQLI = ["' OR '1'='1", "UNION SELECT username,password FROM users--",
"'; DROP TABLE sessions;--", "1' AND SLEEP(5)--"]
XSS = ["<script>alert(1)</script>", "<img src=x onerror=alert(document.cookie)>"]
NORMAL_PATHS = ["/", "/login", "/api/products", "/dashboard", "/static/app.js",
"/api/orders?page=2", "/health"]
# Columns used when exporting CSV.
CSV_FIELDS = ["timestamp", "source", "category", "severity", "message",
"src_ip", "dst_ip", "user", "dst_port", "path", "action"]
def _internal_ip() -> str:
return f"10.0.{random.randint(0, 5)}.{random.randint(2, 254)}"
def _external_ip() -> str:
return f"{random.randint(11, 223)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(1, 254)}"
def _rec(t: datetime, source: str, category: str, severity: str, message: str, **extra: Any) -> dict[str, Any]:
r = {"timestamp": t.isoformat(timespec="seconds"), "source": source,
"category": category, "severity": severity, "message": message}
r.update(extra)
return r
def _ev_benign(t):
pick = random.choice(["auth", "web", "fw"])
if pick == "auth":
u, ip = random.choice(USERS), _internal_ip()
msg = f"Accepted password for {u} from {ip} port {random.randint(40000, 60000)} ssh2"
return [_rec(t, "auth", "benign", "info", msg, src_ip=ip, user=u, action="login_success")]
if pick == "web":
ip, path = _external_ip(), random.choice(NORMAL_PATHS)
msg = f'{ip} - - "GET {path} HTTP/1.1" 200 {random.randint(200, 8000)}'
return [_rec(t, "nginx", "benign", "info", msg, src_ip=ip, status=200, path=path)]
ip, dport = _external_ip(), random.choice([80, 443])
msg = f"ALLOW IN={ip} OUT= PROTO=TCP DPT={dport}"
return [_rec(t, "firewall", "benign", "info", msg, src_ip=ip, dst_port=dport, action="allow")]
def _ev_brute_force(t):
u, ip = random.choice(USERS), random.choice(BAD_IPS + [_external_ip()])
out = []
n = random.randint(8, 25)
for i in range(n):
tt = t + timedelta(seconds=i * random.randint(1, 3))
msg = f"Failed password for {u} from {ip} port {random.randint(40000, 60000)} ssh2"
out.append(_rec(tt, "auth", "brute_force", "high", msg, src_ip=ip, user=u, action="login_failed"))
if random.random() < 0.3:
tt = t + timedelta(seconds=n * 2)
out.append(_rec(tt, "auth", "brute_force", "critical",
f"Accepted password for {u} from {ip} port 51234 ssh2",
src_ip=ip, user=u, action="login_success", note="success_after_bruteforce"))
return out
def _ev_port_scan(t):
ip = random.choice(BAD_IPS + [_external_ip()])
out = []
for i, p in enumerate(random.sample(PORTS, k=random.randint(8, 12))):
tt = t + timedelta(milliseconds=i * random.randint(50, 400))
msg = f"DROP IN={ip} OUT= PROTO=TCP DPT={p} FLAGS=SYN"
out.append(_rec(tt, "firewall", "port_scan", "medium", msg, src_ip=ip, dst_port=p, action="drop"))
return out
def _ev_web_attack(t):
ip = random.choice(BAD_IPS + [_external_ip()])
if random.random() < 0.5:
payload, kind = random.choice(SQLI), "sqli"
path = f"/api/products?id={payload}"
status = random.choice([500, 403, 200])
else:
payload, kind = random.choice(XSS), "xss"
path = f"/search?q={payload}"
status = random.choice([200, 403])
msg = f'{ip} - - "GET {path} HTTP/1.1" {status} {random.randint(0, 500)}'
return [_rec(t, "nginx", "web_attack", "high", msg, src_ip=ip, status=status, path=path, attack_kind=kind)]
def _ev_priv_esc(t):
u = random.choice([x for x in USERS if x not in ADMIN_USERS])
host = random.choice(HOSTS)
cmd = random.choice(["/bin/bash", "/usr/bin/cat /etc/shadow", "/usr/bin/passwd root", "/bin/su -"])
msg = f"sudo: {u} : TTY=pts/0 ; PWD=/home/{u} ; USER=root ; COMMAND={cmd}"
return [_rec(t, "system", "priv_esc", "critical", msg, user=u, host=host, command=cmd, action="sudo")]
def _ev_data_exfil(t):
ip = random.choice(BAD_IPS)
src = _internal_ip()
mb = random.randint(500, 8000)
msg = (f"OUTBOUND src={src} dst={ip} bytes={mb * 1024 * 1024} proto=TCP dport=443 "
f"duration={random.randint(60, 600)}s")
return [_rec(t, "firewall", "data_exfil", "critical", msg, src_ip=src, dst_ip=ip, bytes_mb=mb,
off_hours=(t.hour < 6 or t.hour > 22))]
def _ev_c2_beacon(t):
ip = random.choice(BAD_IPS)
src = _internal_ip()
out = []
interval = random.choice([30, 60, 300])
for i in range(random.randint(5, 10)):
tt = t + timedelta(seconds=i * interval)
msg = (f"CONN src={src} dst={ip} dport=443 bytes={random.randint(200, 900)} interval={interval}s")
out.append(_rec(tt, "firewall", "c2_beacon", "critical", msg, src_ip=src, dst_ip=ip, beacon_interval=interval))
return out
GENERATORS: dict[str, Callable] = {
"benign": _ev_benign, "brute_force": _ev_brute_force, "port_scan": _ev_port_scan,
"web_attack": _ev_web_attack, "priv_esc": _ev_priv_esc, "data_exfil": _ev_data_exfil,
"c2_beacon": _ev_c2_beacon,
}
PROFILES = {
"mixed": {"benign": 0.70, "brute_force": 0.06, "port_scan": 0.06, "web_attack": 0.07,
"priv_esc": 0.04, "data_exfil": 0.03, "c2_beacon": 0.04},
"realistic": {"benign": 0.92, "brute_force": 0.025, "port_scan": 0.02, "web_attack": 0.02,
"priv_esc": 0.006, "data_exfil": 0.004, "c2_beacon": 0.005},
"attack": {"benign": 0.20, "brute_force": 0.16, "port_scan": 0.14, "web_attack": 0.16,
"priv_esc": 0.12, "data_exfil": 0.10, "c2_beacon": 0.12},
}
def generate(count: int, profile: str = "mixed", days: int = 7,
seed: int | None = None, now: datetime | None = None) -> list[dict[str, Any]]:
"""Generate up to `count` log records.
seed=None (default) -> different output every call (uses process RNG).
Pass an int seed only when you need reproducible data.
"""
if seed is not None:
random.seed(seed)
profile_weights = PROFILES.get(profile, PROFILES["mixed"])
cats = list(profile_weights.keys())
weights = [profile_weights[c] for c in cats]
base = (now or datetime.now(timezone.utc)) - timedelta(days=days)
span = max(days, 1) * 24 * 3600
records: list[dict[str, Any]] = []
guard = 0
while len(records) < count and guard < count * 4 + 50:
guard += 1
cat = random.choices(cats, weights=weights, k=1)[0]
t = base + timedelta(seconds=random.randint(0, span))
records.extend(GENERATORS[cat](t))
records.sort(key=lambda r: r["timestamp"])
return records[:count]
def generate_live(count: int, profile: str = "mixed") -> list[dict[str, Any]]:
"""Generate records stamped at 'now' for realtime emission (unique each call)."""
now = datetime.now(timezone.utc)
records = generate(count, profile=profile, days=0, seed=None, now=now)
# Re-stamp close to now so the live feed shows current time.
for offset, rec in enumerate(records):
rec["timestamp"] = (now + timedelta(milliseconds=offset)).isoformat(timespec="seconds")
return records
def to_csv(records: list[dict[str, Any]]) -> str:
buf = io.StringIO()
writer = csv.DictWriter(buf, fieldnames=CSV_FIELDS, extrasaction="ignore")
writer.writeheader()
for rec in records:
writer.writerow({k: rec.get(k, "") for k in CSV_FIELDS})
return buf.getvalue()