UEBA 用户行为分析实战:异常登录检测配置

UEBA 用户行为分析是一种以行为基线为核心的检测方法,它不依赖单条特征规则,而是先统计账号在正常状态下的登录时间、来源 IP、目标主机与操作频率,再用偏离度判断异常。对没有预算采购商业 SIEM 的中小企业内网来说,用 Elastic Stack 加一台采集节点就能搭起一套可用的 UEBA,把异地登录、异常时段操作、账号横向移动这几类最容易被忽略的风险捞出来。

适用场景

  • 内网已经有 Wazuh、Syslog 或 ELK,但规则只有「登录失败即告警」这类静态判断,一天几百条误报,运维直接把告警关了。
  • 需要检测的目标集中在四类:非常用地点或非常用时段的成功登录、针对单一账号的高频暴力破解、同一账号短时间登录多台主机的横向移动、内部人员异常提权与批量数据下载。
  • 规模在 50 至 500 台主机之间,没有专职安全运营人员,希望告警数量可控且每条都能解释清楚。

前置条件

  • Elasticsearch 8.x 与 Kibana 8.x(单节点即可,8 GB 内存起步)。
  • 被监控主机安装 Filebeat 8.x;Windows 主机改用 Winlogbeat,采集安全事件日志 ID 4624(登录成功)、4625(登录失败)、4672(特权分配)。
  • Linux 主机启用 auditd,并保留 /var/log/auth.log(Debian 系)或 /var/log/secure(RHEL 系)。
  • 一个能读取审计日志的采集账号,以及可执行 curl 的管理终端。

原理说明

UEBA 的处理链条只有三步,理解这三步就能理解后面所有配置的用途。

第一步是采集。需要落地的事件只有三类:登录事件(谁、从哪个 IP、登录哪台主机、成功了没有)、进程执行事件(谁执行了什么命令、是否以 root 身份)、网络连接事件(登录后连了哪些内网端口)。登录事件回答「有没有异常账号活动」,进程与网络事件回答「异常账号做了什么」。

第二步是建立基线。把事件按「账号 × 主机 × 时间窗口」三维聚合,得到每个账号的历史分布。例如运维账号 zhangsan 过去 30 天只在 08:30 至 19:30 之间、从 10.10.20.0/24 网段、登录过 6 台主机;这就是它的正常画像。基线的价值在于它天然贴合你的业务节奏,不需要人工去猜阈值。

第三步是偏离检测。把当前事件与基线比较,超出合理区间的部分按严重度分级。关键在于告警要分层:新 IP 登录属于「提示」,非工作时段提权属于「高危」,单账号 10 分钟登录 5 台主机属于「紧急」。分级之后告警才有人看。

操作步骤

1. 采集层:auditd 落地身份与提权事件

新建审计规则文件,覆盖账号文件变更、sudo 配置变更和低权限账号的提权执行行为。

sudo tee /etc/audit/rules.d/ueba.rules > /dev/null <<'EOF'
-w /etc/passwd -p wa -k ueba_identity
-w /etc/shadow -p wa -k ueba_identity
-w /etc/sudoers -p wa -k ueba_priv
-a always,exit -F arch=b64 -S execve -F auid>=1000 -F euid=0 -k ueba_privcmd
EOF

sudo augenrules --load
sudo auditctl -s | grep -E 'enabled|rules'

第三条规则是关键:只记录普通账号(auid>=1000)通过提权执行命令的动作,服务账号自身的高频 execve 不会进入日志,能把采集量压下去一个数量级。

2. 采集层:Filebeat 发送到 Elasticsearch

sudo tee /etc/filebeat/filebeat.yml > /dev/null <<'EOF'
filebeat.inputs:
  - type: filestream
    id: audit-log
    enabled: true
    paths: [/var/log/audit/audit.log]
    fields: { log_type: audit }
  - type: filestream
    id: auth-log
    enabled: true
    paths: [/var/log/auth.log]
    fields: { log_type: auth }

processors:
  - add_host_metadata: ~

output.elasticsearch:
  hosts: ["http://127.0.0.1:9200"]
  username: "elastic"
  password: "${ES_PWD}"
EOF

sudo ES_PWD='你的密码' filebeat test output
sudo systemctl enable --now filebeat

3. 存储层:索引生命周期策略

登录日志保留 90 天足够做基线,热数据保留 7 天即可。先建 ILM 策略,再套到模板上。

curl -u elastic:$ES_PWD -X PUT "http://127.0.0.1:9200/_ilm/policy/ueba-90d" \
  -H 'Content-Type: application/json' -d '{
  "policy": {
    "phases": {
      "hot":    { "actions": { "rollover": { "max_primary_shard_size": "20gb", "max_age": "7d" } } },
      "warm":   { "min_age": "7d",  "actions": { "shrink": { "number_of_shards": 1 }, "forcemerge": { "max_num_segments": 1 } } },
      "delete": { "min_age": "90d", "actions": { "delete": {} } }
    }
  }
}'

4. 基线层:用聚合查询统计账号画像

下面的 Python 脚本从 Elasticsearch 拉取最近 30 天的登录成功记录,按账号聚合出常用 IP、常用时段与常用主机三份画像,输出为 JSON 供检测环节比对。

#!/usr/bin/env python3
import json, requests

ES = "http://127.0.0.1:9200"
AUTH = ("elastic", "你的密码")

def agg_by(field):
    body = {
        "size": 0,
        "query": {
            "bool": {
                "filter": [
                    {"range": {"@timestamp": {"gte": "now-30d"}}},
                    {"match_phrase": {"message": "Accepted"}}
                ]
            }
        },
        "aggs": {
            "user": {
                "terms": {"field": "user.name", "size": 500},
                "aggs": {field: {"terms": {"field": field, "size": 200}}}
            }
        }
    }
    r = requests.post(ES + "/filebeat-*/_search", auth=AUTH, json=body, timeout=60)
    r.raise_for_status()
    out = {}
    for b in r.json()["aggregations"]["user"]["buckets"]:
        out[b["key"]] = [x["key"] for x in b[field]["buckets"]]
    return out

baseline = {
    "src_ip":    agg_by("source.ip"),
    "host":      agg_by("host.name"),
    "generated": "auto",
}
json.dump(baseline, open("/etc/ueba/baseline.json", "w"),
          ensure_ascii=False, indent=2)
print("账号数:", len(baseline["src_ip"]))

脚本建议通过 cron 每天凌晨重建一次基线:0 3 * * * /usr/local/bin/ueba_baseline.py。基线是滚动的,人员与网络变化会自动被吸收。

5. 检测层:四条覆盖核心风险的行为规则

在 Kibana 的 Stack Management 中新建检测规则,或用 ElastAlert 2 承载。以下查询可直接粘贴到 Kibana Discover 的 KQL 输入框验证。

  • 暴力破解:5 分钟内同一账号登录失败超过 10 次。
    message:"Failed password" and user.name:*
    # 聚合维度:user.name + source.ip,阈值 10,窗口 5m
  • 异常时段登录:本地时间 23:00 至 06:00 之间的成功登录。
    message:"Accepted" and not @timestamp.hour:(8 or 9 or 10 or 11 or 12 or 13 or 14 or 15 or 16 or 17 or 18 or 19)
  • 横向移动:同一账号 10 分钟内成功登录 3 台以上不同主机。
    message:"Accepted"
    # 聚合维度:user.name,cardinality 统计 host.name,阈值 3,窗口 10m
  • 异常提权:非白名单账号触发 ueba_privcmd 或 sudo 提权。
    event.module:"auditd" and auditd.log.key:"ueba_privcmd" and not user.name:(ansible or deploy)

6. 响应层:告警分级与联动

curl -u elastic:$ES_PWD -X POST "http://127.0.0.1:9200/_watcher/watch/ueba_critical" \
  -H 'Content-Type: application/json' -d '{
  "trigger": { "schedule": { "interval": "5m" } },
  "input": {
    "search": {
      "request": {
        "indices": ["filebeat-*"],
        "body": {
          "query": { "bool": { "filter": [
            { "match_phrase": { "message": "Accepted" } },
            { "range": { "@timestamp": { "gte": "now-5m" } } }
          ] } }
        }
      }
    }
  },
  "condition": { "compare": { "ctx.payload.hits.total": { "gt": 0 } } },
  "actions": { "log": { "logging": { "text": "UEBA 命中异常登录,请核查来源 IP" } } }
}'

配置验证

按顺序执行以下四条命令,覆盖采集、索引、基线、检测四个环节。

# 1. 确认审计规则已加载
sudo auditctl -l | grep ueba

# 2. 确认索引已写入且有数据
curl -u elastic:$ES_PWD -s "http://127.0.0.1:9200/_cat/indices/filebeat-*?v&h=index,docs.count,store.size"

# 3. 确认基线文件已生成
jq 'keys | length' /etc/ueba/baseline.json

# 4. 确认检测规则能命中
curl -u elastic:$ES_PWD -s "http://127.0.0.1:9200/filebeat-*/_count" \
  -H 'Content-Type: application/json' \
  -d '{"query":{"match_phrase":{"message":"Failed password"}}}'

验证新 IP 检测是否生效,可以在测试机上用未出现过的源地址登录一次,观察 5 分钟内是否出现对应告警;如果没有,优先检查 source.ip 字段是否被 Filebeat 正确解析,这是 UEBA 中最常见的一处配置遗漏。

常见问题

FAQ 1:只采集 execve 就让 Elasticsearch 磁盘暴涨,怎么控制?

execve 是采集量最大的事件类型。三个措施按优先级使用:一是把 auditd 规则限定为 auid>=1000 且 euid=0,只保留提权执行;二是在 Filebeat 的 processors 中只保留 user.name、host.name、process.name、process.args、@timestamp 五个字段,其余用 drop_fields 丢弃;三是启用上面的 ILM 策略,热数据 7 天后转 warm 并 merge 成单分片。实测三个措施叠加后,500 台主机的日增索引可以控制在 15 GB 以内。

FAQ 2:基线还没有建立起来,前两周是不是没有检测能力?

冷启动期不要开偏离检测,否则告警全是噪声。建议前 14 天只部署采集与基线脚本,检测环节使用固定阈值(如登录失败 10 次、单账号 3 台主机),并且只记录告警、不做任何自动阻断。等基线文件覆盖到全部账号后再切换到偏离度判断,此时告警量通常会下降六成以上,而真实命中率反而上升。

FAQ 3:内网使用 DHCP,机器经常换 IP,新 IP 告警会被刷爆怎么办?

把比对维度从精确 IP 换成网段。在基线脚本中把 source.ip 的前三段提取为 source.subnet 字段(例如 10.10.20),以网段而非单地址作为画像维度;同时在检测规则里维护一份办公网段白名单(如 10.10.0.0/16),来自白名单网段的登录直接降级为记录而不告警。跨网段登录才值得推送到告警通道。

总结

UEBA 的门槛不在算法,而在采集面和基线质量。落到实操上有三条要点:一是采集只取登录、提权执行、网络连接三类事件,其余一律过滤,量控住在可维护区间;二是基线必须滚动重建,让组织变化自然沉淀进去,而不是一次配置长期不动;三是告警按严重度分级,把「提示」类留在看板里,只把「高危」和「紧急」推到人手上。把这套流程跑通之后,内网最容易被忽略的异地登录与账号横向移动就有了可见性,后续再叠加阻断逻辑才有意义。