利用 Elastic API 实现自定义 AI 驱动的 SOAR

45 阅读11分钟

作者:来自 Elastic Kevin Umsted

安全团队面临着无休止的警报、复杂的调查以及资源有限的艰巨挑战。这不仅是要发现威胁,还要快速且高效地应对。Elastic Security 一直提供用于检测、调查和响应的预构建能力。但真正让 Elastic 脱颖而出的是其开放、以 API 为先的方式,使你能够在安全运营中心(SOC)构建并自动化特定的工作流程。

在这篇博客中,我们将展示 Elastic 可扩展 API 如何通过一个常见的 “实例” 解锁 AI 驱动的安全编排、自动化与响应(security orchestration, automation, and response - SOAR)新可能性。你将学习如何结合 Elastic 的安全平台、AI 以及像 Slack 这样的协作工具来设计、自动化并持续完善你自己的响应剧本。

利用 Elastic API 强化你的工作流程

在这个场景中,SOC 的检测工程团队正面临一个熟悉的难题:未能满足关键警报的服务水平目标。尽管团队已部署了强大的工具来提高平均响应时间(mean time to respond - MTTR),但仍在努力应对庞大而紧迫的警报量。因此,一些关键警报有时会被延迟处理,甚至未能在预期时间内解决。为了解决这一问题,团队希望借助自动化、AI 以及 Elastic 强大的 API 进一步优化工作流程。

现在,想象这样一个工作流程:每个关键警报都能被一致地检测、分类并响应,分析人员能全程引导并监督每一步。以下是如何使用 Elastic 灵活的 API 和像 Slack 这样的常用协作工具实现这一解决方案。

我们来分解这个项目的步骤:

  • 使用 Elasticsearch Python 客户端监控一个 Elastic 集群。

  • 监控任何被标记为关键的警报。

  • 将警报 JSON 通过 API 发送给 Elastic AI Assistant for Security。

  • 获取 AI 响应。

  • 将 AI 响应发布到 Slack 频道。

  • 在 Slack 消息中标记相关安全分析人员,以通知他们并在 Slack 中直接进行操作。

事件时间线

让我们来梳理一下事件的时间线。首先,Elastic Security 收到一个关键警报,涉及一台受端点检测与响应(Elastic Defend)保护的重要 Windows 服务器。在这个案例中,Elastic 检测到 Windows Management Instrumentation (WMI) 被用来创建注册表项,从而实现对该关键服务器的持久访问。

Elastic Security 警报

Python 可以每分钟查询一次 Elastic,以获取任何关键警报。当发现此类警报时,将提取警报的完整 JSON 有效负载以进行处理。下面是一个可用于实时监控 Elastic 中关键警报的 Python 示例:

`

1.  es = Elasticsearch(
2.      os.getenv("ELASTICSEARCH_URL"),
3.      api_key=ELASTICSEARCH_API_KEY
4.  )

6.  def monitor_alerts():
7.      from datetime import datetime, timedelta, timezone
8.      import time

10.      polling_interval = 60  # Poll every 60 seconds
11.      index_patterns = {
12.          "endpoint": {
13.              "pattern": ".ds-logs-endpoint.alerts*",
14.              "timestamp_field": "Responses.@timestamp"
15.          },
16.          "security": {
17.              "pattern": ".internal.alerts-security.alerts-default-*",
18.              "timestamp_field": "@timestamp"
19.          }
20.      }

22.      last_timestamps = {
23.          key: datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
24.          for key in index_patterns
25.      }

27.      while True:
28.          for key, config in index_patterns.items():
29.              pattern = config["pattern"]
30.              ts_field = config["timestamp_field"]

32.              query = {
33.                  "query": {
34.                      "bool": {
35.                          "filter": [
36.                              {"range": {ts_field: {"gt": last_timestamps[key]}}},
37.                              {"term": {"kibana.alert.severity": "critical"}}
38.                          ]
39.                      }
40.                  },
41.                  "sort": [{ts_field: {"order": "asc"}}]
42.              }

44.              try:
45.                  response = es.search(index=pattern, body=query)
46.                  alerts = response['hits']['hits']
47.                  if alerts:
48.                      print(f"Found {len(alerts)} critical alert(s) for {key} since {last_timestamps[key]}")
49.                      max_ts = last_timestamps[key]
50.                      for alert in alerts:
51.                          alert_id = alert.get('_id')
52.                          if alert_id in processed_alert_ids:
53.                              continue
54.                          processed_alert_ids.add(alert_id)

56.                          alert_json = alert['_source']
57.                          process_alert(alert_json)

59.                          alert_ts = get_custom_alert_timestamp(alert_json, ts_field)
60.                          if alert_ts and alert_ts > max_ts:
61.                              max_ts = alert_ts
62.                      dt = datetime.fromisoformat(max_ts.replace("Z", "+00:00"))
63.                      dt += timedelta(seconds=1)
64.                      last_timestamps[key] = dt.isoformat().replace("+00:00", "Z")
65.              except Exception as e:
66.                  print(f"Error fetching critical alerts for {key}: {str(e)}")

68.          time.sleep(polling_interval)

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

捕获的 JSON 然后通过 API 请求发送到 Elastic AI Assistant for Security。一个 prompt 会附加到该 JSON,以为 AI Assistant 提供生成适当响应所需的上下文。

Python prompt 代码

`

1.  def process_alert(alert_json):
2.      alert_str = json.dumps(alert_json, indent=2)
3.      prompt = (
4.          f"Please look at this JSON:\n{alert_str}\n\n"
5.          "You are a security analyst and you want to run commands to start a forensic investigation for this alert. "
6.          "Make sure you summarize the alert in one paragraph. "
7.          "Summarize the alert and then give 5 commands that you would run to start your IR investigation. "
8.          "The next 3 commands should be dedicated to commands that would remediate the malware or malicious activity. "
9.          "The total commands should be 8. Make sure you include the affected host via the IP address in the alert summary. "
10.          "The commands should be in a list format and clearly separated into Investigation Commands and Remediation Commands. For example: \n"
11.          "Investigation Commands:\n1. command\n2. command\n... \nRemediation Commands:\n1. command\n2. command\n..."
12.      )    print("Alert Received")
13.      response = chat_complete(prompt)
14.      print("AI Responded")

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

Python API 调用

`

1.  def chat_complete(question, thread_ts=None, use_existing_conversation=False):
2.      url = f"{KIBANA_URL}/api/security_ai_assistant/chat/complete"
3.      headers = {
4.          "kbn-xsrf": "true",
5.          "Content-Type": "application/json",
6.      }

8.      payload = {
9.          "messages": [{"role": "user", "content": question}],
10.          "connectorId": CONNECTOR_ID,
11.          "persist": True
12.      }

14.      if use_existing_conversation and thread_ts and thread_ts in conversation_map:
15.          payload["conversationId"] = conversation_map[thread_ts]

17.      response = requests.post(
18.          url,
19.          auth=(USERNAME, PASSWORD),
20.          headers=headers,
21.          json=payload,
22.          stream=True
23.      )
24.      response.raise_for_status()

26.      full_response = ""
27.      for line in response.iter_lines():
28.          if line:
29.              decoded_line = line.decode('utf-8')
30.              try:
31.                  event = json.loads(decoded_line)
32.                  if "data" in event:
33.                      full_response += event["data"]
34.                  if "conversationId" in event and thread_ts:
35.                      conversation_map[thread_ts] = event["conversationId"]
36.              except json.JSONDecodeError:
37.                  continue

39.      return full_response.strip()

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

AI 助手接收 API 请求并使用大型语言模型(LLM)生成响应。这个响应包括一个可操作的警报摘要以及推荐的调查和缓解命令。

AI 助手还可以确定应该在 Slack 消息中标记哪位分析师。它通过查询一个包含 SOC 中分析师信息的自定义知识库来实现这一点。这个自定义知识功能非常强大,大幅提升了 AI 助手的有效性。

通过在自定义知识库中集中机构知识,AI 助手最大限度地减少了获取关键信息的延迟。这提升了整个 SOC 的操作工作流程的速度和一致性。

了解如何设置自定义 AI 助手知识库

一旦 AI 助手返回其响应,使用 Python 将消息及相应的分析人员 @ 提及发送到指定的 Slack 频道。

将响应发送到 Slack

`

1.  response = chat_complete(prompt)
2.  print("AI Responded")

4.  if response:
5.      clean_response = response.replace("**", "") 

7.      inv_match = re.search(r"(?i)\s*Investigation Commands:\s*", clean_response)
8.      rem_match = re.search(r"(?i)\s*Remediation Commands:\s*", clean_response)

10.      if not inv_match or not rem_match:
11.          print("Could not find both 'Investigation Commands:' and 'Remediation Commands:' in the response.")
12.          return

14.      summary = clean_response[:inv_match.start()].strip()
15.      inv_commands_part = clean_response[inv_match.end():rem_match.start()].strip()
16.      rem_commands_part = clean_response[rem_match.end():].strip()

18.      inv_commands = re.findall(r"^\s*\d+\.\s+(.+)$", inv_commands_part, re.MULTILINE)
19.      rem_commands = re.findall(r"^\s*\d+\.\s+(.+)$", rem_commands_part, re.MULTILINE)

21.      # Extract the handler from AI (e.g., "Who handles critical windows alerts?")
22.      handler_question = f"Who handles {operating_system.lower()} alerts? Respond with just their name."
23.      handler_response = chat_complete(handler_question).strip()
24.      handler_id = get_user_id_by_name(handler_response)
25.      handler_mention = f"<@{handler_id}>" if handler_id else handler_response

27.      full_message = (
28.          f"New Alert Detected\n\n{clean_response}\n\n"
29.          f"Alert assigned to:\n{handler_mention}\n\n"
30.          "Do you want me to run the investigation and remediation commands?"
31.      )

33.      blocks = [
34.          {
35.              "type": "section",
36.              "text": {
37.                  "type": "mrkdwn",
38.                  "text": full_message
39.              }
40.          },
41.          {
42.              "type": "actions",
43.              "elements": [
44.                  {
45.                      "type": "button",
46.                      "text": {"type": "plain_text", "text": "Yes"},
47.                      "action_id": "run_commands",
48.                      "value": "run"
49.                  },
50.                  {
51.                      "type": "button",
52.                      "text": {"type": "plain_text", "text": "No"},
53.                      "action_id": "do_not_run",
54.                      "value": "do_not_run"
55.                  }
56.              ]
57.          }
58.      ]

60.      result = app.client.chat_postMessage(
61.          channel=ALERT_CHANNEL,
62.          blocks=blocks,
63.          text="New Alert Detected"
64.      )

66.      alert_data_map[result['ts']] = {
67.          "alert_json": alert_json,
68.          "investigation_commands": inv_commands,
69.          "remediation_commands": rem_commands,
70.          "host_ip": host_ip,
71.          "ai_summary": summary
72.      }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

Slack 消息:

此阶段,Slack 消息会生成,包含警报的有意义摘要、受影响的主机,以及建议的调查和修复命令列表。分配给该警报的分析人员会在消息中被 @ 提及,并且会看到“是/否”选项,用于批准在主机上执行命令。

在此场景中,分析人员点击“是”并批准自动响应。

为了执行命令,使用 Python 和 Windows 远程管理(WinRM)。受影响主机的 IP 地址和命令列表会从 AI 助手的响应中提取,并按顺序在目标机器上执行。

Python 命令执行

`

1.  import winrm

3.  def execute_winrm_command(host_ip: str, command: str, winrm_domain: str, winrm_username: str, winrm_password: str) -> str:
4.      session = winrm.Session(
5.          host_ip,
6.          auth=(f"{winrm_domain}\\{winrm_username}", winrm_password),
7.          transport='ntlm'
8.      )
9.      full_cmd = (
10.          "$ProgressPreference = 'SilentlyContinue'; "
11.          "$VerbosePreference = 'SilentlyContinue'; "
12.          "$WarningPreference = 'SilentlyContinue'; "
13.          f"try {{ {command} | Format-List | Out-String -Width 120 }} "
14.          "catch {{ 'Error: ' + $_.Exception.Message }}"
15.      )
16.      result = session.run_ps(full_cmd)
17.      output = result.std_out.decode('utf-8', errors='ignore').strip()
18.      return output if output else "No output returned."

20.  def execute_winrm_commands(host_ip: str, commands: list, winrm_domain: str, winrm_username: str, winrm_password: str) -> dict:

22.      outputs = {}
23.      session = winrm.Session(
24.          host_ip,
25.          auth=(f"{winrm_domain}\\{winrm_username}", winrm_password),
26.          transport='ntlm'
27.      )
28.      for command in commands:
29.          full_cmd = (
30.              "$ProgressPreference = 'SilentlyContinue'; "
31.              "$VerbosePreference = 'SilentlyContinue'; "
32.              "$WarningPreference = 'SilentlyContinue'; "
33.              f"try {{ {command} | Format-List | Out-String -Width 120 }} "
34.              "catch {{ 'Error: ' + $_.Exception.Message }}"
35.          )
36.          result = session.run_ps(full_cmd)
37.          output = result.std_out.decode('utf-8', errors='ignore').strip()
38.          outputs[command] = output if output else "No output returned."
39.      return outputs

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

分析人员执行的命令:

每个操作都被完整跟踪和可审计。应用程序为每个事件在 Elastic Security 中创建一个新案例,并附上所有 AI 摘要、执行的命令以及响应过程中生成的所有输出,确保所有证据都被保存以备将来参考或合规需求。

Python 创建案例代码

``

1.  def create_case(alert_json, command_output, ai_summary):
2.      description = ai_summary + "\n\n"
3.      if command_output:
4.          description += "Commands Executed:\n\n"
5.          for cmd, output in command_output.items():
6.              truncated_output = output[:500] + "..." if len(output) > 500 else output
7.              description += f"Command: `{cmd}`\n\nOutput:\n```\n{truncated_output}\n```\n\n"

9.      if len(description) > 30000:
10.          description = description[:29997] + "..."

12.      title = "Investigation for Alert"

14.      case_payload = {
15.          "title": title,
16.          "description": description,
17.          "tags": ["auto-generated", "investigation"],
18.          "connector": {"id": "none", "name": "none", "type": ".none", "fields": None},
19.          "owner": "securitySolution",
20.          "settings": {"syncAlerts": True},
21.          "severity": "medium"
22.      }

24.      url = f"{KIBANA_URL}/api/cases"
25.      headers = {"kbn-xsrf": "true", "Content-Type": "application/json"}

27.      try:
28.          response = requests.post(
29.              url,
30.              auth=(USERNAME, PASSWORD),
31.              headers=headers,
32.              json=case_payload
33.          )
34.          if response.status_code == 200:
35.              case_id = response.json().get('id')
36.              print(f"Successfully created case: {case_id}")
37.              return case_id
38.          else:
39.              print(f"Failed to create case: {response.status_code} - {response.text}")
40.              return None
41.      except requests.RequestException as e:
42.          print(f"Request failed: {str(e)}")
43.          return None

``AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

Kibana 安全案例:

Elastic 提供无与伦比的灵活性

Elastic Security 不仅限于开箱即用的检测和响应,还支持无缝集成企业 AI 和协作工具(如 Slack)的定制自动化流程。

利用 Elastic 强大的 APIs 和开源基础,我们能够:

  • 编程式监控关键警报

  • 使用 Elastic AI Assistant for Security 生成富有上下文的可操作响应

  • 以可控、可审计的方式自动化分类和补救

  • 直接集成到分析师每天使用的协作工具中

Elastic 开放、API 优先的架构为安全团队提供了无与伦比的灵活性,可以构建最适合他们的工作流程。这就是基于 Elastic 构建的力量。无论你是通过 AI 扩展响应能力,自定义警报工作流程,还是将 Elastic 嵌入现有的 SecOps 堆栈,平台都设计成以你需要的方式工作。

Elastic 为 AI 驱动的 SOAR 提供无与伦比的灵活性

准备好自己动手了吗?

开始使用 Elastic AI Assistant API 文档构建你的工作流,或者联系我们深入了解 Elastic 开放平台如何满足你独特的 SOC 需求。

本文中描述的任何功能或特性的发布时间和内容,完全由 Elastic 自行决定。任何当前不可用的功能可能无法按时或根本不会发布。

本文中可能使用或提及了第三方生成式 AI 工具,这些工具由各自所有者拥有和运营。Elastic 无法控制这些第三方工具,对其内容、操作或使用不承担任何责任,也不对你使用这些工具可能导致的任何损失或损害负责。使用 AI 工具处理个人、敏感或机密信息时请谨慎。你提交的任何数据可能会被用于 AI 训练或其他目的。无法保证你提供的信息会被安全或保密地保存。你应在使用前熟悉任何生成式 AI 工具的隐私政策和使用条款。

Elastic、Elasticsearch 及相关标志是 Elasticsearch N.V. 在美国及其他国家的商标、徽标或注册商标。所有其他公司和产品名称均为其各自所有者的商标、徽标或注册商标。

原文:Harnessing Elastic APIs for custom AI-driven SOAR | Elastic Blog