前言
JNDI注入在高版本下显得比较鸡肋,不过基于本地Factory的利用方式还是值得学习的。
Tomcat-BeanFactory
比较出名的可利用Factory,而且Tomcat使用范围比较广。
该类中的getObjectInstance函数可以调用一个对象的函数,要求满足三个条件。
- 要有public的无参构造函数:
Object bean = beanClass.getConstructor().newInstance();
- 要调用的函数为public:
Method method = forced.get(propName);
...
method.invoke(bean, valueArray);
- 要调用的函数有且只有一个String类型的参数
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = String.class;
...
forced.put(param, beanClass.getMethod(setterName, paramTypes));
- 其实还有一些隐含的条件,比如它的bean在通过无参构造函数实例化后,到调用该函数之间没有其他操作,也就是说可控输入就只剩下参数了(或许可以通过多次调用提前控制一些成员)。又比如它无法进行链式调用,所以反射链难以实现,要找到利用方式基本只有那种输入命令->执行,或者ClassLoader的奇妙玩法了。
存在漏洞或危险操作的依赖
通过ELProcessor、GroovyShell、Yaml、MVEL、Xstream、bsh等存在漏洞的工具的玩法。
Mlet
MLet是一个ClassLoader,继承自URLClassLoader,其public函数addURL可以通过一个字符串URL定义远程资源:
public void addURL(String url) throws ServiceNotFoundException {
try {
URL ur = new URL(url);
if (!Arrays.asList(getURLs()).contains(ur))
super.addURL(ur);
} catch (MalformedURLException e) {
...
}
}
远程类需要加载并初始化才能执行其中的静态代码块,问题在于我们通过BeanFactory实例化并调用的Mlet是一个与环境隔离的ClassLoader,我们无法通过这里之外的加载/实例化点完成利用,因为环境中使用的ClassLoader不是这个Mlet。
而在Mlet类中,可以调用的loadClass函数来自父类的父类ClassLoader,而loadClass在加载失败时会调用findClass从资源中定义类:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
}
...
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
...
}
}
...
}
}
// Mlet
protected Class<?> findClass(String name) throws ClassNotFoundException {
return findClass(name, currentClr);
}
Class<?> findClass(String name, ClassLoaderRepository clr)
throws ClassNotFoundException {
Class<?> c = null;
MLET_LOGGER.logp(Level.FINER, MLet.class.getName(), "findClass", name);
// Try looking in the JAR:
try {
c = super.findClass(name);
...
}
}
// URLClassLoader
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
}
...
} else {
return null;
}
}
}, acc);
}
...
}
然而loadClass的第二个参数固定为false,也就是说仅仅是加载也无法执行静态代码块,必须要走到实例化的步骤才行。而ClassLoader中自然是不会有实例化类的操作的,所以这种方式也就走不通了。
NashornScriptEngineFactory
有无参构造函数:
public NashornScriptEngineFactory() {
}
有危险函数:
public ScriptEngine getScriptEngine(String... args) {
return this.newEngine((String[])Objects.requireNonNull(args), getAppClassLoader(), (ClassFilter)null);
}
可以链式利用:
new NashornScriptEngineFactory().getScriptEngine("js").eval("java.lang.Runtime.getRuntime().exec('calc')");
然而BeanFactory似乎实现不了链式调用,寄。
Mvel
一种表达式,需要依赖:
<!-- https://mvnrepository.com/artifact/org.mvel/mvel2 -->
<dependency>
<groupId>org.mvel</groupId>
<artifactId>mvel2</artifactId>
<version>2.4.14.Final</version>
</dependency>
MVEL类没有无参构造函数,但是有很多静态public函数,比如eval、evalToString和evalToBoolean,可以在mvel2包里面其他public类中找一找有没有调用这些静态函数的地方。
单纯用文本搜索做起来会比较麻烦,因为调用了eval的地方实在有点多,看起来很麻烦,如果用codeQL等工具污点分析就会方便很多,详见参考文章。
ShellSession类有一个无参构造函数:
public ShellSession() {
...
}
其exec函数只有一个String类型参数:
public void exec(String command) {
for (String c : command.split("\n")) {
inBuffer.append(c);
_exec();
}
}
其_exec函数会调用内部定义好的Command们的execute函数:
try {
commands.get(inTokens[0]).execute(this, passParameters);
}
PushContext的execute函数中会调用MVEL类的eval函数:
public Object execute(ShellSession session, String[] args) {
session.setCtxObject(MVEL.eval(args[0], session.getCtxObject(), session.getVariables()));
return "Changed Context";
}
MemoryUserDatabaseFactory
继承了ObjectFactory接口,其getObjectInstance函数中会实例化一个MemoryUserDatabase对象,该对象可以设置几个属性:
MemoryUserDatabase database = new MemoryUserDatabase(name.toString());
RefAddr ra = null;
ra = ref.get("pathname");
if (ra != null) {
database.setPathname(ra.getContent().toString());
}
ra = ref.get("readonly");
if (ra != null) {
database.setReadonly(Boolean.parseBoolean(ra.getContent().toString()));
}
ra = ref.get("watchSource");
if (ra != null) {
database.setWatchSource(Boolean.parseBoolean(ra.getContent().toString()));
}
然后调用其open和save函数,分别可以触发xxe和tomcat下的rce漏洞。
JDBC
详见参考文章。