Building Blue Watch: a tiny SIEM with a big attitude

Building Blue Watch: a tiny SIEM with a big attitude

构建 Blue Watch:一个“脾气火爆”的迷你 SIEM

Okay so I’m building a mini SIEM. If you just went “a mini what?” same energy I had a week ago. A SIEM (Security Information and Event Management system) is basically the thing that watches your logs, notices when something shady is happening, and yells about it. Big companies pay big money for big versions of this. I am making a smol one, called Blue Watch, mostly out of stubbornness and curiosity. Rule number one I set for myself: config over code. Detection logic should live in a config file, not be hardcoded spaghetti buried in if statements. Future me will thank present me. Probably.

好吧,我正在构建一个迷你 SIEM。如果你刚才心想“迷你什么?”,我和你一周前的反应一模一样。SIEM(安全信息和事件管理系统)本质上就是一种监控日志、发现可疑活动并发出警报的工具。大公司会花大价钱购买大型 SIEM 系统,而我出于倔强和好奇心,正在制作一个名为“Blue Watch”的微型版本。我为自己设定的第一条规则是:配置优于代码。检测逻辑应该存在于配置文件中,而不是硬编码在杂乱的 if 语句里。未来的我应该会感谢现在的我,大概吧。

Step zero: fake my own crime scene. Before writing a single line of detection logic, I needed logs to detect things in. Real-world logs are messy and you never actually know the “right answer” — so instead I hand-crafted a single deliberate attack story and wrote it out as a realistic auth.log file, syslog-style timestamps and all:

  • 🕵️ An attacker brute-forces their way into an admin account (lots of failed logins, then… success)
  • 🐚 Admin (now compromised) creates a sneaky new user — a classic “leave myself a backdoor” move
  • 🔓 That new backdoor account immediately goes digging somewhere it really shouldn’t
  • 👩‍💻 Meanwhile, a totally innocent employee logs in from a normal IP at roughly the same time, just doing her job That last one was the important bit — I needed to know my detector could tell the difference between an attacker and someone just… checking their email.

第零步:伪造我自己的犯罪现场。在编写任何检测逻辑之前,我需要日志来作为检测对象。现实世界的日志非常混乱,你永远无法确定“正确答案”是什么。因此,我手工编写了一个精心设计的攻击故事,并将其写成了一个逼真的 auth.log 文件,包含了 syslog 风格的时间戳等细节:

  • 🕵️ 攻击者暴力破解进入管理员账户(大量登录失败,然后……成功了)
  • 🐚 管理员(现已被入侵)创建了一个隐蔽的新用户——这是典型的“给自己留后门”操作
  • 🔓 那个新的后门账户立即开始在不该去的地方进行探测
  • 👩‍💻 与此同时,一名完全无辜的员工在差不多同一时间从正常 IP 登录,只是在做她的日常工作 最后一点至关重要——我需要确保我的检测器能够区分攻击者和仅仅是在……查收邮件的普通用户。

Day 1: teaching myself regex, the hard way. I will be honest: I did not know regex before this. Now I have opinions about it. I built a parser that reads raw log lines and pulls structure out of chaos using named capture groups, character classes, and way too much trial and error. Three different line “shapes” (logins, privilege escalations, account creations) each needed their own pattern. The real win wasn’t the regex though — it was writing a normalize() step that takes whatever any of the three patterns spat out and mushes it into one consistent event shape. Doesn’t matter which log line it came from, downstream code doesn’t need to care. Bugs squashed along the way: unterminated strings, mysterious NoneType crashes (regex didn’t match, oops), off-by-one group numbering, and — my personal favorite — an IndentationError caused by tabs sneaking in next to spaces like a tiny gremlin.

第一天:自学正则表达式,以最艰难的方式。老实说,在此之前我并不懂正则,但现在我对它有了自己的看法。我构建了一个解析器,通过命名捕获组、字符类以及大量的试错,从混乱的原始日志行中提取出结构。三种不同的日志“形态”(登录、权限提升、账户创建)各自需要独立的模式。不过,真正的胜利不在于正则本身,而在于编写了一个 normalize() 步骤,它能将三种模式输出的任何内容统一转换为一致的事件格式。无论它来自哪种日志行,下游代码都不需要关心。在此过程中修复的 Bug 包括:未闭合的字符串、神秘的 NoneType 崩溃(正则未匹配到,哎呀)、差一错误(off-by-one)的组编号,以及我个人最喜欢的——由制表符混入空格中像小妖精一样导致的 IndentationError(缩进错误)。

Day 2: rules as data, not dogma. This was the fun part. Instead of writing “if failed login count > 5” buried somewhere in Python, I moved every detection rule into a YAML file. Each rule even gets tagged with a real MITRE ATT&CK technique ID, because if I’m going to pretend to be a security engineer I might as well pretend properly. Ended up with four rules covering: repeated failed logins, repeated suspicious IP activity, privilege escalation right after login, and — the spiciest one — persistence via account creation (a.k.a. “the attacker just planted a way back in”).

第二天:规则即数据,而非教条。这是最有趣的部分。我没有把“如果登录失败次数 > 5”写死在 Python 代码里,而是将所有检测规则移到了 YAML 文件中。每条规则甚至都标记了真实的 MITRE ATT&CK 技术 ID,既然我要装作一名安全工程师,那就要装得像样点。最终我制定了四条规则,涵盖了:重复登录失败、重复的可疑 IP 活动、登录后立即提权,以及最“劲爆”的一条——通过创建账户实现持久化(即“攻击者刚刚埋下了一个后门”)。

Day 3: the detector finally detects something. Two core functions carried the whole day: One that groups events by any field a rule asks for (a user, an IP, whatever) and checks it against a threshold — so one function now powers multiple rules instead of copy-pasted near-duplicates. One that checks whether event A is followed by event B within some time window, using real datetime math instead of vibes. And then — the best kind of bug — a false positive. My privilege-escalation rule was matching people to each other’s activity just because the timing lined up. My innocent employee’s login was accidentally getting blamed for the backdoor account’s actions, purely by coincidence of timestamps. Fixed it by adding a same_user toggle to the rule config, since some sequences genuinely need “same person did both things” and others (like sudo → new account) very much don’t. End result: all four rules fired exactly once each, correctly reconstructing the entire attack from first failed login to backdoor to the shadow file — and zero false alarms on the one honest person in the logs. 🎉

第三天:检测器终于检测到了东西。两个核心函数支撑了全天的工作:一个是根据规则要求的任何字段(用户、IP 等)对事件进行分组,并根据阈值进行检查——这样,一个函数就能驱动多条规则,而无需复制粘贴近乎重复的代码。另一个是检查事件 A 是否在某个时间窗口内紧随事件 B 发生,使用的是真实的日期时间计算,而不是凭感觉。然后,我遇到了最好的一种 Bug——误报。我的提权规则因为时间巧合,把不同人的活动匹配到了一起。我那无辜员工的登录行为因为时间戳的巧合,被错误地归咎于后门账户的操作。我通过在规则配置中添加一个 same_user 开关解决了这个问题,因为有些序列确实需要“同一个人完成两件事”,而另一些(如 sudo → 创建新账户)则完全不需要。最终结果:四条规则各触发了一次,准确地还原了从第一次登录失败到植入后门再到修改 shadow 文件的整个攻击过程,并且对日志中那个诚实的人没有产生任何误报。🎉

What’s next? Day 4 is scorer.py — turning a pile of individual alerts into one aggregate threat score per user/IP, so instead of “here are 4 alerts,” Blue Watch can say “hey, this person is very clearly the problem.” More soon 👁️🔵 xoxo. Following along? This is a build-in-public series — feedback, questions, and “actually you should use X instead” comments always welcome.

接下来做什么?第四天是 scorer.py——将一堆独立的警报转化为每个用户/IP 的综合威胁评分。这样,Blue Watch 就不再是说“这里有 4 个警报”,而是说“嘿,这个人显然就是问题所在”。更多内容敬请期待 👁️🔵 xoxo。在关注这个系列吗?这是一个“公开构建”系列——欢迎随时提供反馈、提问,或者发表“其实你应该用 X”之类的评论。