Spaces:
Running
Running
File size: 9,446 Bytes
4ddf4fa 20b4565 4ddf4fa 4ab39cb 4ddf4fa 3361d72 4ddf4fa 20b4565 4ddf4fa 20b4565 4ab39cb 20b4565 4ab39cb 20b4565 4ab39cb 20b4565 4ab39cb 20b4565 2e467f6 20b4565 4ab39cb 20b4565 2e467f6 20b4565 4ab39cb 20b4565 2e467f6 20b4565 4ab39cb 20b4565 4ddf4fa 2e467f6 20b4565 582e92d 20b4565 afa2824 4ab39cb 4ddf4fa 20b4565 4ddf4fa 20b4565 4ddf4fa 4ab39cb 582e92d 20b4565 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
#!/usr/bin/env python3
"""C2Sentinel Demo"""
import gradio as gr
import json
import re
from datetime import datetime
from huggingface_hub import hf_hub_download
# Load model from HuggingFace (force_download ensures latest version)
hf_hub_download(repo_id="danielostrow/c2sentinel", filename="c2sentinel.py", local_dir=".", force_download=True)
hf_hub_download(repo_id="danielostrow/c2sentinel", filename="c2_sentinel.safetensors", local_dir=".", force_download=True)
hf_hub_download(repo_id="danielostrow/c2sentinel", filename="c2_sentinel.json", local_dir=".", force_download=True)
hf_hub_download(repo_id="danielostrow/c2sentinel", filename="normalization_params.npz", local_dir=".", force_download=True)
from c2sentinel import C2Sentinel
sentinel = C2Sentinel.load('c2_sentinel')
EXAMPLES = {
"C2 Beacon": [{"timestamp": 1705600000+i*60, "dst_ip": "45.33.32.156", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500} for i in range(8)],
"Metasploit": [{"timestamp": 1705600000+i*30, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300} for i in range(6)],
"SSH Keepalive": [{"timestamp": 1705600000+i*30, "dst_ip": "192.168.1.10", "dst_port": 22, "bytes_sent": 48, "bytes_recv": 48} for i in range(6)],
"Web Traffic": [{"timestamp": 1705600000+i*5, "dst_ip": f"93.184.{i}.34", "dst_port": 443, "bytes_sent": 500+i*100, "bytes_recv": 15000+i*5000} for i in range(5)],
}
def parse_ts(s):
if not s: return None
try:
t = float(s)
return t/1000 if t > 1e12 else t
except: pass
for f in ["%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S", "%b %d %H:%M:%S"]:
try:
dt = datetime.strptime(str(s).strip(), f)
if dt.year == 1900: dt = dt.replace(year=2026)
return dt.timestamp()
except: pass
return None
def parse_logs(content):
conns = []
for line in content.strip().split('\n'):
line = line.strip()
if not line or line.startswith('#'): continue
c = None
# JSON
try:
o = json.loads(line)
c = {
'timestamp': parse_ts(o.get('timestamp') or o.get('ts') or o.get('@timestamp') or o.get('time')),
'dst_ip': o.get('dst_ip') or o.get('dest_ip') or o.get('id.resp_h') or o.get('DestinationIP') or o.get('dst'),
'dst_port': o.get('dst_port') or o.get('dest_port') or o.get('id.resp_p') or o.get('DestinationPort') or o.get('dport'),
'bytes_sent': o.get('bytes_sent') or o.get('orig_bytes') or o.get('SentBytes') or 100,
'bytes_recv': o.get('bytes_recv') or o.get('resp_bytes') or o.get('ReceivedBytes') or 100,
}
except: pass
# Zeek
if not c:
p = line.split('\t')
if len(p) >= 10:
try:
c = {'timestamp': parse_ts(p[0]), 'dst_ip': p[4] if p[4]!='-' else None, 'dst_port': int(p[5]) if p[5]!='-' else 443,
'bytes_sent': int(p[9]) if p[9]!='-' else 100, 'bytes_recv': int(p[10]) if len(p)>10 and p[10]!='-' else 100}
except: pass
# Syslog
if not c:
m = re.match(r'^(\w{3}\s+\d+\s+\d+:\d+:\d+)\s+\S+\s+\S+:\s*(.*)$', line)
if m:
ip = re.search(r'(\d+\.\d+\.\d+\.\d+)', m.group(2))
if ip:
c = {'timestamp': parse_ts(m.group(1)), 'dst_ip': ip.group(1), 'dst_port': 443, 'bytes_sent': 100, 'bytes_recv': 100}
# Key=value
if not c and '=' in line:
kv = dict(re.findall(r'(\w+)=(\S+)', line))
ip = kv.get('DestAddress') or kv.get('DestinationIp') or kv.get('dst') or kv.get('RemoteAddress')
if ip:
c = {'timestamp': parse_ts(kv.get('TimeGenerated') or kv.get('EventTime')) or datetime.now().timestamp(),
'dst_ip': ip, 'dst_port': int(kv.get('DestPort', 443)), 'bytes_sent': 100, 'bytes_recv': 100}
# CSV
if not c and ',' in line:
for p in line.split(','):
ip = re.search(r'(\d+\.\d+\.\d+\.\d+)', p)
if ip:
c = {'timestamp': datetime.now().timestamp(), 'dst_ip': ip.group(1), 'dst_port': 443, 'bytes_sent': 100, 'bytes_recv': 100}
break
# Fallback
if not c:
ip = re.search(r'(\d+\.\d+\.\d+\.\d+)', line)
if ip:
c = {'timestamp': datetime.now().timestamp(), 'dst_ip': ip.group(1), 'dst_port': 443, 'bytes_sent': 100, 'bytes_recv': 100}
if c and c.get('dst_ip') and c.get('timestamp'):
c['dst_port'] = int(c.get('dst_port') or 443)
c['bytes_sent'] = int(c.get('bytes_sent') or 100)
c['bytes_recv'] = int(c.get('bytes_recv') or 100)
conns.append(c)
return conns
def analyze(text, file, example, thresh, wl, bl):
try:
sentinel.whitelist_ips = set()
sentinel.blacklist_ips = set()
if wl: sentinel.add_whitelist(ips=[x.strip() for x in wl.split(',') if x.strip()])
if bl: sentinel.add_blacklist(ips=[x.strip() for x in bl.split(',') if x.strip()])
conns = []
if file:
with open(file, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
conns = parse_logs(content)
if not conns:
try: conns = json.loads(content)
except: pass
if not conns and example in EXAMPLES:
conns = EXAMPLES[example]
if not conns and text:
conns = parse_logs(text)
if not conns:
try: conns = json.loads(text)
except: pass
if not conns: return "No valid connections found"
if len(conns) < 3: return f"Need 3+ connections (found {len(conns)})"
r = sentinel.analyze(conns, threshold=thresh)
out = f"### {'🚨 C2 DETECTED: '+r.c2_type if r.is_c2 else '✅ No C2 Detected'}\n\n"
out += f"**Probability:** {r.c2_probability:.0%} | **Confidence:** {r.confidence:.0%} | **Connections:** {r.connections_analyzed}\n\n"
if r.matched_legitimate_pattern:
out += f"**Matched Pattern:** {r.matched_legitimate_pattern}\n\n"
if r.risk_factors:
out += "**Risk Factors:**\n"
for rf in r.risk_factors[:5]:
out += f"- {rf}\n"
out += "\n"
if r.mitigating_factors:
out += "**Mitigating Factors:**\n"
for mf in r.mitigating_factors[:3]:
out += f"- {mf}\n"
out += "\n"
# Show destination summary
if r.destination_summary:
out += "**Destinations:**\n"
for dest, count in list(r.destination_summary.get('destinations', {}).items())[:5]:
out += f"- `{dest}` ({count} connections)\n"
out += "\n"
# Show time range
if r.time_range:
duration = r.time_range.get('duration', 0)
out += f"**Time Span:** {duration:.0f} seconds\n\n"
# If C2 detected, show the flagged connections
if r.is_c2 and r.suspicious_connections:
out += "**Flagged Connections:**\n```\n"
out += f"{'#':<3} {'Timestamp':<12} {'Destination':<22} {'Sent':<8} {'Recv':<8}\n"
out += "-" * 55 + "\n"
for sc in r.suspicious_connections[:10]:
ts = sc.get('timestamp', 0)
ts_str = str(int(ts))[-6:] if ts else "N/A"
dst = f"{sc.get('dst_ip', '?')}:{sc.get('dst_port', '?')}"
out += f"{sc.get('index', 0):<3} {ts_str:<12} {dst:<22} {sc.get('bytes_sent', 0):<8} {sc.get('bytes_recv', 0):<8}\n"
if len(r.suspicious_connections) > 10:
out += f"... and {len(r.suspicious_connections) - 10} more\n"
out += "```\n\n"
# Show IOCs for threat intel
if r.is_c2 and r.iocs:
out += "**IOCs for Threat Intel:**\n"
out += f"- IPs: `{', '.join(r.iocs.get('ip_addresses', []))}`\n"
out += f"- Ports: `{', '.join(map(str, r.iocs.get('ports', [])))}`\n"
if r.iocs.get('timing_signature'):
ts = r.iocs['timing_signature']
out += f"- Beacon Interval: ~{ts.get('mean_interval', 0):.1f}s (CV: {ts.get('interval_cv', 0):.2f})\n"
return out
except Exception as e:
return f"Error: {e}"
with gr.Blocks(title="C2Sentinel") as demo:
gr.Markdown("## C2Sentinel - Beacon Detection\n[Docs](https://huggingface.co/danielostrow/c2sentinel)")
with gr.Row():
with gr.Column(scale=2):
file_in = gr.File(label="Log File (drag & drop or click)", type="filepath")
text_in = gr.Textbox(label="Or Paste Logs", lines=5, placeholder="Paste log content here...")
ex_in = gr.Dropdown(choices=[""] + list(EXAMPLES.keys()), value="", label="Example")
with gr.Column(scale=1):
thresh = gr.Slider(0.3, 0.8, 0.5, label="Threshold")
wl = gr.Textbox(label="Whitelist", placeholder="8.8.8.8")
bl = gr.Textbox(label="Blacklist", placeholder="10.10.10.10")
btn = gr.Button("Analyze", variant="primary")
out = gr.Markdown()
btn.click(analyze, [text_in, file_in, ex_in, thresh, wl, bl], out)
demo.launch(server_name="0.0.0.0", server_port=7860)
|