This commit is contained in:
rookit
2026-04-21 16:40:15 +08:00
parent 17f4db4956
commit b27b8c984d
6 changed files with 40 additions and 317 deletions
+5
View File
@@ -17,6 +17,11 @@ class BaseModule(BaseAPI):
self.agent_state = None
self.debug_message_id = None # 设置为非None以启用Debug模式
def read_stream_head_ids(self, n): # 调试时使用,读取最近的n条消息
redis_stream_api = RedisStreamAPI()
messages = redis_stream_api.read_stream_head_ids(self.module_name, n=n)
return messages
def read_stream_message(self) -> dict:
"""读取消息"""
redis_stream_api = RedisStreamAPI()
@@ -111,8 +111,8 @@ class Module(BaseModule):
correlation_uid = Correlation.generate_correlation_uid(
rule_id=self.module_name,
time_window="24h",
keys=[principal_user, target_user, account_id],
time_window="5m",
keys=[account_id],
timestamp=event_time_formatted
)
@@ -172,13 +172,13 @@ class Module(BaseModule):
alert_model.artifacts = None
if alert_enrichments:
alert_model.alert_enrichments = alert_enrichments
alert_model.enrichments = alert_enrichments
else:
alert_model.alert_enrichments = None
alert_model.enrichments = None
# 保存告警
saved_alert_row_id = Alert.create(alert_model)
self.logger.info(f"Alert created: {saved_alert_row_id} with disposition {disposition}")
self.logger.info(f"Alert created: {saved_alert_row_id}")
# 5. Case 处理 (Case Management)
try:
@@ -219,5 +219,8 @@ if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ASP.settings")
django.setup()
module = Module()
module.debug_message_id = "1776309110386-0"
module.run()
message_ids = module.read_stream_head_ids(100)
for message_id in message_ids:
module.debug_message_id = message_id
module.run()
@@ -1,308 +0,0 @@
import json
from datetime import datetime
from typing import Optional, Union, Dict, Any, List
from langchain_core.messages import HumanMessage
from langgraph.graph import StateGraph, END
from langgraph.graph.state import CompiledStateGraph
from langgraph.types import Command
from pydantic import BaseModel, Field
from Lib.api import get_current_time_str
from Lib.basemodule import LanggraphModule
from Lib.llmapi import BaseAgentState
from PLUGINS.LLM.llmapi import LLMAPI
from PLUGINS.SIRP.grouprule import Correlation, CorrelationConfig
from PLUGINS.SIRP.sirpapi import Alert, Case
from PLUGINS.SIRP.sirpcoremodel import ArtifactType, ArtifactRole, Severity, Impact, Disposition, AlertAction, Confidence, AlertAnalyticType, ProductCategory, \
AlertPolicyType, AlertRiskLevel, AlertStatus, CasePriority, CaseStatus, ArtifactModel, AlertModel, CaseModel
class AnalyzeResult(BaseModel):
"""Structure for extracting C2 communication analysis result"""
original_severity: Severity = Field(description="Original alert severity", default=Severity.UNKNOWN)
new_severity: Severity = Field(description="Recommended new severity level", default=Severity.UNKNOWN)
confidence: Confidence = Field(description="Confidence level assessment", default=Confidence.UNKNOWN)
analysis_rationale: str = Field(description="Analysis process and reasons", default=None)
attack_stage: Optional[Union[str, Dict[str, Any]]] = Field(description="e.g., 'T1071 - Application Layer Protocol', 'Command and Control'", default=None)
recommended_actions: Optional[Union[str, Dict[str, Any]]] = Field(description="e.g., 'Isolate host 10.1.1.5'", default=None)
class AgentState(BaseAgentState):
analyze_result: AnalyzeResult = None
class Module(LanggraphModule):
THREAD_NUM = 2
def __init__(self):
super().__init__()
self.init()
def init(self):
def alert_preprocess_node(state: AgentState):
"""
Read one alert from Redis Stream and preprocess into AlertModel.
Extract artifacts and create AlertModel with proper structure.
"""
raw_message = self.read_stream_message()
if raw_message is None:
return Command(update={}, goto=END)
alert_raw = raw_message
event_name = alert_raw.get("eventName", "AttachUserPolicy")
event_time = alert_raw.get("eventTime", alert_raw.get("@timestamp", ""))
rule_name = "AWS IAM Privilege Escalation via AttachUserPolicy"
user_identity = alert_raw.get("userIdentity", {})
principal_user = user_identity.get("userName", "unknown-user")
principal_arn = user_identity.get("arn", "unknown-arn")
principal_id = user_identity.get("principalId", "unknown-id")
access_key_id = user_identity.get("accessKeyId", "unknown-key")
request_params = alert_raw.get("requestParameters", {})
target_user = request_params.get("userName", "unknown-target")
policy_arn = request_params.get("policyArn", "unknown-policy")
source_ip = alert_raw.get("sourceIPAddress", "unknown-ip")
aws_region = alert_raw.get("awsRegion", "unknown-region")
account_id = alert_raw.get("recipientAccountId", alert_raw.get("cloud.account.id", "unknown-account"))
user_agent = alert_raw.get("userAgent", "unknown-agent")
event_id = alert_raw.get("eventID", "")
risk_score = alert_raw.get("event.risk_score", alert_raw.get("risk_score", 100))
log_level = alert_raw.get("log.level", "critical")
message = alert_raw.get("message", "")
severity_map = {
"critical": Severity.CRITICAL,
"high": Severity.HIGH,
"medium": Severity.MEDIUM,
"low": Severity.LOW,
"informational": Severity.INFORMATIONAL,
}
severity = severity_map.get(log_level.lower(), Severity.CRITICAL)
event_time_formatted = event_time if event_time else get_current_time_str()
artifacts: List[ArtifactModel] = [
ArtifactModel(
type=ArtifactType.USER,
role=ArtifactRole.ACTOR,
value=principal_user,
name="Principal User"
), ArtifactModel(
type=ArtifactType.OTHER,
role=ArtifactRole.ACTOR,
value=principal_arn,
name="Principal ARN"
), ArtifactModel(
type=ArtifactType.USER,
role=ArtifactRole.TARGET,
value=target_user,
name="Target User"
), ArtifactModel(
type=ArtifactType.IP_ADDRESS,
role=ArtifactRole.ACTOR,
value=source_ip,
name="Source IP"
), ArtifactModel(
type=ArtifactType.OTHER,
role=ArtifactRole.RELATED,
value=policy_arn,
name="Policy ARN"
), ArtifactModel(
type=ArtifactType.OTHER,
role=ArtifactRole.RELATED,
value=account_id,
name="AWS Account ID"
)
]
if access_key_id and access_key_id != "unknown-key":
artifacts.append(ArtifactModel(
type=ArtifactType.OTHER,
role=ArtifactRole.ACTOR,
value=access_key_id,
name="Access Key ID"
))
correlation_config = CorrelationConfig(
rule_id=self.module_name,
time_window="24h",
keys=[principal_user, target_user, account_id]
)
group_rule = Correlation(config=correlation_config)
correlation_uid = group_rule.generate_correlation_uid(timestamp=event_time_formatted)
alert_model = AlertModel(
title=f"AWS IAM Privilege Escalation: {principal_user} attached {policy_arn.split('/')[-1]} to {target_user}",
src_url=f"AWS CloudTrail - Event ID: {event_id}",
severity=severity,
status=AlertStatus.NEW,
status_detail="New alert received from AWS CloudTrail - awaiting analysis",
disposition=Disposition.DETECTED,
action=AlertAction.OBSERVED,
rule_id=self.module_name,
rule_name=rule_name,
source_uid=event_id,
correlation_uid=correlation_uid,
analytic_type=AlertAnalyticType.BEHAVIORAL,
analytic_name="AWS IAM Behavioral Anomaly Detection",
analytic_desc="Detects suspicious IAM policy attachment operations that indicate privilege escalation attempts or backdoor account creation",
analytic_state=None,
product_category=ProductCategory.CLOUD,
product_name="AWS CloudTrail",
product_vendor="Amazon AWS",
product_feature="CloudTrail Logging",
first_seen_time=event_time_formatted,
last_seen_time=event_time_formatted,
desc=message or f"IAM user {principal_user} attached policy {policy_arn} to user {target_user} in account {account_id}",
data_sources=["AWS CloudTrail"],
labels=["iam-privilege-escalation", "aws-cloudtrail", f"account-{account_id}"],
raw_data=json.dumps(alert_raw),
unmapped=json.dumps({
"userAgent": user_agent,
"awsRegion": aws_region,
"requestID": alert_raw.get("requestID", ""),
"eventVersion": alert_raw.get("eventVersion", "")
}),
tactic="T1098.003 - AWS IAM Account Manipulation",
technique="Privilege Escalation",
sub_technique="Create IAM Access Keys",
mitigation="Enable IAM access analyzer, enforce MFA, use service control policies to restrict policy attachment",
policy_name="AWS IAM Access Policy",
policy_type=AlertPolicyType.IDENTITY_POLICY,
policy_desc="IAM identity-based policy that grants permissions to AWS services",
impact=Impact.CRITICAL if severity in [Severity.CRITICAL, Severity.HIGH] else Impact.HIGH,
confidence=Confidence.HIGH,
risk_level=AlertRiskLevel.CRITICAL if severity == Severity.CRITICAL else AlertRiskLevel.HIGH,
)
alert_model.artifacts = artifacts
saved_alert_row_id = Alert.create(alert_model)
alert_model.row_id = saved_alert_row_id
self.logger.debug(f"Alert created with Rowid: {saved_alert_row_id}")
try:
existing_cases = Case.get_by_correlation_uid(correlation_uid, lazy_load=True)
if existing_cases and len(existing_cases) > 0:
existing_case = existing_cases[0]
self.logger.debug(f"Found existing case with correlation_uid: {correlation_uid}, Case ID: {existing_case.row_id}")
update_case = CaseModel(alerts=[*existing_case.alerts, saved_alert_row_id], row_id=existing_case.row_id)
Case.update(update_case)
self.logger.debug(f"Alert {saved_alert_row_id} added to existing case {existing_case.row_id}")
else:
self.logger.debug(f"No existing case found for correlation_uid: {correlation_uid}, creating new case")
new_case = CaseModel(
title=f"AWS IAM Privilege Escalation: {principal_user}{target_user}",
severity=severity,
impact=Impact.CRITICAL if severity in [Severity.CRITICAL, Severity.HIGH] else Impact.HIGH,
priority=CasePriority.CRITICAL if severity == Severity.CRITICAL else CasePriority.HIGH,
confidence=Confidence.HIGH,
status=CaseStatus.NEW,
description=f"AWS IAM privilege escalation detected: {principal_user} attached {policy_arn} to {target_user}",
category=ProductCategory.CLOUD,
tags=["iam-privilege-escalation", "aws-cloudtrail", f"account-{account_id}"],
correlation_uid=correlation_uid,
alerts=[saved_alert_row_id]
)
case_uid = Case.create(new_case)
self.logger.debug(f"New case created with UID: {case_uid}, Alert: {saved_alert_row_id}")
except Exception as e:
self.logger.error(f"Error creating/updating case for correlation_uid {correlation_uid}: {str(e)}")
# return Command(update={}, goto=END)
return {"alert": alert_model}
def alert_analyze_node(state: AgentState):
"""
Analyze the alert using AI (LLM) with structured few-shot examples for AWS IAM privilege escalation detection.
Leverages threat intelligence and cloud behavior patterns.
"""
system_prompt_template = self.load_system_prompt_template("senior_cloud_security_expert")
current_date = datetime.now().strftime("%Y-%m-%d")
system_message = system_prompt_template.format(current_date=current_date)
few_shot_examples = [
]
alert = state.alert
messages = [
system_message,
*few_shot_examples,
HumanMessage(content=alert.model_dump_json_for_ai()),
]
llm_api = LLMAPI()
llm = llm_api.get_model(tag=["fast", "structured_output"])
llm_structured = llm.with_structured_output(AnalyzeResult)
response: AnalyzeResult = llm_structured.invoke(messages)
return {"analyze_result": response}
def alert_output_node(state: AgentState):
"""
Save analysis result to AlertModel and persist using SIRP API.
Updates severity, confidence, and enriches with AI-generated insights.
"""
alert_model: AlertModel = state.alert
analyze_result: AnalyzeResult = state.analyze_result
alert_model.severity = analyze_result.new_severity
alert_model.confidence = analyze_result.confidence
alert_model.summary_ai = str(analyze_result.analysis_rationale)
if analyze_result.recommended_actions:
alert_model.remediation = str(analyze_result.recommended_actions)
labels = list(alert_model.labels) if alert_model.labels else []
if analyze_result.attack_stage and analyze_result.confidence in [Confidence.HIGH, Confidence.MEDIUM]:
labels.append("confirmed-privilege-escalation")
if analyze_result.new_severity in [Severity.CRITICAL, Severity.HIGH]:
labels.append("high-priority-incident")
alert_model.labels = labels
update_alert_row_id = Alert.update(alert_model)
self.logger.info(
f"AWS IAM Privilege Escalation Alert saved with RowID: {update_alert_row_id}, Severity: {analyze_result.new_severity}, Confidence: {analyze_result.confidence}")
return {}
workflow = StateGraph(AgentState)
workflow.add_node("alert_preprocess_node", alert_preprocess_node)
workflow.add_node("alert_analyze_node", alert_analyze_node)
workflow.add_node("alert_output_node", alert_output_node)
workflow.set_entry_point("alert_preprocess_node")
workflow.add_edge("alert_preprocess_node", "alert_analyze_node")
workflow.add_edge("alert_analyze_node", "alert_output_node")
workflow.set_finish_point("alert_output_node")
self.graph: CompiledStateGraph = workflow.compile(checkpointer=self.get_checkpointer())
return True
if __name__ == "__main__":
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ASP.settings")
django.setup()
module = Module()
module.debug_message_id = "1772179651858-0"
module.run()
@@ -26,6 +26,7 @@ metadata:
- 模块文件名必须与 SIEM rule 名称完全一致(含大小写)——Rule 名 = Redis Stream 名 = 文件名,三者强约束,任意一处不一致框架将无法路由告警。
- 编写代码前必须先获取 raw_alert 样本,不得凭空猜测字段结构。
- 编写代码前必须读取 `PLUGINS/SIRP/sirpcoremodel.py`,所有 enum 值只能使用该文件中实际定义的值,不得凭记忆或推断自行发明。
- 所有模块必须继承 `BaseModule` 并实现 `run()` 方法。
- SIRP 数据层级:`Case → Alert → Artifact`(三级体系)。Artifact 是调查的最小原子实体(一个 IP、一个用户名),应尽量从 raw_alert 中提取;Alert 挂在 Case 下;同类告警通过 `correlation_uid` 聚合到同一个 Case。Enrichment 是独立于三级体系之外的横切附加层,可按需挂载到 Case / Alert / Artifact 任意一级。
- 参考实现:`MODULES/Cloud-01-AWS-IAM-Privilege-Escalation-via-AttachUserPolicy.py`
@@ -85,6 +86,8 @@ metadata:
### Step 5 — 编写模块代码
**前置动作:** 读取 `PLUGINS/SIRP/sirpcoremodel.py`,确认所有需要用到的 enum 的合法值,再开始写代码。
按以下结构生成 `MODULES/<rule-name>.py`
```python
+17
View File
@@ -124,6 +124,23 @@ class RedisStreamAPI(object):
with ThreadPoolExecutor(max_workers=1) as executor:
return executor.submit(_fetch).result(timeout=timeout)
def read_stream_head_ids(self, stream_name: str, n: int, timeout: Optional[float] = None) -> List[str]:
"""
读取指定 stream 的前 n 条消息的 ID非阻塞.
:param stream_name: Stream 的名称.
:param n: 要读取的消息数量.
:param timeout: 超时时间None 表示不限制.
"""
def _fetch():
messages = self.redis_client.xrange(stream_name, min='-', max='+', count=n)
return [msg_id.decode() if isinstance(msg_id, bytes) else msg_id for msg_id, _ in messages]
if timeout is None:
return _fetch()
with ThreadPoolExecutor(max_workers=1) as executor:
return executor.submit(_fetch).result(timeout=timeout)
def read_stream_message_by_id(self, stream_name: str, message_id: str, timeout: Optional[float] = None) -> dict:
"""
读取指定 ID 的消息精确匹配非阻塞.
+5 -2
View File
@@ -4,7 +4,7 @@ from typing import List, Union
from typing import Literal
ValidTimeWindows = Literal['10m', '30m', '1h', '2h', '4h', '8h', '12h', '24h', '7d', '30d']
ValidTimeWindows = Literal['5m','10m', '30m', '1h', '2h', '4h', '8h', '12h', '24h', '7d', '30d']
class Correlation(object):
@@ -29,8 +29,11 @@ class Correlation(object):
def generate_correlation_uid(cls,
rule_id: str,
time_window: ValidTimeWindows = "24h",
timestamp: datetime = datetime.now(timezone.utc),
timestamp: datetime = None,
keys: List[Union[str, None]] = None) -> str:
if timestamp is None:
timestamp = datetime.now(timezone.utc)
keys = keys or []
time_bucket = cls._get_time_bucket(timestamp, time_window)
key_parts = [rule_id, time_bucket]