Zion Boggan
repos/security-portfolio/soc-automation-lab/index.html
zionboggan.com ↗
293 lines · html
History for this file →
1
<!doctype html>
2
<html lang="en">
3
<head>
4
<meta charset="utf-8">
5
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
<title>SOC Automation Lab | Zion Boggan</title>
7
<meta name="description" content="An end-to-end detection-to-response pipeline wiring Wazuh detection into Shuffle SOAR and TheHive case management, deployed and validated live against a replayed SSH brute force.">
8
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%230c0e12'/%3E%3Ctext x='16' y='22' font-family='monospace' font-size='15' fill='%236cc7b8' text-anchor='middle'%3Ezb%3C/text%3E%3C/svg%3E">
9
<style>
10
  :root{
11
    --bg:#0c0e12; --bg2:#0f1217; --panel:#14181f; --panel2:#171c24;
12
    --line:#222936; --line2:#2c3543;
13
    --ink:#e8eaed; --soft:#c3cad4; --muted:#8a94a3; --faint:#5d6675;
14
    --accent:#6cc7b8; --accent-dim:#274b47;
15
    --maxw:1020px;
16
  }
17
  *{box-sizing:border-box;}
18
  html{scroll-behavior:smooth;}
19
  body{margin:0;background:var(--bg);color:var(--ink);
20
    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
21
    font-size:16px;line-height:1.65;-webkit-font-smoothing:antialiased;}
22
  .mono{font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;}
23
  a{color:var(--accent);text-decoration:none;}
24
  a:hover{color:#8fe0d2;}
25
  .wrap{max-width:var(--maxw);margin:0 auto;padding:0 24px;}
26
 
27
  /* nav */
28
  nav{position:sticky;top:0;z-index:20;background:rgba(12,14,18,.82);
29
    backdrop-filter:blur(10px);border-bottom:1px solid var(--line);}
30
  nav .wrap{display:flex;align-items:center;justify-content:space-between;height:58px;}
31
  nav .brand{font-weight:600;letter-spacing:.2px;}
32
  nav .brand .dot{color:var(--accent);}
33
  nav .links{display:flex;gap:26px;font-size:13.5px;}
34
  nav .links a{color:var(--muted);}
35
  nav .links a:hover{color:var(--ink);}
36
  @media(max-width:680px){nav .links{display:none;}}
37
 
38
  /* hero */
39
  header.hero{padding:74px 0 54px;border-bottom:1px solid var(--line);
40
    background:radial-gradient(900px 380px at 78% -10%, #11201e 0%, transparent 60%);}
41
  .avail{font-size:12.5px;letter-spacing:1.5px;text-transform:uppercase;color:var(--accent);
42
    display:flex;align-items:center;gap:9px;margin-bottom:20px;}
43
  .avail .pulse{width:7px;height:7px;border-radius:50%;background:var(--accent);
44
    box-shadow:0 0 0 0 rgba(108,199,184,.5);animation:p 2.4s infinite;}
45
  @keyframes p{0%{box-shadow:0 0 0 0 rgba(108,199,184,.45)}70%{box-shadow:0 0 0 8px rgba(108,199,184,0)}100%{box-shadow:0 0 0 0 rgba(108,199,184,0)}}
46
  h1{font-size:clamp(34px,6vw,52px);line-height:1.05;margin:0 0 8px;letter-spacing:-1px;font-weight:680;}
47
  .hero .sub{font-size:clamp(16px,2.4vw,20px);color:var(--soft);margin:0 0 24px;font-weight:500;}
48
  .hero .lede{max-width:660px;color:var(--soft);font-size:17px;margin:0 0 28px;}
49
  .hero .lede b{color:var(--ink);font-weight:600;}
50
  .cta{display:flex;flex-wrap:wrap;gap:12px;align-items:center;}
51
  .btn{display:inline-flex;align-items:center;gap:8px;padding:10px 18px;border-radius:8px;
52
    font-size:14.5px;font-weight:550;border:1px solid var(--line2);color:var(--ink);background:var(--panel);}
53
  .btn:hover{border-color:var(--accent-dim);background:var(--panel2);color:var(--ink);}
54
  .btn.primary{background:var(--accent);color:#06231f;border-color:var(--accent);font-weight:650;}
55
  .btn.primary:hover{background:#8fe0d2;color:#06231f;}
56
  .meta{margin-top:26px;display:flex;flex-wrap:wrap;gap:8px 22px;font-size:13px;color:var(--muted);}
57
  .meta .mono{color:var(--faint);}
58
 
59
  /* sections */
60
  section{padding:64px 0;border-bottom:1px solid var(--line);}
61
  .shead{display:flex;align-items:baseline;gap:14px;margin-bottom:30px;}
62
  .shead .idx{font-size:13px;color:var(--accent);letter-spacing:1px;}
63
  .shead h2{font-size:14px;letter-spacing:2px;text-transform:uppercase;color:var(--muted);margin:0;font-weight:600;}
64
  .shead .rule{flex:1;height:1px;background:var(--line);}
65
 
66
  /* flagship */
67
  .flag{background:linear-gradient(180deg,var(--panel) 0%,var(--bg2) 100%);
68
    border:1px solid var(--line2);border-radius:14px;overflow:hidden;}
69
  .flag .top{padding:30px 32px 8px;}
70
  .flag .tag{font-size:12px;letter-spacing:1.5px;text-transform:uppercase;color:var(--accent);margin-bottom:12px;}
71
  .flag h3{font-size:27px;margin:0 0 6px;letter-spacing:-.4px;}
72
  .flag h3 .v{font-size:13px;color:var(--muted);font-weight:500;margin-left:8px;letter-spacing:0;}
73
  .flag .grid{display:grid;grid-template-columns:1.25fr 1fr;gap:30px;padding:14px 32px 30px;}
74
  .flag p{color:var(--soft);margin:0 0 16px;}
75
  .flag .stats{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:6px;}
76
  .stat{background:var(--bg);border:1px solid var(--line);border-radius:9px;padding:13px 15px;}
77
  .stat .n{font-size:21px;font-weight:680;color:var(--ink);}
78
  .stat .k{font-size:12px;color:var(--muted);margin-top:2px;}
79
  .spec{background:var(--bg);border:1px solid var(--line);border-radius:10px;padding:18px 18px;}
80
  .spec .sk{font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--faint);margin-bottom:10px;}
81
  .spec ul{margin:0;padding:0;list-style:none;font-size:13.5px;}
82
  .spec li{padding:6px 0;border-top:1px solid var(--line);color:var(--soft);display:flex;justify-content:space-between;gap:14px;}
83
  .spec li:first-child{border-top:none;}
84
  .spec li span{color:var(--muted);}
85
  .flag .foot{padding:0 32px 28px;display:flex;gap:18px;flex-wrap:wrap;font-size:14px;}
86
  @media(max-width:720px){.flag .grid{grid-template-columns:1fr;}}
87
 
88
  /* lab cards */
89
  .cards{display:grid;grid-template-columns:1fr 1fr;gap:20px;}
90
  @media(max-width:680px){.cards{grid-template-columns:1fr;}}
91
  .card{border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--panel);
92
    display:flex;flex-direction:column;transition:border-color .15s,transform .15s;}
93
  .card:hover{border-color:var(--accent-dim);transform:translateY(-2px);}
94
  .card .thumb{height:172px;overflow:hidden;border-bottom:1px solid var(--line);background:#fff;}
95
  .card .thumb img{width:100%;height:100%;object-fit:cover;object-position:top left;display:block;}
96
  .card .body{padding:18px 20px 20px;display:flex;flex-direction:column;flex:1;}
97
  .card h3{margin:0 0 9px;font-size:17px;}
98
  .card p{margin:0 0 14px;font-size:14px;color:var(--soft);flex:1;}
99
  .tags{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px;}
100
  .tags span{font-size:11.5px;color:var(--muted);background:var(--bg);border:1px solid var(--line);
101
    border-radius:5px;padding:3px 8px;}
102
  .card .lnk{font-size:13.5px;font-family:ui-monospace,Menlo,monospace;}
103
  .card .lnk::after{content:" โ†’";}
104
 
105
  /* research */
106
  .rlede{color:var(--soft);max-width:680px;margin:-6px 0 26px;}
107
  .research{display:flex;flex-direction:column;gap:0;border:1px solid var(--line);border-radius:12px;overflow:hidden;}
108
  .ritem{display:grid;grid-template-columns:120px 1fr auto;gap:18px;align-items:center;
109
    padding:18px 22px;border-top:1px solid var(--line);}
110
  .ritem:first-child{border-top:none;}
111
  .ritem:hover{background:var(--panel);}
112
  .ritem .cls{font-size:11px;letter-spacing:.5px;text-transform:uppercase;color:var(--accent);}
113
  .ritem h3{margin:0 0 3px;font-size:16px;}
114
  .ritem p{margin:0;font-size:13.5px;color:var(--muted);}
115
  .ritem .go{font-family:ui-monospace,Menlo,monospace;font-size:13px;white-space:nowrap;}
116
  @media(max-width:680px){.ritem{grid-template-columns:1fr;gap:6px;}.ritem .go{margin-top:4px;}}
117
  .progs{margin-top:22px;}
118
  .progs .sk{font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--faint);margin-bottom:11px;}
119
  .progs .row{display:flex;flex-wrap:wrap;gap:7px;}
120
  .progs .row span{font-size:12.5px;color:var(--soft);background:var(--panel);border:1px solid var(--line);
121
    border-radius:6px;padding:4px 10px;}
122
 
123
  /* credentials */
124
  .cred{display:grid;grid-template-columns:1.1fr 1fr;gap:28px;}
125
  @media(max-width:680px){.cred{grid-template-columns:1fr;}}
126
  .cred p{color:var(--soft);margin:0 0 14px;}
127
  .cred .role{font-size:14px;color:var(--muted);}
128
  .cred .role b{color:var(--ink);font-weight:600;}
129
  .certs{list-style:none;margin:0;padding:0;}
130
  .certs li{padding:9px 0;border-top:1px solid var(--line);font-size:14px;color:var(--soft);
131
    display:flex;gap:10px;align-items:baseline;}
132
  .certs li:first-child{border-top:none;}
133
  .certs li .c{color:var(--accent);font-family:ui-monospace,Menlo,monospace;font-size:12px;}
134
 
135
  footer{padding:46px 0 64px;}
136
  footer .row{display:flex;flex-wrap:wrap;justify-content:space-between;gap:18px;align-items:center;}
137
  footer .links a{color:var(--soft);margin-right:20px;font-size:14px;}
138
  footer .note{color:var(--faint);font-size:12.5px;max-width:520px;}
139
 
140
  /* detail pages */
141
  .detail-hero{padding:40px 0 28px;}
142
  .back{display:inline-block;font-size:13px;color:var(--muted);margin-bottom:22px;font-family:ui-monospace,Menlo,monospace;}
143
  .back:hover{color:var(--ink);}
144
  .kicker{font-size:12px;letter-spacing:2px;text-transform:uppercase;color:var(--accent);margin-bottom:13px;font-family:ui-monospace,Menlo,monospace;}
145
  .detail-hero h1{font-size:clamp(28px,5vw,42px);margin:0 0 12px;letter-spacing:-.6px;}
146
  .detail-hero .tagline{font-size:clamp(16px,2.2vw,19px);color:var(--soft);max-width:780px;margin:0 0 18px;}
147
  .facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(148px,1fr));gap:12px;margin-top:24px;}
148
  figure{margin:0;}
149
  .shot{border:1px solid var(--line2);border-radius:12px;overflow:hidden;background:#fff;margin:30px 0 6px;}
150
  .shot img,.shot video{display:block;width:100%;height:auto;}
151
  figcaption{font-size:13px;color:var(--muted);margin:11px 2px 0;}
152
  .content{padding:6px 0 0;}
153
  .content h2{font-size:13px;letter-spacing:2px;text-transform:uppercase;color:var(--muted);margin:44px 0 16px;font-weight:600;border-top:1px solid var(--line);padding-top:30px;}
154
  .content h2.first{border-top:none;padding-top:6px;margin-top:18px;}
155
  .content p{color:var(--soft);margin:0 0 16px;}
156
  .content ul,.content ol{color:var(--soft);margin:0 0 16px;padding-left:22px;}
157
  .content li{margin:6px 0;}
158
  .content strong{color:var(--ink);font-weight:600;}
159
  .content code{font-family:ui-monospace,Menlo,monospace;font-size:13px;background:var(--panel2);border:1px solid var(--line);border-radius:4px;padding:1px 5px;color:var(--soft);}
160
  .content pre{background:var(--bg2);border:1px solid var(--line2);border-radius:10px;padding:15px 18px;overflow-x:auto;margin:0 0 18px;}
161
  .content pre code{background:none;border:none;padding:0;font-size:12.5px;color:var(--soft);line-height:1.62;}
162
  .content table{width:100%;border-collapse:collapse;margin:2px 0 20px;font-size:13.5px;}
163
  .content th{text-align:left;color:var(--muted);font-weight:600;border-bottom:1px solid var(--line2);padding:9px 12px;font-size:11px;letter-spacing:.6px;text-transform:uppercase;}
164
  .content td{color:var(--soft);border-bottom:1px solid var(--line);padding:9px 12px;vertical-align:top;}
165
  .content td code{font-size:12px;}
166
  .gallery{margin-top:8px;}
167
  .repo-line{margin:42px 0 0;color:var(--faint);font-size:12.5px;font-family:ui-monospace,Menlo,monospace;}
168
</style>
169
<link rel="canonical" href="https://zionboggan.com/soc-automation-lab/">
170
<meta name="author" content="Zion Boggan">
171
<meta name="robots" content="index, follow, max-image-preview:large">
172
<meta property="og:type" content="article">
173
<meta property="og:site_name" content="Zion Boggan">
174
<meta property="og:title" content="SOC Automation Lab | Zion Boggan">
175
<meta property="og:description" content="An end-to-end detection-to-response pipeline wiring Wazuh detection into Shuffle SOAR and TheHive case management, deployed and validated live against a replayed SSH brute force.">
176
<meta property="og:url" content="https://zionboggan.com/soc-automation-lab/">
177
<meta property="og:image" content="https://zionboggan.com/assets/soc-automation-lab/01-wazuh-threat-hunting.png">
178
<meta name="twitter:card" content="summary_large_image">
179
<meta name="twitter:title" content="SOC Automation Lab | Zion Boggan">
180
<meta name="twitter:description" content="An end-to-end detection-to-response pipeline wiring Wazuh detection into Shuffle SOAR and TheHive case management, deployed and validated live against a replayed SSH brute force.">
181
<meta name="twitter:image" content="https://zionboggan.com/assets/soc-automation-lab/01-wazuh-threat-hunting.png">
182
<script type="application/ld+json">{"@context":"https://schema.org","@type":"TechArticle","headline":"SOC Automation Lab","description":"An end-to-end detection-to-response pipeline wiring Wazuh detection into Shuffle SOAR and TheHive case management, deployed and validated live against a replayed SSH brute force.","url":"https://zionboggan.com/soc-automation-lab/","image":"https://zionboggan.com/assets/soc-automation-lab/01-wazuh-threat-hunting.png","author":{"@type":"Person","name":"Zion Boggan","url":"https://zionboggan.com"},"publisher":{"@type":"Person","name":"Zion Boggan"}}</script>
183
</head>
184
<body>
185
<nav><div class="wrap">
186
  <a class="brand mono" href="/" style="color:var(--ink)">zion_boggan<span class="dot">.</span></a>
187
  <span class="links">
188
    <a href="/#oversight">Oversight</a>
189
    <a href="/#labs">Labs</a>
190
    <a href="/#research">Research</a>
191
    <a href="/#background">Background</a>
192
    <a href="/">Home</a>
193
  </span>
194
</div></nav>
195
<header class="hero detail-hero"><div class="wrap">
196
  <a class="back" href="/#labs">&larr; All work</a>
197
  <div class="kicker">SOC AUTOMATION</div>
198
  <h1>SOC Automation Lab</h1>
199
  <p class="tagline">An end-to-end detection-to-response pipeline wiring Wazuh detection into Shuffle SOAR and TheHive case management, deployed and validated live against a replayed SSH brute force.</p>
200
  <div class="tags"><span>Wazuh</span><span>TheHive</span><span>Shuffle</span><span>Cortex</span><span>MITRE ATT&amp;CK</span><span>SOAR</span><span>VirusTotal</span><span>AlienVault OTX</span><span>Docker</span><span>Sysmon</span></div>
201
  <div class="facts"><div class="stat"><div class="n">7</div><div class="k">Services in the SIEM stack</div></div><div class="stat"><div class="n">11</div><div class="k">Custom detection rules</div></div><div class="stat"><div class="n">340</div><div class="k">Alerts in the live test</div></div><div class="stat"><div class="n">53</div><div class="k">SSH auth failures ingested</div></div><div class="stat"><div class="n">10</div><div class="k">Integration handoff level</div></div><div class="stat"><div class="n">1</div><div class="k">Agent enrolled (forge)</div></div></div>
202
  <div class="cta" style="margin-top:24px"></div>
203
</div></header>
204
<section><div class="wrap">
205
  <figure class="shot"><img loading="lazy" src="/assets/soc-automation-lab/01-wazuh-threat-hunting.png" alt="Threat Hunting dashboard from the live test: 340 total alerts, 53 SSH authentication failures from the enrolled agent, broken down by MITRE ATT&amp;CK technique."></figure><figcaption>Threat Hunting dashboard from the live test: 340 total alerts, 53 SSH authentication failures from the enrolled agent, broken down by MITRE ATT&amp;CK technique.</figcaption>
206
  <div class="content">
207
  <h2>Architecture and data flow</h2>
208
<p>The pipeline runs detection, orchestration, and case management as three decoupled layers, with the SIEM only needing to know one webhook URL:</p><ol><li>Agents on Windows and Linux endpoints ship logs and Sysmon events to the Wazuh manager over an encrypted channel on 1514/tcp.</li><li>The manager decodes events against the bundled ruleset plus the custom rules in <code>local_rules.xml</code>.</li><li>Any alert at level 10 or higher matches the <code>&lt;integration&gt;</code> block and the manager invokes <code>custom-thehive</code>, which forwards a normalized payload to the Shuffle webhook.</li><li>Shuffle resolves the indicator of interest (source or destination IP), pulls reputation from VirusTotal and OTX, and computes a verdict score.</li><li>A TheHive case is opened with the score-derived severity, the indicator is attached as an observable flagged as an IOC, and the analyst channel gets a message linking the case.</li></ol><p>The handoff lives in the manager's <code>ossec.conf</code> &mdash; level 10 is the line between &ldquo;stays in the dashboard&rdquo; and &ldquo;worth a case&rdquo;:</p><pre><code>&lt;integration&gt;
209
  &lt;name&gt;custom-thehive&lt;/name&gt;
210
  &lt;hook_url&gt;SET_FROM_ENV_SHUFFLE_WEBHOOK_URL&lt;/hook_url&gt;
211
  &lt;level&gt;10&lt;/level&gt;
212
  &lt;alert_format&gt;json&lt;/alert_format&gt;
213
&lt;/integration&gt;</code></pre>
214
<h2>The stack</h2>
215
<p>The SIEM and case-management side is one Compose project of seven services: the three Wazuh components, plus TheHive backed by Cassandra and Elasticsearch with Cortex available for observable analyzers. Versions are pinned across the board &mdash; Wazuh 4.9.0, TheHive 5.4, Cortex 3.1.8, Cassandra 4.1, Elasticsearch 7.17.20. A trimmed excerpt:</p><pre><code>services:
216
  wazuh.indexer:
217
    image: wazuh/wazuh-indexer:4.9.0
218
    ports:
219
      - "9200:9200"
220
    environment:
221
      - OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
222
      - bootstrap.memory_lock=true
223
 
224
  wazuh.manager:
225
    image: wazuh/wazuh-manager:4.9.0
226
    depends_on:
227
      - wazuh.indexer
228
    ports:
229
      - "1514:1514"
230
      - "1515:1515"
231
      - "55000:55000"
232
 
233
  thehive:
234
    image: strangebee/thehive:5.4.0-1
235
    depends_on:
236
      - cassandra
237
      - elasticsearch
238
      - cortex
239
    ports:
240
      - "${THEHIVE_PORT}:9000"</code></pre><p>Shuffle runs as its own four-service Compose project (<code>shuffle-database</code> on OpenSearch 2.14.0, <code>shuffle-backend</code>, <code>shuffle-frontend</code>, <code>shuffle-orborus</code>, all pinned to 1.4.0) so it survives a teardown of the SIEM side.</p>
241
<h2>Custom detections</h2>
242
<p><code>local_rules.xml</code> adds eleven detections on top of the Wazuh ruleset, each mapped to a MITRE ATT&CK technique so cases arrive tagged:</p><table><thead><tr><th>Rule</th><th>Detection</th><th>Technique</th></tr></thead><tbody><tr><td>100101</td><td>Process launched from a user-writable path</td><td>T1059</td></tr><tr><td>100102</td><td>Office application spawns a scripting host</td><td>T1566 / T1059.001</td></tr><tr><td>100110</td><td>Suspicious LSASS access (credential dumping)</td><td>T1003.001</td></tr><tr><td>100120 / 100121</td><td>New service and scheduled-task persistence</td><td>T1543.003 / T1053.005</td></tr><tr><td>100200 / 100201</td><td>SSH and RDP brute force</td><td>T1110</td></tr><tr><td>100210&ndash;100212</td><td>CTI watchlist hits (IP, domain, hash)</td><td>T1071 / T1204</td></tr></tbody></table><p>The LSASS-access rule is the kind of thing the bundled ruleset doesn't ship &mdash; it keys off a Sysmon process-access event against <code>lsass.exe</code> with one of the granted-access masks that credential dumpers request:</p><pre><code>&lt;rule id="100110" level="12"&gt;
243
  &lt;if_sid&gt;61609&lt;/if_sid&gt;
244
  &lt;field name="win.eventdata.targetImage" type="pcre2"&gt;(?i)\\lsass\.exe$&lt;/field&gt;
245
  &lt;field name="win.eventdata.grantedAccess" type="pcre2"&gt;0x1010|0x1410|0x143a|0x1fffff&lt;/field&gt;
246
  &lt;description&gt;Suspicious LSASS access - possible credential dumping&lt;/description&gt;
247
  &lt;mitre&gt;
248
    &lt;id&gt;T1003.001&lt;/id&gt;
249
  &lt;/mitre&gt;
250
&lt;/rule&gt;</code></pre>
251
<h2>The SOAR workflow</h2>
252
<p>The exported Shuffle workflow (<code>wazuh-thehive-enrichment.json</code>) is a webhook trigger fanning into seven actions: a router that picks the indicator, parallel VirusTotal and OTX lookups, a Python scoring step, TheHive case creation, observable attachment, and a Slack notification. The router and both lookups feed the scoring node, which gates everything downstream. The scoring step turns reputation counts into a TheHive severity:</p><pre><code>vt = int($action_vt.last_analysis_stats.malicious or 0)
253
otx = int($action_otx.pulse_info.count or 0)
254
score = vt * 2 + otx
255
severity = 3 if score &gt;= 6 else (2 if score &gt;= 2 else 1)
256
return {"score": score, "severity": severity, "vt_malicious": vt, "otx_pulses": otx}</code></pre><p>The case node carries the agent, the rule, the reputation counts, and the raw log into the description, and tags the case with the rule's MITRE id. A missing VirusTotal result is coerced to zero rather than failing the case creation, so the free tier's 4 req/min limit never drops an alert. The indicator is then attached as an observable flagged as an IOC, so it flows into TheHive's observable history and can be swept against other cases.</p>
257
<h2>Live validation</h2>
258
<p>The lab was stood up single-node with a Linux endpoint (<code>forge</code>, Ubuntu 22.04) enrolled and reporting in, then an SSH brute force was replayed against it. The Threat Hunting dashboard showed <strong>340</strong> total alerts with <strong>53</strong> real SSH authentication failures ingested from the agent, every detection auto-mapped to MITRE ATT&CK across Password Guessing, SSH, and Brute Force categories. The escalation rule that fired collapses a burst of Wazuh's per-failure 5710 events into one level-10 brute-force alert:</p><pre><code>&lt;rule id="100200" level="10" frequency="8" timeframe="120"&gt;
259
  &lt;if_matched_sid&gt;5710&lt;/if_matched_sid&gt;
260
  &lt;same_source_ip /&gt;
261
  &lt;description&gt;SSH brute force - 8 failed logins from $(srcip) in 120s&lt;/description&gt;
262
  &lt;mitre&gt;
263
    &lt;id&gt;T1110&lt;/id&gt;
264
  &lt;/mitre&gt;
265
&lt;/rule&gt;</code></pre><p>For CTI-list hits, rule 100210 is also wired to active response: the manager issues a <code>firewall-drop</code> on the endpoint for 600 seconds while the analyst confirms. The block is scoped to a single rule on purpose &mdash; auto-blocking on a noisier rule would be a fast way to firewall yourself out of your own hosts.</p>
266
<h2>Agent enrollment</h2>
267
<p>The manager runs registration on 1515/tcp with <code>force</code> enabled so a re-enrolling host reclaims its slot rather than piling up duplicates. Linux endpoints enroll through a helper that adds the Wazuh apt repo and installs the agent pinned to the manager's version:</p><pre><code>curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --no-default-keyring \
268
    --keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg --import
269
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" \
270
    &gt; /etc/apt/sources.list.d/wazuh.list
271
apt-get update
272
WAZUH_MANAGER="${WAZUH_MANAGER}" WAZUH_AGENT_GROUP="${WAZUH_AGENT_GROUP}" \
273
    apt-get install -y "wazuh-agent=${WAZUH_VERSION}"
274
systemctl enable --now wazuh-agent</code></pre><p>Windows hosts use a PowerShell counterpart that pulls the MSI and registers it into the Sysmon-aware <code>windows</code> group, so process-creation and network events arrive with enough context for the rules above to be useful:</p><pre><code>WAZUH_MANAGER=REDACTED-IP ./scripts/enroll-agent.sh
275
.\scripts\enroll-agent.ps1 -Manager REDACTED-IP -Group windows</code></pre>
276
<h2>Deployment notes</h2>
277
<p>A few things that bit during bring-up and are worth knowing before running this somewhere real:</p><ul><li><strong>Indexer compatibility.</strong> Filebeat 7.10 refuses to publish to an indexer that reports an OpenSearch 2.x version, so the indexer config sets <code>compatibility.override_main_response_version: true</code>. Without it the manager produces alerts but nothing reaches the indexer and the dashboard stays empty.</li><li><strong>Memory locking under LXC.</strong> The committed config locks memory (<code>bootstrap.memory_lock=true</code>, <code>memlock: -1</code>), which is correct for bare metal. Inside an unprivileged LXC the kernel caps locked memory and the indexer and Elasticsearch fail with an rlimit error; the fix is a <code>docker-compose.override.yml</code> that disables locking rather than editing the committed file.</li><li><strong>vm.max_map_count.</strong> Must be at least 262144 on the host for both the indexer and Elasticsearch; in an LXC it's inherited from the node, so it has to be set there.</li><li><strong>CTI watchlists.</strong> The <code>cti-malicious-*</code> CDB lists are seeded by <code>deploy.sh</code> after the stack is up &mdash; they have to be writable so the manager can compile them, which rules out bind-mounting them read-only.</li></ul><p>This is a lab build. Production would split the Wazuh indexer and TheHive's Cassandra and Elasticsearch onto their own nodes with real resource headroom.</p>
278
  </div>
279
  <div class="gallery"><figure class="shot"><img loading="lazy" src="/assets/soc-automation-lab/02-wazuh-agent.png" alt="The enrolled Linux endpoint (forge, Ubuntu 22.04) active and shipping telemetry to the Wazuh manager."></figure><figcaption>The enrolled Linux endpoint (forge, Ubuntu 22.04) active and shipping telemetry to the Wazuh manager.</figcaption></div>
280
  <p class="repo-line">Repository &middot; github.com/zionboggan/soc-automation-lab</p>
281
</div></section>
282
<footer><div class="wrap row">
283
  <div class="links">
284
    <a href="/">Portfolio</a>
285
    <a href="https://www.linkedin.com/in/zion-boggan">LinkedIn</a>
286
    <a href="https://oversightprotocol.dev/">Oversight</a>
287
    <a href="mailto:zionboggan0@gmail.com">Email</a>
288
  </div>
289
  <div class="note">Built and deployed on a self-hosted Proxmox homelab. This page mirrors the
290
  project's documentation and results so the work is fully viewable here.</div>
291
</div></footer>
292
</body>
293
</html>